import {
  // GraphComponent,
  // GraphOrientation,
  // GraphPoint,
  GraphType,
  InteractionLine
} from "common/Graph";
import { Range } from "common/RangeInput";
import { Option } from "common/SearchSelect";
import { Interaction } from "models";
import {
  AirQuality,
  AnalogInput,
  Calcium,
  Capacitance,
  CO2,
  Conductivity,
  DHT,
  Ethylene,
  GPS,
  GrainCable,
  Invalid,
  Lidar,
  Light,
  Modem,
  Nitrate,
  O2,
  OnOffInput,
  OnOffOutput,
  ORP,
  PH,
  Potassium,
  Power,
  Pressure,
  PressureCable,
  StepperMotor,
  Temperature,
  Trigger,
  Voltage,
  VPD,
  Weight,
  CapacitorCable
} from "pbHelpers/ComponentTypes";
//import { multilineGrainCableData } from "pbHelpers/ComponentTypes/GrainCable";
//import { multilineCapCableData } from "./ComponentTypes/CapacitorCable";
import { findInteractionsAsSource } from "pbHelpers/Interaction";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";

import { notNull, or } from "utils/types";
//import { emptyComponentId } from "./Component";
//import { multilinePressureCableData } from "./ComponentTypes/PressureCable";
import { describeMeasurement, MeasurementDescriber } from "./MeasurementDescriber";
import { convertedUnitMeasurement, MeasurementsFor, UnitMeasurement } from "models/UnitMeasurement";
import { Sen5x } from "./ComponentTypes/Sen5x";
import { VibrationCable } from "./ComponentTypes/VibrationCable";
import { dragerGasDongle } from "./ComponentTypes/DragerGasDongle";
import { AreaData } from "charts/measurementCharts/AreaGraph";
import { LineData, Point } from "charts/measurementCharts/MultiLineGraph";
import moment, { Moment } from "moment";
import { avg } from "utils";
import { Airflow } from "./ComponentTypes/Airflow";

const COMPONENT_TYPE_MAP = new Map<quack.ComponentType, Function>([
  [quack.ComponentType.COMPONENT_TYPE_INVALID, Invalid],
  [quack.ComponentType.COMPONENT_TYPE_POWER, Power],
  [quack.ComponentType.COMPONENT_TYPE_TEMPERATURE, Temperature],
  [quack.ComponentType.COMPONENT_TYPE_BOOLEAN_OUTPUT, OnOffOutput],
  [quack.ComponentType.COMPONENT_TYPE_EDGE_TRIGGERED, Trigger],
  [quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, GrainCable],
  [quack.ComponentType.COMPONENT_TYPE_PRESSURE, Pressure],
  [quack.ComponentType.COMPONENT_TYPE_GPS, GPS],
  [quack.ComponentType.COMPONENT_TYPE_BOOLEAN_INPUT, OnOffInput],
  [quack.ComponentType.COMPONENT_TYPE_DHT, DHT],
  [quack.ComponentType.COMPONENT_TYPE_MODEM, Modem],
  [quack.ComponentType.COMPONENT_TYPE_LIGHT, Light],
  [quack.ComponentType.COMPONENT_TYPE_CO2, CO2],
  [quack.ComponentType.COMPONENT_TYPE_STEPPER_MOTOR, StepperMotor],
  [quack.ComponentType.COMPONENT_TYPE_ANALOG_INPUT, AnalogInput],
  [quack.ComponentType.COMPONENT_TYPE_O2, O2],
  [quack.ComponentType.COMPONENT_TYPE_ETHYLENE, Ethylene],
  [quack.ComponentType.COMPONENT_TYPE_ORP, ORP],
  [quack.ComponentType.COMPONENT_TYPE_PH, PH],
  [quack.ComponentType.COMPONENT_TYPE_VOLTAGE, Voltage],
  [quack.ComponentType.COMPONENT_TYPE_CALCIUM, Calcium],
  [quack.ComponentType.COMPONENT_TYPE_POTASSIUM, Potassium],
  [quack.ComponentType.COMPONENT_TYPE_NITRATE, Nitrate],
  [quack.ComponentType.COMPONENT_TYPE_CONDUCTIVITY, Conductivity],
  [quack.ComponentType.COMPONENT_TYPE_CAPACITANCE, Capacitance],
  [quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, VPD],
  [quack.ComponentType.COMPONENT_TYPE_WEIGHT, Weight],
  [quack.ComponentType.COMPONENT_TYPE_AIR_QUALITY, AirQuality],
  [quack.ComponentType.COMPONENT_TYPE_LIDAR, Lidar],
  [quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE, PressureCable],
  [quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE, CapacitorCable],
  [quack.ComponentType.COMPONENT_TYPE_SEN5X, Sen5x],
  [quack.ComponentType.COMPONENT_TYPE_VIBRATION_CHAIN, VibrationCable],
  [quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, dragerGasDongle],
  [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, Airflow]
]);

