import * as React from "react";
import {createRef, Fragment} from "react";
import {createStyles, Theme, withStyles, WithStyles, WithTheme} from "@material-ui/core";
import Grid from "@material-ui/core/Grid";
import Card from "@material-ui/core/Card";
import CardHeader from "@material-ui/core/CardHeader";
import CardContent from "@material-ui/core/CardContent";
import MeasurementPicker from "./components/MeasurementPicker";
import {createApiRequestStore} from "../../../api/ApiRequestStore";
import {
    DataPoint,
    EnergyAndIrradianceData,
    Instrument,
    InstrumentEnergy,
    Inverter,
    Margins,
    MeasurementOptions,
    MonitoredAreaEnergy,
    PerformanceRatioData,
    PickedMeasurements,
    Size,
    SumEnergyData
} from "./MySitePageTypes";
import {
    DataPointDto,
    EnergyAndIrradianceDataDto,
    MeasurementPickerDto,
    PerformanceRatioDataDto,
    PickedMeasurementsDto,
    SumEnergyDataDto
} from "../../../api/RequestBodyData";
import {createErrorStore} from "../../../common/errorHandling/ErrorStore";
import {ErrorDialogData} from "../../../common/errorHandling/ErrorMapper";
import InstrumentEnergyAndIrradianceChart from "./components/InstrumentEnergyAndIrradianceChart";
import Loader from "../../../common/Loader";
import PerformanceRatioChart from "./components/PerformanceRatioChart";
import SumEnergyChart from "./components/SumEnergyChart";
import SnackbarAlert from "../../../common/errorHandling/SnackbarAlert";


const styles = (theme: Theme) =>
    createStyles({
        root: {
            flexGrow: 1,
        },
        paper: {
            padding: theme.spacing(2),
            textAlign: 'center',
            color: theme.palette.text.secondary,
        },
    });

type State = {
    measurementOptions: MeasurementOptions,
    pickedMeasurements: PickedMeasurements,
    instrumentEnergyAndIrradiance?: EnergyAndIrradianceData[]
    sumEnergy?: SumEnergyData
    performanceRatio?: PerformanceRatioData
    size: Size
    margins: Margins,
    isLoadingData: boolean,
    isDataLoaded: boolean
}


type Props = {
    handleErrorDialogOpen: (dialogData: ErrorDialogData) => void
    handleErrorDialogClose: () => void
    drawerWidth: number
} & WithStyles<typeof styles> & WithTheme


class MySitePage extends React.Component<Props, State> {
    requestStore = createApiRequestStore();
    errorStore = createErrorStore();
    private cardContentRef = createRef<HTMLDivElement>();

    readonly state: State = {
        instrumentEnergyAndIrradiance: undefined,
        sumEnergy: undefined,
        performanceRatio: undefined,
        measurementOptions: {},
        pickedMeasurements: {},
        size: {
            height: 0,
            width: 0
        },
        margins: {
            top: this.props.theme.spacing(3),
            bottom: this.props.theme.spacing(3),
            left: this.props.theme.spacing(3),
            right: this.props.theme.spacing(3)
        },
        isLoadingData: false,
        isDataLoaded: false
    };

    handleWithInverterChecked = (index: number) => this.setState({
        ...this.state,
        measurementOptions: {
            ...this.state.measurementOptions,
            withInverter: this.state.measurementOptions.withInverter?.map((inv, invIndex) => {
                return index === invIndex ? {...inv, isChecked: !inv.isChecked} : inv;
            })
        }

    });

    handleWithoutInverterChecked = (index: number) => this.setState({
        ...this.state,
        measurementOptions: {
            ...this.state.measurementOptions,
            withoutInverter: this.state.measurementOptions.withoutInverter?.map((ins, insIndex) => {
                return index === insIndex ? {...ins, isChecked: !ins.isChecked} : ins;
            })
        }
    });

    handleListItemClick = (index: number) => {
        console.log("clicked list item no: " + index);
        this.setState({
            ...this.state,
            measurementOptions: {
                ...this.state.measurementOptions,
                withInverter: this.state.measurementOptions.withInverter?.map((inv, invIndex) => {
                    return index === invIndex ? {...inv, isOpen: !inv.isOpen} : inv;

                })
            }
        })
    };


