import { Box, CircularProgress, MenuItem, TextField } from "@mui/material";
import GrainDryingChart, { GrainDryingPoint } from "charts/GrainDryingChart";
import { GrainCable } from "models/GrainCable";
import { Plenum } from "models/Plenum";
import { UnitMeasurement } from "models/UnitMeasurement";
import moment from "moment";
import { quack } from "protobuf-ts/quack";
import React, { useEffect, useState } from "react";
import { avg } from "utils";

interface Props {
  plenums: Plenum[];
  cables: GrainCable[];
  measurementMap: Map<string, UnitMeasurement[]>;
  returnLastVPD: (lastVPD: GrainDryingPoint | undefined) => void;
  newXDomain?: number[] | string[];
  multiGraphZoom?: (domain: number[] | string[]) => void;
  multiGraphZoomOut?: boolean;
  //perBushelCFM?: number;
}

interface UnitMeasurementHelper {
  timestamp: string;
  compKey: string;
  measurementType: quack.MeasurementType;
  value: number;
}

interface NodeOption {
  name: string;
  number: number;
}

export default function BinGraphsVPD(props: Props) {
  const {
    plenums,
    cables,
    measurementMap,
    newXDomain,
    multiGraphZoom,
    multiGraphZoomOut,
    returnLastVPD
    //perBushelCFM
  } = props;
  const [vpdData, setVpdData] = useState<GrainDryingPoint[]>([]);
  const [plenum, setPlenum] = useState<string>("");
  const [cable, setCable] = useState<string>("");
  const [cableOptions, setCableOptions] = useState<GrainCable[]>([]);
  const [calculating, setCalculating] = useState(false);
  const [selectedNode, setSelectedNode] = useState(-1);
  const [nodeOps, setNodeOps] = useState<NodeOption[]>([]);

  useEffect(() => {
    let plenum: string = "";
    if (plenums[0]) {
      plenum = plenums[0].key();
    }
    let ops: GrainCable[] = [];
    let cable: string = "";
    cables.forEach(c => {
      if (c.humidities.length > 0) {
        ops.push(c);
        if (ops.length === 1) {
          cable = c.key();
          let nodes: NodeOption[] = [];
          c.humidities.forEach((hum, i) => {
            nodes.push({
              name: "Node" + (i + 1),
              number: i
            });
          });
          setNodeOps(nodes);
        }
      }
    });
    setPlenum(plenum);
    setCable(cable);
    setCableOptions(ops);
  }, [plenums, cables]);

  const buildHelperArray = (
    plenumUM: UnitMeasurement[],
    cableUM: UnitMeasurement[],
    node?: number
  ) => {
    let helper: UnitMeasurementHelper[] = [];
    //doing the plenum and the cable seperately because the plenum will always be done the same way however the cable will be done differently if there is a node selected
    plenumUM.forEach(unitM => {
      if (
        unitM.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE ||
        unitM.type === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT
      ) {
        unitM.values.forEach((valSet, i) => {
          helper.push({
            timestamp: unitM.timestamps[i],
            measurementType: unitM.type,
            compKey: unitM.componentId,
            value: avg(valSet.values)
          });
        });
      }
    });
    cableUM.forEach(unitM => {
      if (
        unitM.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE ||
        unitM.type === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT
      ) {
        unitM.values.forEach((valSet, i) => {
          let newHelper = {
            timestamp: unitM.timestamps[i],
            measurementType: unitM.type,
            compKey: unitM.componentId,
            value: node !== undefined ? valSet.values[node] : avg(valSet.values)
          };
          helper.push(newHelper);
        });
      }
    });
    helper.sort((a, b) => {
      return moment(a.timestamp).valueOf() - moment(b.timestamp).valueOf();
    });
    return helper;
  };

  // T = temperature im degrees Celsius
  // RH = relative humidity (%)
  const calculateVPD = (T: number, RH: number) => {
    const es = 0.6108 * Math.exp((17.27 * T) / (T + 237.3));
    const ea = (RH / 100) * es;
    return ea - es;
  };

  const buildData = (measurements: UnitMeasurementHelper[]) => {
    let data: GrainDryingPoint[] = [];

    let recent: GrainDryingPoint | undefined;

    let currentPlenumTemp: number;
    let currentPlenumHum: number;
    let currentCableTemp: number;
    let currentCableHum: number;
    if (measurements[0]) {
      let lastTime = moment(measurements[0].timestamp);
      measurements.forEach(m => {
        let thisTime = moment(m.timestamp);
        if (m.compKey === plenum) {
          if (m.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) {
            currentPlenumTemp = m.value;
          } else if (m.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT) {
            currentPlenumHum = m.value;
          }
        }
        if (m.compKey === cable) {
          if (m.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) {
            currentCableTemp = m.value;
          } else if (m.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT) {
            currentCableHum = m.value;
          }
        }
        if (currentPlenumTemp && currentPlenumHum && currentCableTemp && currentCableHum) {
          if (thisTime.diff(lastTime, "seconds") >= 2) {
            lastTime = thisTime;
            let plenumVPD = calculateVPD(currentPlenumTemp, currentPlenumHum);
            let cableVPD = calculateVPD(currentCableTemp, currentCableHum);
            let dryScore = cableVPD - plenumVPD;
            // if (perBushelCFM) {
            //   dryScore = dryScore * perBushelCFM;
            // }
            let newPoint: GrainDryingPoint = {
              dryScore: dryScore,
              timestamp: moment(m.timestamp).valueOf()
            };
            data.push(newPoint);
            if (!recent || recent.timestamp < newPoint.timestamp) {
              recent = newPoint;
            }
          }
        }
      });
    }

    setVpdData(data);
    returnLastVPD(recent);
  };

  //calculate the vpd using the average values of the nodes
  useEffect(() => {
    setSelectedNode(-1);
    setVpdData([]);
    if (plenum && cable) {
      let plenumUM = measurementMap.get(plenum);
      let cableUM = measurementMap.get(cable);
      if (plenumUM && cableUM) {
        setCalculating(true);
        let measurements = buildHelperArray(plenumUM, cableUM);
        buildData(measurements);
        setCalculating(false);
      }
    }
  }, [plenum, cable, measurementMap]); // eslint-disable-line react-hooks/exhaustive-deps

  //calculate the vpd using the specified node value
  const nodeVPD = (node: number) => {
    if (plenum && cable) {
      setVpdData([]);
      let plenumUM = measurementMap.get(plenum);
      let cableUM = measurementMap.get(cable);
      if (plenumUM && cableUM) {
        setCalculating(true);
        let measurements: UnitMeasurementHelper[] = [];
        if (node === -1) {
          measurements = buildHelperArray(plenumUM, cableUM);
        } else {
          measurements = buildHelperArray(plenumUM, cableUM, node);
        }
        buildData(measurements);
        setCalculating(false);
      }
    }
  };

  return (
    <React.Fragment>
      {plenums.length > 1 && (
        <TextField
          style={{ marginRight: 15 }}
          label="Plenum"
          variant="outlined"
          select
          id="plenumSelector"
          value={plenum}
          onChange={e => {
            setPlenum(e.target.value as string);
          }}>
          {plenums.map(k => {
            return (
              <MenuItem key={k.key()} value={k.key()}>
                {k.name()}
              </MenuItem>
            );
          })}
        </TextField>
      )}
      {cableOptions.length > 1 && (
        <TextField
          style={{ marginRight: 15 }}
          label="Cable"
          variant="outlined"
          select
          id="cableSelector"
          value={cable}
          onChange={e => {
            setCable(e.target.value as string);
          }}>
          {cableOptions.map(k => {
            return (
              <MenuItem key={k.key()} value={k.key()}>
                {k.name()}
              </MenuItem>
            );
          })}
        </TextField>
      )}
      <TextField
        label="Node"
        variant="outlined"
        select
        id="nodeSelector"
        value={selectedNode}
        onChange={e => {
          setSelectedNode(+e.target.value);
          nodeVPD(+e.target.value);
        }}>
        <MenuItem key={"noNode"} value={-1}>
          Average Score
        </MenuItem>
        {nodeOps.map(n => {
          return (
            <MenuItem key={n.name} value={n.number}>
              {n.name}
            </MenuItem>
          );
        })}
      </TextField>
      {calculating ? (
        <Box minHeight={100} display="flex" justifyContent="center" alignItems="center">
          <CircularProgress />
        </Box>
      ) : (
        vpdData.length > 0 && (
          <GrainDryingChart
            data={vpdData}
            showStroke
            displayY
            newXDomain={newXDomain}
            multiGraphZoom={multiGraphZoom}
            multiGraphZoomOut={multiGraphZoomOut}
          />
        )
      )}
    </React.Fragment>
  );
}
