const {
  controlButtonRegisters,
  motorParameterInputRegisters,
  readonlyParamRegisters,
  speedCommandRegister,
  statusRegisters
} = require('./registers')
const {
  parseHexInteger
} = require('./base-utils')
const {
  getSharedInputDefault,
  mergeInputValues,
  setSharedInputValues,
  updateDriverParams,
  toFiniteNumber
} = require('./calculation-context')
const {
  calculateParameterInputWriteValue,
  calculateSpeedCommandWriteValue,
  SCALE_MAX,
  formatFixedValue
} = require('./conversions')
const {
  updateStatusRegisterWords
} = require('./status-format')
const {
  floatToWords,
  getRegisterWordCache,
  toRegisterWord,
  toAddressKey,
  wordsToFloat
} = require('./register-value-utils')
const {
  appendInputUnit
} = require('./input-value-utils')

const AUTO_READ_MIN_INTERVAL = 100
const AUTO_READ_MAX_INTERVAL = 3000
const DEFAULT_AUTO_READ_INTERVAL = 100
const MOTOR_PARAM_START_ADDRESS = 0x60
const MOTOR_PARAM_WORD_COUNT = 8
const DRIVER_PARAM_START_ADDRESS = 0xA0
const STATUS_START_ADDRESS = 0xC0
const USER_STATUS_START_ADDRESS = 0xD3
const MAX_USER_STATUS_COUNT = statusRegisters.filter((item) => item.name.indexOf('用户状态字') === 0).length
const DEFAULT_USER_STATUS_COUNT = 0

function getRegisterSpanWordCount(registers, startAddress) {
  const endAddress = registers.reduce((maxAddress, item) => {
    const address = parseHexInteger(item.address)
    if (!Number.isFinite(address)) return maxAddress

    return Math.max(maxAddress, address + (item.registerCount || 1))
  }, startAddress)

  return endAddress - startAddress
}

const DRIVER_PARAM_WORD_COUNT = getRegisterSpanWordCount(readonlyParamRegisters, DRIVER_PARAM_START_ADDRESS)
const BASE_STATUS_WORD_COUNT = USER_STATUS_START_ADDRESS - STATUS_START_ADDRESS
const DRIVER_SUMMARY_REGISTER_NAMES = [
  '芯片型号',
  '全区 Flash 校验码',
  '型号'
]

function isDriverSummaryRegister(item) {
  return DRIVER_SUMMARY_REGISTER_NAMES.includes(item.name)
}

function getDriverReadonlyParamRegisters(registers = readonlyParamRegisters) {
  return registers.filter((item) => !isDriverSummaryRegister(item))
}

function getInputValues(registers) {
  return registers.reduce((result, item) => {
    result[item.name] = toFiniteNumber(item.inputValue, getSharedInputDefault(item.name))
    return result
  }, {})
}

function updateMotorWriteValues(registers) {
  const inputValues = getInputValues(registers)

  return registers.map((item) => ({
    ...item,
    writeValue: calculateParameterInputWriteValue(item, item.inputValue, inputValues)
  }))
}

function formatReadInputValue(item, value) {
  if (!Number.isFinite(value)) return ''
  if (item.name === 'LD' || item.name === 'LQ') return formatFixedValue(value, 6)
  if (item.name === 'RS') return formatFixedValue(value, 4)
  if (item.type === 'float') return formatFixedValue(value, 2)

  return String(Math.round(value))
}

function formatHexWord(value) {
  return `0x${(Number(value) & 0xFFFF).toString(16).toUpperCase().padStart(4, '0')}`
}

function wordsToAscii(words, startIndex, byteLength) {
  const chars = []
  const wordCount = Math.ceil(byteLength / 2)

  for (let index = 0; index < wordCount; index += 1) {
    const word = Number(words[startIndex + index])
    if (!Number.isInteger(word)) break

    const bytes = [(word >> 8) & 0xFF, word & 0xFF]

    for (const byte of bytes) {
      if (chars.length >= byteLength || byte === 0) {
        return chars.join('').trim() || '--'
      }

      if (byte >= 0x20 && byte <= 0x7E) {
        chars.push(String.fromCharCode(byte))
      }
    }
  }

  return chars.join('').trim() || '--'
}