export interface Subtype {
  key: any;
  value: string;
  friendlyName: string;
  defaultCableID?: number;
}

export interface AreaChartData {
  area: AreaData[];
  average: AreaData[];
}

export interface LineChartData {
  lines: LineData[];
  average: LineData[];
}

// interface NodeDetails {
//   labels: string[];
//   colours: string[];
// }

export interface ComponentMeasurement {
  label: string;
  colour: any;
  graphType: GraphType;
  measurementType: quack.MeasurementType;
  extract: Function;
  isErrorMeasurement: (measurement?: quack.Measurement | null) => boolean;
}

export interface ComponentTypeExtension {
  type: quack.ComponentType;
  subtypes: Array<Subtype>;
  friendlyName: string;
  description: string;
  isController: boolean;
  isSource: boolean;
  isCalibratable: boolean;
  hasFan?: boolean;
  isArray?: boolean;
  hasDimensions?: boolean;
  multiSensor?: boolean; //for components that have more than one sensor that are not connected ie not a chain/cable
  addressTypes: Array<quack.AddressType>;
  interactionResultTypes: Array<quack.InteractionResultType>;
  states: Array<string>;
  measurements: Array<ComponentMeasurement>;
  measurementSummary?: Function; //Deprecated: this summary used the old measurement structure
  unitMeasurementSummary: (
    measurements: convertedUnitMeasurement
  ) => Summary[];
  minMeasurementPeriodMs: number;
  minCycleTimeMs?: number;
  icon?: (theme?: "light" | "dark") => string | undefined;
  areaChartData: (
    measurement: pond.UnitMeasurementsForComponent,
    smoothingAverages?: number,
    filters?: GraphFilters
  ) => AreaChartData;
  lineChartData: (
    measurement: pond.UnitMeasurementsForComponent,
    smoothingAverages?: number,
    filters?: GraphFilters
  ) => LineChartData;
  secondaryComponentSettings?: () => pond.ComponentSettings[]
}

export interface Summary {
  label: string;
  value: any;
  colour: any;
  type: quack.MeasurementType;
  error?: boolean;
}

export function simpleSummary(
  measurement: quack.Measurement,
  describer: MeasurementDescriber
): Summary {
  let value = describer.withUnits(measurement);
  if (measurement.edgeTriggered) {
    switch (measurement.subtype) {
      case quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_WIND_SPEED:
        //summary math is done in MeasurementSummary as the measurement interval from the component is required
        value = describer.withoutUnits(measurement);
        break;
      case quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_RAIN:
        if (
          measurement.edgeTriggered.rises &&
          measurement.edgeTriggered.rises >= measurement.edgeTriggered.falls
        ) {
          measurement.edgeTriggered.rises = measurement.edgeTriggered.rises * 0.2794;
        }
        value = describer.withUnits(measurement);
        break;
    }
  }
  if (measurement.analogInput) {
    if (measurement.subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_FUEL) {
      if (Number(describer.withoutUnits(measurement)) > 200) {
        value = "Unplugged";
      }
    }
    if (measurement.subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_WIND_DIRECTION) {
      value = getWindDirection(Number(value)).dString;
    }
  }
  return {
    label: describer.label(),
    value: value,
    colour: describer.colour(measurement),
    type: describer.type()
  };
}

export function simpleSummaries(
  measurement: quack.Measurement,
  ...describers: MeasurementDescriber[]
): Summary[] {
  let summaries: Summary[] = [];
  describers.forEach(describer => {
    summaries.push(simpleSummary(measurement, describer));
  });
  return summaries;
}

export function unitMeasurementSummary(
  convertedMeasurement: MeasurementsFor,
  describer: MeasurementDescriber,
): Summary {
  let vals: string[] = [];
  convertedMeasurement.values.forEach(val => {
    vals.push(val + describer.unit());
  });
  let v = vals.join(", ");
  if (describer.enumerations().length > 0) {
    v = describer.enumerations()[parseFloat(vals[0])];
  }
  return {
    colour: describer.colour(),
    label: describer.label(),
    type: describer.type(),
    value: v,
    error: convertedMeasurement.error
  };
}

