const {
  padHex
} = require('../../utils/base-utils.js')
const {
  bytesToWords,
  toByteArray
} = require('../../utils/binary-utils.js')
const {
  BYTE_ORDER_HIGH,
  appendCrc16Ccitt,
  crc16Ccitt,
  hasValidCrc16Ccitt
} = require('../../utils/crc.js')

const PROTOCOL_NAME = 'storage-access'

const CMD_ERR_MASK = 0x80
const CMD_CONTROL_FLAG = 0x40
const CMD_SPECIAL_OP_MASK = 0x3F
const CMD_WRITE_MASK = 0x08
const CMD_ADDRESS_MODE_MASK = 0x07
const CMD_RESERVED_MASK = 0x30
const CMD_CONTROL = CMD_CONTROL_FLAG
const CONTROL_RESPONSE_HEADER_LENGTH = 1
const ADDRESS16_BYTE_LENGTH = 2
const ADDRESS32_BYTE_LENGTH = 4

const AREA = {
  CODEINFO: 0x00,
  ADDR32: 0x07,
  DATA: 0x01,
  IDATA: 0x02,
  XDATA: 0x03,
  CODE: 0x04
}

const CONTROL_OP = {
  RESET: 0x01
}

const AREA_NAMES = {
  [AREA.CODEINFO]: 'CODEINFO',
  [AREA.ADDR32]: 'ADDR32',
  [AREA.DATA]: 'DATA',
  [AREA.IDATA]: 'IDATA',
  [AREA.XDATA]: 'XDATA',
  [AREA.CODE]: 'CODE'
}

const AREA_BY_NAME = {
  CODEINFO: AREA.CODEINFO,
  CODE_INFO: AREA.CODEINFO,
  ADDR32: AREA.ADDR32,
  ADDRESS32: AREA.ADDR32,
  DATA: AREA.DATA,
  IDATA: AREA.IDATA,
  XDATA: AREA.XDATA,
  CODE: AREA.CODE
}

const EXCEPTION_MESSAGES = {
  0x01: '非法命令',
  0x02: '非法区域',
  0x03: '非法地址',
  0x04: '非法长度',
  0x05: '写保护',
  0x06: '设备忙',
  0x07: '格式错误',
  0x08: '访问被拒绝',
  0x09: '内部错误',
  0x0A: '对齐错误',
  0x0B: '范围溢出',
  0x0C: '不支持的操作'
}

const DEFAULT_MAX_FRAME_BYTES = 64
const UNLIMITED_FRAME_BYTES = 0
const MAX_UINT16 = 0xFFFF
const MAX_UINT32 = 0xFFFFFFFF
const MAX_PAYLOAD_BYTES = MAX_UINT16

const READ_REQUEST_LENGTH_16 = 1 + ADDRESS16_BYTE_LENGTH + 2 + 2
const READ_REQUEST_LENGTH_32 = 1 + ADDRESS32_BYTE_LENGTH + 2 + 2
const WRITE_REQUEST_OVERHEAD_16 = 1 + ADDRESS16_BYTE_LENGTH + 2 + 2
const WRITE_REQUEST_OVERHEAD_32 = 1 + ADDRESS32_BYTE_LENGTH + 2 + 2
const READ_RESPONSE_OVERHEAD_16 = 1 + ADDRESS16_BYTE_LENGTH + 2 + 2
const READ_RESPONSE_OVERHEAD_32 = 1 + ADDRESS32_BYTE_LENGTH + 2 + 2
const WRITE_RESPONSE_LENGTH_16 = 1 + ADDRESS16_BYTE_LENGTH + 2 + 2
const WRITE_RESPONSE_LENGTH_32 = 1 + ADDRESS32_BYTE_LENGTH + 2 + 2
const EXCEPTION_RESPONSE_LENGTH = 4
const CODE_INFO_DESCRIPTOR_ADDRESS = 0
const CODE_INFO_DESCRIPTOR_BYTE_LENGTH = 9
const CODE_INFO_DESCRIPTOR_RESPONSE_LENGTH = 1 + CODE_INFO_DESCRIPTOR_BYTE_LENGTH + 2
const MEMORY_ENDIAN_MARK_BIG = 0x55AA
const MEMORY_ENDIAN_MARK_LITTLE = 0xAA55
const MEMORY_ENDIAN = {
  BIG: 'big',
  LITTLE: 'little'
}

