import {
  Avatar,
  Box,
  Button,
  Card,
  CardHeader,
  LinearProgress,
  Theme,
  Tooltip,
  Typography
} from "@mui/material";
import { ZoomOut } from "@mui/icons-material";
import AreaGraph, { AreaData } from "charts/measurementCharts/AreaGraph";
import MultiLineGraph, { LineData, Point } from "charts/measurementCharts/MultiLineGraph";
import { GetDefaultDateRange } from "common/time/DateRange";
import TimeBar from "common/time/TimeBar";
import UnitMeasurementSummary from "component/UnitMeasurementSummary";
import { useThemeType } from "hooks";
import { Component } from "models";
import { Gate } from "models/Gate";
import { UnitMeasurement } from "models/UnitMeasurement";
import moment, { Moment } from "moment";
import { GetComponentIcon } from "pbHelpers/ComponentType";
import { describeMeasurement, MeasurementDescriber } from "pbHelpers/MeasurementDescriber";
import { useGateAPI, useGlobalState } from "providers";
import React, { useEffect, useState } from "react";
import { avg } from "utils";
import GateFlowGraph from "./GateFlowGraph";
import { makeStyles } from "@mui/styles";
import { cloneDeep } from "lodash";

interface Props {
  gate: Gate;
  display: "analytics" | "sensors";
  compMap: Map<string, Component>;
  device: string | number;
  pressure: string;
  ambient: string;
  tempChain: string;
  redKey: string;
  greenKey: string;
  setPCAState: (state: boolean) => void;
}

const useStyles = makeStyles((theme: Theme) => ({
    card: {
      position: "relative",
      display: "flex",
      height: "100%",
      flexDirection: "column",
      overflow: "visible"
    },
    cardHeader: {
      padding: theme.spacing(1),
      paddingLeft: theme.spacing(2),
      marginRight: 10
    },
    avatarIcon: {
      width: 33,
      height: 33
    }
  })
);