    loadMeasurementOptions = () => {
        this.requestStore.loadMeasurementOptions()
            .then(response => {
                this.setState({
                    ...this.state,
                    measurementOptions: this.mapDtoToMeasurementOptions(response)
                })
            })
            .catch(error => {
                alert("No instruments loaded for current user.")
            })
    };

    handleSubmit = () => {
        const checkedInverter = this.state.measurementOptions.withInverter?.filter(inv => inv.isChecked);
        const pickedInstrumentsWithoutInverter = this.state.measurementOptions.withoutInverter?.filter(ins => ins.isChecked);
        this.setState({
            ...this.state,
            pickedMeasurements: {
                pickedInverterId: checkedInverter?.map((it) => it.inverterId)[0],
                pickedInstrumentIds: pickedInstrumentsWithoutInverter?.map((it) => it.id)
            }
        }, () => this.loadMeasurementData({
            pickedInverterId: this.state.pickedMeasurements.pickedInverterId,
            pickedInstrumentIds: this.state.pickedMeasurements.pickedInstrumentIds
        }));

    };

    loadMeasurementData = (pickedMeasurements: PickedMeasurementsDto) => {
        this.setState({
            ...this.state, isLoadingData: true,
            isDataLoaded: false,
            instrumentEnergyAndIrradiance: undefined,
            sumEnergy: undefined,
            performanceRatio: undefined,
        });
        this.requestStore.loadMeasurementData(pickedMeasurements)
            .then(response => {
                this.setState({
                    ...this.state,
                    instrumentEnergyAndIrradiance: this.mapDtoToIrradiance(response.instrumentEnergyAndIrradiance),
                    sumEnergy: this.mapDtoToSumEnergy(response.sumEnergy),
                    performanceRatio: this.mapDtoToPerformanceRatio(response.performanceRatio),
                    isLoadingData: false,
                    isDataLoaded: true
                })
            })
            .catch((error) => {
                this.setState({...this.state, isLoadingData: false, isDataLoaded: true});
                const dialogData = this.errorStore.mapErrorResponseToErrorDialogData(error);
                this.props.handleErrorDialogOpen(dialogData);
            })
    };

    private mapDtoToPerformanceRatio = (performanceRatio?: PerformanceRatioDataDto): PerformanceRatioData | undefined => {
        return performanceRatio ? {
            ...performanceRatio,
            minT: this.timestampToDate(performanceRatio.minT),
            maxT: this.timestampToDate(performanceRatio.maxT),
            graphData: this.dataPointTimestampToDate(performanceRatio.graphData)
        } : undefined;
    };

    private mapDtoToSumEnergy = (sumEnergy?: SumEnergyDataDto): SumEnergyData | undefined => {
        return sumEnergy ? {
            minT: this.timestampToDate(sumEnergy.minT),
            maxT: this.timestampToDate(sumEnergy.maxT),
            realEnergy: sumEnergy.realEnergy ? {
                ...sumEnergy.realEnergy,
                graphData: this.dataPointTimestampToDate(sumEnergy.realEnergy.graphData)
            } : undefined,
            theoreticalEnergy: this.mapDtoToTheoreticalEnergy(sumEnergy)
        } : undefined;
    };

    private mapDtoToTheoreticalEnergy = (sumEnergy: SumEnergyDataDto): InstrumentEnergy[] => {
        return sumEnergy.theoreticalEnergy.map((enT): InstrumentEnergy => {
            return {
                ...enT,
                graphData: this.dataPointTimestampToDate(enT.graphData)
            }
        });
    };

    private mapDtoToIrradiance = (instrumentEnergyAndIrradiance: EnergyAndIrradianceDataDto[] | undefined): EnergyAndIrradianceData[] | undefined => {
        return instrumentEnergyAndIrradiance ? instrumentEnergyAndIrradiance.map((enIr: EnergyAndIrradianceDataDto): EnergyAndIrradianceData => {
            return {
                ...enIr,
                minT: this.timestampToDate(enIr.minT),
                maxT: this.timestampToDate(enIr.maxT),
                irradiance: this.dataPointTimestampToDate(enIr.irradiance),
                monitoredAreaEnergy: this.mapDtoToMonitoredAreaEnergy(enIr)
            }
        }) : undefined
    };