const STORAGE_CRC_OPTIONS = {
  byteOrder: BYTE_ORDER_HIGH
}
const VALID_AREAS = [AREA.CODEINFO, AREA.ADDR32, AREA.DATA, AREA.IDATA, AREA.XDATA, AREA.CODE]
const MEMORY_AREAS = [AREA.CODEINFO, AREA.ADDR32, AREA.DATA, AREA.IDATA, AREA.XDATA, AREA.CODE]
const WRITABLE_AREAS = [AREA.ADDR32, AREA.DATA, AREA.IDATA, AREA.XDATA]
const RESERVED_AREAS = [0x05, 0x06]

function toByte(value, label) {
  if (!Number.isInteger(value) || value < 0 || value > 0xFF) {
    throw new Error(`${label}必须在 0x00 至 0xFF 之间`)
  }

  return value
}

function toWord(value, label) {
  if (!Number.isInteger(value) || value < 0 || value > 0xFFFF) {
    throw new Error(`${label}必须在 0x0000 至 0xFFFF 之间`)
  }

  return value
}

function toUint32(value, label) {
  if (!Number.isInteger(value) || value < 0 || value > MAX_UINT32) {
    throw new Error(`${label}必须在 0x00000000 至 0xFFFFFFFF 之间`)
  }

  return value
}

function normalizeArea(value) {
  if (typeof value === 'string') {
    const area = AREA_BY_NAME[value.trim().toUpperCase()]
    if (area !== undefined) return area
  }

  const area = toByte(Number(value), '存储区域')
  if (VALID_AREAS.indexOf(area) < 0) {
    throw new Error('存储区域必须为 codeinfo/data/idata/xdata/code/addr32')
  }

  return area
}

function isAddress32Area(area) {
  return Number(area) === AREA.ADDR32
}

function getAddressFieldByteLength(area) {
  return isAddress32Area(area) ? ADDRESS32_BYTE_LENGTH : ADDRESS16_BYTE_LENGTH
}

function getMemoryHeaderLength(area) {
  return 1 + getAddressFieldByteLength(area) + 2
}

function getReadRequestLength(area) {
  if (Number(area) === AREA.CODEINFO) return 3

  return getMemoryHeaderLength(area) + 2
}

function getWriteRequestOverhead(area) {
  return getMemoryHeaderLength(area) + 2
}

function getReadResponseOverhead(area) {
  if (Number(area) === AREA.CODEINFO) return 1 + 2

  return getMemoryHeaderLength(area) + 2
}

function getWriteResponseLength(area) {
  return getMemoryHeaderLength(area) + 2
}

function isCodeInfoDescriptorArea(area) {
  return Number(area) === AREA.CODEINFO
}

function normalizeMemoryArea(value) {
  const area = normalizeArea(value)
  if (MEMORY_AREAS.indexOf(area) < 0) {
    throw new Error('存储访问区域必须为 codeinfo/data/idata/xdata/code/addr32')
  }

  return area
}

function toByteLength(value, label = '字节长度', maxPayload = MAX_PAYLOAD_BYTES) {
  const byteLength = toWord(Number(value), label)
  if (byteLength === 0) {
    throw new Error(`${label}必须大于 0`)
  }
  if (maxPayload > 0 && byteLength > maxPayload) {
    throw new Error(`单帧最多访问 ${maxPayload} 字节`)
  }

  return byteLength
}

function splitWord(value) {
  return [(value >> 8) & 0xFF, value & 0xFF]
}

function splitDword(value) {
  const normalizedValue = Number(value) >>> 0

  return [
    (normalizedValue >>> 24) & 0xFF,
    (normalizedValue >>> 16) & 0xFF,
    (normalizedValue >>> 8) & 0xFF,
    normalizedValue & 0xFF
  ]
}

function readWord(bytes, offset) {
  return (((bytes[offset] || 0) << 8) | (bytes[offset + 1] || 0)) & 0xFFFF
}

function readDword(bytes, offset) {
  return (
    ((bytes[offset] || 0) * 0x1000000)
    + (((bytes[offset + 1] || 0) << 16) >>> 0)
    + (((bytes[offset + 2] || 0) << 8) >>> 0)
    + (bytes[offset + 3] || 0)
  ) >>> 0
}

function normalizeDescriptorAddressWidth(value) {
  const numberValue = Number(value)
  if (numberValue === 16 || numberValue === 32) return numberValue

  throw new Error('CodeInfo 描述符地址长度必须为 16 或 32')
}

