const {
  getDataType,
  normalizeGroup
} = require('../../domain/parameter-groups/model.js')
const {
  parseStructCatalog,
  parseStructDefinition: parseStructDefinitionSource
} = require('../../domain/parameter-groups/struct-parser.js')

function getRegisterByteLengthFromConfig(register) {
  const dataType = getDataType(register.dataType).key
  if (dataType === 'ascii' || dataType === 'utf8') return Math.max(1, Number(register.textByteLength) || 1)
  if (dataType === 'float' || dataType === 'int32_t' || dataType === 'uint32_t') return 4
  if (dataType === 'int8_t' || dataType === 'uint8_t') return 1

  return 2
}

function getRegistersByteLength(registers = []) {
  const explicitByteEnds = registers.map((register) => {
    const byteStart = Number(register && register.byteStart)
    if (!Number.isFinite(byteStart)) return null

    if (register.isBitField) {
      const bitOffset = Math.min(Math.max(Math.floor(Number(register.bitOffset) || 0), 0), 7)
      const bitWidth = Math.max(1, Math.round(Number(register.bitWidth) || 1))

      return Math.max(0, Math.floor(byteStart)) + Math.max(1, Math.ceil((bitOffset + bitWidth) / 8))
    }

    return Math.max(0, Math.floor(byteStart)) + getRegisterByteLengthFromConfig(register)
  }).filter((value) => Number.isFinite(value))

  if (explicitByteEnds.length) return Math.max.apply(null, explicitByteEnds)

  return registers.reduce((total, register) => total + getRegisterByteLengthFromConfig(register), 0)
}

function normalizeSymbolText(value) {
  return String(value || '')
    .replace(/^(?:IDATA|XDATA|DATA|CODE)[\s:_-]+/i, '')
    .replace(/^_+/, '')
    .replace(/[^A-Za-z0-9]/g, '')
    .toLowerCase()
}

function findStructCompletion(group, catalog) {
  const symbolName = group.sourceSymbolName || group.name
  const direct = catalog.variablesByName[normalizeSymbolText(symbolName)]
    || catalog.variablesByName[symbolName]

  if (direct) return direct

  const normalizedSymbol = normalizeSymbolText(symbolName)
  const normalizedType = normalizeSymbolText(group.sourceSymbolType)
  const expectedBytes = Number(group.sourceByteLength || group.byteLength || 0)

  const matchedStruct = catalog.structs.find((structInfo) => {
    const normalizedStructName = normalizeSymbolText(structInfo.name)
    if (normalizedType && normalizedType === normalizedStructName) {
      return true
    }
    if (normalizedSymbol && (
      normalizedSymbol === normalizedStructName
      || normalizedSymbol.indexOf(normalizedStructName) >= 0
      || normalizedStructName.indexOf(normalizedSymbol) >= 0
    )) {
      return true
    }

    return expectedBytes > 0 && getRegistersByteLength(structInfo.registers) === expectedBytes
  })

  return matchedStruct
    ? {
      name: symbolName,
      registers: matchedStruct.registers,
      structName: matchedStruct.name
    }
    : null
}

function createCompletedRegisters(group, completion) {
  const existingRemarksByByteStart = (Array.isArray(group.registers) ? group.registers : []).reduce((remarks, register) => {
    const byteStart = Number(register && register.byteStart)
    const remark = String(register && register.remark ? register.remark : '').trim()
    if (Number.isFinite(byteStart) && remark) remarks[Math.floor(byteStart)] = remark

    return remarks
  }, {})

  return completion.registers.map((register) => ({
    ...register,
    isStructField: true,
    remark: register.remark || existingRemarksByByteStart[Math.floor(Number(register.byteStart) || 0)] || '',
    sourceMemoryArea: group.sourceMemoryArea,
    sourceMemoryClass: group.sourceMemoryClass,
    sourceSymbolName: group.sourceSymbolName,
    sourceSymbolType: completion.structName || register.sourceSymbolType
  }))
}

function completeStructInstanceGroups(groups, sourceText, options = {}) {
  const catalog = parseStructCatalog(sourceText)
  let completedCount = 0
  let skippedCount = 0

  const nextGroups = groups.map((group) => {
    if (!group.sourceSymbolName || !group.sourceMemoryArea) return group

    const completion = findStructCompletion(group, catalog)
    if (!completion || !completion.registers || !completion.registers.length) {
      skippedCount += 1
      return group
    }

    const expectedBytes = Number(group.sourceByteLength || group.byteLength || 0)
    const actualBytes = getRegistersByteLength(completion.registers)
    if (expectedBytes > 0 && actualBytes !== expectedBytes && options.strictLength !== false) {
      skippedCount += 1
      return group
    }

    completedCount += 1

    return normalizeGroup({
      ...group,
      layout: 'struct',
      quantity: completion.registers.length,
      registers: createCompletedRegisters(group, completion)
    })
  })

  return {
    completedCount,
    groups: nextGroups,
    skippedCount,
    structCount: catalog.structs.length,
    variableCount: Object.keys(catalog.variablesByName).length
  }
}

function parseStructDefinition(sourceText) {
  return parseStructDefinitionSource(sourceText)
}

module.exports = {
  completeStructInstanceGroups,
  getRegistersByteLength,
  parseStructDefinition
}
