const {
  controlState
} = require('../../domain/motor-control/data.js')
const {
  parseHexInteger
} = require('../../utils/base-utils.js')
const transport = require('../../transport/ble-core.js')
const bootloaderService = require('../bootloader/service.js')
const settingsService = require('../../store/settings-store.js')
const modbusClient = require('../../protocols/modbus-rtu/client.js')
const motorControlProtocol = require('./protocol-service.js')

let state = {
  ...controlState.createInitialState(),
  ...bootloaderService.getState()
}
let autoReadTimer = null
let unsubscribeSettings = null
let unsubscribeTransport = null
const subscribers = []

function getState() {
  return {
    ...state
  }
}

function notify() {
  const nextState = getState()

  subscribers.slice().forEach((subscriber) => {
    subscriber(nextState)
  })
}

function setState(changedData) {
  state = {
    ...state,
    ...changedData
  }
  notify()
}

function applySettingsState(settings) {
  const autoReadInterval = controlState.clampNumber(
    settings.statusPollInterval,
    controlState.AUTO_READ_MIN_INTERVAL,
    controlState.AUTO_READ_MAX_INTERVAL,
    state.autoReadInterval
  )
  const userStatusCount = controlState.getUserStatusCount(settings.userStatusCount)

  setState({
    autoReadInterval,
    userStatusCount
  })

  if (state.autoReadStatus) {
    scheduleAutoReadStatus(autoReadInterval)
  }
}

function subscribe(subscriber) {
  if (typeof subscriber !== 'function') return () => {}

  subscribers.push(subscriber)
  subscriber(getState())

  return () => {
    const index = subscribers.indexOf(subscriber)
    if (index >= 0) subscribers.splice(index, 1)
  }
}

function init() {
  transport.init()
  bootloaderService.init()
  settingsService.init()

  if (!unsubscribeSettings) {
    unsubscribeSettings = settingsService.subscribe(applySettingsState)
  }

  if (unsubscribeTransport) return

  unsubscribeTransport = transport.subscribe((transportState) => {
    const nextState = controlState.applyTransportState(state, transportState)

    if (nextState.autoReadStatus === false) {
      stopAutoReadStatus()
    }

    setState(nextState)
  })

  bootloaderService.subscribe((bootloaderState) => {
    setState(bootloaderState)
  })
}

function syncSharedInputs() {
  controlState.setSharedInputValues(state.motorParameterInputRegisters)
}

function applyControlReadValues(coilValues) {
  setState(controlState.applyControlReadValues(state, coilValues))
}

function applyMotorReadWords(words, startAddress = controlState.MOTOR_PARAM_START_ADDRESS) {
  const registerWordCache = controlState.getRegisterWordCache(startAddress, words)
  const motorState = controlState.applyMotorParameterReadValues(state, registerWordCache)
  const nextState = {
    ...state,
    ...motorState
  }

  setState({
    ...motorState,
    ...controlState.applySpeedCommandReadValue(nextState, registerWordCache[0x68])
  })
}

function applyDriverReadWords(words) {
  const changedState = controlState.applyDriverParameterReadValues(state, words)
  if (changedState.chipModel) {
    bootloaderService.setChipModel(changedState.chipModel)
  }
  setState(changedState)
}

function applyStatusReadWords(words, startAddress = controlState.STATUS_START_ADDRESS) {
  setState(controlState.applyStatusReadValues(words, startAddress))
}

function updateMotorParameterInput(index, value) {
  setState(controlState.applyMotorParameterInput(state, index, value))
}

function updateMotorParameterBlur(index, value) {
  setState(controlState.applyMotorParameterBlur(state, index, value))
}

function updateSpeedCommandInput(value) {
  setState(controlState.applySpeedCommandInput(state, value))
}

function updateSpeedCommandBlur(value) {
  setState(controlState.applySpeedCommandBlur(state, value))
  sendSpeedCommand()
}

function getSpeedCommandWriteWord() {
  const writeValue = Number(state.speedCommand.writeValue)
  if (!Number.isFinite(writeValue)) return null

  const word = Math.round(writeValue)

  return word >= 0 && word <= 0xFFFF ? word : null
}

async function sendSpeedCommand() {
  const slaveAddress = modbusClient.getSharedSlaveAddress()
  if (slaveAddress === null) return false

  const writeWord = getSpeedCommandWriteWord()
  if (writeWord === null) {
    transport.showCommandAlert('转速命令错误', '请检查转速命令输入值')
    return false
  }

  const address = parseHexInteger(state.speedCommand.address)
  const response = await modbusClient.writeSingleRegister(
    slaveAddress,
    address,
    writeWord,
    '转速命令',
    'speed-command-write'
  )

  if (response) {
    setState({
      ...controlState.clearSpeedCommandDirty(state),
      systemTip: '转速命令已下发'
    })
    return true
  }

  return false
}

async function sendControlCommand(key) {
  const button = state.controlButtons
    .concat(state.controlActionButtons || [])
    .find((item) => item.key === key)
  if (!button) return

  const response = await motorControlProtocol.writeControlButton(
    button,
    {
      kind: 'control-write'
    }
  )

  if (response) {
    setState(controlState.applyControlSuccess(state, button))
  }
}

async function readControlStatus() {
  const slaveAddress = modbusClient.getSharedSlaveAddress()
  if (slaveAddress === null) return false

  const startAddress = 0x00
  const quantity = 3
  const coilValues = await modbusClient.readBitValues(
    slaveAddress,
    0x01,
    startAddress,
    quantity,
    '控制状态读取',
    'control-status-read'
  )

  if (!coilValues) return false

  setState({
    ...controlState.applyControlReadValues(state, coilValues),
    systemTip: '控制状态读取完成'
  })

  return true
}