function parseMemoryEndianMark(bytes, offset) {
  const marker = readWord(bytes, offset)
  if (marker === MEMORY_ENDIAN_MARK_BIG) {
    return {
      marker,
      memoryEndian: MEMORY_ENDIAN.BIG
    }
  }
  if (marker === MEMORY_ENDIAN_MARK_LITTLE) {
    return {
      marker,
      memoryEndian: MEMORY_ENDIAN.LITTLE
    }
  }

  return {
    marker,
    memoryEndian: ''
  }
}

function normalizeMaxFrameBytes(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES) {
  const numberValue = Number(maxFrameBytes)
  if (Number.isFinite(numberValue) && Math.round(numberValue) === UNLIMITED_FRAME_BYTES) return UNLIMITED_FRAME_BYTES
  if (Number.isFinite(numberValue) && numberValue > 0) return Math.round(numberValue)

  return DEFAULT_MAX_FRAME_BYTES
}

function resolveDescriptorMaxFrameBytes(configuredMaxFrameBytes, descriptorMaxFrameBytes) {
  const configured = normalizeMaxFrameBytes(configuredMaxFrameBytes)
  const descriptor = normalizeMaxFrameBytes(descriptorMaxFrameBytes)
  if (descriptor === UNLIMITED_FRAME_BYTES) return configured
  if (configured === UNLIMITED_FRAME_BYTES) return descriptor

  return Math.min(configured, descriptor)
}

function getPayloadLimitFromFrame(maxFrameBytes, overhead) {
  const frameBytes = normalizeMaxFrameBytes(maxFrameBytes)
  if (frameBytes === UNLIMITED_FRAME_BYTES) return MAX_PAYLOAD_BYTES

  return Math.max(0, Math.min(MAX_PAYLOAD_BYTES, frameBytes - overhead))
}

function getMaxReadByteLength(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES, area = AREA.ADDR32) {
  return getPayloadLimitFromFrame(maxFrameBytes, getReadResponseOverhead(area))
}

function getMaxWriteByteLength(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES, area = AREA.ADDR32) {
  return getPayloadLimitFromFrame(maxFrameBytes, getWriteRequestOverhead(area))
}

function buildCommand(area, isWrite = false) {
  const normalizedArea = normalizeMemoryArea(area)

  if (RESERVED_AREAS.indexOf(normalizedArea) >= 0) {
    throw new Error('存储访问区域号 0x05/0x06 暂时保留')
  }
  if (isWrite && WRITABLE_AREAS.indexOf(normalizedArea) < 0) {
    throw new Error(`${AREA_NAMES[normalizedArea] || '该'} 区不可写`)
  }

  return (isWrite ? CMD_WRITE_MASK : 0x00) | normalizedArea
}

function buildSpecialCommand(operation) {
  const op = toByte(Number(operation), '特殊指令') & CMD_SPECIAL_OP_MASK
  if (op <= 0) {
    throw new Error('特殊指令必须在 0x01 至 0x3F 之间')
  }

  return CMD_CONTROL_FLAG | op
}

function decodeCommand(command) {
  const cmd = toByte(Number(command), '命令字')
  const sourceCommand = cmd & ~CMD_ERR_MASK
  const isControl = !!(sourceCommand & CMD_CONTROL_FLAG)
  const operation = isControl ? (sourceCommand & CMD_SPECIAL_OP_MASK) : 0
  const area = isControl ? CMD_CONTROL : (sourceCommand & CMD_ADDRESS_MODE_MASK)
  const reservedBits = sourceCommand & CMD_RESERVED_MASK

  return {
    addressBytes: isControl ? 0 : getAddressFieldByteLength(area),
    area,
    command: cmd,
    hasError: !!(cmd & CMD_ERR_MASK),
    hasInvalidSpecialOperation: isControl && operation === 0,
    hasReservedBits: !isControl && reservedBits !== 0,
    isAddress32: !isControl && isAddress32Area(area),
    isControl,
    isWrite: !isControl && !!(sourceCommand & CMD_WRITE_MASK),
    operation,
    sourceCommand
  }
}

function hasValidStorageCrc(bytes) {
  const frame = toByteArray(bytes)
  if (frame.length < 3) return false
  if (frame.length >= 4) return hasValidCrc16Ccitt(frame, STORAGE_CRC_OPTIONS)

  const expected = crc16Ccitt(frame.slice(0, -2), STORAGE_CRC_OPTIONS)
  const received = (((frame[frame.length - 2] || 0) << 8) | (frame[frame.length - 1] || 0)) & 0xFFFF
  return expected === received
}