export function unitMeasurementSummaries(
  measurements: convertedUnitMeasurement,
  compType: quack.ComponentType,
  subtype?: number
): Summary[] {
  let summaries: Summary[] = [];
  measurements.measurementsFor.forEach(measurement => {
    let describer = describeMeasurement(measurement.type, compType, subtype);
    summaries.push(unitMeasurementSummary(measurement, describer));
  });
  return summaries;
}

export function simpleMeasurement(describer: MeasurementDescriber): ComponentMeasurement {
  return {
    label: describer.label(),
    colour: describer.colour(),
    graphType: describer.graph(),
    measurementType: describer.type(),
    extract: function(measurement: quack.Measurement): any {
      return describer.forDisplay(measurement);
    },
    isErrorMeasurement: (measurement?: quack.Measurement | null): boolean => {
      return describer.isErrorMeasurement(measurement);
    }
  };
}

export function simpleMeasurements(...describers: MeasurementDescriber[]): ComponentMeasurement[] {
  let measurements: ComponentMeasurement[] = [];
  describers.forEach(describer => {
    measurements.push(simpleMeasurement(describer));
  });
  return measurements;
}

export interface GraphFilters {
  isTableCellMode?: boolean;
  grainType?: pond.Grain;
  grainFilledTo?: number;
  selectedNodes?: Array<any>;
  selectedInteractions?: Array<string>;
  selectedOverlays?: Array<string>;
  ranges?: Range[];
}

export function extension(type: quack.ComponentType, subtype: number = 0): ComponentTypeExtension {
  if (COMPONENT_TYPE_MAP.has(type)) {
    let fn = COMPONENT_TYPE_MAP.get(type);
    if (fn !== undefined) {
      return fn(subtype) as ComponentTypeExtension;
    }
  }
  return Invalid(subtype);
}

// PUBLIC FUNCTIONS
export function getSubtypes(type: quack.ComponentType): Array<Subtype> {
  return extension(type).subtypes;
}

export function getFriendlyName(type: quack.ComponentType, subtype: number = 0): string {
  return extension(type, subtype).friendlyName;
}

export function getDescription(type: quack.ComponentType, subtype: number = 0): string {
  return extension(type, subtype).description;
}

export function isController(type: quack.ComponentType): boolean {
  return extension(type).isController;
}

export function isSource(type: quack.ComponentType): boolean {
  return extension(type).isSource;
}

export function isCalibratable(type: quack.ComponentType): boolean {
  return extension(type).isCalibratable;
}

export function hasFan(type: quack.ComponentType): boolean {
  return extension(type).hasFan ?? false;
}

export function hasDimensions(type: quack.ComponentType): boolean {
  return extension(type).hasDimensions ?? false;
}

export function isMultiSensor(type: quack.ComponentType): boolean {
  return extension(type).multiSensor ?? false;
}

export function getAddressTypes(
  type: quack.ComponentType,
  subtype: number = 0
): Array<quack.AddressType> {
  return extension(type, subtype).addressTypes;
}

export function getInteractionResultTypes(
  type: quack.ComponentType
): Array<quack.InteractionResultType> {
  return extension(type).interactionResultTypes;
}

export function getMeasurements(
  type: quack.ComponentType,
  subtype: number = 0
): Array<ComponentMeasurement> {
  return extension(type, subtype).measurements;
}

export async function getMeasurementSummary(
  type: quack.ComponentType,
  subtype: number,
  measurement: quack.IMeasurement,
  filters: GraphFilters
): Promise<Array<Summary>> {
  let sumFunc = extension(type, subtype).measurementSummary
  return sumFunc ? sumFunc(measurement, filters) : [];
}

const validNodes = (nodeVals: number[], filters?: GraphFilters) => {
  let validNodes = nodeVals;
  let selectedNodes = filters?.selectedNodes;
  let grainFillNode = filters?.grainFilledTo;
  if (selectedNodes && grainFillNode) {
    if (selectedNodes.includes("grain")) {
      validNodes = nodeVals.slice(0, grainFillNode);
    }
    if (selectedNodes.includes("air")) {
      validNodes = nodeVals.slice(grainFillNode);
    }
  }
  return validNodes;
};

