const {
  DATA_TYPE_OPTIONS,
  DEFAULT_DATA_TYPE,
  DEFAULT_TEXT_BYTE_LENGTH,
  GROUP_LAYOUT_STRUCT,
  MAX_TEXT_BYTE_LENGTH
} = require('./constants.js')

function getDataType(dataType) {
  return DATA_TYPE_OPTIONS.find((item) => item.key === dataType)
    || DATA_TYPE_OPTIONS.find((item) => item.key === DEFAULT_DATA_TYPE)
    || DATA_TYPE_OPTIONS[0]
}

function getDataTypeIndex(dataType) {
  return Math.max(0, DATA_TYPE_OPTIONS.findIndex((item) => item.key === getDataType(dataType).key))
}

function normalizeTextByteLength(value, fallback = DEFAULT_TEXT_BYTE_LENGTH) {
  const numberValue = Number(value)
  const rounded = Number.isFinite(numberValue) ? Math.round(numberValue) : fallback

  return Math.min(Math.max(rounded, 1), MAX_TEXT_BYTE_LENGTH)
}

function alignEvenByteLength(byteLength) {
  const length = Math.max(1, Math.round(Number(byteLength) || 1))

  return length % 2 === 0 ? length : length + 1
}

function getRegisterTextByteLength(register = {}) {
  return normalizeTextByteLength(register.textByteLength, DEFAULT_TEXT_BYTE_LENGTH)
}

function getRegisterRawByteLength(register = {}) {
  const candidates = [
    register.sourceByteLength,
    register.rawByteLength,
    register.textByteLength,
    register.byteLength
  ]

  for (const value of candidates) {
    const numberValue = Number(value)
    if (Number.isFinite(numberValue) && numberValue > 0) {
      return Math.max(1, Math.floor(numberValue))
    }
  }

  return 1
}

function isStructLayout(layout) {
  return layout === GROUP_LAYOUT_STRUCT
}

function isBitFieldRegister(register = {}) {
  return !!register.isBitField
}

function normalizeBitOffset(value) {
  const numberValue = Math.floor(Number(value) || 0)

  return Math.min(Math.max(numberValue, 0), 7)
}

function normalizeBitWidth(value) {
  const numberValue = Math.round(Number(value) || 1)

  return Math.min(Math.max(numberValue, 1), 32)
}

function getBitFieldByteLength(register = {}) {
  const bitOffset = normalizeBitOffset(register.bitOffset)
  const bitWidth = normalizeBitWidth(register.bitWidth)

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

function getBitFieldMaxValue(register = {}) {
  const bitWidth = normalizeBitWidth(register.bitWidth)

  return bitWidth >= 32 ? 0xFFFFFFFF : Math.pow(2, bitWidth) - 1
}

function getRegisterByteLength(dataType, register = {}) {
  if (isBitFieldRegister(register)) return getBitFieldByteLength(register)

  const type = getDataType(dataType)
  if (type.kind === 'raw') return getRegisterRawByteLength(register)
  if (type.kind === 'text') {
    const byteLength = getRegisterTextByteLength(register)

    return isStructLayout(register.layout) ? byteLength : alignEvenByteLength(byteLength)
  }

  return type.byteLength || ((type.wordCount || 1) * 2)
}

function getRegisterWordCount(dataType, register = {}) {
  return Math.max(1, Math.ceil(getRegisterByteLength(dataType, register) / 2))
}

function getByteSpanWordCount(byteOffset, byteLength) {
  return Math.max(1, Math.ceil((Math.max(0, Number(byteOffset) || 0) + Math.max(1, Number(byteLength) || 1)) / 2))
}

function getRegisterWordCountAtOffset(dataType, byteOffset, register = {}) {
  const byteLength = getRegisterByteLength(dataType, register)
  return getByteSpanWordCount(byteOffset, byteLength)
}

function isTextRegister(dataType) {
  return getDataType(dataType).kind === 'text'
}

function getEncodeByteLimit(register) {
  return isTextRegister(register.dataType) ? getRegisterTextByteLength(register) : getRegisterByteLength(register.dataType, register)
}

function isByteRegister(dataType) {
  const key = getDataType(dataType).key

  return key === 'int8_t' || key === 'uint8_t'
}

function isBitRegisterType(registerType) {
  return registerType === 'coil' || registerType === 'discrete'
}

function isHexRegister(dataType) {
  return getDataType(dataType).key === 'hex'
}

function isRawRegister(dataType) {
  return getDataType(dataType).kind === 'raw'
}

function isNumericRegister(dataType) {
  return getDataType(dataType).kind === 'number'
}

function supportsRange(dataType) {
  return isNumericRegister(dataType) || isHexRegister(dataType)
}

function supportsUnit(dataType) {
  return isNumericRegister(dataType)
}

function getRegisterValueTypeLabel(dataType) {
  return getDataType(dataType).label
}

module.exports = {
  alignEvenByteLength,
  getBitFieldByteLength,
  getBitFieldMaxValue,
  getDataType,
  getDataTypeIndex,
  getEncodeByteLimit,
  getRegisterByteLength,
  getRegisterRawByteLength,
  getRegisterTextByteLength,
  getRegisterValueTypeLabel,
  getRegisterWordCount,
  getRegisterWordCountAtOffset,
  isBitFieldRegister,
  isBitRegisterType,
  isByteRegister,
  isHexRegister,
  isNumericRegister,
  isRawRegister,
  isTextRegister,
  normalizeBitOffset,
  normalizeBitWidth,
  normalizeTextByteLength,
  supportsRange,
  supportsUnit
}