function appendStorageCrc(bytes) {
  return appendCrc16Ccitt(bytes, STORAGE_CRC_OPTIONS)
}

function buildReadFrame(area, address, byteLength, options = {}) {
  const normalizedArea = normalizeMemoryArea(area)
  if (isCodeInfoDescriptorArea(normalizedArea)) {
    return buildCodeInfoDescriptorFrame()
  }

  const addressBytes = getAddressFieldByteLength(normalizedArea)
  const startAddress = addressBytes === ADDRESS32_BYTE_LENGTH
    ? toUint32(Number(address), '内存地址')
    : toWord(Number(address), '内存地址')
  const maxByteLength = getMaxReadByteLength(options.maxFrameBytes, normalizedArea)
  const length = toByteLength(Number(byteLength), '读取字节长度', maxByteLength || MAX_PAYLOAD_BYTES)
  const command = buildCommand(normalizedArea, false)
  const addressParts = addressBytes === ADDRESS32_BYTE_LENGTH ? splitDword(startAddress) : splitWord(startAddress)
  const lengthParts = splitWord(toWord(length, '读取字节长度'))

  return appendStorageCrc([command].concat(addressParts, lengthParts))
}

function buildWriteFrame(area, address, bytes, options = {}) {
  const normalizedArea = normalizeMemoryArea(area)
  if (WRITABLE_AREAS.indexOf(normalizedArea) < 0) {
    throw new Error(`${AREA_NAMES[normalizedArea] || '该'} 区不可写`)
  }

  const addressBytes = getAddressFieldByteLength(normalizedArea)
  const startAddress = addressBytes === ADDRESS32_BYTE_LENGTH
    ? toUint32(Number(address), '内存地址')
    : toWord(Number(address), '内存地址')
  const dataBytes = toByteArray(bytes).map((byte) => toByte(Number(byte), '写入字节'))
  const maxByteLength = getMaxWriteByteLength(options.maxFrameBytes, normalizedArea)
  const length = toByteLength(dataBytes.length, '写入字节长度', maxByteLength || MAX_PAYLOAD_BYTES)
  const command = buildCommand(normalizedArea, true)
  const addressParts = addressBytes === ADDRESS32_BYTE_LENGTH ? splitDword(startAddress) : splitWord(startAddress)
  const lengthParts = splitWord(toWord(length, '写入字节长度'))

  return appendStorageCrc([command].concat(addressParts, lengthParts, dataBytes))
}

function buildControlFrame(operation, dataBytes = []) {
  const command = buildSpecialCommand(operation)
  const payload = toByteArray(dataBytes).map((byte) => toByte(Number(byte), '指令数据'))

  return appendStorageCrc([command].concat(payload))
}

function buildCodeInfoDescriptorFrame() {
  return appendStorageCrc([buildCommand(AREA.CODEINFO, false)])
}

function parseCodeInfoDescriptorBytes(bytes) {
  const dataBytes = toByteArray(bytes)
  if (dataBytes.length < CODE_INFO_DESCRIPTOR_BYTE_LENGTH) {
    throw new Error('CodeInfo 描述符长度无效')
  }

  return {
    codeInfoAddress: readDword(dataBytes, 0),
    codeInfoAddressWidth: dataBytes[6] & 0xFF,
    codeInfoByteLength: readWord(dataBytes, 4),
    codeInfoDescriptorBytes: dataBytes.slice(0, CODE_INFO_DESCRIPTOR_BYTE_LENGTH),
    codeInfoMaxPacketLength: readWord(dataBytes, 7),
    codeInfoMemoryEndian: MEMORY_ENDIAN.BIG,
    codeInfoMemoryEndianMark: 0
  }
}

function formatHex(bytes) {
  return toByteArray(bytes).map((byte) => byte.toString(16).padStart(2, '0').toUpperCase()).join(' ')
}