    private mapDtoToMonitoredAreaEnergy = (enIr: EnergyAndIrradianceDataDto): MonitoredAreaEnergy[] | undefined => {
        return enIr.monitoredAreaEnergy ? enIr.monitoredAreaEnergy.map((maE): MonitoredAreaEnergy => {
            return {
                ...maE,
                graphData: this.dataPointTimestampToDate(maE.graphData)
            }
        }) : undefined;
    };

    private dataPointTimestampToDate = (graphData: DataPointDto[]): DataPoint[] => {
        return graphData.map((p): DataPoint => {
            return {
                ...p,
                time: this.timestampToDate(p.time)
            }
        });
    };

    private timestampToDate = (timestamp: number) => new Date(timestamp);

    private mapDtoToMeasurementOptions = (response: MeasurementPickerDto) => {
        return {
            withInverter: response.withInverter ? response.withInverter.map((inv): Inverter => {
                return {
                    ...inv,
                    isChecked: false,
                    isOpen: false,
                    instruments: inv.instruments.map((ins): Instrument => {
                        return {
                            ...ins,
                            isChecked: false
                        }
                    })
                }
            }) : undefined,
            withoutInverter: response.withoutInverter ? response.withoutInverter.map((ins): Instrument => {
                return {
                    ...ins,
                    isChecked: false
                }
            }) : undefined
        };
    };

    componentDidMount(): void {
        this.loadMeasurementOptions();
        window.addEventListener("resize", this.getWindowWidth);
        this.getWindowWidth()
    }

    private getWindowWidth = () => {
        const ref = this.cardContentRef.current;
        this.setState({
            ...this.state,
            size: {
                width: ref ? ref.offsetWidth - this.state.margins.left - this.state.margins.right : window.innerWidth - 2 * this.state.margins.left - 2 * this.state.margins.right - this.props.drawerWidth,
                height: 400
            }
        })
    };


    render() {
        return <div className={this.props.classes.root}>
            <Grid container spacing={3}>
                {this.renderMeasurementPicker()}
                {this.state.isLoadingData ? <Grid item sm={12} xs={12}><Loader/></Grid> : this.renderCharts()}
            </Grid>
        </div>
    }

    private renderMeasurementPicker() {
        return <Grid item sm={6} xs={12}>
            <Card>
                <CardContent>
                    <MeasurementPicker options={this.state.measurementOptions}
                                       handleConnectedChecked={this.handleWithInverterChecked}
                                       handleNotConnectedChecked={this.handleWithoutInverterChecked}
                                       handleClick={this.handleListItemClick}
                                       handleSubmit={this.handleSubmit}
                    />
                </CardContent>
            </Card>
        </Grid>;
    }

    private renderCharts = () => {
        if (!this.state.isDataLoaded) {
            return undefined
        }
        if (this.state.pickedMeasurements.pickedInverterId) {
            const inverterIds = this.state.pickedMeasurements.pickedInverterId;
            const noInverterDataMessage = (append: string = "") => `No data from ${append} inverter (${inverterIds}) received`;
            const noInstrumentData = `Sensor(s) connected to`;

            if (this.state.instrumentEnergyAndIrradiance && this.state.sumEnergy && this.state.performanceRatio)
                return [
                    this.renderInstrumentIrradianceAndEnergyChart(this.state.instrumentEnergyAndIrradiance,),
                    this.renderSumEnergyComparisonChart(this.state.sumEnergy),
                    this.renderPerformanceRatioChart(this.state.performanceRatio),
                    !this.state.sumEnergy!.realEnergy ? <SnackbarAlert
                        message={`Unable to display inverter energy and performance ratio graphs. No data from inverter ${inverterIds} received.`}/> : undefined
                ];

            else if (this.state.instrumentEnergyAndIrradiance && this.state.sumEnergy && !this.state.performanceRatio)
                return [
                    this.renderInstrumentIrradianceAndEnergyChart(this.state.instrumentEnergyAndIrradiance),
                    this.renderSumEnergyComparisonChart(this.state.sumEnergy),
                    <SnackbarAlert message={`Unable to display performance ratio graph. ${noInverterDataMessage()}`}/>
                ];

            else if (this.state.instrumentEnergyAndIrradiance && !this.state.sumEnergy) {
                return [
                    this.renderInstrumentIrradianceAndEnergyChart(this.state.instrumentEnergyAndIrradiance),
                    <SnackbarAlert
                        message={`Unable to display theoretical to real energy comparison graph and performance ratio graph. ${noInverterDataMessage(noInstrumentData)}`}/>
                ];
            } else if (!this.state.instrumentEnergyAndIrradiance)
                return <SnackbarAlert message={`Unable to display any graphs. ${noInverterDataMessage()}`}/>


        } else if (this.state.pickedMeasurements.pickedInstrumentIds) {
            const noDataMessage = `No data from selected Sensor(s) (${this.state.pickedMeasurements.pickedInstrumentIds.join(", ")}) received`;

            if (this.state.instrumentEnergyAndIrradiance && this.state.sumEnergy)
                return [
                    this.renderInstrumentIrradianceAndEnergyChart(this.state.instrumentEnergyAndIrradiance),
                    this.renderSumEnergyComparisonChart(this.state.sumEnergy),
                ];
            else if (this.state.instrumentEnergyAndIrradiance && !this.state.sumEnergy)
                return [
                    this.renderInstrumentIrradianceAndEnergyChart(this.state.instrumentEnergyAndIrradiance),
                    <SnackbarAlert message={`Unable to Display sensors sum energy graph. ${noDataMessage}`}/>
                ];
            else if (!this.state.instrumentEnergyAndIrradiance)
                return [
                    <SnackbarAlert message={`Unable to display any graphs. ${noDataMessage}`}/>
                ];
        }


    };