export function simpleAreaChartData(
  measurement: pond.UnitMeasurementsForComponent,
  smoothingAverages?: number,
  filters?: GraphFilters
): AreaChartData {
  let areaData: AreaChartData = {
    area: [],
    average: []
  };
  let firstTime: Moment | undefined;
  let lastTime: Moment | undefined;
  // let areaData: AreaData[] = [];
  // let avgData: AreaData[] = [];
  let maxVals: number[] = [];
  let minVals: number[] = [];
  measurement.values.forEach((val, j) => {
    let currentMax = Math.min(...validNodes(val.values, filters));
    let currentMin = Math.max(...validNodes(val.values, filters));
    let dataPoint: AreaData = {
      timestamp: moment(measurement.timestamps[j]).valueOf(),
      value: [currentMax, currentMin]
    };
    if (minVals.length === 0) {
      firstTime = moment(measurement.timestamps[j]);
    }
    maxVals.push(currentMax);
    minVals.push(currentMin);
    if (minVals.length >= (smoothingAverages ?? 0)) {
      lastTime = moment(measurement.timestamps[j]);
    }
    if (firstTime && lastTime) {
      areaData.average.push({
        timestamp:
          Math.abs(firstTime.diff(lastTime)) / 2 +
          Math.min(firstTime.valueOf(), lastTime.valueOf()),
        value: [avg(maxVals), avg(minVals)]
      });
      firstTime = undefined;
      lastTime = undefined;
      maxVals = [];
      minVals = [];
    }
    areaData.area.push(dataPoint);
  });
  return areaData;
}

export function simpleLineChartData(
  componentType: quack.ComponentType,
  measurement: pond.UnitMeasurementsForComponent,
  smoothingAverages?: number,
  filters?: GraphFilters
): LineChartData {
  let lineData: LineChartData = {
    lines: [],
    average: []
  };

  let firstTime: Moment | undefined;
  let lastTime: Moment | undefined;
  let valMap: Map<number, number[]> = new Map<number, number[]>();
  if (showMultilineCable(componentType, filters)) {
    measurement.values.forEach((vals, j) => {
      let includedVals: Point[] = [];
      let avgVals: Point[] = [];
      vals.values.forEach((val, k) => {
        if (filters?.selectedNodes?.includes(k.toString())) {
          includedVals.push({
            value: val,
            node: k
          });
          let entry = valMap.get(k);
          if (entry) {
            entry.push(val);
            if (entry.length === smoothingAverages) {
              lastTime = moment(measurement.timestamps[j]);
              avgVals.push({
                node: k,
                value: avg(entry)
              });
            }
          } else {
            valMap.set(k, [val]);
            firstTime = moment(measurement.timestamps[j]);
          }
        }
      });
      let newLineData: LineData = {
        timestamp: moment(measurement.timestamps[j]).valueOf(),
        points: includedVals
      };
      lineData.lines.push(newLineData);
      if (firstTime && lastTime) {
        lineData.average.push({
          points: avgVals,
          timestamp:
            Math.abs(firstTime.diff(lastTime)) / 2 +
            Math.min(firstTime.valueOf(), lastTime.valueOf())
        });
        firstTime = undefined;
        lastTime = undefined;
        valMap = new Map<number, number[]>();
      }
    });
  } else {
    measurement.values.forEach((vals, i) => {
      let avgVals: Point[] = [];
      let newLineData: LineData = {
        timestamp: moment(measurement.timestamps[i]).valueOf(),
        points: vals.values.map((val, i) => ({ value: val, node: i }))
      };
      lineData.lines.push(newLineData);
      vals.values.forEach((val, k) => {
        let entry = valMap.get(k);
        if (entry) {
          entry.push(val);
          if (entry.length === smoothingAverages) {
            lastTime = moment(measurement.timestamps[i]);
            avgVals.push({
              node: k,
              value: avg(entry)
            });
          }
        } else {
          valMap.set(k, [val]);
          firstTime = moment(measurement.timestamps[i]);
        }
      });
      if (firstTime && lastTime) {
        lineData.average.push({
          points: avgVals,
          timestamp:
            Math.abs(firstTime.diff(lastTime)) / 2 +
            Math.min(firstTime.valueOf(), lastTime.valueOf())
        });
        firstTime = undefined;
        lastTime = undefined;
        valMap = new Map<number, number[]>();
      }
    });
  }
  return lineData;
}

