import { lightGreen, teal } from "@mui/material/colors";
import GrainCableDarkIcon from "assets/components/grainCableDark.png";
import GrainCableLightIcon from "assets/components/grainCableLight.png";
import TemperatureHumidityDarkIcon from "assets/components/temperatureHumidityDark.png";
import TemperatureHumidityLightIcon from "assets/components/temperatureHumidityLight.png";
import { ExtractMoisture, grainName } from "grain";
//import { GraphPoint } from "common/Graph";
//import moment from "moment";
import {
  AreaChartData,
  ComponentMeasurement,
  ComponentTypeExtension,
  GraphFilters,
  LineChartData,
  simpleAreaChartData,
  simpleLineChartData,
  Summary,
  unitMeasurementSummaries
} from "pbHelpers/ComponentType";
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import { avg, roundTo } from "utils/numbers";
import { notNull, or } from "utils/types";
import { randomInt } from "utils/random";
import { convertedUnitMeasurement } from "models/UnitMeasurement";

export interface Node {
  temperature: number;
  humidity: number;
}

export function extractGrainCable(grainCable: quack.GrainCable | null | undefined): Node[] {
  if (!grainCable) return [];
  let temperatures = grainCable.celciusTimes_10
    ? grainCable.celciusTimes_10
    : or((grainCable as any).celciusTimes10 as any, []);
  let humidities = grainCable.relativeHumidityTimes_100
    ? grainCable.relativeHumidityTimes_100
    : or((grainCable as any).relativeHumidityTimes100, []);
  let numNodes = Math.min(temperatures.length, humidities.length);
  let nodes = [];
  for (let i = 0; i < numNodes; i++) {
    let temperature = temperatures[i] / 10.0;
    let humidity = humidities[i] / 100.0;
    if (temperature !== -127) nodes.push({ temperature, humidity });
  }
  return nodes;
}

export function extractNodes(measurement: quack.IMeasurement | null | undefined): Node[] {
  if (!measurement) return [];
  return extractGrainCable(measurement?.grainCable);
}

export function randomGrainCable(
  numNodes: number,
  tempLow: number,
  tempHigh: number
): quack.GrainCable {
  let temperatures: number[] = [];
  let humidities: number[] = [];
  for (let i = 0; i < numNodes; i++) {
    temperatures.push(randomInt(tempLow * 10, tempHigh * 10));
    humidities.push(randomInt(0, 10000));
  }

  return quack.GrainCable.fromObject({
    celciusTimes_10: temperatures,
    relativeHumidityTimes_100: humidities
  });
}

function justGrain(filters?: GraphFilters): boolean {
  return !!filters && !!filters.selectedNodes && filters.selectedNodes.includes("grain");
}

function justAir(filters?: GraphFilters): boolean {
  return !!filters && !!filters.selectedNodes && filters.selectedNodes.includes("air");
}

const getMinMeasurementPeriodMs = (subtype: number) => {
  switch (subtype) {
    case 1:
      return 1000;
    default:
      return 600000;
  }
};

