const {
  MAX_MODBUS_DMA_BYTES,
  MODBUS_CRC_OPTIONS,
  getReadResponseByteLength,
  hasValidCrc16Modbus
} = require('./frame.js')
const {
  padHex
} = require('../../utils/base-utils.js')
const {
  bytesToWords
} = require('../../utils/binary-utils.js')

const MODBUS_EXCEPTION_MESSAGES = {
  0x01: '非法功能',
  0x02: '非法数据地址',
  0x03: '非法数据值',
  0x04: '从站设备故障',
  0x05: '确认',
  0x06: '从站设备忙',
  0x08: '存储奇偶性错误',
  0x0A: '网关路径不可用',
  0x0B: '网关目标设备响应失败'
}
const UNLIMITED_FRAME_BYTES = 0

function normalizeMaxFrameBytes(maxFrameBytes = MAX_MODBUS_DMA_BYTES) {
  const numberValue = Number(maxFrameBytes)
  if (Number.isFinite(numberValue) && Math.round(numberValue) === UNLIMITED_FRAME_BYTES) return UNLIMITED_FRAME_BYTES
  if (Number.isFinite(numberValue) && numberValue > 0) return Math.round(numberValue)

  return MAX_MODBUS_DMA_BYTES
}

function parseModbusResponse(bytes) {
  if (!Array.isArray(bytes) || bytes.length < 5 || !hasValidCrc16Modbus(bytes, MODBUS_CRC_OPTIONS)) return null

  const slaveAddress = bytes[0]
  const functionCode = bytes[1]

  if (functionCode & 0x80) {
    return {
      exceptionCode: bytes[2],
      functionCode,
      isException: true,
      slaveAddress,
      sourceFunctionCode: functionCode & 0x7F
    }
  }

  if (functionCode === 0x01 || functionCode === 0x02) {
    const byteCount = bytes[2]
    const dataEnd = 3 + byteCount
    if (bytes.length < dataEnd + 2) return null

    return {
      byteCount,
      dataBytes: bytes.slice(3, dataEnd),
      functionCode,
      isException: false,
      slaveAddress
    }
  }

  if (functionCode === 0x03 || functionCode === 0x04) {
    const byteCount = bytes[2]
    const dataEnd = 3 + byteCount
    if (bytes.length < dataEnd + 2) return null

    return {
      byteCount,
      dataBytes: bytes.slice(3, dataEnd),
      functionCode,
      isException: false,
      slaveAddress,
      words: bytesToWords(bytes.slice(3, dataEnd))
    }
  }

  if (functionCode === 0x05 || functionCode === 0x06 || functionCode === 0x10) {
    return {
      address: ((bytes[2] << 8) | bytes[3]) & 0xFFFF,
      functionCode,
      isException: false,
      quantityOrValue: ((bytes[4] << 8) | bytes[5]) & 0xFFFF,
      slaveAddress
    }
  }

  return {
    functionCode,
    isException: false,
    slaveAddress
  }
}

function parseModbusRequest(bytes) {
  if (!Array.isArray(bytes) || bytes.length < 6 || !hasValidCrc16Modbus(bytes, MODBUS_CRC_OPTIONS)) return null

  const slaveAddress = bytes[0]
  const functionCode = bytes[1]
  const address = ((bytes[2] << 8) | bytes[3]) & 0xFFFF
  let quantity = 1
  let value

  if (functionCode === 0x01 || functionCode === 0x02 || functionCode === 0x03 || functionCode === 0x04 || functionCode === 0x10) {
    quantity = ((bytes[4] << 8) | bytes[5]) & 0xFFFF
  }
  if (functionCode === 0x05 || functionCode === 0x06) {
    value = ((bytes[4] << 8) | bytes[5]) & 0xFFFF
  }

  return {
    address,
    functionCode,
    kind: 'raw-hex',
    quantity,
    value,
    slaveAddress
  }
}

function getExpectedResponseLength(expected, responseFunctionCode, responseBytes = []) {
  if (!expected) return 0

  if (responseFunctionCode === (expected.functionCode | 0x80)) {
    return 5
  }

  if (responseFunctionCode === 0x01 || responseFunctionCode === 0x02) {
    if (responseBytes.length < 3) return 0

    return 3 + Number(responseBytes[2] || 0) + 2
  }

  if (responseFunctionCode === 0x03 || responseFunctionCode === 0x04) {
    if (responseBytes.length < 3) return 0

    return 3 + Number(responseBytes[2] || 0) + 2
  }

  if (responseFunctionCode === 0x05 || responseFunctionCode === 0x06 || responseFunctionCode === 0x10) {
    return 8
  }

  return 0
}