function parseStorageAccessResponse(bytes) {
  const frame = toByteArray(bytes)
  if (frame.length < 3 || !hasValidStorageCrc(frame)) return null

  const command = frame[0] & 0xFF
  const decoded = decodeCommand(command)
  if (decoded.isControl) return parseStorageControlResponse(frame, decoded)

  if (decoded.hasError) {
    if (frame.length !== EXCEPTION_RESPONSE_LENGTH) return null

    return {
      area: decoded.area,
      areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
      command,
      exceptionCode: frame[1] & 0xFF,
      isException: true,
      isWrite: decoded.isWrite,
      protocol: PROTOCOL_NAME,
      sourceCommand: decoded.sourceCommand
    }
  }

  if (decoded.hasReservedBits) return null
  if (!AREA_NAMES[decoded.area]) return null

  if (isCodeInfoDescriptorArea(decoded.area)) {
    if (decoded.isWrite || frame.length !== CODE_INFO_DESCRIPTOR_RESPONSE_LENGTH) return null

    const dataBytes = frame.slice(1, 1 + CODE_INFO_DESCRIPTOR_BYTE_LENGTH)
    const response = {
      address: CODE_INFO_DESCRIPTOR_ADDRESS,
      addressWidth: 0,
      area: decoded.area,
      areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
      byteLength: CODE_INFO_DESCRIPTOR_BYTE_LENGTH,
      command,
      dataBytes,
      isException: false,
      isAddress32: false,
      isWrite: false,
      protocol: PROTOCOL_NAME,
      words: bytesToWords(dataBytes.length % 2 === 0 ? dataBytes : dataBytes.concat(0))
    }

    return {
      ...response,
      ...parseCodeInfoDescriptorBytes(dataBytes)
    }
  }

  const addressBytes = decoded.addressBytes
  const headerLength = getMemoryHeaderLength(decoded.area)

  if (decoded.isWrite) {
    if (frame.length !== getWriteResponseLength(decoded.area)) return null

    return {
      address: decoded.isAddress32 ? readDword(frame, 1) : readWord(frame, 1),
      addressWidth: decoded.isAddress32 ? 32 : 16,
      area: decoded.area,
      areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
      byteLength: readWord(frame, 1 + addressBytes),
      command,
      dataBytes: [],
      isException: false,
      isAddress32: decoded.isAddress32,
      isWrite: true,
      protocol: PROTOCOL_NAME
    }
  }

  if (frame.length < getReadResponseOverhead(decoded.area)) return null

  const address = decoded.isAddress32 ? readDword(frame, 1) : readWord(frame, 1)
  const byteLength = readWord(frame, 1 + addressBytes)
  const dataStart = headerLength
  const dataEnd = dataStart + byteLength
  if (frame.length !== dataEnd + 2) return null

  const dataBytes = frame.slice(dataStart, dataEnd)

  const response = {
    address,
    addressWidth: decoded.isAddress32 ? 32 : 16,
    area: decoded.area,
    areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
    byteLength,
    command,
    dataBytes,
    isException: false,
    isAddress32: decoded.isAddress32,
    isWrite: false,
    protocol: PROTOCOL_NAME,
    words: bytesToWords(dataBytes.length % 2 === 0 ? dataBytes : dataBytes.concat(0))
  }

  return response
}

function parseStorageControlResponse(frame, decoded) {
  if (decoded.hasInvalidSpecialOperation) return null

  if (decoded.hasError) {
    if (frame.length !== EXCEPTION_RESPONSE_LENGTH) return null

    return {
      area: CMD_CONTROL,
      areaName: 'CONTROL',
      command: frame[0] & 0xFF,
      exceptionCode: frame[1] & 0xFF,
      isControl: true,
      isException: true,
      isWrite: false,
      operation: decoded.operation,
      protocol: PROTOCOL_NAME,
      sourceCommand: decoded.sourceCommand
    }
  }

  if (frame.length < CONTROL_RESPONSE_HEADER_LENGTH + 2) return null

  const dataStart = CONTROL_RESPONSE_HEADER_LENGTH
  const dataEnd = frame.length - 2
  const byteLength = Math.max(0, dataEnd - dataStart)

  const dataBytes = frame.slice(dataStart, dataEnd)

  return {
    area: CMD_CONTROL,
    areaName: 'CONTROL',
    byteLength,
    command: frame[0] & 0xFF,
    dataBytes,
    isControl: true,
    isException: false,
    isWrite: false,
    operation: decoded.operation,
    protocol: PROTOCOL_NAME
  }
}