async function readMotorParameters() {
  if (state.isReadingMotor) return

  const slaveAddress = modbusClient.getSharedSlaveAddress()
  if (slaveAddress === null) return

  setState({
    errorText: '',
    isReadingMotor: true,
    systemTip: ''
  })

  try {
    const words = await modbusClient.readRegisterWords(
      slaveAddress,
      0x03,
      controlState.MOTOR_PARAM_START_ADDRESS,
      controlState.MOTOR_PARAM_WORD_COUNT,
      '电机参数读取',
      'motor-main-read',
      { showModal: true }
    )
    if (!words) return

    const registerWordCache = controlState.getRegisterWordCache(controlState.MOTOR_PARAM_START_ADDRESS, words)

    setState({
      ...controlState.applyMotorParameterReadValues(state, registerWordCache),
      systemTip: '电机参数读取完成'
    })
  } finally {
    setState({
      isReadingMotor: false
    })
  }
}

async function writeMotorParameters() {
  if (state.isWritingMotor) return

  const slaveAddress = modbusClient.getSharedSlaveAddress()
  if (slaveAddress === null) return

  const mainWrite = controlState.buildMotorMainWriteValues(state)
  if (!mainWrite.values) {
    transport.showCommandAlert('参数错误', mainWrite.errorText)
    return
  }

  setState({
    errorText: '',
    isWritingMotor: true,
    systemTip: ''
  })

  try {
    const mainResponse = await modbusClient.writeMultipleRegisters(
      slaveAddress,
      controlState.MOTOR_PARAM_START_ADDRESS,
      mainWrite.values,
      '电机参数写入',
      'motor-main-write'
    )
    if (!mainResponse) return

    setState({
      ...controlState.clearMotorParameterDirty(state),
      systemTip: '电机参数写入完成'
    })
  } finally {
    setState({
      isWritingMotor: false
    })
  }
}

async function readDriverParameters() {
  if (state.isReadingDriver) return

  const slaveAddress = modbusClient.getSharedSlaveAddress()
  if (slaveAddress === null) return

  setState({
    errorText: '',
    isReadingDriver: true,
    systemTip: ''
  })

  try {
    const words = await modbusClient.readRegisterWords(
      slaveAddress,
      0x04,
      controlState.DRIVER_PARAM_START_ADDRESS,
      controlState.DRIVER_PARAM_WORD_COUNT,
      '驱动器参数读取',
      'driver-read',
      { showModal: true }
    )

    if (words) {
      const changedState = controlState.applyDriverParameterReadValues(state, words)
      if (changedState.chipModel) {
        bootloaderService.setChipModel(changedState.chipModel)
      }
      setState({
        ...changedState,
        systemTip: '驱动器参数读取完成'
      })
    }
  } finally {
    setState({
      isReadingDriver: false
    })
  }
}

function chooseFirmwareFile(source) {
  return bootloaderService.chooseFirmwareFile(source)
}

function startFirmwareUpgrade() {
  stopAutoReadStatus()
  setState({
    autoReadStatus: false
  })

  return bootloaderService.startUpgrade()
}

function readProgramChecksum() {
  return bootloaderService.readProgramChecksum()
}

function handshakeBootloader() {
  return bootloaderService.sendHandshakeKeepAlive()
}

function exitBootloader() {
  return bootloaderService.exitBootloader()
}

function setAutoReadStatus(autoReadStatus) {
  setState({
    autoReadStatus
  })

  if (autoReadStatus) {
    scheduleAutoReadStatus(0)
    return
  }

  stopAutoReadStatus()
}

async function readStatus(options = {}) {
  if (options.auto && !state.connectedDevice) return false

  const slaveAddress = modbusClient.getSharedSlaveAddress()
  if (slaveAddress === null) return false

  const words = await modbusClient.readRegisterWords(
    slaveAddress,
    0x04,
    controlState.STATUS_START_ADDRESS,
    controlState.getStatusWordCount(state.userStatusCount),
    '状态读取',
    'status-read',
    { showModal: !options.auto }
  )

  if (words) {
    setState({
      ...controlState.applyStatusReadValues(words, controlState.STATUS_START_ADDRESS),
      systemTip: options.auto ? '' : '状态读取完成'
    })
  }

  return words
}

function scheduleAutoReadStatus(delay) {
  stopAutoReadStatus()

  autoReadTimer = setTimeout(async () => {
    if (!state.autoReadStatus) return

    await readStatus({
      auto: true
    })
    scheduleAutoReadStatus(state.autoReadInterval)
  }, delay)
}

function stopAutoReadStatus() {
  if (!autoReadTimer) return

  clearTimeout(autoReadTimer)
  autoReadTimer = null
}

module.exports = {
  getState,
  init,
  applyControlReadValues,
  applyDriverReadWords,
  applyMotorReadWords,
  applyStatusReadWords,
  exitBootloader,
  handshakeBootloader,
  readControlStatus,
  readDriverParameters,
  readMotorParameters,
  readProgramChecksum,
  readStatus,
  sendControlCommand,
  setAutoReadStatus,
  chooseFirmwareFile,
  startFirmwareUpgrade,
  subscribe,
  syncSharedInputs,
  updateMotorParameterBlur,
  updateMotorParameterInput,
  updateSpeedCommandBlur,
  updateSpeedCommandInput,
  writeMotorParameters
}
