const {
  BYTE_ORDER_LOW,
  appendCrc16Modbus,
  hasValidCrc16Modbus
} = require('./crc')

const MODBUS_CRC_OPTIONS = {
  byteOrder: BYTE_ORDER_LOW
}
const MAX_MODBUS_DMA_BYTES = 64
const MODBUS_READ_RESPONSE_OVERHEAD = 5
const MODBUS_WRITE_MULTIPLE_REQUEST_OVERHEAD = 9
const MAX_READ_REGISTER_QUANTITY = Math.floor((MAX_MODBUS_DMA_BYTES - MODBUS_READ_RESPONSE_OVERHEAD) / 2)
const MAX_READ_COIL_QUANTITY = (MAX_MODBUS_DMA_BYTES - MODBUS_READ_RESPONSE_OVERHEAD) * 8
const MAX_WRITE_MULTIPLE_REGISTER_QUANTITY = Math.floor((MAX_MODBUS_DMA_BYTES - MODBUS_WRITE_MULTIPLE_REQUEST_OVERHEAD) / 2)
const UNLIMITED_FRAME_BYTES = 0

function toByte(value, label) {
  if (!Number.isInteger(value) || value < 0 || value > 0xFF) {
    throw new Error(`${label}必须在 0x00 至 0xFF 之间`)
  }

  return value
}

function toWord(value, label) {
  if (!Number.isInteger(value) || value < 0 || value > 0xFFFF) {
    throw new Error(`${label}必须在 0x0000 至 0xFFFF 之间`)
  }

  return value
}

function splitWord(value) {
  return [(value >> 8) & 0xFF, value & 0xFF]
}

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 getMaxReadQuantity(functionCode, maxFrameBytes = MAX_MODBUS_DMA_BYTES) {
  const frameBytes = normalizeMaxFrameBytes(maxFrameBytes)
  if (frameBytes === UNLIMITED_FRAME_BYTES) return 0xFFFF

  const dataBytes = frameBytes - MODBUS_READ_RESPONSE_OVERHEAD
  if (dataBytes <= 0) return 0
  if (functionCode === 0x01 || functionCode === 0x02) return dataBytes * 8
  if (functionCode === 0x03 || functionCode === 0x04) return Math.floor(dataBytes / 2)

  return 0
}

function getMaxWriteMultipleRegisterQuantity(maxFrameBytes = MAX_MODBUS_DMA_BYTES) {
  const frameBytes = normalizeMaxFrameBytes(maxFrameBytes)
  if (frameBytes === UNLIMITED_FRAME_BYTES) return 0xFFFF

  return Math.max(0, Math.floor((frameBytes - MODBUS_WRITE_MULTIPLE_REQUEST_OVERHEAD) / 2))
}

function buildReadFrame(slaveAddress, functionCode, address, quantity, options = {}) {
  const slave = toByte(slaveAddress, '从站地址')
  const command = toByte(functionCode, '功能码')
  const startAddress = toWord(address, '寄存器地址')
  const registerQuantity = toWord(quantity, '读取数量')
  const maxQuantity = getMaxReadQuantity(command, options.maxFrameBytes)

  if (![0x01, 0x02, 0x03, 0x04].includes(command)) {
    throw new Error('当前功能码不是读取命令')
  }
  if (registerQuantity === 0) {
    throw new Error('读取数量必须大于 0')
  }
  if ([0x03, 0x04].includes(command) && maxQuantity > 0 && registerQuantity > maxQuantity) {
    throw new Error(`单帧最多读取 ${maxQuantity} 个寄存器`)
  }
  if ((command === 0x01 || command === 0x02) && maxQuantity > 0 && registerQuantity > maxQuantity) {
    throw new Error(`单帧最多读取 ${maxQuantity} 个位状态`)
  }

  return appendCrc16Modbus(
    [slave, command].concat(splitWord(startAddress), splitWord(registerQuantity)),
    MODBUS_CRC_OPTIONS
  )
}

function buildWriteSingleCoilFrame(slaveAddress, address, checked) {
  const slave = toByte(slaveAddress, '从站地址')
  const startAddress = toWord(address, '线圈地址')
  const outputValue = checked ? 0xFF00 : 0x0000

  return appendCrc16Modbus(
    [slave, 0x05].concat(splitWord(startAddress), splitWord(outputValue)),
    MODBUS_CRC_OPTIONS
  )
}

function buildWriteSingleRegisterFrame(slaveAddress, address, value) {
  const slave = toByte(slaveAddress, '从站地址')
  const startAddress = toWord(address, '寄存器地址')
  const registerValue = toWord(value, '写入值')

  return appendCrc16Modbus(
    [slave, 0x06].concat(splitWord(startAddress), splitWord(registerValue)),
    MODBUS_CRC_OPTIONS
  )
}

function buildWriteMultipleRegistersFrame(slaveAddress, address, values, options = {}) {
  const slave = toByte(slaveAddress, '从站地址')
  const startAddress = toWord(address, '寄存器地址')
  const maxQuantity = getMaxWriteMultipleRegisterQuantity(options.maxFrameBytes)

  if (!Array.isArray(values) || values.length === 0) {
    throw new Error('请输入至少一个寄存器写入值')
  }
  if (maxQuantity > 0 && values.length > maxQuantity) {
    throw new Error(`单帧最多写入 ${maxQuantity} 个寄存器`)
  }

  const registerBytes = values.reduce((result, value) => {
    return result.concat(splitWord(toWord(value, '写入值')))
  }, [])

  return appendCrc16Modbus(
    [slave, 0x10]
      .concat(splitWord(startAddress), splitWord(values.length), [registerBytes.length], registerBytes),
    MODBUS_CRC_OPTIONS
  )
}

function formatHex(bytes) {
  return bytes.map((byte) => byte.toString(16).padStart(2, '0').toUpperCase()).join(' ')
}

function getReadResponseByteLength(functionCode, quantity) {
  if (functionCode === 0x01 || functionCode === 0x02) return MODBUS_READ_RESPONSE_OVERHEAD + Math.ceil(Number(quantity || 0) / 8)
  if (functionCode === 0x03 || functionCode === 0x04) return MODBUS_READ_RESPONSE_OVERHEAD + Number(quantity || 0) * 2

  return 0
}

module.exports = {
  MAX_MODBUS_DMA_BYTES,
  MAX_READ_COIL_QUANTITY,
  MAX_READ_REGISTER_QUANTITY,
  MAX_WRITE_MULTIPLE_REGISTER_QUANTITY,
  MODBUS_CRC_OPTIONS,
  UNLIMITED_FRAME_BYTES,
  buildReadFrame,
  buildWriteMultipleRegistersFrame,
  buildWriteSingleCoilFrame,
  buildWriteSingleRegisterFrame,
  formatHex,
  getMaxWriteMultipleRegisterQuantity,
  getMaxReadQuantity,
  getReadResponseByteLength,
  hasValidCrc16Modbus
}