export default function GateGraphs(props: Props) {
  const { gate, display, compMap, device, pressure, ambient, tempChain, greenKey, redKey, setPCAState } = props;
  const [{ as }] = useGlobalState();
  const [xDomain, setXDomain] = useState<string[] | number[]>(["dataMin", "dataMax"]);
  const [zoomed, setZoomed] = useState(false);
  const defaultDateRange = GetDefaultDateRange();
  const [startDate, setStartDate] = useState<Moment>(defaultDateRange.start);
  const [endDate, setEndDate] = useState<Moment>(defaultDateRange.end);
  const themeType = useThemeType();
  const classes = useStyles();
  const [{ user }] = useGlobalState();
  const [compMeasurements, setCompMeasurements] = useState<Map<string, UnitMeasurement[]>>(
    new Map<string, UnitMeasurement[]>()
  );
  const gateAPI = useGateAPI();
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    if (loading) return;
    setLoading(true);
    let measurementMap: Map<string, UnitMeasurement[]> = new Map<string, UnitMeasurement[]>();
    gateAPI
      .listGateMeasurements(gate.key, startDate.toISOString(), endDate.toISOString(), as)
      .then(resp => {
        resp.data.measurements.forEach(um => {
          let unitMeasurement = UnitMeasurement.any(um, user);
          let entry = measurementMap.get(unitMeasurement.componentId);
          if (entry) {
            entry.push(unitMeasurement);
          } else {
            measurementMap.set(unitMeasurement.componentId, [unitMeasurement]);
          }
        });
        setLoading(false);
        setCompMeasurements(measurementMap);
      });
  }, [gate, endDate, gateAPI, startDate, as]); // eslint-disable-line react-hooks/exhaustive-deps

  const updateDateRange = (newStartDate: any, newEndDate: any) => {
    let range = GetDefaultDateRange();
    range.start = newStartDate;
    range.end = newEndDate;
    setStartDate(newStartDate);
    setEndDate(newEndDate);
  };

  const zoomOut = () => {
    setXDomain(["dataMin", "dataMax"]);
    setZoomed(false);
  };

  const lineGraph = (
    unitMeasurement: UnitMeasurement,
    describer: MeasurementDescriber,
    averages?: number
  ) => {
    if (unitMeasurement.values.length > 0) {
      let firstTime: Moment | undefined;
      let lastTime: Moment | undefined;
      //build line data to pass to graph
      let lineData: LineData[] = [];
      let averagedData: LineData[] = [];
      let valMap: Map<number, number[]> = new Map<number, number[]>();

      unitMeasurement.values.forEach((vals, i) => {
        let avgVals: Point[] = [];
        let newLineData: LineData = {
          timestamp: moment(unitMeasurement.timestamps[i]).valueOf(),
          points: vals.values.map((val, i) => ({ value: val, node: i }))
        };
        lineData.push(newLineData);
        if (averages) {
          vals.values.forEach((val, k) => {
            let entry = valMap.get(k);
            if (entry) {
              entry.push(val);
              if (entry.length >= averages) {
                lastTime = moment(unitMeasurement.timestamps[i]);
                avgVals.push({
                  node: k,
                  value: avg(entry)
                });
              }
            } else {
              valMap.set(k, [val]);
              firstTime = moment(unitMeasurement.timestamps[i]);
            }
          });
        }
        if (firstTime && lastTime) {
          averagedData.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 (
        <MultiLineGraph
          customHeight={300}
          key={unitMeasurement.type}
          lineData={lineData}
          //averagedData={showAveraged ? averagedData : []}
          numLines={unitMeasurement.values.length > 1 ? unitMeasurement.values[0].values.length : 0}
          describer={describer}
          tooltip
          newXDomain={xDomain}
          multiGraphZoom={domain => {
            setXDomain(domain);
            setZoomed(true);
          }}
          multiGraphZoomOut
        />
      );
    }
  };

  const areaGraph = (
    unitMeasurement: UnitMeasurement,
    describer: MeasurementDescriber,
    averages?: number
  ) => {
    if (unitMeasurement.timestamps.length > 0) {
      let firstTime: Moment | undefined;
      let lastTime: Moment | undefined;
      //build line data to pass to graph
      let areaData: AreaData[] = [];
      let avgData: AreaData[] = [];
      let maxVals: number[] = [];
      let minVals: number[] = [];

      unitMeasurement.values.forEach((val, j) => {
        let currentMax = Math.min(...val.values);
        let currentMin = Math.max(...val.values);
        let dataPoint: AreaData = {
          timestamp: moment(unitMeasurement.timestamps[j]).valueOf(),
          value: [currentMax, currentMin]
        };
        if (averages) {
          if (minVals.length === 0) {
            firstTime = moment(unitMeasurement.timestamps[j]);
          }
          maxVals.push(currentMax);
          minVals.push(currentMin);
          if (minVals.length >= averages) {
            lastTime = moment(unitMeasurement.timestamps[j]);
          }
        }
        if (firstTime && lastTime) {
          avgData.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.push(dataPoint);
      });
      return (
        <AreaGraph
          key={unitMeasurement.type}
          customHeight={350}
          data={areaData}
          //averagedData={showAveraged ? avgData : []}
          describer={describer}
          tooltip
          newXDomain={xDomain}
          multiGraphZoom={domain => {
            setXDomain(domain);
            setZoomed(true);
          }}
          multiGraphZoomOut
        />
      );
    }
  };

  const graphHeader = (comp: Component) => {
    const componentIcon = GetComponentIcon(comp.settings.type, comp.settings.subtype, themeType);
    console.log(comp)
    let measurement = cloneDeep(comp.status.measurement)
    let umArray = measurement.map(um => UnitMeasurement.create(um, user))
    return (
      <CardHeader
        avatar={
          <Avatar
            variant="square"
            src={componentIcon}
            className={classes.avatarIcon}
            alt={comp.name()}
          />
        }
        title={<Typography style={{ fontSize: 25, fontWeight: 650 }}>{comp.name()}</Typography>}
        subheader={
          <UnitMeasurementSummary
            component={comp}
            reading={UnitMeasurement.convertLastMeasurement(umArray)}
          />
        }
      />
    );
  };

  const graphCard = (component: Component, unitMeasurements: UnitMeasurement[]) => {
    return (
      <Card raised key={component.key()} style={{ padding: 10, marginBottom: 15 }}>
        {graphHeader(component)}
        {unitMeasurements.map(um => {
          if (um.values[0] && um.values[0].values.length > 1) {
            return areaGraph(
              um,
              describeMeasurement(um.type, component.type(), component.subType()),
              component.settings.smoothingAverages
            );
          } else {
            return lineGraph(
              um,
              describeMeasurement(um.type, component.type(), component.subType()),
              component.settings.smoothingAverages
            );
          }
        })}
      </Card>
    );
  }

  /**
   * This function creates the charts for the gate page using the MAIN components on the gate, it DOES NOT show any other components that could be on the device
   * @returns list of cards for the charts
   */
  const sensorGraphs = () => {
    let graphs: JSX.Element[] = []

    //pressure
    let pressureComp = compMap.get(pressure)
    let pressureReadings = compMeasurements.get(pressure)
    if(pressureComp && pressureReadings){
      graphs.push(graphCard(pressureComp, pressureReadings))
    }
    //T/H chain
    let tempComp = compMap.get(tempChain)
    let tempReadings = compMeasurements.get(tempChain)
    if(tempComp && tempReadings){
      graphs.push(graphCard(tempComp, tempReadings))
    }
    //ambient
    let ambientComp = compMap.get(ambient)
    let ambientReadings = compMeasurements.get(ambient)
    if (ambientComp && ambientReadings){
      graphs.push(graphCard(ambientComp, ambientReadings))
    }
    //green
    let greenComp = compMap.get(greenKey)
    let greenReadings = compMeasurements.get(greenKey)
    if (greenComp && greenReadings){
      graphs.push(graphCard(greenComp, greenReadings))
    }
    //red
    let redComp = compMap.get(redKey)
    let redReadings = compMeasurements.get(redKey)
    if (redComp && redReadings){
      graphs.push(graphCard(redComp, redReadings))
    }
    return graphs
  }

  const analyticGraphs = () => {
    return (
      <Box>
        <GateFlowGraph
          newXDomain={xDomain}
          device={device}
          start={startDate}
          end={endDate}
          gate={gate}
          ambient={ambient}
          pressureComponent={pressure}
          setPCAState={(state: boolean) => {
            setPCAState(state);
          }}
          multiGraphZoom={domain => {
            setXDomain(domain);
            setZoomed(true);
          }}
        />
      </Box>
    );
  };

  const showGraphs = () => {
    switch (display) {
      case "sensors":
        return sensorGraphs();
      case "analytics":
        return analyticGraphs();
      default:
        return <Box>Unknown Graph Type Selected</Box>;
    }
  };

  return (
    <React.Fragment>
      <Box display="flex" justifyContent="space-between" alignItems="center" marginBottom={2}>
        <TimeBar updateDateRange={updateDateRange} startDate={startDate} endDate={endDate} />
        {zoomed && (
          <Tooltip title="Zoom Out Graphs">
            <Button variant="outlined" onClick={zoomOut}>
              <ZoomOut />
            </Button>
          </Tooltip>
        )}
      </Box>
      {loading ? <LinearProgress /> : showGraphs()}
    </React.Fragment>
  );
}
