const {
  paramsState: paramsPageState
} = require('../../domain/motor-control/data.js')
const {
  expandItems,
  getAreaKey,
  getGroupItems,
  makeReadSpans,
  mergeReadValues,
  parseRegisterAddress
} = require('../../domain/motor-control/register-groups.js')
const transport = require('../../transport/ble-core.js')
const modbusClient = require('../../protocols/modbus-rtu/client.js')
const {
  floatToWords,
  toRegisterWord
} = require('../../utils/register-value-utils.js')

function hasWriteValue(value) {
  return value !== '' && value !== undefined && value !== null && value !== '--'
}

function toWriteNumber(value) {
  if (!hasWriteValue(value)) return null

  const numberValue = Number(value)
  if (!Number.isFinite(numberValue)) return null

  return numberValue
}

async function readGroup(data, groupKey) {
  const slaveAddress = modbusClient.getSharedSlaveAddress()
  if (slaveAddress === null) return false

  const items = expandItems(getGroupItems(data, groupKey))
  const coilItems = items.filter((item) => getAreaKey(item) === 'coil')
  const holdingItems = items.filter((item) => getAreaKey(item) === 'holding')
  const inputItems = items.filter((item) => getAreaKey(item) === 'input')
  const coilSpans = makeReadSpans(coilItems)
  const holdingSpans = makeReadSpans(holdingItems)
  const inputSpans = makeReadSpans(inputItems)
  const readValues = {
    coils: {},
    words: {}
  }
  let sent = false

  if (coilSpans.length) {
    sent = true
    const values = await modbusClient.readSpans(
      slaveAddress,
      0x01,
      coilSpans,
      '参数读取',
      'params-read'
    )
    if (!values) return false
    mergeReadValues(readValues, values)
  }

  if (holdingSpans.length) {
    sent = true
    const values = await modbusClient.readSpans(
      slaveAddress,
      0x03,
      holdingSpans,
      '参数读取',
      'params-read'
    )
    if (!values) return false
    mergeReadValues(readValues, values)
  }

  if (inputSpans.length) {
    sent = true
    const values = await modbusClient.readSpans(
      slaveAddress,
      0x04,
      inputSpans,
      '参数读取',
      'params-read'
    )
    if (!values) return false
    mergeReadValues(readValues, values)
  }

  if (!sent) {
    transport.showCommandAlert('参数读取', '当前分组没有可读取的寄存器')
    return false
  }

  if (!Object.keys(readValues.coils).length && !Object.keys(readValues.words).length) {
    return false
  }

  return paramsPageState.applyReadValues(data, readValues)
}

async function buildHoldingWriteEntries(slaveAddress, items) {
  const normalEntries = []
  const byteGroups = {}

  items.forEach((item) => {
    if (getAreaKey(item) !== 'holding') return

    if (item.type === 'uint8_t' && item.bytePosition) {
      const address = parseRegisterAddress(item.address)
      const group = byteGroups[address] || {
        address,
        high: null,
        low: null
      }
      group[item.bytePosition] = item
      byteGroups[address] = group
      return
    }

    const writeNumber = toWriteNumber(item.writeValue)
    if (writeNumber === null) return

    const words = item.type === 'float'
      ? floatToWords(writeNumber)
      : [toRegisterWord(writeNumber)]

    if (!words || words.some((word) => word === null)) return

    normalEntries.push({
      address: parseRegisterAddress(item.address),
      label: item.name,
      values: words
    })
  })

  for (const addressText of Object.keys(byteGroups)) {
    const group = byteGroups[addressText]
    const highValue = group.high ? toRegisterWord(group.high.writeValue) : null
    const lowValue = group.low ? toRegisterWord(group.low.writeValue) : null

    if (highValue === null && lowValue === null) continue

    let baseWord = 0
    if (highValue === null || lowValue === null) {
      const readWord = await modbusClient.readSingleHoldingWord(
        slaveAddress,
        group.address,
        '读取配对寄存器',
        'params-pair-read'
      )
      if (!Number.isInteger(readWord)) continue

      baseWord = readWord
    }

    const nextHigh = highValue === null ? ((baseWord >> 8) & 0xFF) : highValue
    const nextLow = lowValue === null ? (baseWord & 0xFF) : lowValue

    if (nextHigh > 0xFF || nextLow > 0xFF) continue

    normalEntries.push({
      address: group.address,
      label: '8位参数',
      values: [(nextHigh << 8) | nextLow]
    })
  }

  return normalEntries.sort((left, right) => left.address - right.address)
}

async function writeGroup(data, groupKey) {
  const slaveAddress = modbusClient.getSharedSlaveAddress()
  if (slaveAddress === null) return false

  const items = expandItems(getGroupItems(data, groupKey))
  const coilItems = items.filter((item) => getAreaKey(item) === 'coil' && item.isDirty)
  const holdingItems = items.filter((item) => getAreaKey(item) === 'holding')
  let sent = false

  for (const item of coilItems) {
    const checked = Number(item.writeValue) !== 0
    const address = parseRegisterAddress(item.address)

    sent = true
    const response = await modbusClient.writeSingleCoil(
      slaveAddress,
      address,
      checked,
      item.name,
      'params-coil-write'
    )
    if (!response) return false
  }

  const holdingEntries = await buildHoldingWriteEntries(slaveAddress, holdingItems)
  for (const entry of holdingEntries) {
    sent = true
    const response = await modbusClient.writeMultipleRegisters(
      slaveAddress,
      entry.address,
      entry.values,
      entry.label,
      'params-holding-write'
    )
    if (!response) return false
  }

  if (!sent) {
    transport.showCommandAlert('参数写入', '当前分组没有可写入的参数')
  }

  return sent
}

async function writeSwitchRegister(item) {
  const slaveAddress = modbusClient.getSharedSlaveAddress()
  if (slaveAddress === null || !item) return false

  const address = parseRegisterAddress(item.address)
  const checked = Number(item.writeValue) !== 0

  return modbusClient.writeSingleCoil(
    slaveAddress,
    address,
    checked,
    item.name,
    'params-switch-write'
  )
}

module.exports = {
  readGroup,
  writeGroup,
  writeSwitchRegister
}