function parseStorageAccessRequest(bytes) {
  const frame = toByteArray(bytes)
  if (frame.length < 3 || !hasValidStorageCrc(frame)) return null

  const command = frame[0] & 0xFF

  const decoded = decodeCommand(command)
  if (decoded.isControl) return parseStorageControlRequest(frame, decoded)
  if (decoded.hasError) return null
  if (decoded.hasReservedBits) return null
  if (!AREA_NAMES[decoded.area]) return null
  if (isCodeInfoDescriptorArea(decoded.area)) {
    if (decoded.isWrite || frame.length !== getReadRequestLength(decoded.area)) return null

    return {
      address: CODE_INFO_DESCRIPTOR_ADDRESS,
      addressWidth: 0,
      area: decoded.area,
      areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
      byteLength: CODE_INFO_DESCRIPTOR_BYTE_LENGTH,
      command,
      dataBytes: [],
      isAddress32: false,
      isWrite: false,
      kind: 'raw-hex',
      operation: 'read',
      protocol: PROTOCOL_NAME,
      quantity: CODE_INFO_DESCRIPTOR_BYTE_LENGTH
    }
  }
  if (frame.length < READ_REQUEST_LENGTH_16) return null

  const addressBytes = decoded.addressBytes
  const headerLength = getMemoryHeaderLength(decoded.area)
  if (frame.length < headerLength + 2) return null

  const address = decoded.isAddress32 ? readDword(frame, 1) : readWord(frame, 1)
  const byteLength = readWord(frame, 1 + addressBytes)
  const expectedLength = decoded.isWrite
    ? headerLength + byteLength + 2
    : headerLength + 2

  if (byteLength <= 0 || frame.length !== expectedLength) return null

  return {
    address,
    addressWidth: decoded.isAddress32 ? 32 : 16,
    area: decoded.area,
    areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
    byteLength,
    command,
    dataBytes: decoded.isWrite ? frame.slice(headerLength, headerLength + byteLength) : [],
    isAddress32: decoded.isAddress32,
    isWrite: decoded.isWrite,
    kind: 'raw-hex',
    operation: decoded.isWrite ? 'write' : 'read',
    protocol: PROTOCOL_NAME,
    quantity: byteLength
  }
}

function parseStorageControlRequest(frame, decoded = decodeCommand(frame && frame[0])) {
  if (frame.length < 3 || decoded.hasInvalidSpecialOperation) return null

  const dataStart = CONTROL_RESPONSE_HEADER_LENGTH
  const dataEnd = frame.length - 2
  const byteLength = Math.max(0, dataEnd - dataStart)

  return {
    area: CMD_CONTROL,
    areaName: 'CONTROL',
    byteLength,
    command: frame[0] & 0xFF,
    dataBytes: frame.slice(dataStart, dataEnd),
    isControl: true,
    isWrite: false,
    kind: 'storage-control',
    operation: decoded.operation,
    protocol: PROTOCOL_NAME
  }
}

function getExpectedResponseLength(expected, responseCommand, responseBytes = []) {
  if (!expected) return 0
  const command = Number(responseCommand) & 0xFF
  if (command === (expected.command | CMD_ERR_MASK)) return EXCEPTION_RESPONSE_LENGTH
  if (command !== expected.command) return 0

  if (expected.isControl) {
    if (responseBytes.length < CONTROL_RESPONSE_HEADER_LENGTH) return 0

    return CONTROL_RESPONSE_HEADER_LENGTH + Number(expected.expectedByteLength || 0) + 2
  }

  if (expected.operation === 'write' || expected.isWrite) {
    return getWriteResponseLength(expected.area)
  }

  if (isCodeInfoDescriptorArea(expected.area)) {
    return CODE_INFO_DESCRIPTOR_RESPONSE_LENGTH
  }

  const headerLength = getMemoryHeaderLength(expected.area)
  if (responseBytes.length < headerLength) return 0

  return headerLength + readWord(responseBytes, 1 + getAddressFieldByteLength(expected.area)) + 2
}

function isExpectedResponse(response, expected) {
  if (!response || !expected) return false
  const sourceCommand = response.isException ? response.sourceCommand : response.command
  if (sourceCommand !== expected.command) return false
  if (expected.isControl) {
    if (!response.isControl) return false
    if (response.isException) return true
    if (response.operation !== expected.operation) return false

    return true
  }
  if (!response.isException && response.area !== expected.area) return false
  if (response.isException) return true
  if (response.address !== expected.address) return false
  if (response.byteLength !== expected.byteLength) return false
  if (!response.isWrite && (!Array.isArray(response.dataBytes) || response.dataBytes.length !== expected.byteLength)) return false

  return true
}