function isExpectedResponse(response, expected) {
  if (response.functionCode === 0x01 || response.functionCode === 0x02) {
    return Array.isArray(response.dataBytes) && response.dataBytes.length >= Math.ceil(expected.quantity / 8)
  }

  if (response.functionCode === 0x03 || response.functionCode === 0x04) {
    return Array.isArray(response.words) && response.words.length >= expected.quantity
  }

  if (response.functionCode === 0x10) {
    return response.address === expected.address && response.quantityOrValue === expected.quantity
  }

  if (response.functionCode === 0x05 || response.functionCode === 0x06) {
    if (response.address !== expected.address) return false
    if (Number.isInteger(expected.value)) return response.quantityOrValue === expected.value

    return true
  }

  return true
}

function getExceptionText(code) {
  return MODBUS_EXCEPTION_MESSAGES[code] || '未知异常'
}

function formatExceptionMessage(response) {
  const sourceFunctionCode = response && response.sourceFunctionCode
  const exceptionCode = response && response.exceptionCode
  const exceptionText = getExceptionText(exceptionCode)

  return `设备返回异常帧：功能码 0x${padHex(sourceFunctionCode, 2)}，异常码 0x${padHex(exceptionCode, 2)}（${exceptionText}）`
}

function getReadBufferHint(expected) {
  return expected ? getReadResponseByteLength(expected.functionCode, expected.quantity) : 0
}

function alignResponseBuffer(buffer, expected) {
  if (!Array.isArray(buffer) || !buffer.length || !expected) return

  const expectedFunctionCodes = [expected.functionCode, expected.functionCode | 0x80]
  let matchIndex = -1

  for (let index = 0; index < buffer.length - 1; index += 1) {
    if (buffer[index] !== expected.slaveAddress) continue
    if (!expectedFunctionCodes.includes(buffer[index + 1])) continue

    matchIndex = index
    break
  }

  if (matchIndex > 0) {
    buffer.splice(0, matchIndex)
  } else if (matchIndex < 0 && buffer.length > 2) {
    buffer.splice(0, buffer.length - 1)
  }
}

function readResponseFromBuffer(buffer, expected, options = {}) {
  if (!Array.isArray(buffer) || !buffer.length || !expected) {
    return {
      status: 'pending'
    }
  }

  alignResponseBuffer(buffer, expected)

  while (buffer.length >= 2) {
    const responseFunctionCode = buffer[1]
    const responseLength = getExpectedResponseLength(expected, responseFunctionCode, buffer)

    if (!responseLength) {
      return {
        status: 'pending'
      }
    }

    const frameLimit = normalizeMaxFrameBytes(
      options.maxFrameBytes === undefined ? expected.maxFrameBytes : options.maxFrameBytes
    )
    if (frameLimit > 0 && responseLength > frameLimit) {
      return {
        frameLimit,
        responseLength,
        status: 'frame-too-long'
      }
    }

    if (buffer.length < responseLength) {
      return {
        status: 'pending'
      }
    }

    const frameBytes = buffer.slice(0, responseLength)
    const response = parseModbusResponse(frameBytes)
    if (!response) {
      return {
        frameBytes,
        status: 'invalid'
      }
    }

    const responseCode = response.isException ? response.sourceFunctionCode : response.functionCode
    if (response.slaveAddress !== expected.slaveAddress || responseCode !== expected.functionCode) {
      buffer.shift()
      alignResponseBuffer(buffer, expected)
      continue
    }

    if (response.isException) {
      return {
        message: formatExceptionMessage(response),
        response,
        status: 'exception'
      }
    }

    if (!isExpectedResponse(response, expected)) {
      return {
        response,
        status: 'mismatch'
      }
    }

    buffer.splice(0, responseLength)
    return {
      response,
      status: 'complete'
    }
  }

  return {
    status: 'pending'
  }
}

module.exports = {
  MODBUS_EXCEPTION_MESSAGES,
  formatExceptionMessage,
  getExceptionText,
  getExpectedResponseLength,
  getReadBufferHint,
  isExpectedResponse,
  parseModbusRequest,
  parseModbusResponse,
  readResponseFromBuffer
}