//TODO: deprecated function, new graphs are handled in measurementChart (using reCharts instead of victory)
// export function getComponentVisual(
//   type: quack.ComponentType,
//   subtype: number,
//   measurements: pond.Measurement[],
//   interactions: Interaction[],
//   overlays: pond.ComponentOverlays[],
//   orientation: GraphOrientation,
//   filters?: GraphFilters
// ) {
//   if (type === quack.ComponentType.COMPONENT_TYPE_GPS) {
//     return <GPSMap data={measurements} />;
//   }

//   let graphProps: any = getGraphProps(
//     type,
//     subtype,
//     measurements,
//     interactions,
//     overlays,
//     orientation,
//     filters
//   );

//   let ext = extension(type, subtype);
//   return (
//     <Grid2 container direction="column">
//       {ext.measurements.map((m, i) => {
//         return (
//           <Grid2 key={i}>
//             <React.Fragment>
//               <Graph
//                 {...graphProps}
//                 graphComponent={graphProps["graphComponent" + (i + 1).toString()]}
//               />
//             </React.Fragment>
//           </Grid2>
//         );
//       })}
//     </Grid2>
//   );
// }

export function hasInteractionResultType(
  componentType: quack.ComponentType,
  match: quack.InteractionResultType
): boolean {
  return extension(componentType).interactionResultTypes.includes(match);
}

export function isToggleable(type: quack.ComponentType): boolean {
  return hasInteractionResultType(type, quack.InteractionResultType.INTERACTION_RESULT_TYPE_TOGGLE);
}

export function isRunnable(type: quack.ComponentType): boolean {
  return hasInteractionResultType(type, quack.InteractionResultType.INTERACTION_RESULT_TYPE_RUN);
}

export function isSettable(type: quack.ComponentType): boolean {
  return hasInteractionResultType(type, quack.InteractionResultType.INTERACTION_RESULT_TYPE_SET);
}

export function primaryMeasurement(type: quack.ComponentType): quack.MeasurementType {
  let ext = extension(type);
  return ext.measurements.length > 0
    ? ext.measurements[0].measurementType
    : quack.MeasurementType.MEASUREMENT_TYPE_INVALID;
}

export function GetComponentIcon(
  type: quack.ComponentType,
  subtype?: number,
  theme?: "light" | "dark"
): string | undefined {
  let describer = extension(type, subtype);
  return describer.icon ? describer.icon(theme) : undefined;
}

export function GetSecondaryComponents(type: quack.ComponentType, subtype?: number): pond.ComponentSettings[] {
  let describer = extension(type, subtype)
  return describer.secondaryComponentSettings ? describer.secondaryComponentSettings() : []
}

//TODO: deprecated function, new graphs are handled in measurementChart (using reCharts instead of victory)
// export function getGraphProps(
//   componentType: quack.ComponentType,
//   subtype: number,
//   measurements: pond.Measurement[],
//   interactions: Interaction[],
//   overlays: pond.ComponentOverlays[],
//   orientation: GraphOrientation,
//   filters?: GraphFilters
// ): any {
//   const componentID: quack.IComponentID = quack.ComponentID.fromObject(
//     or(measurements[0].measurement, {
//       id: emptyComponentId()
//     }).id as any
//   );
//   let graphProps = {
//     orientation: orientation
//   } as any;
//   let ext = extension(componentType, subtype);
//   ext.measurements.forEach((componentMeasurement: ComponentMeasurement, index: number) => {
//     let describer = describeMeasurement(
//       componentMeasurement.measurementType,
//       componentType,
//       subtype
//     );
//     let range =
//       filters && filters.ranges && filters.ranges[index] ? filters.ranges[index] : undefined;
//     let selectedInteractions =
//       filters && filters.selectedInteractions ? filters.selectedInteractions : [];
//     let interactionLines = getInteractionLines(
//       componentType,
//       subtype,
//       componentMeasurement.measurementType,
//       componentID,
//       interactions,
//       selectedInteractions
//     );
//     let selectedOverlays = filters?.selectedOverlays ?? [];
//     let overlayAreas = getOverlayAreas(
//       componentMeasurement.measurementType,
//       overlays,
//       selectedOverlays
//     );
//     graphProps["graphComponent" + (index + 1).toString()] = {
//       label: componentMeasurement.label,
//       data: [],
//       tick: describer.unit(),
//       colour: componentMeasurement.colour,
//       type: componentMeasurement.graphType,
//       range: range,
//       isBoolean:
//         componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN,
//       isPercent:
//         componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT ||
//         (componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_ANALOG &&
//           subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_FUEL),
//       interactionLines: interactionLines,
//       states: ext.states,
//       decimals: describer.decimals(),
//       overlays: overlayAreas
//     } as GraphComponent;
//   });

