import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/pond";
import { or } from "utils/types";
import { clone, cloneDeep } from "lodash";
import { Component } from "models";
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
import { extractGrainCable } from "pbHelpers/ComponentTypes";

export class GrainCable {
  public settings: pond.ComponentSettings = pond.ComponentSettings.create();
  public status: pond.ComponentStatus = pond.ComponentStatus.create();
  public lastReading: string = "";
  //bear in mind these three arrays are reversed due to the bin svg needing the first node on the bottom of the image and the svg being drawn from the top
  public temperatures: number[] = [];
  public humidities: number[] = [];
  public grainMoistures: number[] = [];
  public topNode: number = 0;
  public excludedNodes: number[] = [];

  public static create(comp: Component): GrainCable {
    let my = new GrainCable();
    my.settings = comp.settings;
    my.status = comp.status;

    let temps: number[] = [];
    let hums: number[] = [];
    let emc: number[] = [];
    let lastReading = "";
    if (comp.status.measurement.length > 0) {
      comp.status.measurement.forEach(um => {
        if (um.timestamps[0]) {
          lastReading = um.timestamps[0];
        }
        if (um.values[0] && um.values[0].values.length > 0) {
          if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) {
            let revTemps = clone(um.values[0].values);
            temps = revTemps.reverse();
          }
          if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT) {
            let revHums = clone(um.values[0].values);
            hums = revHums.reverse();
          }
          if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC) {
            let revMoist = clone(um.values[0].values);
            emc = revMoist.reverse();
          }
        }
      });
    } else {
      let nodes = extractGrainCable(comp.status.lastMeasurement?.measurement?.grainCable).reverse();
      if (nodes.length > 0) {
        nodes.forEach(node => {
          hums.push(node.humidity);
          temps.push(node.temperature);
        });
      }
    }
    my.temperatures = temps;
    my.humidities = hums;
    my.grainMoistures = emc;
    my.lastReading = lastReading;
    my.topNode = comp.settings.grainFilledTo;
    my.excludedNodes = comp.settings.excludedNodes;
    return my;
  }

  public static createPond(comp: pond.Component): GrainCable {
    let my = new GrainCable();
    my.settings = comp.settings ? comp.settings : pond.ComponentSettings.create();
    my.status = comp.status ? comp.status : pond.ComponentStatus.create();

    let temps: number[] = [];
    let hums: number[] = [];
    if (comp.lastMeasurement.length > 0) {
      comp.lastMeasurement.forEach(um => {
        if (um.values[0]) {
          if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) {
            temps = um.values[0].values.reverse();
          }
          if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT) {
            hums = um.values[0].values.reverse();
          }
          //TODO-CS: could expand this to have the emc in the cable model as well
          // if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_EMC){
          //   emc = um.values[0].values
          // }
        }
      });
    } else {
      let nodes = extractGrainCable(
        comp?.status?.lastMeasurement?.measurement?.grainCable
      ).reverse();
      if (nodes.length > 0) {
        nodes.forEach(node => {
          hums.push(node.humidity);
          temps.push(node.temperature);
        });
      }
    }
    my.temperatures = temps;
    my.humidities = hums;

    return my;
  }

  public static any(data: any): GrainCable {
    let comp = pond.Component.fromObject(cloneDeep(data));
    let my = GrainCable.createPond(comp);
    if (data && data.status && data.status.lastMeasurement) {
      my.status.lastMeasurement = data.status.lastMeasurement;
    }
    return my;
  }

  public update(other: GrainCable) {
    this.settings = other.settings;
    this.status = other.status;
  }

  public humidColour() {
    return describeMeasurement(
      quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
      quack.ComponentType.COMPONENT_TYPE_DHT
    ).colour();
  }

  private farenheit(temp: number) {
    return temp * (9 / 5) + 32;
  }

  public tempColour() {
    return describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE).colour();
  }

  public minTemp(unit = pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS) {
    if (unit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT)
      return this.farenheit(Math.min(...this.temperatures));
    return Math.min(...this.temperatures);
  }

  public minHumidity() {
    return Math.min(...this.humidities);
  }

  public minMoisture() {
    return Math.min(...this.grainMoistures);
  }

  public aveTemp(unit = pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS) {
    if (unit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT)
      return this.farenheit(
        this.temperatures.reduce((p: any, c: any) => p + c, 0) / this.temperatures.length
      );
    return this.temperatures.reduce((p: any, c: any) => p + c, 0) / this.temperatures.length;
  }

  public aveHumidity() {
    return this.humidities.reduce((p: any, c: any) => p + c, 0) / this.humidities.length;
  }

  public maxTemp(unit = pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS) {
    if (unit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT)
      return this.farenheit(Math.max(...this.temperatures));
    return Math.max(...this.temperatures);
  }

  public maxHumidity() {
    return Math.max(...this.humidities);
  }

  public maxMoisture() {
    return Math.min(...this.grainMoistures);
  }

  public name(): string {
    return this.settings.name !== "" ? this.settings.name : "Component " + this.key();
  }

  public key(): string {
    return this.settings.key;
  }

  public location(): quack.ComponentID {
    return quack.ComponentID.fromObject({
      type: this.settings.type,
      addressType: this.settings.addressType,
      address: this.settings.address
    });
  }

  public locationString(): string {
    return (
      or(this.settings.type, 0).toString() +
      "-" +
      or(this.settings.addressType, 0).toString() +
      "-" +
      or(this.settings.address, 0).toString()
    );
  }

  public type(): quack.ComponentType {
    return this.settings.type;
  }

  public subType(): number {
    return this.settings.subtype;
  }

  //return the grain cable as a component
  public asComponent(): Component {
    let component = Component.create();
    component.settings = this.settings;
    component.status = this.status;

    //this section of the function will be deprecated as the last measurement in the new structure is in the status again
    let lastMeasurements = [];
    let lastTemp = pond.UnitMeasurementsForComponent.create();
    lastTemp.componentId = this.settings.key;
    lastTemp.timestamps = [this.lastReading];
    lastTemp.type = quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE;
    lastTemp.values = [pond.ValueArray.create()];
    lastTemp.values[0].values = this.temperatures;
    lastMeasurements.push(lastTemp);

    let lastHum = pond.UnitMeasurementsForComponent.create();
    lastHum.componentId = this.settings.key;
    lastHum.timestamps = [this.lastReading];
    lastHum.type = quack.MeasurementType.MEASUREMENT_TYPE_PERCENT;
    lastHum.values = [pond.ValueArray.create()];
    lastHum.values[0].values = this.humidities;
    lastMeasurements.push(lastHum);

    if (this.grainMoistures.length > 0) {
      let lastEMC = pond.UnitMeasurementsForComponent.create();
      lastEMC.componentId = this.settings.key;
      lastEMC.timestamps = [this.lastReading];
      lastEMC.type = quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC;
      lastEMC.values = [pond.ValueArray.create()];
      lastEMC.values[0].values = this.grainMoistures;
      lastMeasurements.push(lastEMC);
    }
    component.lastMeasurement = lastMeasurements;
    return component;
  }

  /**
   * Filters out the excluded nodes and anything above the top node of the cable, note that the nodes are reversed during creation if a grain cable ie: node 1 is the end of the array
   * 
   * @param unReversed will tell the function to reverse the array back to its original state 
   * @returns array of active node values
   */
  public filteredNodes(unReversed?: boolean): {temps: number[], humids: number[], moistures: number[]} {
    //make clones of the node values and reverse them back to their original state for index purposes
    let temps: number [] = cloneDeep(this.temperatures).reverse()
    let humids: number [] = cloneDeep(this.humidities).reverse()
    let moistures: number[] = cloneDeep(this.grainMoistures).reverse()

    //filter out the excluded nodes
    temps = this.filter(temps)
    humids = this.filter(humids)
    moistures = this.filter(moistures)

    if(!unReversed){
      temps.reverse()
      humids.reverse()
      moistures.reverse()
    }

    return {temps, humids, moistures}
  }

  private filter(values: number[]): number[] {
    let filtered: number[] = []
    values.forEach((val, index) => {
      //if the node is not excluded AND is either under the top node OR top node is not set 
      if(!this.excludedNodes.includes(index) && (index < this.topNode || this.topNode === 0)){
        filtered.push(val)
      }
    })
    return filtered
  }
}