export function GrainCable(subtype: number = 0): ComponentTypeExtension {
  let temperature = describeMeasurement(
    quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
    quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE,
    subtype
  );
  let moisture = describeMeasurement(
    quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
    quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE,
    subtype
  );
  return {
    type: quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE,
    subtypes: [
      {
        key: quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_NONE,
        value: "GRAIN_CABLE_SUBTYPE_NONE",
        friendlyName: "OPI Cable"
      },
      {
        key: quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_FROG,
        value: "GRAIN_CABLE_SUBTYPE_FROG",
        friendlyName: "BinStick"
      },
      {
        key: quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_FROG,
        value: "GRAIN_CABLE_SUBTYPE_FROG",
        friendlyName: "Plenum - Temp/Humidity",
        defaultCableID: 1
      },
      {
        key: quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_FROG,
        value: "GRAIN_CABLE_SUBTYPE_FROG",
        friendlyName: "Ambient",
        defaultCableID: 3
      },
      {
        key: quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_DIAG,
        value: "GRAIN_CABLE_SUBTYPE_DIAG",
        friendlyName: "Port Diagnostics"
      },
      {
        key: quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_REPROG,
        value: "GRAIN_CABLE_SUBTYPE_REPROG",
        friendlyName: "OPI Cable Reprogrammer"
      },
      {
        key: quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_SHT35,
        value: "GRAIN_CABLE_SUBTYPE_SHT35",
        friendlyName: "OPI Cable v2"
      },
      {
        key: quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_TEMP,
        value: "GRAIN_CABLE_SUBTYPE_TEMP",
        friendlyName: "OPI Temp Only"
      },
      {
        key: quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_CABLE_DIAG,
        value: "GRAIN_CABLE_SUBTYPE_OPI_CABLE_DIAG",
        friendlyName: "OPI Cable Diagnostics"
      },
      {
        key: quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_CABLE_REPROG,
        value: "GRAIN_CABLE_SUBTYPE_OPI_CABLE_REPROG",
        friendlyName: "OPI Grain Cable Reprogrammer"
      },
      {
        key: quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_AUTOMATIC,
        value: "GRAIN_CABLE_SUBTYPE_OPI_AUTOMATIC",
        friendlyName: "OPI Automatic"
      },
      {
        key: quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_ANALOG,
        value: "GRAIN_CABLE_SUBTYPE_ANALOG",
        friendlyName: "Analog Cable"
      },
      {
        key: quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_AGI_SMART_CABLE_V2,
        value: "GRAIN_CABLE_SUBTYPE_AGI_SMART_CABLE_V2",
        friendlyName: "AGI Smart Cable"
      }
    ],
    friendlyName: "Grain Cable",
    description: "Measures temperature, humidity, and moisture (depends on the grain type)",
    isController: false,
    isSource: true,
    isArray: true,
    isCalibratable: false,
    addressTypes: [quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, quack.AddressType.ADDRESS_TYPE_I2C],
    interactionResultTypes: [],
    states: [],
    measurements: [
      {
        measurementType: quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
        label: temperature.label(),
        colour: temperature.colour(),
        graphType: temperature.graph(),
        extract: function(measurement: quack.Measurement, filters?: GraphFilters): any {
          let temperatures = extractNodes(measurement).map(n => n.temperature);
          if (filters && filters.grainFilledTo && justGrain(filters)) {
            temperatures = temperatures.slice(0, filters.grainFilledTo);
          } else if (filters && filters.grainFilledTo && justAir(filters)) {
            temperatures = temperatures.slice(filters.grainFilledTo);
          }
          if (temperatures.length <= 0) return null;

          let tempMeasurements: any = {
            low: Math.min(...temperatures),
            high: Math.max(...temperatures)
          };

          temperatures.forEach((measurement, i) => {
            tempMeasurements["node" + i] = measurement;
          });

          return tempMeasurements;
        },
        isErrorMeasurement: (measurement: quack.Measurement): boolean => {
          let temperatures = extractNodes(measurement).map(n => n.temperature);
          let errorValue = temperature.GetErrorValue();
          if (temperatures.some(t => t === errorValue)) {
            return true;
          }
          return false;
        }
      } as ComponentMeasurement,
      {
        measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
        label: moisture.label(),
        colour: moisture.colour(),
        graphType: moisture.graph(),
        extract: function(measurement: quack.Measurement, filters?: GraphFilters): any {
          let grainType = or(filters, { grainType: pond.Grain.GRAIN_NONE }).grainType;
          let nodes = extractNodes(measurement);
          let moistures: number[] = [];
          let split = isBinSplit(filters);
          let splitAt = binSplitAt(filters);
          nodes.forEach((n, i) => {
            if (split && justGrain(filters) && i >= splitAt) {
            } else if (split && justAir(filters) && i < splitAt) {
            } else {
              let grain = split && i >= splitAt ? pond.Grain.GRAIN_NONE : grainType;
              moistures.push(ExtractMoisture(grain, n.temperature, n.humidity));
            }
          });
          if (moistures.length <= 0) return null;

          let moistureMeasurements: any = {
            low: Math.min(...moistures),
            high: Math.max(...moistures)
          };

          moistures.forEach((measurement, i) => {
            moistureMeasurements["node" + i] = measurement;
          });

          return moistureMeasurements;
        },
        isErrorMeasurement: (measurement: quack.Measurement): boolean => {
          let humidities = extractNodes(measurement).map(n => n.humidity);
          let errorValue = moisture.GetErrorValue();
          if (humidities.some(h => h === errorValue)) {
            return true;
          }
          return false;
        }
      } as ComponentMeasurement
    ],
    measurementSummary: async function(
      measurement: quack.Measurement,
      filters: GraphFilters
    ): Promise<Array<Summary>> {
      if (!measurement || !measurement.grainCable || !filters) {
        Promise.reject();
      }
      let summary: Array<Summary> = [];

      const showTemperature = notNull(
        or(measurement.grainCable as any, { celciusTimes10: null }).celciusTimes10
      );
      const showMoisture =
        showTemperature &&
        notNull(
          or(measurement.grainCable as any, { relativeHumidityTimes100: null })
            .relativeHumidityTimes100
        );

      const grainType = or(filters.grainType, pond.Grain.GRAIN_NONE);
      const isTableCellMode = or(filters.isTableCellMode, false);

      let nodes = extractNodes(measurement);
      let split = isBinSplit(filters);
      let splitAt = binSplitAt(filters);
      let grainNodes = split ? nodes.slice(0, splitAt) : nodes;
      let airNodes = split ? nodes.slice(splitAt, nodes.length) : [];
      let formattedAirTemperature = "";
      let formattedGrainTemperature = "";
      let formattedAirMoisture = "";
      let formattedGrainMoisture = "";

      if (isTableCellMode) {
        formattedAirTemperature =
          "[" + airNodes.map(n => roundTo(n.temperature, 2).toString() + "°C").join(", ") + "]";
        formattedAirMoisture =
          "[" +
          airNodes
            .map(
              n =>
                roundTo(
                  ExtractMoisture(pond.Grain.GRAIN_NONE, n.temperature, n.humidity),
                  2
                ).toString() + "% WB"
            )
            .join(", ") +
          "]";
        formattedGrainTemperature =
          "[" + grainNodes.map(n => roundTo(n.temperature, 2).toString() + "°C").join(", ") + "]";
        formattedGrainMoisture =
          "[" +
          grainNodes
            .map(
              n =>
                roundTo(ExtractMoisture(grainType, n.temperature, n.humidity), 2).toString() +
                "% WB"
            )
            .join(", ") +
          "]";
      } else {
        let airTemperatureAvg = avg(airNodes.map(n => n.temperature));
        let airMoistureTotal = 0;
        airNodes.forEach(
          n =>
            (airMoistureTotal += ExtractMoisture(pond.Grain.GRAIN_NONE, n.temperature, n.humidity))
        );
        let airMoistureAvg = airNodes.length <= 0 ? 0 : airMoistureTotal / airNodes.length;
        let grainTemperatureAvg = avg(grainNodes.map(n => n.temperature));
        let grainMoistureTotal = 0;
        grainNodes.forEach(
          n => (grainMoistureTotal += ExtractMoisture(grainType, n.temperature, n.humidity))
        );
        let grainMoistureAvg = grainNodes.length <= 0 ? 0 : grainMoistureTotal / grainNodes.length;
        formattedAirTemperature = roundTo(airTemperatureAvg, 2).toString() + "°C";
        formattedAirMoisture = roundTo(airMoistureAvg, 2).toString() + "% WB";
        formattedGrainTemperature = roundTo(grainTemperatureAvg, 2).toString() + "°C";
        formattedGrainMoisture = roundTo(grainMoistureAvg, 2).toString() + "% WB";
      }

      if (showTemperature) {
        summary.push({
          label: split ? "Temperature (Grain)" : "Temperature",
          value: formattedGrainTemperature,
          colour: "#2196f3"
        } as Summary);
      }
      if (showTemperature && split) {
        summary.push({
          label: "Temperature (Air)",
          value: formattedAirTemperature,
          colour: "#2196f3"
        } as Summary);
      }
      if (showMoisture) {
        summary.push({
          label: split ? "Moisture (Grain)" : "Moisture",
          value: formattedGrainMoisture,
          colour: teal["500"]
        } as Summary);
      }
      if (showMoisture && split) {
        summary.push({
          label: "Moisture (Air)",
          value: formattedAirMoisture,
          colour: teal["500"]
        } as Summary);
      }
      if (!isTableCellMode && showMoisture) {
        summary.push({
          label: "Grain Type",
          value: grainName(grainType),
          colour: grainType === pond.Grain.GRAIN_NONE ? "" : lightGreen["700"]
        } as Summary);
      }
      return Promise.resolve(summary);
    },
    unitMeasurementSummary: (
      measurements: convertedUnitMeasurement,
    ): Summary[] => {
      return unitMeasurementSummaries(
        measurements,
        quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE,
        subtype,
      );
    },
    areaChartData: (
      measurement: pond.UnitMeasurementsForComponent,
      smoothingAverages?: number,
      filters?: GraphFilters
    ): AreaChartData => {
      return simpleAreaChartData(measurement, smoothingAverages, filters);
    },
    lineChartData: (
      measurement: pond.UnitMeasurementsForComponent,
      smoothingAverages?: number,
      filters?: GraphFilters
    ): LineChartData => {
      return simpleLineChartData(
        quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE,
        measurement,
        smoothingAverages,
        filters
      );
    },
    minMeasurementPeriodMs: getMinMeasurementPeriodMs(subtype),
    icon: (theme?: "light" | "dark"): string | undefined => {
      switch (subtype) {
        case quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_FROG:
          return theme === "light" ? TemperatureHumidityDarkIcon : TemperatureHumidityLightIcon;
        default:
          return theme === "light" ? GrainCableDarkIcon : GrainCableLightIcon;
      }
    }
  };
}

