import {
  Card,
  CircularProgress,
  Typography
} from "@mui/material";
import { blue, grey, red, yellow, orange } from "@mui/material/colors";
import BarGraph, { BarData } from "charts/BarGraph";
import { Bin } from "models";
import moment, { Moment } from "moment";
import { pond } from "protobuf-ts/pond";
import { useBinAPI, useGlobalState } from "providers";
import { useCallback, useEffect, useState } from "react";
import { Legend } from "recharts";
import { getGrainUnit } from "utils";
import BinLevelAreaGraph from "./BinLevelAreaGraph";

interface Props {
  binLoading: boolean;
  bin: Bin;
  colour: string;
  fertilizerBin: boolean;
  startDate: Moment;
  endDate: Moment;
  customHeight?: number | string;
}

interface InventoryAt {
  timestamp: number;
  value: number;
}

export default function BinLevelOverTime(props: Props) {
  const { bin, fertilizerBin, colour, startDate, endDate, customHeight } = props;
  const binAPI = useBinAPI();
  const [{as}] = useGlobalState();
  const [inventoryData, setInventoryData] = useState<InventoryAt[]>([]);
  const [dataLoading, setDataLoading] = useState(false);
  const [capacity, setCapacity] = useState<number | undefined>();
  
  const [modeData, setModeData] = useState<BarData[]>([]);
  const [histDataLoading, setHistDataLoading] = useState(false);

  const getFill = (mode: pond.BinMode) => {
    let fill = "";
    switch (mode) {
      case pond.BinMode.BIN_MODE_STORAGE:
        fill = yellow[500];
        break;
      case pond.BinMode.BIN_MODE_COOLDOWN:
        fill = orange[500];
        break;
      case pond.BinMode.BIN_MODE_DRYING:
        fill = red[500];
        break;
      case pond.BinMode.BIN_MODE_HYDRATING:
        fill = blue[500];
        break;
      default:
        fill = grey[500];
        break;
    }
    return fill;
  };

  //loads the data from the bin history table
  const loadHistoryData = useCallback(() => {
    setDataLoading(true);
    let cap = bin.settings.specs?.bushelCapacity;
    if (fertilizerBin && cap) {
      cap = cap * 35.239;
    }
    if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && cap) {
      cap = cap / bin.bushelsPerTonne();
    }
    setCapacity(cap);
    binAPI
      .listHistoryBetween(bin.key(), 500, startDate.toISOString(), endDate.toISOString())
      .then(resp => {
        let data: BarData[] = [];
        let lastBushels = -1;
        let currentMode: pond.BinMode | undefined = undefined
        let modeData: BarData[] = []
        resp.data.history.forEach(hist => {
          //build the data for the inventory bar graph
          if (hist.settings?.inventory) {
            let bushels = hist.settings.inventory.grainBushels ?? 0;
            if (bushels !== lastBushels) {
              let newData: InventoryAt = {
                timestamp: moment(hist.timestamp).valueOf(),
                value: fertilizerBin
                  ? Math.round(bushels * 35.239)
                  : getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT
                  ? Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100
                  : bushels
              };
              data.push(newData);
              lastBushels = bushels;
            }
          }
          //build the data for the mode change bar graph
          let histBin = Bin.create()
          histBin.settings = hist.settings ?? pond.BinSettings.create()
          if(hist.settings && currentMode !== hist.settings.mode){
            currentMode = hist.settings.mode
            modeData.push({
              timestamp: moment(hist.timestamp).valueOf(),
              value: 1,
              fill: getFill(currentMode)
            })
          }
        });
        let currentTime = moment().valueOf();
        if (data.length === 0) {
          let bushels = bin.bushels();
          data.push({
            value: fertilizerBin
              ? Math.round(bushels * 35.239)
              : getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT
              ? Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100
              : bushels,
            timestamp: currentTime
          });
        }
        if(modeData.length === 0){
          modeData.push({
            timestamp: currentTime,
            value: 1,
            fill: getFill(bin.settings.mode)
          })
        }
        setInventoryData(data);
        setModeData(modeData)
      })
      .finally(() => {
        setDataLoading(false);
      });
  },[startDate, endDate, bin, fertilizerBin])

  //loads the data from the measuremnts table as a unit measurement
  const loadMeasurementData = useCallback(() => {
    setDataLoading(true);
    let cap = bin.settings.specs?.bushelCapacity;
    if (fertilizerBin && cap) {
      cap = cap * 35.239;
    }
    if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && cap) {
      cap = cap / bin.bushelsPerTonne();
    }
    setCapacity(cap);
    let autoBarData: BarData[] = [];

    //load the measurment data to build the bushel bars
    binAPI
      .listBinMeasurements(bin.key(), startDate, endDate, 500, 0, "asc")
      .then(resp => {
        let lastBushels = -1;

        resp.data.measurements.forEach(objMeasurement => {
          //set the bushel cable measurements
          let m = pond.UnitMeasurementsForObject.fromObject(objMeasurement);

          //val will be an array of only a single number since this is not a measurement that involves nodes
          m.values.forEach((val, i) => {
            if (val.values[0] && m.timestamps[i]) {
              if (lastBushels !== val.values[0]) {
                autoBarData.push({
                  value: val.values[0],
                  timestamp: moment(m.timestamps[i]).valueOf(),
                });
                lastBushels = val.values[0];
              }
            }
          });
        });
        if (autoBarData.length === 0) {
          let bushels = bin.bushels();
          let currentTime = moment().valueOf();
          autoBarData.push({
            value: fertilizerBin
              ? Math.round(bushels * 35.239)
              : getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT
              ? Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100
              : bushels,
            timestamp: currentTime
          });
        }
        setInventoryData(autoBarData);
      })
      .catch(err => {})
      .finally(() => {
        setDataLoading(false);
      });
  },[startDate, endDate, bin, fertilizerBin])

  const modeDataFromHistory = useCallback(()=>{
    if (histDataLoading) return;
    setHistDataLoading(true)
    binAPI
      .listHistoryBetween(bin.key(), 500, startDate.toISOString(), endDate.toISOString())
      .then(resp => {
        //loop through the history finding all the times the bin mode changed
        let currentMode: pond.BinMode | undefined = undefined
        let modeData: BarData[] = []
        resp.data.history.forEach(hist => {
          let histBin = Bin.create()
          histBin.settings = hist.settings ?? pond.BinSettings.create()
          if(hist.settings && currentMode !== hist.settings.mode){
            currentMode = hist.settings.mode
            modeData.push({
              timestamp: moment(hist.timestamp).valueOf(),
              value: 1,
              fill: getFill(currentMode)
            })
          }
        })
        if(modeData.length === 0){
          modeData.push({
            timestamp: moment().valueOf(),
            value: 1,
            fill: getFill(bin.settings.mode)
          })
        }
        setModeData(modeData)
      })
      .catch(err => {

      })
      .finally(()=>{
        setHistDataLoading(false)
      })
  },[bin, startDate, endDate])


  useEffect(() => {
    //dont call a load function if loading is already in progress
    if (dataLoading || bin.key() === "") return;
    //check what is controlling the bin inventory
    let control = bin.inventoryControl()
    //for automatic lidar and cables get the data from measurements as an object measurement
    if(control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR ||
       control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC ||
       control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART
    ){
      //load the measurement data for the bar graph
      loadMeasurementData()
      //load the data for the storage mode changes
      modeDataFromHistory()
      
      //for manual and hybrid (lidar or in the future, cables) get the data from bin_history
    }else if(control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_MANUAL ||
      control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR ||
      control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_UNKNOWN
    ){
      loadHistoryData()
    }

  },[bin, loadMeasurementData, loadHistoryData])

  const legend = () => {
    return (
      <Legend
        verticalAlign="bottom"
        payload={[
          { value: "Drying", type: "square", id: "drying", color: red[500] },
          { value: "Cooldown", type: "square", id: "cooldown", color: orange[500] },
          { value: "Storage", type: "square", id: "storage", color: yellow[500] },
          { value: "Hydrating", type: "square", id: "hydrating", color: blue[500] }
        ]}
      />
    );
  };

  const inventoryChart = () => {
    if(inventoryData.length > 10){
      return (
        <BinLevelAreaGraph 
          data={inventoryData}
          fill={colour}
        />
      )
    }else{
      return (
        <BarGraph
        customHeight={customHeight}
        data={inventoryData}
        barColour={colour}
        yMax={capacity}
        yMin={0}
        labelColour="white"
        labels
        useGradient
        />
      );
    }
  };

  const binModeChart = () => {
    return (
      <BarGraph 
        data={modeData}
        customHeight={100}
        hideYAxis
        graphLegend={legend()}
        yMax={1}
        yMin={0}
      />
    )
  }

  return (
    <Card raised style={{ padding: 5 }}>
      <Typography style={{ fontSize: 25, fontWeight: 650, marginLeft: 20 }}>
        Grain Levels Over Time
      </Typography>
      {dataLoading ? <CircularProgress /> : inventoryChart()}
      {binModeChart()}
    </Card>
  );
}