function getExceptionText(code) {
  return EXCEPTION_MESSAGES[code] || '未知异常'
}

function formatExceptionMessage(response) {
  const sourceCommand = response && response.sourceCommand
  const exceptionCode = response && response.exceptionCode
  const exceptionText = getExceptionText(exceptionCode)

  return `设备返回异常帧：命令 0x${padHex(sourceCommand, 2)}，异常码 0x${padHex(exceptionCode, 2)}（${exceptionText}）`
}

function getReadBufferHint(expected) {
  if (!expected) return 0
  if (expected.isControl) return expected.responseBufferHint || CONTROL_RESPONSE_HEADER_LENGTH + Number(expected.expectedByteLength || 0) + 2
  if (expected.operation === 'write' || expected.isWrite) {
    return getWriteResponseLength(expected.area)
  }
  if (isCodeInfoDescriptorArea(expected.area)) {
    return CODE_INFO_DESCRIPTOR_RESPONSE_LENGTH
  }

  return getReadResponseOverhead(expected.area) + Number(expected.byteLength || expected.quantity || 0)
}

function alignResponseBuffer(buffer, expected) {
  if (!Array.isArray(buffer) || !buffer.length || !expected) return

  const expectedCommands = [expected.command, expected.command | CMD_ERR_MASK]
  let matchIndex = -1

  for (let index = 0; index < buffer.length; index += 1) {
    if (expectedCommands.indexOf(buffer[index]) < 0) continue

    matchIndex = index
    break
  }

  if (matchIndex > 0) {
    buffer.splice(0, matchIndex)
  } else if (matchIndex < 0 && buffer.length > 1) {
    buffer.splice(0, buffer.length - 1)
  }
}

function readResponseFromBuffer(buffer, expected, options = {}) {
  if (!Array.isArray(buffer) || !buffer.length || !expected) {
    return {
      status: 'pending'
    }
  }

  alignResponseBuffer(buffer, expected)

  while (buffer.length >= 1) {
    const responseCommand = buffer[0]
    const responseLength = getExpectedResponseLength(expected, responseCommand, buffer)

    if (!responseLength) {
      return {
        status: 'pending'
      }
    }

    const frameLimit = normalizeMaxFrameBytes(
      options.maxFrameBytes === undefined ? expected.maxFrameBytes : options.maxFrameBytes
    )
    if (frameLimit > 0 && responseLength > frameLimit) {
      return {
        frameLimit,
        responseLength,
        status: 'frame-too-long'
      }
    }

    if (buffer.length < responseLength) {
      return {
        status: 'pending'
      }
    }

    const frameBytes = buffer.slice(0, responseLength)
    const response = parseStorageAccessResponse(frameBytes)
    if (!response) {
      return {
        frameBytes,
        status: 'invalid'
      }
    }

    if (!isExpectedResponse(response, expected)) {
      buffer.shift()
      alignResponseBuffer(buffer, expected)
      continue
    }

    if (response.isException) {
      return {
        message: formatExceptionMessage(response),
        response,
        status: 'exception'
      }
    }

    buffer.splice(0, responseLength)
    return {
      response,
      status: 'complete'
    }
  }

  return {
    status: 'pending'
  }
}

function createExpected(area, address, byteLength, isWrite, kind, options = {}) {
  const normalizedArea = normalizeMemoryArea(area)
  const command = buildCommand(normalizedArea, isWrite)

  return {
    address,
    addressWidth: isAddress32Area(normalizedArea) ? 32 : 16,
    area: normalizedArea,
    byteLength,
    command,
    isAddress32: isAddress32Area(normalizedArea),
    isWrite,
    kind,
    operation: isWrite ? 'write' : 'read',
    protocol: PROTOCOL_NAME,
    quantity: byteLength
  }
}

function createControlExpected(operation, kind = 'storage-control', options = {}) {
  const op = toByte(Number(operation), '特殊指令')
  const command = buildSpecialCommand(op)

  return {
    area: CMD_CONTROL,
    byteLength: 0,
    command,
    expectedByteLength: Number(options.expectedByteLength) || 0,
    isControl: true,
    isWrite: false,
    kind,
    operation: op,
    protocol: PROTOCOL_NAME,
    responseBufferHint: CONTROL_RESPONSE_HEADER_LENGTH + (Number(options.expectedByteLength) || 0) + 2
  }
}

function formatAddress(value) {
  return Number(value || 0).toString(16).toUpperCase()
}