export function isBinSplit(filters?: GraphFilters): boolean {
  return !!filters && or(filters.grainFilledTo, 0) > 0;
}

export function binSplitAt(filters?: GraphFilters): number {
  return filters ? or(filters.grainFilledTo, 0) : 0;
}

//TODO: deprecated function with new measurements
// export function multilineGrainCableData(
//   measurements: Array<pond.Measurement>,
//   filters?: GraphFilters
// ) {
//   let temperature: Array<Array<GraphPoint>> = [];
//   let humidity: Array<Array<GraphPoint>> = []; //humidity is not being used by the graphs
//   let moisture: Array<Array<GraphPoint>> = [];
//   measurements.forEach((measurement: pond.Measurement, i) => {
//     let nodes = extractNodes(measurement.measurement);
//     let selectedNodes = filters ? or(filters.selectedNodes, []) : [];
//     for (let j = 0; j < selectedNodes.length; j++) {
//       if (i === 0) {
//         temperature[j] = [];
//         humidity[j] = [];
//         moisture[j] = [];
//       }
//       let nodeIndex = selectedNodes[j];
//       if (nodeIndex < nodes.length) {
//         let node = nodes[nodeIndex];
//         let ts = moment(measurement.timestamp);
//         temperature[j].push({ x: ts, y: node.temperature });
//         humidity[j].push({ x: ts, y: node.humidity });
//         let grain =
//           isBinSplit(filters) && nodeIndex >= binSplitAt(filters)
//             ? pond.Grain.GRAIN_NONE
//             : or(filters, { grainType: pond.Grain.GRAIN_NONE }).grainType;
//         moisture[j].push({
//           x: ts,
//           y: ExtractMoisture(grain, node.temperature, node.humidity)
//         });
//       }
//     }
//   });
//   return { data1: temperature, data2: moisture, data3: humidity };
// }