//   //for new cables add here to get the data for multiline
//   if (showMultilineCable(componentType, filters)) {
//     let cableData: any = {};
//     if (componentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) {
//       cableData = multilineGrainCableData(measurements, filters);
//     } else if (componentType === quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE) {
//       cableData = multilinePressureCableData(measurements, filters);
//     } else if (componentType === quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE) {
//       cableData = multilineCapCableData(measurements, filters);
//     }
//     //will loop through the keys in cableData
//     //IMPORTANT keys must be data1, data2, etc in the multiline function in the component
//     for (let i = 1; i <= Object.keys(cableData).length; i++) {
//       let gc = "graphComponent" + i;
//       if (graphProps[gc]) {
//         graphProps[gc].data = cableData["data" + i];
//         graphProps[gc].type = GraphType.MULTILINE;
//       }
//     }
//   } else {
//     measurements.forEach((reading: pond.Measurement, readIndex) => {
//       if (notNull(reading) && notNull(reading.timestamp) && notNull(reading.measurement)) {
//         let date = new Date(Date.parse(reading.timestamp));
//         for (let index = 0; index < ext.measurements.length; index++) {
//           let componentMeasurement: ComponentMeasurement = ext.measurements[index];
//           if (componentMeasurement.isErrorMeasurement(reading.measurement)) {
//             break;
//           }
//           let dataPoint = null;
//           switch (componentMeasurement.graphType) {
//             case GraphType.AREA:
//               let avg = componentMeasurement.extract(
//                 or(reading.measurement, {} as quack.Measurement),
//                 filters
//               );
//               if (avg && avg.low !== null && avg.high !== null) {
//                 dataPoint = {
//                   x: date,
//                   y: avg.low,
//                   y0: avg.high
//                 } as GraphPoint;
//               }
//               break;
//             case GraphType.SCATTER:
//               let scatter = componentMeasurement.extract(
//                 or(reading.measurement, {} as quack.Measurement),
//                 filters
//               );
//               if (scatter && scatter.value !== undefined && scatter.value !== null) {
//                 dataPoint = {
//                   x: date,
//                   y: Number(scatter.value),
//                   bubble: or(scatter.bubble, 0)
//                 } as GraphPoint;
//               }
//               break;
//             case GraphType.RADAR:
//               if (subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_WIND_DIRECTION) {
//                 let value = componentMeasurement.extract(
//                   or(reading.measurement, {} as quack.Measurement),
//                   filters
//                 );

//                 dataPoint = {
//                   x: getWindDirection(value).dString,
//                   y: getWindDirection(value).direction
//                 };
//               }
//               break;
//             default:
//               //linear and bar
//               let value = componentMeasurement.extract(
//                 or(reading.measurement, {} as quack.Measurement),
//                 filters
//               );

//               if (value !== undefined && value !== null) {
//                 if (componentType === quack.ComponentType.COMPONENT_TYPE_ANALOG_INPUT) {
//                   if (subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_FUEL) {
//                     if (value > 100) {
//                       value = 0;
//                     }
//                   }
//                 }
//                 if (componentType === quack.ComponentType.COMPONENT_TYPE_EDGE_TRIGGERED) {
//                   if (subtype === quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_WIND_SPEED) {
//                     if (readIndex < measurements.length - 1) {
//                       //do math
//                       //get the time of both the current reading and the reading before it
//                       let currentTime = new Date(
//                         Date.parse(measurements[readIndex].timestamp)
//                       ).valueOf();
//                       let prevTime = new Date(
//                         Date.parse(measurements[readIndex + 1].timestamp)
//                       ).valueOf();

//                       //get the difference in seconds between the two
//                       let deltaTime = (currentTime - prevTime) / 1000;

