import { ArrowBack, ArrowForward } from "@mui/icons-material";
import { Box, Theme, Stepper, Step, StepLabel, Typography, RadioGroup, Radio, FormControlLabel, Grid2, Button, Autocomplete, TextField, DialogTitle, DialogContent, DialogActions, CircularProgress } from "@mui/material";
import { makeStyles } from "@mui/styles";
import ConditioningSelector from "bin/conditioning/conditioningSelector";
import { Component, Device, Interaction } from "models";
import { Ambient } from "models/Ambient";
import { Controller } from "models/Controller";
import { Plenum } from "models/Plenum";
import { pond, quack } from "protobuf-ts/pond";
import { useEffect, useMemo, useState } from "react";
import { ComponentSet } from "bin/conditioning/pickComponentSet"
import { componentIDToString, sameComponentID } from "pbHelpers/Component";
import moment from "moment";
import { lowerCase } from "lodash";
import { GetGrainExtensionMap } from "grain";
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
import { useGlobalState, useInteractionsAPI } from "providers";
import BinConditioningInteraction from "bin/BinConditioningInteraction";
import ConditionDisplay from "./conditionDisplay";
import React from "react";


const useStyles = makeStyles((theme: Theme) => {
  return ({
    stepper: {
      padding: theme.spacing(0.5)
    },
  })
});

interface Step {
  label: string;
  completed?: boolean;
}

export const DryingBaseStepCount = 3

interface Option {
    label: string;
    device: Device;
    icon?: string;
  }

interface Props {
    devices: Device[]
    deviceComponents: Map<number, Component[]>
    binPrefs?: Map<string, pond.BinComponentPreferences>
    grain?: pond.Grain
    cancel: () => void
    confirm: (conflictingInteractions: Interaction[], deviceId: number, newInteractions: pond.MultiInteractionSettings, componentSets: ComponentSet[]) => void 
}

const steps = [{label: "Style"}, {label: "Device"}, {label: "Interaction"}]

