const {
  parseHexInteger
} = require('../../utils/base-utils.js')
const transport = require('../../transport/ble-core.js')
const modbusProtocol = require('../../protocols/modbus-rtu/index.js')
const {
  decodeRegisterValue,
  formatCoilDisplayValue,
  formatRegisterValue,
  getDataType,
  getGroupEncodedWords,
  getRegisterEncodedWords,
  getRegisterWordsFromWordCache,
  getRegisterWriteValueText,
  isBitRegisterType,
  isByteRegister,
  parseCoilValue,
  splitWordSpans
} = require('../../domain/parameter-groups/model.js')

function getWriteSpanMaxQuantity(totalQuantity, maxPacketLength) {
  if (maxPacketLength === 0) return Math.max(1, totalQuantity)

  return Math.max(1, modbusProtocol.client.getMaxWriteMultipleRegisterQuantity(maxPacketLength))
}

async function readGroup(group, options = {}) {
  const totalQuantity = Math.max(1, group.wordQuantity || group.quantity || 0)
  const wordCache = {}
  const slaveAddress = modbusProtocol.client.getSharedSlaveAddress()
  if (slaveAddress === null) return null

  const values = await modbusProtocol.client.readSpans(
    slaveAddress,
    group.functionCode,
    [{
      address: group.startAddress,
      quantity: totalQuantity
    }],
    group.name || '参数组读取',
    'parameter-group-read',
    {
      maxFrameBytes: options.maxPacketLength,
      showModal: options.showModal !== false
    }
  )
  if (!values) return null

  if (isBitRegisterType(group.registerType)) {
    Object.keys(values.coils || {}).forEach((addressText) => {
      wordCache[parseHexInteger(addressText)] = Number(values.coils[addressText]) ? 1 : 0
    })
  } else {
    Object.keys(values.words || {}).forEach((addressText) => {
      wordCache[parseHexInteger(addressText)] = Number(values.words[addressText]) & 0xFFFF
    })
  }

  return wordCache
}

function getRegisterWordsForWrite(group) {
  const words = group.isStructLayout
    ? getGroupEncodedWords(group)
    : Array.from({ length: Math.max(1, group.wordQuantity || 1) }, () => 0)

  if (group.isStructLayout) return words

  for (let index = 0; index < group.registers.length; index += 1) {
    const register = group.registers[index]
    const registerWords = getRegisterEncodedWords(register)

    if (!Array.isArray(registerWords) || !registerWords.length) {
      throw new Error(`${register.name || `寄存器 ${index + 1}`} 没有有效写入值`)
    }

    const dataType = getDataType(register.dataType).key
    const relativeAddress = Math.max(0, register.address - group.startAddress)
    if (isByteRegister(dataType)) {
      const byteValue = Number(registerWords[0]) & 0xFF
      const currentWord = words[relativeAddress] || 0
      words[relativeAddress] = register.byteOffset === 0
        ? (((byteValue << 8) | (currentWord & 0x00FF)) & 0xFFFF)
        : (((currentWord & 0xFF00) | byteValue) & 0xFFFF)
    } else {
      for (let offset = 0; offset < register.registerCount; offset += 1) {
        words[relativeAddress + offset] = Number(registerWords[offset]) & 0xFFFF
      }
    }
  }

  return words
}

function createWrittenRegisterSnapshots(group, words = []) {
  const writtenWordCache = words.reduce((cache, word, offset) => {
    cache[group.startAddress + offset] = word
    return cache
  }, {})

  return group.registers.map((register) => {
    const rawWords = getRegisterWordsFromWordCache(register, writtenWordCache) || []
    const rawValue = decodeRegisterValue(register, rawWords)
    const displayValue = formatRegisterValue(register, rawValue)

    return {
      rawWords,
      rawValue,
      displayValue
    }
  })
}

async function writeCoilGroup(group, options = {}) {
  const slaveAddress = modbusProtocol.client.getSharedSlaveAddress()
  if (slaveAddress === null) return null

  const writtenRegisters = []
  for (let index = 0; index < group.registers.length; index += 1) {
    const register = group.registers[index]
    const coilValue = parseCoilValue(getRegisterWriteValueText(register))

    if (coilValue === null) {
      transport.showCommandAlert('参数组写入', `${register.name || `寄存器 ${index + 1}`} 没有有效写入值`)
      return null
    }

    const response = await modbusProtocol.client.writeSingleCoil(
      slaveAddress,
      group.startAddress + index,
      !!coilValue,
      register.name || group.name || '参数组写入',
      'parameter-group-coil-write',
      {
        maxFrameBytes: options.maxPacketLength
      }
    )
    if (!response) return null

    writtenRegisters.push({
      rawBytes: [],
      rawValue: coilValue,
      rawWords: [],
      displayValue: formatCoilDisplayValue(coilValue)
    })
  }

  return writtenRegisters
}

async function writeRegisterGroup(group, options = {}) {
  const slaveAddress = modbusProtocol.client.getSharedSlaveAddress()
  if (slaveAddress === null) return null

  let words
  try {
    words = getRegisterWordsForWrite(group)
  } catch (error) {
    transport.showCommandAlert('参数组写入', error.message || '寄存器组没有有效写入值')
    return null
  }

  const writtenRegisters = createWrittenRegisterSnapshots(group, words)
  const maxWriteQuantity = getWriteSpanMaxQuantity(words.length, options.maxPacketLength)
  const spans = splitWordSpans(group.startAddress, words.length, maxWriteQuantity)
  let cursor = 0

  for (const span of spans) {
    const spanWords = words.slice(cursor, cursor + span.quantity)
    cursor += span.quantity

    const response = await modbusProtocol.client.writeMultipleRegisters(
      slaveAddress,
      span.address,
      spanWords,
      group.name || '参数组写入',
      'parameter-group-write',
      {
        maxFrameBytes: options.maxPacketLength
      }
    )
    if (!response) return null
  }

  return writtenRegisters
}

async function writeGroup(group, options = {}) {
  return group.registerType === 'coil'
    ? writeCoilGroup(group, options)
    : writeRegisterGroup(group, options)
}

module.exports = {
  readGroup,
  writeGroup
}