function clampNumber(value, minValue, maxValue, fallback) {
  const numberValue = toFiniteNumber(value, NaN)
  if (!Number.isFinite(numberValue)) return fallback

  return Math.min(Math.max(Math.round(numberValue), minValue), maxValue)
}

function getUserStatusCount(value) {
  return clampNumber(value, 0, MAX_USER_STATUS_COUNT, DEFAULT_USER_STATUS_COUNT)
}

function getStatusWordCount(userStatusCount = DEFAULT_USER_STATUS_COUNT) {
  return BASE_STATUS_WORD_COUNT + getUserStatusCount(userStatusCount)
}

function cloneRegister(item) {
  return {
    ...item
  }
}

function createInitialState() {
  return {
    autoReadInterval: DEFAULT_AUTO_READ_INTERVAL,
    autoReadStatus: false,
    connectedDevice: null,
    controlActionButtons: controlButtonRegisters.filter((item) => item.momentary).map(cloneRegister),
    controlButtons: controlButtonRegisters.filter((item) => !item.momentary).map(cloneRegister),
    errorText: '',
    isAwaitingResponse: false,
    isReadingDriver: false,
    isReadingMotor: false,
    isSending: false,
    isWritingMotor: false,
    chipModel: '--',
    flashChecksum: '--',
    motorModel: '--',
    motorParameterInputRegisters,
    readonlyParamRegisters: getDriverReadonlyParamRegisters(),
    speedCommand: speedCommandRegister,
    systemTip: '',
    userStatusCount: DEFAULT_USER_STATUS_COUNT
  }
}

function applyTransportState(data, transportState) {
  const nextState = {
    connectedDevice: transportState.connectedDevice,
    errorText: transportState.errorText,
    isAwaitingResponse: transportState.isAwaitingResponse,
    isSending: transportState.isSending,
    systemTip: transportState.systemTip
  }

  if (!transportState.connectedDevice && data.autoReadStatus) {
    nextState.autoReadStatus = false
  }

  if (!transportState.connectedDevice) {
    nextState.isReadingDriver = false
    nextState.isReadingMotor = false
    nextState.isWritingMotor = false
  }

  return nextState
}

function applyMotorParameterInput(data, index, value) {
  const changedRegisters = data.motorParameterInputRegisters.map((item, currentIndex) => {
    if (currentIndex !== index) return item

    return {
      ...item,
      isDirty: true,
      inputValue: value
    }
  })
  const nextRegisters = updateMotorWriteValues(changedRegisters)
  const inputValues = mergeInputValues(nextRegisters)

  setSharedInputValues(nextRegisters)

  return {
    motorParameterInputRegisters: nextRegisters,
    speedCommand: {
      ...data.speedCommand,
      isDirty: true,
      writeValue: calculateSpeedCommandWriteValue(data.speedCommand.inputValue, inputValues)
    }
  }
}

function applySpeedCommandInput(data, inputValue) {
  const inputValues = mergeInputValues(data.motorParameterInputRegisters)

  return {
    speedCommand: {
      ...data.speedCommand,
      isDirty: true,
      inputValue,
      writeValue: calculateSpeedCommandWriteValue(inputValue, inputValues)
    }
  }
}

function applySpeedCommandReadValue(data, rawValue) {
  const wordValue = Number(rawValue)
  if (!Number.isInteger(wordValue)) return {}

  const inputValues = mergeInputValues(data.motorParameterInputRegisters)
  const speedBase = toFiniteNumber(inputValues['速度基准'])
  const inputValue = speedBase > 0
    ? appendInputUnit(data.speedCommand, formatFixedValue(wordValue / SCALE_MAX * speedBase, 2))
    : data.speedCommand.inputValue

  return {
    speedCommand: {
      ...data.speedCommand,
      isDirty: false,
      inputValue,
      writeValue: String(wordValue & 0xFFFF)
    }
  }
}

