const {
  bytesToWords
} = require('../../utils/binary-utils.js')
const transport = require('../../transport/ble-core.js')
const storageAccessMemory = require('../storage-access/memory-service.js')
const {
  decodeRegisterFromByteCache,
  decodeRegisterValue,
  formatRegisterValue,
  getGroupEncodedBytes,
  getRegisterBytesFromByteCache,
  getRegisterEncodedBytes,
  getRegisterWordsFromByteCache,
  getRegisterWordsFromWordCache
} = require('../../domain/parameter-groups/model.js')

const STORAGE_ACCESS_MEMORY_TYPES = {
  DATA: 0x01,
  BIT: 0x01,
  IDATA: 0x02,
  XDATA: 0x03,
  CODE: 0x04
}
const STORAGE_ACCESS_CODE_AREA = 0x04

function getMemoryType(group = {}) {
  const memoryArea = String(group.sourceMemoryArea || '').trim().toUpperCase()

  return Object.prototype.hasOwnProperty.call(STORAGE_ACCESS_MEMORY_TYPES, memoryArea)
    ? STORAGE_ACCESS_MEMORY_TYPES[memoryArea]
    : null
}

function isMemoryGroup(group = {}) {
  return getMemoryType(group) !== null
}

function isByteAddressedGroup(group = {}) {
  return group.addressUnit === 'byte' || group.addressUnit === 'bytes' || isMemoryGroup(group)
}

function getMemoryAddress(group = {}) {
  const sourceAddress = Number(group.sourceAddress)
  if (Number.isFinite(sourceAddress)) return Math.max(0, Math.floor(sourceAddress)) & 0xFFFF

  return Math.max(0, Number(group.startAddress) || 0) & 0xFFFF
}

function getMemoryByteLength(group = {}) {
  const sourceByteLength = Number(group.sourceByteLength)
  if (Number.isFinite(sourceByteLength) && sourceByteLength > 0) return Math.min(0xFFFF, Math.ceil(sourceByteLength))

  const byteLength = Number(group.byteLength)
  if (Number.isFinite(byteLength) && byteLength > 0) return Math.min(0xFFFF, Math.ceil(byteLength))

  return Math.max(1, Math.ceil((Number(group.wordQuantity) || 1) * 2))
}

function bytesToPaddedWords(bytes = []) {
  return bytesToWords(bytes.length % 2 === 0 ? bytes : bytes.concat(0))
}

function fillByteCacheFromBytes(byteCache, startAddress, bytes = []) {
  bytes.forEach((byte, offset) => {
    byteCache[startAddress + offset] = Number(byte) & 0xFF
  })
}

function fillWordCacheFromBytes(wordCache, startAddress, bytes = []) {
  const words = bytesToPaddedWords(bytes)

  words.forEach((word, offset) => {
    wordCache[startAddress + offset] = Number(word) & 0xFFFF
  })
}

function createWrittenRegisterSnapshots(group, wordCache) {
  return group.registers.map((register) => {
    const rawBytes = isByteAddressedGroup(group)
      ? (getRegisterBytesFromByteCache(register, wordCache) || [])
      : []
    const rawWords = isByteAddressedGroup(group)
      ? (getRegisterWordsFromByteCache(register, wordCache) || [])
      : (getRegisterWordsFromWordCache(register, wordCache) || [])
    const rawValue = isByteAddressedGroup(group)
      ? decodeRegisterFromByteCache(register, wordCache)
      : decodeRegisterValue(register, rawWords)

    return {
      rawBytes,
      rawWords,
      rawValue,
      displayValue: formatRegisterValue(register, rawValue)
    }
  })
}

function createWrittenRegisterSnapshot(group, register, byteCache) {
  const snapshots = createWrittenRegisterSnapshots({
    ...group,
    registers: [register]
  }, byteCache)

  return snapshots[0] || null
}

function groupHasBitFields(group = {}) {
  return (Array.isArray(group.registers) ? group.registers : []).some((register) => !!register.isBitField)
}