//this is our simple drying mode the way it currently works, just building the dialog to be a little more trnasparent and customizable as to what the interaction is doing
export default function DryingMode(props: Props){
    const {devices, deviceComponents, binPrefs, grain, cancel, confirm} = props
    const grainExtensionMap = GetGrainExtensionMap();
    const classes = useStyles()
    const [{user}] = useGlobalState()
    const interactionAPI = useInteractionsAPI()
    const [dryingMethod, setDryingMethod] = useState("air")//whether they are using natural air or a heater
    const [options, setOptions] = useState<Option[]>([])
    const [deviceOption, setDeviceOption] = useState<Option>()
    const [selectedDevice, setSelectedDevice] = useState<Device | undefined>()
    const [plenums, setPlenums] = useState<Plenum[]>([])
    const [ambients, setAmbients] = useState<Ambient[]>([])
    const [heaters, setHeaters] = useState<Controller[]>([])
    const [fans, setFans] = useState<Controller[]>([])
    const [currentStep, setCurrentStep] = useState(0)
    const [interactionLoading,setInteractionsLoading] = useState(false)
    
    // possibly all 4 of these will need to be passed up, at least the conflicting, toAdd, and sets will
    const [componentSets, setComponentSets] = useState<ComponentSet[]>([])
    const [existingInteractions, setExistingInteractions] = useState<Interaction[]>([])
    const [conflictingInteractions, setConflictingInteractions] = useState<Interaction[]>([])
    const [interactionsToAdd, setInteractionsToAdd] = useState<pond.MultiInteractionSettings>()


    const [sourceMap, setSourceMap] = useState<Map<string, Component>>(new Map());
    const [sinkMap, setSinkMap] = useState<Map<string, Component>>(new Map());



    //sort the components according to the bin preferences
    useEffect(()=>{
      if (!selectedDevice) return;
      if (!deviceComponents.get(selectedDevice.id())) return;
      var plenums: Plenum[] = [];
      var ambients: Ambient[] = [];
      //var grainCables: GrainCable[] = [];
      var heaters: Controller[] = [];
      var fans: Controller[] = [];
      var sinkMap: Map<string, Component> = new Map()
      var sourceMap: Map<string, Component> = new Map()

      deviceComponents.get(selectedDevice.id())!.forEach(comp => {
        let pref = binPrefs?.get(comp.key());
        if (pref) {
          if (pref.type) {
            if (pref.type === pond.BinComponent.BIN_COMPONENT_PLENUM){
                plenums.push(Plenum.create(comp));
                sourceMap.set(comp.locationString(), comp)
            }
            if (pref.type === pond.BinComponent.BIN_COMPONENT_AMBIENT){
                ambients.push(Ambient.create(comp));
                sourceMap.set(comp.locationString(), comp)
            }
            // if (pref.type === pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE)
            //   grainCables.push(GrainCable.create(comp));
            if (pref.type === pond.BinComponent.BIN_COMPONENT_HEATER) {
              let heater = Controller.create(comp);
              heater.settings.defaultOutputState = quack.OutputMode.OUTPUT_MODE_OFF;
              heaters.push(heater);
              sinkMap.set(comp.locationString(), comp)
            }
            if (pref.type === pond.BinComponent.BIN_COMPONENT_FAN) {
              let fan = Controller.create(comp)
              fan.settings.defaultOutputState = quack.OutputMode.OUTPUT_MODE_OFF;
              fans.push(fan);
              sinkMap.set(comp.locationString(), comp)
            }
          }
        }
      });
      setPlenums(plenums);
      setAmbients(ambients);
      setHeaters(heaters);
      setFans(fans);
      setSourceMap(sourceMap)
      setSinkMap(sinkMap)

      //also load the interactions for the selected device so that we can find any conflicting ones
      setInteractionsLoading(true)
      interactionAPI.listInteractionsByDevice(selectedDevice.id()).then(resp => {
        setExistingInteractions(resp)
      }).finally(() => {
        setInteractionsLoading(false)
      })
    }, [deviceComponents, selectedDevice, binPrefs]);


    useEffect(() => {
        let o: Option[] = [];
        devices.forEach(device => {
          let newOption: Option = {device: device, label: device.name()}
          if(deviceOption === undefined) {
            setDeviceOption(newOption)
            setSelectedDevice(device)
          }
          o.push(newOption);
        });
        setOptions(o);
      }, [devices, setOptions]);

      const buildHeaterInteraction = (
        sensor: Plenum | Ambient,
        heater: Controller,
      ): pond.InteractionSettings => {
        let interaction = pond.InteractionSettings.create({
          source: sensor.location(),
          sink: heater.location(),
          schedule: pond.InteractionSchedule.create({
            timeOfDayStart: "00:00",
            timeOfDayEnd: "24:00",
            timezone: moment.tz.guess(),
            weekdays: moment.weekdays().map(d => lowerCase(d))
          }),
          notifications: pond.InteractionNotifications.create({
            reports: true
          }),
          result: pond.InteractionResult.create({
            type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_TOGGLE,
            value: 1
          })
        });
    
        let temp = 0;
        let hum = 0;
            
        if(grain){
          //get the values using the grain type
          let ext = grainExtensionMap.get(grain)
          if (ext) {
            temp = ext.setTempC
            hum = ext.targetMC
          }
        }else{//otherwise use the default drying interaction
          temp = 40
          hum = 20
        }
          
        let conditions = [];
        let humidityCondition = pond.InteractionCondition.create({
          measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
          comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN,
          value: Math.round(
            describeMeasurement(
              quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
              sensor.settings.type,
              sensor.settings.subtype,
              undefined,
              user
            ).toStored(hum)
          )
        });
        conditions.push(humidityCondition);
    
        //since the measurement describers function to stored does a conversion into celsius if the users pref is fahrenheit for temperature we need to convert the preset value into fahrenheit
        if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
          temp = Math.round((temp * (9 / 5) + 32) * 100) / 100;
        }
        let tempVal = describeMeasurement(
          quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
          sensor.settings.type,
          sensor.settings.subtype,
          undefined,
          user
        ).toStored(temp);
    
        let tempCondition = pond.InteractionCondition.create({
          measurementType: quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
          comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
          value: Math.round(tempVal) //and then round the converted value since interaction conditions wont take floats
        });
        conditions.push(tempCondition);
        interaction.conditions = conditions;
        //the the heater output to auto so that when the components are update it uses the new mode
        heater.settings.defaultOutputState = quack.OutputMode.OUTPUT_MODE_AUTO
        return interaction;
      };

      const buildFanInteraction = (
        sensor: Plenum | Ambient,
        fan: Controller,
        tempComparison: "greater" | "less",
        humidityComparison: "greater" | "less",
      ) => {
        let interaction = pond.InteractionSettings.create({
          source: sensor.location(),
          sink: fan.location(),
          schedule: pond.InteractionSchedule.create({
            timeOfDayStart: "00:00",
            timeOfDayEnd: "24:00",
            timezone: moment.tz.guess(),
            weekdays: moment.weekdays().map(d => lowerCase(d))
          }),
          notifications: pond.InteractionNotifications.create({
            reports: true
          }),
          result: pond.InteractionResult.create({
            type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_TOGGLE,
            value: 1
          })
        });
    
        let tempPreset =  0;
        let humidityPreset =  0;
        if(grain){
          //get the values using the grain type
          let ext = grainExtensionMap.get(grain)
          if (ext) {
            tempPreset = ext.setTempC
            humidityPreset = ext.targetMC
          }
        }else{//otherwise use the default drying interaction
          tempPreset = 40
          humidityPreset = 20
        }
        
    
        let conditions = [];
        let fanConditionOne = pond.InteractionCondition.create({
          measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
          comparison:
            humidityComparison === "greater"
              ? quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN
              : quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
          value: describeMeasurement(
            quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
            sensor.settings.type,
            sensor.settings.subtype,
            undefined,
            user
          ).toStored(humidityPreset)
        });
        conditions.push(fanConditionOne);
    
        //since the measurement describers function toStored does a conversion into celsius for temperature if the users pref is fahrenheit we need to convert the preset value into fahrenheit
        if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
          tempPreset = Math.round((tempPreset * (9 / 5) + 32) * 100) / 100;
        }
        let tempVal = describeMeasurement(
          quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
          sensor.settings.type,
          sensor.settings.subtype,
          undefined,
          user
        ).toStored(tempPreset);
    
        let fanConditionTwo = pond.InteractionCondition.create({
          measurementType: quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
          comparison:
            tempComparison === "greater"
              ? quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN
              : quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
          value: Math.round(tempVal) //and then round the converted value since the interaction does not take decimals
        });
        conditions.push(fanConditionTwo);
    
        interaction.conditions = conditions;
        //set the output mode to auto in the fan so that when the components are updated it uses the new mode
        fan.settings.defaultOutputState = quack.OutputMode.OUTPUT_MODE_AUTO 
        return interaction;
      };

      //this function just takes in the sets as they change and creates the list of conflicting interactions and the settings to create the new ones and returns them
  const createInteractions = (sets: ComponentSet[]) => {
    if(!selectedDevice) return undefined //if there is no device that was selected, do nothing
    let multiInteractionSettings: pond.MultiInteractionSettings = pond.MultiInteractionSettings.create()
    let conflictingInteractions: Interaction[] = []
    let linkedComponents = deviceComponents.get(selectedDevice.id());
    //loop through the sets
    sets.forEach((set) => {
      //loop through the controllers
      set.controllers.forEach(controller => {
        //filter the conflicting interactions for that controller
        let c = existingInteractions.filter(i => {
          let conflicting = false;
          if (linkedComponents) {
            linkedComponents.forEach(comp => {
              if (sameComponentID(comp.location(), i.settings.sink)) {
                conflicting = true;
              }
            });
          }
          return conflicting;
        });
        conflictingInteractions = conflictingInteractions.concat(c)
        // if the controller is a heater create heater interaction
        if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_HEATER){
          multiInteractionSettings.interactions.push(buildHeaterInteraction(set.sensor, controller))
        }else if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_AERATION_FAN ||
                 controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_EXHAUST_FAN){// if the controller is a fan
            //if it is a combination set using a heater as well just turn the fan on
            if(set.combo){
              controller.settings.defaultOutputState = quack.OutputMode.OUTPUT_MODE_ON
            }else{
              //otherwise make a fan interaction
              multiInteractionSettings.interactions.push(buildFanInteraction(set.sensor, controller, "greater", "less"))
            }
        }
      })
    })
    setComponentSets([...sets])
    return {
      conflicting: conflictingInteractions,
      toAdd: multiInteractionSettings
    }
  }

    const stepper = () => {
        return (
          <Stepper
            activeStep={currentStep}
            alternativeLabel
            classes={{
              root: classes.stepper
            }}>
            {steps.map((s, i) => (
              <Step key={i}>
                <StepLabel>{s.label}</StepLabel>
              </Step>
            ))}
          </Stepper>
        );   
    }

    const styleStep = () => {
        return (
            <Box>
                <Typography>Select your drying method</Typography>
                <RadioGroup 
                    value={dryingMethod}
                    onChange={(_, value) => {
                        setDryingMethod(value)
                    }}>
                    <FormControlLabel 
                        control={<Radio />}
                        value={"air"}
                        label={"Natural Air"}
                    />
                    <FormControlLabel 
                        control={<Radio />}
                        value={"heat"}
                        label={"Supplemental Heat"}
                    />
                </RadioGroup>
            </Box>
        )
    }

    const deviceSelector = () => {
        return (
          <Autocomplete
            disablePortal
            options={options}
            value={deviceOption}
            fullWidth
            getOptionLabel={option => option.label || ""}
            onChange={(_, newValue) => {
              if(newValue){
                setSelectedDevice(newValue.device);
              }
            }}
            renderInput={params => <TextField {...params} variant="outlined" label="Device" />}
          />
        )
      }

    const deviceStep = () => {
        return (
            <Box>
                <Typography>Devices</Typography>
                {deviceSelector()}
                {interactionLoading ? <CircularProgress /> :
                  <ConditioningSelector 
                    plenums={plenums} 
                    ambients={ambients} 
                    fans={fans} 
                    heaters={heaters} 
                    //presets={presets}
                    binMode={pond.BinMode.BIN_MODE_DRYING}
                    updateSets={(sets) => {
                      //update the component sets that will be used for the interactions
                      //when the sets change it will create the interactions and any conflicting ones that need to be removed will be put into a list
                      //then when the submit button gets clicked it will use the conflicting list and toAdd to remove conflicting interactions and add the new ones
                      let i = createInteractions(sets)
                      if(i !== undefined){
                        setConflictingInteractions(i.conflicting)
                        setInteractionsToAdd(i.toAdd)
                      }
                    }}
                  />
                }
            </Box>
        )
    }

    //this step will use the conflicting interactions and interactions to add to display them and allow users to make changes
    const interactionStep = () => {
        console.log(interactionsToAdd)
        return (
            <Box>
                <Typography>Interactions</Typography>
                {interactionsToAdd?.interactions.map((interaction, index) => {
                    let temp = Interaction.create()
                    temp.settings = interaction
                    let sink = sinkMap.get(componentIDToString(interaction.sink))
                    let source = sourceMap.get(componentIDToString(interaction.source))
                    if(sink && source){
                        return (
                            <ConditionDisplay
                            key={index}
                            interaction={temp}
                            sink={sink}
                            source={source}
                            changeConditions={(newSettings) => {
                                console.log(newSettings)
                            }}
                            />
                        )
                    }
                    })}
            </Box>
        )
    }

    const stepperContent = () => {
        switch(currentStep){
            case 1:
                return deviceStep()
            case 2:
                return interactionStep()
            default:
                return styleStep()
        }
    }

    const actions = () => {
        return (
            <React.Fragment>
                {/* close - tells the parent to close the dialog without doing anything, always visible */}
                <Button onClick={cancel}>Close</Button>
                {/* back - goes back to the previous step, hidden on the forst step */}
                {currentStep !== 0 && <Button onClick={() => {setCurrentStep(currentStep-1)}}>Back</Button>}
                {/* next - goes to the next step, hidden on the last step */}
                {currentStep !== steps.length - 1 && <Button onClick={() => {setCurrentStep(currentStep+1)}}>Next</Button>}
                {/* confirm - tells the parent to build the stages using the data, only visible on the last step */}
                {currentStep === steps.length - 1 && interactionsToAdd && selectedDevice && <Button onClick={() => {confirm(conflictingInteractions, selectedDevice.id(), interactionsToAdd, componentSets)}}>Confirm</Button>}
            </React.Fragment>
        )
    }

    

    return (
        <React.Fragment>
            <DialogTitle>Bin Drying</DialogTitle>
            <DialogContent>
                {stepper()}
                {stepperContent()}
            </DialogContent>
            <DialogActions>
                {actions()}
            </DialogActions>
        </React.Fragment>
    )
}