function getControlButtonWriteValue(button) {
  if (!button) return 0

  return button.writeValue
}

function getNextControlButton(button) {
  if (button.momentary) return button

  return {
    ...button,
    name: button.nextName,
    nextName: button.name,
    nextWriteValue: button.writeValue,
    writeValue: button.nextWriteValue
  }
}

function applyControlSuccess(data, button) {
  if (!button) return {}

  if (button.momentary) {
    return {
      systemTip: `${button.name}已下发`
    }
  }

  return {
    controlButtons: data.controlButtons.map((item) => (
      item.key === button.key ? getNextControlButton(item) : item
    )),
    systemTip: `${button.name}已下发`
  }
}

function getControlButtonFromRead(button, value) {
  if (!button || button.momentary) return button

  const readValue = Number(value)
  if (!Number.isFinite(readValue)) return button
  if (Number(button.writeValue) === readValue) return getNextControlButton(button)
  if (Number(button.nextWriteValue) === readValue) return button

  return button
}

function applyControlReadValues(data, coilValues = {}) {
  return {
    controlButtons: data.controlButtons.map((item) => {
      const value = coilValues[toAddressKey(item.address)]

      return value === undefined ? item : getControlButtonFromRead(item, value)
    })
  }
}

function buildMotorMainWriteValues(data) {
  const registerMap = data.motorParameterInputRegisters.reduce((result, item) => {
    result[item.name] = item
    return result
  }, {})
  const ldWords = floatToWords(registerMap.LD && registerMap.LD.inputValue)
  const lqWords = floatToWords(registerMap.LQ && registerMap.LQ.inputValue)
  const rsWords = floatToWords(registerMap.RS && registerMap.RS.inputValue)
  const polePairsWord = toRegisterWord(registerMap['极对数'] && registerMap['极对数'].inputValue)
  const speedBaseWord = toRegisterWord(registerMap['速度基准'] && registerMap['速度基准'].inputValue)

  if (!ldWords || !lqWords || !rsWords || !Number.isInteger(polePairsWord) || !Number.isInteger(speedBaseWord)) {
    return {
      errorText: '请检查 LD、LQ、RS、极对数和速度基准的输入值',
      values: null
    }
  }

  return {
    errorText: '',
    values: ldWords.concat(lqWords, rsWords, [polePairsWord, speedBaseWord])
  }
}

function applyMotorParameterReadValues(data, registerWordCache) {
  const nextRegisters = data.motorParameterInputRegisters.map((item) => {
    let readValue = null

    if (item.name === 'LD' && registerWordCache[0x60] !== undefined && registerWordCache[0x61] !== undefined) {
      readValue = wordsToFloat(registerWordCache[0x60], registerWordCache[0x61])
    } else if (item.name === 'LQ' && registerWordCache[0x62] !== undefined && registerWordCache[0x63] !== undefined) {
      readValue = wordsToFloat(registerWordCache[0x62], registerWordCache[0x63])
    } else if (item.name === 'RS' && registerWordCache[0x64] !== undefined && registerWordCache[0x65] !== undefined) {
      readValue = wordsToFloat(registerWordCache[0x64], registerWordCache[0x65])
    } else if (item.name === '极对数' && registerWordCache[0x66] !== undefined) {
      readValue = registerWordCache[0x66]
    } else if (item.name === '速度基准' && registerWordCache[0x67] !== undefined) {
      readValue = registerWordCache[0x67]
    }

    if (readValue === null) return item

    return {
      ...item,
      isDirty: false,
      inputValue: appendInputUnit(item, formatReadInputValue(item, readValue))
    }
  })
  const nextWriteRegisters = updateMotorWriteValues(nextRegisters)
  const inputValues = mergeInputValues(nextWriteRegisters)

  setSharedInputValues(nextWriteRegisters)

  return {
    motorParameterInputRegisters: nextWriteRegisters,
    speedCommand: {
      ...data.speedCommand,
      writeValue: calculateSpeedCommandWriteValue(data.speedCommand.inputValue, inputValues)
    }
  }
}

