import CableDarkIcon from "assets/components/grainCableDark.png";
import CableLightIcon from "assets/components/grainCableLight.png";
import { GraphPoint } from "common/Graph";
import { convertedUnitMeasurement } from "models/UnitMeasurement";
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";

interface PressureNode {
  pascals: number;
}

function extractNodes(measurement: quack.IMeasurement | null | undefined): PressureNode[] {
  if (!measurement || !measurement.pressureCable) return [];
  let pressureNodes = or(measurement.pressureCable as any, { pascals: [] }).pascals;
  let numNodes = pressureNodes.length;
  let nodes: PressureNode[] = [];
  for (let i = 0; i < numNodes; i++) {
    let pressure = pressureNodes[i];
    nodes.push({ pascals: pressure });
  }
  return nodes;
}

export function PressureCable(subtype: number = 0): ComponentTypeExtension {
  let pressure = describeMeasurement(
    quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE,
    quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE,
    subtype
  );
  return {
    type: quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE,
    subtypes: [
      {
        key: 0,
        value: "PRESSURE_CABLE",
        friendlyName: "Pressure Cable"
      },
      {
        key: 0, //there are no subtypes for pressure cable so just use 0 as the key in order to display the option that would be less confusing to farmers
        value: "PRESSURE_CABLE",
        friendlyName: "Plenum - Pressure",
        defaultCableID: 2
      }
    ],
    friendlyName: "Pressure Cable",
    description: "Measures pressure along several nodes",
    isController: false,
    isSource: true,
    isArray: true,
    hasFan: true,
    isCalibratable: false,
    addressTypes: [quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY],
    interactionResultTypes: [],
    states: [],
    measurements: [
      {
        measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE,
        label: pressure.label(),
        colour: pressure.colour(),
        graphType: pressure.graph(),
        extract: function(measurement: quack.Measurement, filters?: GraphFilters): any {
          let pressures = extractNodes(measurement).map(n => {
            if (pressure.GetUnit() === "iwg") {
              return n.pascals * 0.0040146307866177;
            }
            return n.pascals;
          });
          if (pressures.length <= 0) return null;

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

          pressures.forEach((pressure, i) => {
            pressureMeasurements["node" + i] = pressure;
          });

          return pressureMeasurements;
        },
        isErrorMeasurement: (measurement: quack.Measurement): boolean => {
          let pressures = extractNodes(measurement).map(n => n.pascals);
          let errorValue = pressure.GetErrorValue();
          if (pressures.some(t => t === errorValue)) {
            return true;
          }
          return false;
        }
      } as ComponentMeasurement
    ],
    measurementSummary: async function(
      measurement: quack.Measurement,
      filters: GraphFilters
    ): Promise<Array<Summary>> {
      if (!measurement || !measurement.pressureCable || !filters) {
        Promise.reject();
      }
      let summary: Array<Summary> = [];

      const showPressure = notNull(or(measurement.pressureCable as any, { pascals: null }).pascals);

      const isTableCellMode = or(filters.isTableCellMode, false);

      let nodes = extractNodes(measurement);
      let formattedPressure = "";

      let factor = 0.001;
      let unit = " kPa";

      // This happens if iwg selected instead of Pa
      if (pressure.GetUnit() === "iwg") {
        factor = 0.0040146307866177;
        unit = " iwg";
      }

      if (isTableCellMode) {
        formattedPressure =
          "[" + nodes.map(n => roundTo(n.pascals * factor, 2).toString() + unit).join(", ") + "]";
      } else {
        let pascalAvg = avg(nodes.map(n => n.pascals)) * factor;
        formattedPressure = roundTo(pascalAvg, 2).toString() + unit;
      }

      if (showPressure) {
        summary.push({
          label: "Pressure",
          value: formattedPressure,
          colour: "rgb(149, 117, 205)"
        } as Summary);
      }
      return Promise.resolve(summary);
    },
    unitMeasurementSummary: (
      measurements: convertedUnitMeasurement,
    ): Summary[] => {
      return unitMeasurementSummaries(
        measurements,
        quack.ComponentType.COMPONENT_TYPE_PRESSURE_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_PRESSURE_CABLE,
        measurement,
        smoothingAverages,
        filters
      );
    },
    minMeasurementPeriodMs: 1000,
    icon: (theme?: "light" | "dark"): string | undefined => {
      return theme === "light" ? CableDarkIcon : CableLightIcon;
    }
  };
}

//TODO: deprecated function with new measurements
export function multilinePressureCableData(
  measurements: Array<pond.Measurement>,
  filters?: GraphFilters
) {
  let pascals: 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) {
        pascals[j] = [];
      }
      let nodeIndex = selectedNodes[j];
      if (nodeIndex < nodes.length) {
        let node = nodes[nodeIndex];
        let ts = moment(measurement.timestamp);
        pascals[j].push({ x: ts, y: node.pascals });
      }
    }
  });
  return { data1: pascals };
}