async function writeMemoryRegister(group, register, options = {}) {
  const memoryType = getMemoryType(group)
  const maxPacketLength = storageAccessMemory.resolveMaxPacketLength(options.maxPacketLength)
  const byteLength = Math.max(1, Number(register.byteLength) || 1)
  const address = Math.max(0, Math.floor(Number(register.address) || getMemoryAddress(group))) & 0xFFFF
  let bytes

  if (memoryType === null) {
    transport.showCommandAlert('内存写入', `暂不支持 ${group.sourceMemoryArea || '未知'} 内存区域`)
    return null
  }
  if (memoryType === STORAGE_ACCESS_CODE_AREA) {
    transport.showCommandAlert('内存写入', 'code 区暂不支持写入')
    return null
  }

  try {
    if (register.isBitField) {
      const baseBytes = await storageAccessMemory.readMemory(
        memoryType,
        address,
        byteLength,
        register.name ? `${register.name} 读改写` : '变量读改写',
        'parameter-group-memory-register-rmw-read',
        {
          maxFrameBytes: maxPacketLength
        }
      )
      if (!baseBytes) return null

      bytes = getGroupEncodedBytes({
        ...group,
        paddedByteLength: byteLength,
        registers: [{
          ...register,
          address,
          byteStart: 0
        }]
      }, baseBytes).slice(0, byteLength)
    } else {
      bytes = getRegisterEncodedBytes(register)
    }
  } catch (error) {
    transport.showCommandAlert('内存写入', error.message || '变量没有有效写入值')
    return null
  }

  if (!Array.isArray(bytes) || !bytes.length) {
    transport.showCommandAlert('内存写入', `${register.name || '变量'} 没有有效写入值`)
    return null
  }

  bytes = bytes.slice(0, byteLength)
  while (bytes.length < byteLength) bytes.push(0)

  const ok = await storageAccessMemory.writeMemory(
    memoryType,
    address,
    bytes,
    register.name || group.name || '变量写入',
    'parameter-group-memory-register-write',
    {
      maxFrameBytes: maxPacketLength
    }
  )
  if (!ok) return null

  const byteCache = {}
  fillByteCacheFromBytes(byteCache, address, bytes)

  return createWrittenRegisterSnapshot(group, register, byteCache)
}

async function readMemoryGroup(group, options = {}) {
  const memoryType = getMemoryType(group)
  const address = getMemoryAddress(group)
  const byteLength = getMemoryByteLength(group)
  const maxPacketLength = storageAccessMemory.resolveMaxPacketLength(options.maxPacketLength)

  if (memoryType === null) {
    transport.showCommandAlert('内存读取', `暂不支持 ${group.sourceMemoryArea || '未知'} 内存区域`)
    return null
  }

  const bytes = await storageAccessMemory.readMemory(
    memoryType,
    address,
    byteLength,
    group.name || '内存读取',
    'parameter-group-memory-read',
    {
      maxFrameBytes: maxPacketLength,
      showModal: options.showModal !== false
    }
  )
  if (!bytes) return null

  const wordCache = {}
  if (isByteAddressedGroup(group)) {
    fillByteCacheFromBytes(wordCache, address, bytes)
  } else {
    fillWordCacheFromBytes(wordCache, address, bytes)
  }

  return wordCache
}

async function writeMemoryGroup(group, options = {}) {
  const memoryType = getMemoryType(group)
  const address = getMemoryAddress(group)
  const byteLength = getMemoryByteLength(group)
  const maxPacketLength = storageAccessMemory.resolveMaxPacketLength(options.maxPacketLength)
  let bytes

  if (memoryType === null) {
    transport.showCommandAlert('内存写入', `暂不支持 ${group.sourceMemoryArea || '未知'} 内存区域`)
    return null
  }
  if (memoryType === STORAGE_ACCESS_CODE_AREA) {
    transport.showCommandAlert('内存写入', 'code 区暂不支持写入')
    return null
  }

  try {
    let baseBytes = null
    if (groupHasBitFields(group)) {
      baseBytes = await storageAccessMemory.readMemory(
        memoryType,
        address,
        byteLength,
        group.name ? `${group.name} 读改写` : '内存读改写',
        'parameter-group-memory-rmw-read',
        {
          maxFrameBytes: maxPacketLength
        }
      )
      if (!baseBytes) return null
    }

    bytes = getGroupEncodedBytes(group, baseBytes)
  } catch (error) {
    transport.showCommandAlert('内存写入', error.message || '寄存器组没有有效写入值')
    return null
  }

  bytes = bytes.slice(0, byteLength)
  const ok = await storageAccessMemory.writeMemory(
    memoryType,
    address,
    bytes,
    group.name || '内存写入',
    'parameter-group-memory-write',
    {
      maxFrameBytes: maxPacketLength
    }
  )
  if (!ok) return null

  const wordCache = {}
  if (isByteAddressedGroup(group)) {
    fillByteCacheFromBytes(wordCache, address, bytes)
  } else {
    fillWordCacheFromBytes(wordCache, address, bytes)
  }

  return createWrittenRegisterSnapshots(group, wordCache)
}

module.exports = {
  getMemoryAddress,
  getMemoryByteLength,
  getMemoryType,
  isByteAddressedGroup,
  isMemoryGroup,
  readMemoryGroup,
  writeMemoryGroup,
  writeMemoryRegister
}
