import { type CTCSS, type DCS, Frequency, type RadioChannel, type RadioMemory, type RadioProgram, type RadioProgrammedChannel, type RadioTone, RadioToneType } from "@springfield/ham-radio-api";
import type { BaofengConfig } from "./baofeng-codec.js";
import type { ILogLayer } from "loglayer";
import { dcsValues } from "./baofeng-dcs-tones.js";

export class BaofengDecoder {
  private config: BaofengConfig;
  private logger: ILogLayer;

  constructor(config: BaofengConfig, logger: ILogLayer) {
    this.config = config;
    this.logger = logger;
  }

  public decode(memory: RadioMemory): RadioProgram {
    const radioProgram: RadioProgram = {
      channels: [],
      settings: {},
    };

    for (let channelNumber = 0; channelNumber < this.config.numberChannels; channelNumber += 1) {
      this.logger.debug(`Decoding channel: ${channelNumber}`);

      const channelAddress = this.getChannelAddress(channelNumber);

      if (memory.contents[channelAddress] !== 0xFF) {
        const channel = this.decodeChannel(memory, channelAddress, channelNumber);
        radioProgram.channels.push(channel);
      }
    }

    return radioProgram;
  }

  private getChannelAddress(channelNumber: number): number {
    return channelNumber * this.config.channelSize;
  }

  private decodeChannel(memory: RadioMemory, channelAddress: number, channelNumber: number): RadioProgrammedChannel {
    const transmitPower = this.decodePower(memory.contents[channelAddress + this.config.powerOffset]);

    const name = this.decodeChannelName(channelNumber, memory);
    const receiveFrequency = this.decodeFrequency(memory.contents, channelAddress, this.config.receiveFrequencyOffset);
    const transmitFrequency = this.decodeFrequency(memory.contents, channelAddress, this.config.transmitFrequencyOffset);
    const receiveTone = this.decodeTone(memory.contents, channelAddress, this.config.receiveToneOffset);
    const transmitTone = this.decodeTone(memory.contents, channelAddress, this.config.transmitToneOffset);

    const radioChannel: RadioChannel = { name, receiveFrequency, receiveTone, transmitFrequency, transmitTone };
    const radioSpecificChannelSettings = { transmitPower };
    return { channelNumber, radioChannel, settings: radioSpecificChannelSettings };
  }

  private decodePower(power: number): number {
    return power === 0x0 ? 5 : 1;
  }

  private decodeChannelName(channelNumber: number, memory: RadioMemory): string {
    // Channel names start at 0x1000 and each name is 16 bytes apart (0x10 offset)
    const channelNameAddress = 0x1000 + (channelNumber * 0x10);

    let channelName = "";

    // Read 7 characters (8th byte should be 0xFF terminator)
    for (let index = 0; index < 7; index += 1) {
      const value = memory.contents[channelNameAddress + index];

      if (value !== 0xFF && value !== 0x00) {
        channelName += String.fromCodePoint(value);
      }
    }

    return channelName.trim();
  }

  private decodeFrequency(memoryData: Uint8Array, channelOffset: number, valueOffset: number): Frequency {
    let value = 0;

    for (let index = 0; index < 4; index += 1) {
      value |= memoryData[channelOffset + valueOffset + index] << (8 * index);
    }

    return Frequency(Number.parseInt(value.toString(16), 10) * 10);
  }

  private decodeTone(memoryData: Uint8Array, channelOffset: number, valueOffset: number): RadioTone {
    // DCS: 1 byte index, CTCSS: 2 bytes value
    const dcsIndex = memoryData[channelOffset + valueOffset];
    const ctcssValue = (memoryData[channelOffset + valueOffset] & 0xFF) | (memoryData[channelOffset + valueOffset + 1] << 8);

    // Heuristic: if the second byte is 0, treat as DCS (index), else CTCSS
    if (memoryData[channelOffset + valueOffset + 1] === 0) {
      const dcs = dcsValues[dcsIndex] ?? 0;
      return { tone: dcs as DCS, type: RadioToneType.DCS };
    }

    return { tone: ctcssValue as CTCSS, type: RadioToneType.CTCSS };
  }
}
