import { Device } from "../device.model"
import type { IForceBoard } from "../../interfaces/device/forceboard.interface"

/**
 * Represents a PitchSix Force Board device.
 * {@link https://pitchsix.com}
 */
export class ForceBoard extends Device implements IForceBoard {
  constructor() {
    super({
      filters: [{ name: "Force Board" }],
      services: [
        {
          name: "Device Information",
          id: "device",
          uuid: "0000180a-0000-1000-8000-00805f9b34fb",
          characteristics: [
            // {
            //   name: "Serial Number String (Blocked)",
            //   id: "serial",
            //   uuid: "00002a25-0000-1000-8000-00805f9b34fb",
            // },
            // {
            //   name: "Firmware Revision String (Blocked)",
            //   id: "firmware",
            //   uuid: "00002a26-0000-1000-8000-00805f9b34f",
            // },
            {
              name: "Manufacturer Name String",
              id: "manufacturer",
              uuid: "00002a29-0000-1000-8000-00805f9b34fb",
            },
          ],
        },
        {
          name: "Battery Service",
          id: "battery",
          uuid: "0000180f-0000-1000-8000-00805f9b34fb",
          characteristics: [
            {
              name: "Battery Level",
              id: "level",
              uuid: "00002a19-0000-1000-8000-00805f9b34fb",
            },
          ],
        },
        {
          name: "Nordic Device Firmware Update (DFU) Service",
          id: "dfu",
          uuid: "0000fe59-0000-1000-8000-00805f9b34fb",
          characteristics: [
            {
              name: "Buttonless DFU",
              id: "dfu",
              uuid: "8ec90003-f315-4f60-9fb8-838830daea50",
            },
          ],
        },
        {
          name: "",
          id: "",
          uuid: "f3641400-00b0-4240-ba50-05ca45bf8abc",
          characteristics: [
            {
              name: "Read + Indicate",
              id: "",
              uuid: "f3641401-00b0-4240-ba50-05ca45bf8abc",
            },
          ],
        },
        {
          name: "Humidity Service",
          id: "humidity",
          uuid: "cf194c6f-d0c1-47b2-aeff-dc610f09bd18",
          characteristics: [
            {
              name: "Humidity Level",
              id: "level",
              uuid: "cf194c70-d0c1-47b2-aeff-dc610f09bd18",
            },
          ],
        },
        {
          name: "Temperature Serivce",
          id: "temperature",
          uuid: "3a90328c-c266-4c76-b05a-6af6104a0b13",
          characteristics: [
            {
              name: "Read",
              id: "level",
              uuid: "3a90328d-c266-4c76-b05a-6af6104a0b13",
            },
          ],
        },
        {
          name: "Forceboard Service",
          id: "forceboard",
          uuid: "9a88d67f-8df2-4afe-9e0d-c2bbbe773dd0",
          characteristics: [
            {
              name: "Write",
              id: "",
              uuid: "9a88d680-8df2-4afe-9e0d-c2bbbe773dd0",
            },
            {
              name: "Read + Indicate",
              id: "",
              uuid: "9a88d681-8df2-4afe-9e0d-c2bbbe773dd0",
            },
            {
              name: "Force Data",
              id: "rx",
              uuid: "9a88d682-8df2-4afe-9e0d-c2bbbe773dd0",
            },
            {
              name: "Tare",
              id: "tare",
              uuid: "9a88d683-8df2-4afe-9e0d-c2bbbe773dd0",
            },
            {
              name: "Read",
              id: "",
              uuid: "9a88d685-8df2-4afe-9e0d-c2bbbe773dd0",
            },
            {
              name: "Threshold",
              id: "threshold",
              uuid: "9a88d686-8df2-4afe-9e0d-c2bbbe773dd0",
            },
            {
              name: "Read + Write",
              id: "",
              uuid: "9a88d687-8df2-4afe-9e0d-c2bbbe773dd0",
            },
            {
              name: "Serial / Read + Write",
              id: "",
              uuid: "9a88d688-8df2-4afe-9e0d-c2bbbe773dd0",
            },
            {
              name: "Read + Write",
              id: "",
              uuid: "9a88d689-8df2-4afe-9e0d-c2bbbe773dd0",
            },
          ],
        },
        {
          name: "Weight Service",
          id: "weight",
          uuid: "467a8516-6e39-11eb-9439-0242ac130002",
          characteristics: [
            {
              name: "Device Mode",
              id: "tx",
              uuid: "467a8517-6e39-11eb-9439-0242ac130002",
            },
            {
              name: "Read + Write",
              id: "",
              uuid: "467a8518-6e39-11eb-9439-0242ac130002",
            },
          ],
        },
      ],
      commands: {
        START_WEIGHT_MEAS: String.fromCharCode(0x04), // Streaming Data Mode: continuously streams force data.
        TARE_SCALE: String.fromCharCode(0x05), // Tare function: zeroes out the current load value
        START_QUICK_MEAS: String.fromCharCode(0x06), // Quick Start Mode: Starts data transmission when a force value exceeds the Threshold and stops data transmission when the force data drops below the Threshold.
        STOP_WEIGHT_MEAS: String.fromCharCode(0x07), // Idle Mode: Force Board is idle.
      },
    })
  }

  /**
   * Retrieves battery or voltage information from the device.
   * @returns {Promise<string | undefined>} A Promise that resolves with the battery or voltage information,
   */
  battery = async (): Promise<string | undefined> => {
    return await this.read("battery", "level", 250)
  }

