const settingsService = require('../../store/settings-store.js')
const transport = require('../../transport/ble-core.js')
const modbusProtocol = require('../../protocols/modbus-rtu/index.js')
const {
  addCoilReadValues,
  addWordReadValues
} = require('../../utils/register-value-utils.js')

function getSharedSlaveAddress(title = '从机地址错误') {
  try {
    return settingsService.getModbusSlaveAddress()
  } catch (error) {
    transport.showCommandAlert(title, error.message)
    return null
  }
}

function formatAddress(value) {
  return Number(value || 0).toString(16).toUpperCase()
}

function getChunkLabel(label, chunks, chunk) {
  if (!label || chunks.length <= 1) return label

  return `${label} ${formatAddress(chunk.address)}-${formatAddress(chunk.address + chunk.quantity - 1)}`
}

function isBroadcastAddress(slaveAddress) {
  return Number(slaveAddress) === 0
}

function showBroadcastReadAlert(label) {
  transport.showCommandAlert(label || '读取命令错误', '广播地址 0x00 不支持读取')
}

async function sendReadChunk(slaveAddress, functionCode, chunk, label, kind, options = {}) {
  if (isBroadcastAddress(slaveAddress)) {
    showBroadcastReadAlert(label)
    return false
  }

  return transport.sendManagedFrame(
    modbusProtocol.buildReadFrame(slaveAddress, functionCode, chunk.address, chunk.quantity, {
      maxFrameBytes: options.maxFrameBytes
    }),
    label,
    {
      address: chunk.address,
      functionCode,
      kind,
      protocol: modbusProtocol.PROTOCOL_NAME,
      quantity: chunk.quantity,
      slaveAddress
    },
    {
      maxFrameBytes: options.maxFrameBytes,
      showModal: options.showModal
    }
  )
}

async function readSpans(slaveAddress, functionCode, spans, label, kind, options = {}) {
  const readValues = {
    coils: {},
    words: {}
  }
  const normalizedSpans = (spans || []).filter((span) => span && span.quantity > 0)

  for (const span of normalizedSpans) {
    const chunks = modbusProtocol.getReadChunks(functionCode, span.address, span.quantity, options)

    for (const chunk of chunks) {
      const responseValue = await sendReadChunk(
        slaveAddress,
        functionCode,
        chunk,
        getChunkLabel(label, chunks, chunk),
        kind,
        options
      )
      if (!responseValue) return null

      if (functionCode === 0x01 || functionCode === 0x02) {
        addCoilReadValues(readValues, chunk.address, chunk.quantity, responseValue)
      } else {
        addWordReadValues(readValues, chunk.address, responseValue)
      }

      if (typeof options.onChunk === 'function') {
        options.onChunk(responseValue, chunk)
      }
    }
  }

  return readValues
}

async function readRegisterWords(slaveAddress, functionCode, startAddress, quantity, label, kind, options = {}) {
  const words = []
  const chunks = modbusProtocol.getReadChunks(functionCode, startAddress, quantity, options)

  for (const chunk of chunks) {
    const responseValue = await sendReadChunk(
      slaveAddress,
      functionCode,
      chunk,
      getChunkLabel(label, chunks, chunk),
      kind,
      options
    )
    if (!responseValue) return null

    const chunkWords = responseValue.words || []
    chunkWords.forEach((word, index) => {
      words[chunk.address - startAddress + index] = Number(word) & 0xFFFF
    })

    if (typeof options.onChunk === 'function') {
      options.onChunk(responseValue, chunk)
    }
  }

  return words
}

async function readBitValues(slaveAddress, functionCode, startAddress, quantity, label, kind, options = {}) {
  const result = await readSpans(
    slaveAddress,
    functionCode,
    [{ address: startAddress, quantity }],
    label,
    kind,
    options
  )

  return result ? result.coils : null
}

async function readSingleHoldingWord(slaveAddress, address, label = '读取配对寄存器', kind = 'holding-word-read') {
  const words = await readRegisterWords(slaveAddress, 0x03, address, 1, label, kind)

  return words && Number.isInteger(words[0]) ? words[0] & 0xFFFF : null
}

function writeSingleCoil(slaveAddress, address, checked, label, kind = 'coil-write', options = {}) {
  const coilValue = checked ? 0xFF00 : 0x0000

  return transport.sendManagedFrame(
    modbusProtocol.buildWriteSingleCoilFrame(slaveAddress, address, checked),
    label,
    isBroadcastAddress(slaveAddress) ? null : {
      address,
      functionCode: 0x05,
      kind,
      protocol: modbusProtocol.PROTOCOL_NAME,
      quantity: 1,
      slaveAddress,
      value: coilValue
    },
    {
      maxFrameBytes: options.maxFrameBytes,
      showModal: options.showModal
    }
  )
}

function writeSingleRegister(slaveAddress, address, value, label, kind = 'register-write', options = {}) {
  return transport.sendManagedFrame(
    modbusProtocol.buildWriteSingleRegisterFrame(slaveAddress, address, value),
    label,
    isBroadcastAddress(slaveAddress) ? null : {
      address,
      functionCode: 0x06,
      kind,
      protocol: modbusProtocol.PROTOCOL_NAME,
      quantity: 1,
      slaveAddress,
      value
    },
    {
      maxFrameBytes: options.maxFrameBytes,
      showModal: options.showModal
    }
  )
}

function writeMultipleRegisters(slaveAddress, address, values, label, kind = 'registers-write', options = {}) {
  return transport.sendManagedFrame(
    modbusProtocol.buildWriteMultipleRegistersFrame(slaveAddress, address, values, {
      maxFrameBytes: options.maxFrameBytes
    }),
    label,
    isBroadcastAddress(slaveAddress) ? null : {
      address,
      functionCode: 0x10,
      kind,
      protocol: modbusProtocol.PROTOCOL_NAME,
      quantity: values.length,
      slaveAddress
    },
    {
      maxFrameBytes: options.maxFrameBytes,
      showModal: options.showModal
    }
  )
}

module.exports = {
  getMaxReadQuantity: modbusProtocol.getMaxReadQuantity,
  getMaxWriteMultipleRegisterQuantity: modbusProtocol.getMaxWriteMultipleRegisterQuantity,
  getReadChunks: modbusProtocol.getReadChunks,
  getSharedSlaveAddress,
  readBitValues,
  readRegisterWords,
  readSingleHoldingWord,
  readSpans,
  splitQuantity: modbusProtocol.splitQuantity,
  writeMultipleRegisters,
  writeSingleCoil,
  writeSingleRegister
}