//                       //divide the ticks by the time between intervals for the average ticks per second and multiply
//                       //by 2.4 to convert to km/h
//                       value = (Number(value) / deltaTime) * 2.4;
//                     } else {
//                       value = 0;
//                     }
//                   }
//                   if (subtype === quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_RAIN) {
//                     value = Math.round(value * 0.2794);
//                   }
//                 }
//                 dataPoint = {
//                   x: date,
//                   y: Number(value)
//                 } as GraphPoint;
//               }
//               break;
//           }
//           if (dataPoint !== null) {
//             graphProps["graphComponent" + (index + 1).toString()].data.push(dataPoint);
//           }
//         }
//       }
//     });
//   }
//   return graphProps;
// }

function getWindDirection(value: number) {
  //determine the general direction based on the value
  //3584 value when in that direction exactly
  let direction: number = 0;
  let dString: string = "";
  if (value >= 3569 && value <= 3599) {
    direction = 9; // N the direction the Arrow would be pointing
    dString = "S"; // the direction the wind is going
  }
  //2387
  if (value >= 2372 && value <= 2402) {
    direction = 10; // NNE
    dString = "SSW";
  }
  //2603
  if (value >= 2588 && value <= 2618) {
    direction = 11; // NE
    dString = "SW";
  }
  //653
  if (value >= 638 && value <= 668) {
    direction = 12; // ENE
    dString = "WSW";
  }
  //718
  if (value >= 703 && value <= 733) {
    direction = 13; // E
    dString = "W";
  }
  //523
  if (value >= 508 && value <= 538) {
    direction = 14; // ESE
    dString = "WNW";
  }
  //1306
  if (value >= 1291 && value <= 1321) {
    direction = 15; // SE
    dString = "NW";
  }
  //945
  if (value >= 930 && value <= 960) {
    direction = 16; // SSE
    dString = "NNW";
  }
  //1857
  if (value >= 1842 && value <= 1872) {
    direction = 1; // S
    dString = "N";
  }
  //1640
  if (value >= 1625 && value <= 1655) {
    direction = 2; // SSW
    dString = "NNE";
  }
  //3165
  if (value >= 3150 && value <= 3180) {
    direction = 3; //SW
    dString = "NE";
  }
  //3072
  if (value >= 3057 && value <= 3087) {
    direction = 4; // WSW
    dString = "ENE";
  }
  //3940
  if (value >= 3925 && value <= 3955) {
    direction = 5; // W
    dString = "E";
  }
  //3684
  if (value >= 3669 && value <= 3699) {
    direction = 6; // WNW
    dString = "ESE";
  }
  //3818
  if (value >= 3803 && value <= 3833) {
    direction = 7; // NW
    dString = "SE";
  }
  //3371
  if (value >= 3356 && value <= 3386) {
    direction = 8; // NNW
    dString = "SSE";
  }

  return { direction, dString };
}

//creates interaction lines for a specific measurement type of a component
function getInteractionLines(
  componentType: quack.ComponentType,
  subtype: number,
  targetMeasurementType: quack.MeasurementType,
  componentID: quack.IComponentID,
  interactions: Interaction[],
  selectedInteractions: any[]
): Array<InteractionLine> {
  let interactionLines: Array<InteractionLine> = [];

  let componentInteractions = findInteractionsAsSource(componentID, interactions);
  componentInteractions.forEach((interaction: Interaction) => {
    if (interaction.settings.conditions && selectedInteractions.includes(interaction.key())) {
      interaction.settings.conditions.forEach(
        (interactionCondition: pond.IInteractionCondition) => {
          //exclude Grain Cable humidity from interaction lines since graph is based on moisture
          if (
            !(
              componentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE &&
              interactionCondition.measurementType ===
                quack.MeasurementType.MEASUREMENT_TYPE_PERCENT
            ) &&
            targetMeasurementType === interactionCondition.measurementType
          ) {
            interactionLines.push(
              createInteractionLine(componentType, subtype, interactionCondition)
            );
          }
        }
      );
    }
  });

  return interactionLines;
}

function getOverlayAreas(
  measurementType: quack.MeasurementType,
  overlays: pond.ComponentOverlays[],
  selectedOverlays: string[]
): Array<pond.ComponentOverlays> {
  let overlayAreas: Array<pond.ComponentOverlays> = [];
  overlays.forEach((o, i) => {
    if (o.measurementType === measurementType && selectedOverlays.includes(i.toString())) {
      overlayAreas.push(o);
    }
  });
  return overlayAreas;
}

