const storageAccessIo = require('./storage-access-io.js')
const {
  decodeRegisterFromByteCache,
  decodeRegisterFromWordCache,
  decodeRegisterValue,
  formatCoilDisplayValue,
  formatRegisterValue,
  normalizeRegister,
  getRegisterBytesFromByteCache,
  getRegisterWordsFromByteCache,
  getRegisterWordsFromWordCache,
  registerTypeIsBit
} = require('../../domain/parameter-groups/model.js')

function normalizeNumericCache(source = {}) {
  const cache = {}

  Object.keys(source || {}).forEach((addressText) => {
    const numericAddress = Number(addressText)
    if (Number.isFinite(numericAddress)) {
      cache[numericAddress] = Number(source[addressText]) & 0xFFFF
    }
  })

  return cache
}

function createRegisterStateFromCache(group, register, wordCache) {
  const rawBytes = registerTypeIsBit(register)
    ? []
    : (storageAccessIo.isByteAddressedGroup(group) ? getRegisterBytesFromByteCache(register, wordCache) : [])
  const rawWords = registerTypeIsBit(register)
    ? []
    : (storageAccessIo.isByteAddressedGroup(group)
      ? getRegisterWordsFromByteCache(register, wordCache)
      : getRegisterWordsFromWordCache(register, wordCache))
  const rawValue = registerTypeIsBit(register)
    ? decodeRegisterFromWordCache(register, wordCache)
    : (storageAccessIo.isByteAddressedGroup(group)
      ? decodeRegisterFromByteCache(register, wordCache)
      : (rawWords ? decodeRegisterValue(register, rawWords) : null))
  const displayValue = rawValue === null || rawValue === undefined
    ? '--'
    : (registerTypeIsBit(register)
      ? formatCoilDisplayValue(rawValue)
      : formatRegisterValue(register, rawValue))
  const preNormalizedRegister = {
    ...register,
    displayValue,
    isDirty: false,
    rawBytes: rawBytes || [],
    rawValue,
    rawWords: rawWords || []
  }
  const nextRegister = normalizeRegister(preNormalizedRegister, group, 0, register.address, register.byteOffset)

  return {
    ...nextRegister,
    id: register.id,
    inputValue: group.writable ? displayValue : register.inputValue
  }
}

function applyReadCacheToGroup(group, wordCache) {
  return {
    ...group,
    registers: group.registers.map((register) => createRegisterStateFromCache(group, register, wordCache))
  }
}

function applyWrittenSnapshotToRegister(register, snapshot = {}) {
  const hasDisplayValue = Object.prototype.hasOwnProperty.call(snapshot, 'displayValue')
  const hasRawBytes = Object.prototype.hasOwnProperty.call(snapshot, 'rawBytes')
  const hasRawValue = Object.prototype.hasOwnProperty.call(snapshot, 'rawValue')
  const hasRawWords = Object.prototype.hasOwnProperty.call(snapshot, 'rawWords')

  const nextRegister = {
    ...register,
    displayValue: hasDisplayValue ? snapshot.displayValue : register.displayValue,
    inputValue: hasDisplayValue ? snapshot.displayValue : register.inputValue,
    isDirty: false,
    rawBytes: hasRawBytes ? snapshot.rawBytes : register.rawBytes,
    rawValue: hasRawValue ? snapshot.rawValue : register.rawValue,
    rawWords: hasRawWords ? snapshot.rawWords : register.rawWords
  }

  return nextRegister
}

function applyWrittenSnapshotsToGroup(group, snapshots = []) {
  let writtenIndex = 0

  return {
    ...group,
    registers: group.registers.map((register, index) => {
      const snapshot = snapshots[writtenIndex] || {}
      writtenIndex += 1

      const nextRegister = applyWrittenSnapshotToRegister(register, snapshot)
      const normalized = normalizeRegister(nextRegister, group, index, nextRegister.address, nextRegister.byteOffset)

      return {
        ...normalized,
        id: register.id,
        inputValue: group.writable ? nextRegister.displayValue : nextRegister.inputValue
      }
    })
  }
}

function applyWrittenSnapshotToGroupRegister(group, registerIndex, snapshot) {
  return {
    ...group,
    registers: group.registers.map((register, currentIndex) => (
      currentIndex === registerIndex
        ? (() => {
          const nextRegister = applyWrittenSnapshotToRegister(register, snapshot || {})
          const normalized = normalizeRegister(nextRegister, group, currentIndex, nextRegister.address, nextRegister.byteOffset)

          return {
            ...normalized,
            id: register.id,
            inputValue: group.writable ? nextRegister.displayValue : nextRegister.inputValue
          }
        })()
        : register
    ))
  }
}

module.exports = {
  applyReadCacheToGroup,
  applyWrittenSnapshotToGroupRegister,
  applyWrittenSnapshotsToGroup,
  normalizeNumericCache
}