function getChunkLabel(label, chunks, chunk) {
  if (!label || chunks.length <= 1) return label

  return `${label} ${formatAddress(chunk.address)}-${formatAddress(chunk.address + chunk.quantity - 1)}`
}

function splitQuantity(startAddress, quantity, maxQuantity) {
  const chunks = []
  let address = Number(startAddress) || 0
  let remaining = Math.max(0, Math.floor(Number(quantity) || 0))
  const chunkLimit = Math.max(1, Math.floor(Number(maxQuantity) || remaining || 1))

  while (remaining > 0) {
    const chunkQuantity = Math.min(remaining, chunkLimit)
    chunks.push({
      address,
      quantity: chunkQuantity
    })
    address += chunkQuantity
    remaining -= chunkQuantity
  }

  return chunks
}

function getReadChunks(startAddress, byteLength, options = {}) {
  const maxByteLength = getMaxReadByteLength(options.maxFrameBytes, options.area)

  return splitQuantity(startAddress, byteLength, maxByteLength || byteLength)
}

function getWriteChunks(startAddress, bytes, options = {}) {
  const sourceBytes = Array.prototype.slice.call(bytes || []).map((byte) => Number(byte) & 0xFF)
  const maxByteLength = getMaxWriteByteLength(options.maxFrameBytes, options.area)
  const chunks = splitQuantity(startAddress, sourceBytes.length, maxByteLength || sourceBytes.length)
  let offset = 0

  return chunks.map((chunk) => {
    const dataBytes = sourceBytes.slice(offset, offset + chunk.quantity)
    offset += chunk.quantity

    return {
      ...chunk,
      dataBytes
    }
  })
}

const response = {
  createControlExpected,
  createExpected,
  formatExceptionMessage,
  getExceptionText,
  getExpectedResponseLength,
  getReadBufferHint,
  isExpectedResponse,
  parseStorageAccessRequest,
  parseStorageAccessResponse,
  readResponseFromBuffer
}

module.exports = {
  AREA,
  AREA_BY_NAME,
  AREA_NAMES,
  ADDRESS16_BYTE_LENGTH,
  ADDRESS32_BYTE_LENGTH,
  CMD_CONTROL,
  CMD_CONTROL_FLAG,
  CMD_SPECIAL_OP_MASK,
  CMD_ADDRESS_MODE_MASK,
  CMD_ERR_MASK,
  CMD_RESERVED_MASK,
  CMD_WRITE_MASK,
  CONTROL_OP,
  CONTROL_RESPONSE_HEADER_LENGTH,
  DEFAULT_MAX_FRAME_BYTES,
  EXCEPTION_MESSAGES,
  EXCEPTION_RESPONSE_LENGTH,
  CODE_INFO_DESCRIPTOR_ADDRESS,
  CODE_INFO_DESCRIPTOR_BYTE_LENGTH,
  MAX_PAYLOAD_BYTES,
  MEMORY_ENDIAN,
  MEMORY_ENDIAN_MARK_BIG,
  MEMORY_ENDIAN_MARK_LITTLE,
  PROTOCOL_NAME,
  READ_REQUEST_LENGTH_16,
  READ_REQUEST_LENGTH_32,
  READ_RESPONSE_OVERHEAD_16,
  READ_RESPONSE_OVERHEAD_32,
  STORAGE_CRC_OPTIONS,
  UNLIMITED_FRAME_BYTES,
  WRITE_REQUEST_OVERHEAD_16,
  WRITE_REQUEST_OVERHEAD_32,
  WRITE_RESPONSE_LENGTH_16,
  WRITE_RESPONSE_LENGTH_32,
  appendStorageCrc,
  buildCommand,
  buildCodeInfoDescriptorFrame,
  buildControlFrame,
  buildReadFrame,
  buildSpecialCommand,
  buildWriteFrame,
  createControlExpected,
  createExpected,
  decodeCommand,
  formatHex,
  getChunkLabel,
  getReadChunks,
  getWriteChunks,
  getMaxReadByteLength,
  getMaxWriteByteLength,
  hasValidStorageCrc,
  normalizeDescriptorAddressWidth,
  normalizeArea,
  normalizeMaxFrameBytes,
  normalizeMemoryArea,
  parseCodeInfoDescriptorBytes,
  resolveDescriptorMaxFrameBytes,
  response,
  splitDword,
  splitQuantity,
  splitWord,
  toByte,
  toByteLength,
  toUint32,
  toWord
}