function createInteractionLine(
  componentType: quack.ComponentType,
  subtype: number,
  interactionCondition: pond.IInteractionCondition
): InteractionLine {
  // default line
  let lineColour = "#FFF";
  let lineValue = or(interactionCondition.value, 0);

  extension(componentType).measurements.forEach((measurement: ComponentMeasurement) => {
    if (interactionCondition.measurementType === measurement.measurementType) {
      let describer = describeMeasurement(
        interactionCondition.measurementType,
        subtype,
        componentType
      );
      lineColour = describer.colour();
      lineValue = describer.toDisplay(lineValue);
    }
  });
  return { value: lineValue, colour: lineColour } as InteractionLine;
}

//detects the cable multiline scenario, new cables add here to show multiline graph
export function showMultilineCable(type: quack.ComponentType, filters?: GraphFilters): boolean {
  let cableTypes = [
    quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE,
    quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE,
    quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE,
    quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE
  ];
  if (!cableTypes.includes(type)) {
    return false;
  }

  return (
    filters !== undefined &&
    filters.selectedNodes !== undefined &&
    filters.selectedNodes.length > 0 &&
    !filters.selectedNodes.includes("all") &&
    !filters.selectedNodes.includes("grain") &&
    !filters.selectedNodes.includes("air")
  );
}

//TODO: Possibly restrict the component options based on device product here
export const GetComponentTypeOptions = (addressTypeRestriction?: quack.AddressType): Option[] => {
  let options: Option[] = [];

  for (let key in quack.ComponentType) {
    if (isNaN(Number(key))) {
      let type = quack.ComponentType[key as keyof typeof quack.ComponentType];
      let typeExtension = extension(type);

      if (type === quack.ComponentType.COMPONENT_TYPE_INVALID) {
        continue;
      }

      if (typeExtension.subtypes.length > 0) {
        typeExtension.subtypes.forEach(subtype => {
          let subtypeExtension = extension(type, subtype.key);
          if (
            !addressTypeRestriction ||
            subtypeExtension.addressTypes.includes(addressTypeRestriction)
          ) {
            let option: Option = {
              value: key + ":" + subtype.key,
              label: subtype.friendlyName,
              group: typeExtension.friendlyName,
              defaultCableID: subtype.defaultCableID
            };
            options.push(option);
          }
        });
      } else {
        if (
          addressTypeRestriction &&
          !typeExtension.addressTypes.includes(addressTypeRestriction)
        ) {
          continue;
        }
        let option = {
          value: key + ":0",
          label: typeExtension.friendlyName,
          group: typeExtension.friendlyName
        };
        options.push(option);
      }
    }
  }

  return options;
};

export const GetNumNodes = (
  type: quack.ComponentType,
  measurement?: quack.Measurement | null
): number => {
  let numNodes = 0;
  switch (type) {
    case quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE:
      if (measurement && measurement.grainCable) {
        let grainCable = measurement.grainCable as any;

        let tempNodes = 0;
        if (grainCable.celciusTimes10) tempNodes = grainCable.celciusTimes10.length;

        let humNodes = 0;
        if (grainCable.relativeHumidityTimes100)
          humNodes = grainCable.relativeHumidityTimes100.length;

        numNodes = Math.min(tempNodes, humNodes);

        /*numNodes = Math.min(
          or(grainCable.celciusTimes_10.length as any, { celciusTimes10: [] }).celciusTimes10.length,
          or(grainCable as any, { relativeHumidityTimes100: [] }).relativeHumidityTimes100.length
        );*/
      }
      break;
    case quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE:
      if (measurement && measurement.pressureCable) {
        let pressureCable = measurement.pressureCable;
        numNodes = or(pressureCable as any, { pascals: [] }).pascals.length;
      }
      break;
    case quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE:
      if (measurement && measurement.capacitorCable) {
        let capacitorCable = measurement.capacitorCable;
        numNodes = Math.min(
          or(capacitorCable as any, { groundRefFf: [] }).groundRefFf.length,
          or(capacitorCable as any, { selfRefFf: [] }).selfRefFf.length
        );
      }
      break;
    default:
      break;
  }
  return numNodes;
};

export const GetNumNodesFromUnitMeasurement = (unitMeasurement: UnitMeasurement) => {
  let nodes = 0;
  if (unitMeasurement.values && unitMeasurement.values.length > 0) {
    let myValues = unitMeasurement.values;
    if (myValues[0] && myValues[0].values.length > 0) {
      nodes = myValues[0].values.length;
    }
  }
  return nodes;
};