  /**
   * Handles data received from the device, processes weight measurements,
   * and updates mass data including maximum and average values.
   * It also handles command responses for retrieving device information.
   *
   * @param {BluetoothRemoteGATTCharacteristic} characteristic - The notification event.
   */
  override handleNotifications = (characteristic: BluetoothRemoteGATTCharacteristic): void => {
    const value: DataView | undefined = characteristic.value
    if (value) {
      // Update timestamp
      this.updateTimestamp()
      if (value.buffer) {
        const receivedTime: number = Date.now()
        const dataArray = new Uint8Array(value.buffer)

        // First two bytes contain the number of samples in the packet
        const numSamples = (dataArray[0] << 8) | dataArray[1]

        // Process each sample (3 bytes per sample)
        for (let i = 0; i < numSamples; i++) {
          const offset = 2 + i * 3 // Skip the first 2 bytes which indicate number of samples
          if (offset + 2 < dataArray.length) {
            // Sample = byte1*32768 + byte2*256 + byte3
            const receivedData = dataArray[offset] * 32768 + dataArray[offset + 1] * 256 + dataArray[offset + 2]

            // Convert from LBS to KG
            const convertedReceivedData = receivedData * 0.453592
            // Tare correction
            const numericData = convertedReceivedData - this.applyTare(convertedReceivedData)
            // Add data to downloadable Array
            this.downloadPackets.push({
              received: receivedTime,
              sampleNum: this.dataPointCount,
              battRaw: 0,
              samples: [convertedReceivedData],
              masses: [numericData],
            })

            // Update massMax
            this.massMax = Math.max(Number(this.massMax), numericData).toFixed(1)

            // Update running sum and count
            const currentMassTotal = Math.max(-1000, numericData)
            this.massTotalSum += currentMassTotal
            this.dataPointCount++

            // Calculate the average dynamically
            this.massAverage = (this.massTotalSum / this.dataPointCount).toFixed(1)

            // Check if device is being used
            this.activityCheck(numericData)

            // Notify with weight data
            this.notifyCallback({
              massMax: this.massMax,
              massAverage: this.massAverage,
              massTotal: Math.max(-1000, numericData).toFixed(1),
            })
          }
        }
      }
    }
  }

  /**
   * Retrieves humidity level from the device.
   * @returns {Promise<string>} A Promise that resolves with the humidity level,
   */
  humidity = async (): Promise<string | undefined> => {
    return await this.read("humidity", "level", 250)
  }

  /**
   * Retrieves manufacturer information from the device.
   * @returns {Promise<string>} A Promise that resolves with the manufacturer information,
   */
  manufacturer = async (): Promise<string | undefined> => {
    return await this.read("device", "manufacturer", 250)
  }

  /**
   * Stops the data stream on the specified device by setting it to Idle mode.
   * @returns {Promise<void>} A promise that resolves when the stream is stopped.
   */
  stop = async (): Promise<void> => {
    await this.write("weight", "tx", this.commands.STOP_WEIGHT_MEAS, 0)
  }

  /**
   * Starts streaming data from the specified device in Streaming Data Mode.
   * @param {number} [duration=0] - The duration of the stream in milliseconds. If set to 0, stream will continue indefinitely.
   * @returns {Promise<void>} A promise that resolves when the streaming operation is completed.
   */
  stream = async (duration = 0): Promise<void> => {
    // Start streaming data - Streaming Data Mode
    await this.write("weight", "tx", this.commands.START_WEIGHT_MEAS, duration)
  }

  /**
   * Sets the threshold in Lbs for the Quick Start mode.
   * @param {number} thresholdLbs - The threshold value in pounds.
   * @returns {Promise<void>} A promise that resolves when the threshold is set.
   */
  threshold = async (thresholdLbs: number): Promise<void> => {
    const thresholdHex = thresholdLbs.toString(16).padStart(6, "0")
    // 3-byte array from the hex string
    const bytes = new Uint8Array(3)
    bytes[0] = parseInt(thresholdHex.substring(0, 2), 16)
    bytes[1] = parseInt(thresholdHex.substring(2, 4), 16)
    bytes[2] = parseInt(thresholdHex.substring(4, 6), 16)

    await this.write("forceboard", "threshold", String.fromCharCode(...bytes), 0)
  }

  /**
   * Tares the Force Board device using a characteristic to zero out the current load value.
   * @returns {Promise<void>} A promise that resolves when the tare operation is completed.
   */
  tareByCharacteristic = async (): Promise<void> => {
    // Send tare command (0x01) to the tare characteristic
    const tareValue = String.fromCharCode(0x01)
    await this.write("forceboard", "tare", tareValue, 0)
  }

  /**
   * Initiates a tare routine via the Device Mode characteristic.
   * Writes 0x05 to the Device Mode characteristic to zero out the current load value.
   * @returns {Promise<void>} A promise that resolves when the tare operation is completed.
   */
  tareByMode = async (): Promise<void> => {
    await this.write("weight", "tx", this.commands.TARE_SCALE, 0)
  }

  /**
   * Retrieves temperature information from the device.
   * @returns {Promise<string>} A Promise that resolves with the temperature information,
   */
  temperature = async (): Promise<string | undefined> => {
    return await this.read("temperature", "level", 250)
  }

  /**
   * Starts the Force Board in Quick Start mode.
   * Writes 0x06 to the Device Mode characteristic.
   * @param {number} [duration=0] - The duration in milliseconds. If set to 0, mode will continue indefinitely.
   * @returns {Promise<void>} A promise that resolves when the operation is completed.
   */
  quick = async (duration = 0): Promise<void> => {
    // Start in Quick Start mode
    await this.write("weight", "tx", this.commands.START_QUICK_MEAS, duration)
  }
}