function clearMotorParameterDirty(data) {
  return {
    motorParameterInputRegisters: data.motorParameterInputRegisters.map((item) => ({
      ...item,
      isDirty: false
    }))
  }
}

function clearSpeedCommandDirty(data) {
  return {
    speedCommand: {
      ...data.speedCommand,
      isDirty: false
    }
  }
}

function applyMotorParameterBlur(data, index, value) {
  const item = data.motorParameterInputRegisters[index]
  if (!item) return {}

  return applyMotorParameterInput(data, index, appendInputUnit(item, value === undefined ? item.inputValue : value))
}

function applySpeedCommandBlur(data, value) {
  return applySpeedCommandInput(
    data,
    appendInputUnit(data.speedCommand, value === undefined ? data.speedCommand.inputValue : value)
  )
}

function applyDriverParameterReadValues(data, words) {
  if (!Array.isArray(words) || words.length < DRIVER_PARAM_WORD_COUNT) return {}

  const carrierFrequencyKHz = (words[0] >> 8) & 0xFF
  const baseVoltage = (words[0] & 0xFF) / 10
  const opAmpGain = words[1] & 0xFFFF
  const samplingResistorMohm = words[2] & 0xFFFF
  const busVoltageDividerRatio = wordsToFloat(words[4], words[5])
  const analogInputDividerRatio = wordsToFloat(words[6], words[7])
  const displayValues = {
    芯片型号: wordsToAscii(words, 8, 8),
    型号: wordsToAscii(words, 12, 16),
    载波频率: String(carrierFrequencyKHz),
    基准电压: formatFixedValue(baseVoltage, 2),
    运放倍数: String(opAmpGain),
    采样电阻: String(samplingResistorMohm),
    '全区 Flash 校验码': formatHexWord(words[3]),
    母线电压分压比: formatFixedValue(busVoltageDividerRatio, 2),
    模拟输入电压分压比: formatFixedValue(analogInputDividerRatio, 2)
  }

  updateDriverParams({
    analogInputDividerRatio,
    baseVoltage,
    busVoltageDividerRatio,
    carrierFrequencyKHz,
    opAmpGain,
    samplingResistorMohm
  })

  return {
    chipModel: displayValues['芯片型号'],
    flashChecksum: displayValues['全区 Flash 校验码'],
    motorModel: displayValues['型号'],
    readonlyParamRegisters: getDriverReadonlyParamRegisters(data.readonlyParamRegisters).map((item) => ({
      ...item,
      displayValue: displayValues[item.name] || item.displayValue || '--'
    }))
  }
}

function applyStatusReadValues(words, startAddress = STATUS_START_ADDRESS) {
  if (!Array.isArray(words) || !words.length) return {}

  updateStatusRegisterWords(statusRegisters, startAddress, words)

  return {}
}

module.exports = {
  AUTO_READ_MAX_INTERVAL,
  AUTO_READ_MIN_INTERVAL,
  DRIVER_PARAM_START_ADDRESS,
  DRIVER_PARAM_WORD_COUNT,
  MAX_USER_STATUS_COUNT,
  MOTOR_PARAM_START_ADDRESS,
  MOTOR_PARAM_WORD_COUNT,
  STATUS_START_ADDRESS,
  applyControlReadValues,
  applyControlSuccess,
  applyDriverParameterReadValues,
  applyMotorParameterBlur,
  applyMotorParameterInput,
  applyMotorParameterReadValues,
  clearMotorParameterDirty,
  clearSpeedCommandDirty,
  applySpeedCommandBlur,
  applySpeedCommandInput,
  applySpeedCommandReadValue,
  applyStatusReadValues,
  applyTransportState,
  buildMotorMainWriteValues,
  clampNumber,
  createInitialState,
  getStatusWordCount,
  getControlButtonWriteValue,
  getRegisterWordCache,
  getUserStatusCount,
  setSharedInputValues
}