    private renderInstrumentIrradianceAndEnergyChart(data: EnergyAndIrradianceData[]) {
        const instrumentsWithZeroMonitoredArea: number[] = [];
        return <Grid item sm={12} xs={12}>
            <Card ref={this.cardContentRef}>
                <>
                    <CardHeader title={"Sensors Irradiance and Energy"}/>
                    {data.map((graphData, graphIndex) => {
                        if (!graphData.monitoredAreaEnergy) {
                            instrumentsWithZeroMonitoredArea.push(graphData.instrumentId)
                        }
                        return <Fragment key={`key-fragment-${graphIndex}`}>
                            <CardHeader subheader={`Sensor #${graphData.instrumentId}`}/>
                            <CardContent>
                                <InstrumentEnergyAndIrradianceChart
                                    graphSize={{...this.state.size, height: this.state.size.height * 3 / 4}}
                                    brushSize={{...this.state.size, height: this.state.size.height * 1 / 4}}
                                    instrumentEnergyAndIrradiance={graphData}
                                    graphIndex={graphIndex}
                                    margins={this.state.margins}
                                />
                            </CardContent>
                        </Fragment>
                    })
                    }
                    {instrumentsWithZeroMonitoredArea.length > 0 ? <SnackbarAlert
                        message={`Unable to display energy for sensors (${instrumentsWithZeroMonitoredArea.join(", ")}).`}/> : undefined}
                </>
            </Card>
        </Grid>;
    }

    private renderSumEnergyComparisonChart(data: SumEnergyData) {
        return <Grid item sm={12} xs={12}>
            <Card>
                <CardHeader title={"Theoretical to Real Energy Comparison"}/>
                <CardContent>
                    <div ref={this.cardContentRef}>
                        <SumEnergyChart
                            graphSize={{...this.state.size, height: this.state.size.height * 3 / 4}}
                            brushSize={{...this.state.size, height: this.state.size.height * 1 / 4}}
                            sumEnergy={data}
                            margins={this.state.margins}/>
                    </div>
                </CardContent>
            </Card>
        </Grid>;
    }

    private renderPerformanceRatioChart(data: PerformanceRatioData) {
        return <Grid item sm={12} xs={12}>
            <Card>
                <CardHeader title={"Performance Ratio"}/>
                <CardContent>
                    <div ref={this.cardContentRef}>
                        <PerformanceRatioChart
                            graphSize={{...this.state.size, height: this.state.size.height * 3 / 4}}
                            brushSize={{...this.state.size, height: this.state.size.height * 1 / 4}}
                            performanceRatio={data}
                            margins={this.state.margins}
                        />
                    </div>
                </CardContent>
            </Card>
        </Grid>;
    }
}

export default withStyles(styles, {withTheme: true})(MySitePage);