import React, {Fragment} from "react";
import * as d3 from 'd3';
import {select} from "d3-selection";
import {DataPoint, InstrumentEnergy, InverterEnergy, Margins, Size, SumEnergyData} from "../MySitePageTypes";
import {
    appendBrushXAxisTicks,
    appendXAxisTicks,
    appendY1axisDescription,
    appendY1AxisTicks,
    DynamicLegend,
    filterData,
    graphColors,
    legendOffset,
    StaticLegend,
    transformXYvaluesToAreas,
    transformXYvaluesToLines
} from "./chartHelpers";
import {brush} from "d3";
import {brushX} from "d3";
import {ScaleTime} from "d3";
import {event} from "d3";

type Props = {
    sumEnergy: SumEnergyData,
    graphSize: Size
    brushSize: Size
    margins: Margins
}

type State = {
    minT: Date,
    maxT: Date
}


class SumEnergyChart extends React.Component<Props, State> {
    graph: SVGSVGElement | undefined;
    brushRef: SVGGElement | undefined;

    readonly state: State = {
        minT: this.props.sumEnergy.minT,
        maxT: this.props.sumEnergy.maxT
    };

    private computeYaxis = (data: InstrumentEnergy[], height: number) => {
        return d3.scaleLinear()
            .domain([0, d3.max(data.flatMap(it => it.graphData.map(it => it.value)))!])
            .range([height - this.props.margins.bottom, 0]);
    };

    private computeXaxis = (minT: Date, maxT: Date) => {
        return d3.scaleTime()
            .domain([minT, maxT])
            .range([this.props.margins.left * 3, this.props.graphSize.width - 100]);
    };

    private renderGraph = () => {
        const graph = select(this.graph!);

        const filteredData = this.filterData();

        const x = this.computeXaxis(filteredData.minT, filteredData.maxT);
        const y = this.computeYaxis(filteredData.theoreticalEnergy, this.props.graphSize.height);
        const theoreticalEnergyAreaChart = transformXYvaluesToAreas(x, y, this.props.graphSize.height - this.props.margins.bottom);

        appendXAxisTicks(graph, x, this.props.graphSize, this.props.margins);
        appendY1AxisTicks(graph, y, this.props.margins);

        if(filteredData.realEnergy){
            const realEnergyLineChart = transformXYvaluesToLines(x, y);
            return [
                this.renderTheoreticalEnergyAreas(theoreticalEnergyAreaChart, filteredData.theoreticalEnergy, true),
                this.renderRealEnergyLines(realEnergyLineChart, filteredData.realEnergy, true)
            ]
        }

        return [
            this.renderTheoreticalEnergyAreas(theoreticalEnergyAreaChart, filteredData.theoreticalEnergy, true),
        ]
    };

    private filterData = (): SumEnergyData => {
        return {
            ...this.props.sumEnergy,
            minT: this.state.minT,
            maxT: this.state.maxT,
            realEnergy: this.props.sumEnergy.realEnergy ? {
                ...this.props.sumEnergy.realEnergy,
                graphData: filterData(this.props.sumEnergy.realEnergy.graphData, this.state.minT, this.state.maxT)
            } : undefined,
            theoreticalEnergy: this.props.sumEnergy.theoreticalEnergy.map((d): InstrumentEnergy => {
                return {
                    ...d,
                    graphData: filterData(d.graphData, this.state.minT, this.state.maxT)
                }
            })
        };
    };

    private renderBrush = () => {
        const brush = select(this.brushRef!);
        const x = this.computeXaxis(this.props.sumEnergy.minT, this.props.sumEnergy.maxT);
        const y = this.computeYaxis(this.props.sumEnergy.theoreticalEnergy, this.props.brushSize.height);
        const theoreticalEnergyAreaChart = transformXYvaluesToAreas(x, y, this.props.brushSize.height - this.props.margins.bottom);
        const realEnergyLineChart = transformXYvaluesToLines(x, y);

        this.applyBrush(brush, x);
        appendBrushXAxisTicks(brush, x, this.props.brushSize, this.props.margins);

        if(this.props.sumEnergy.realEnergy){
            return[
                this.renderTheoreticalEnergyAreas(theoreticalEnergyAreaChart, this.props.sumEnergy.theoreticalEnergy, false),
                this.renderRealEnergyLines(realEnergyLineChart, this.props.sumEnergy.realEnergy, false)
            ]
        }

        return[
            this.renderTheoreticalEnergyAreas(theoreticalEnergyAreaChart, this.props.sumEnergy.theoreticalEnergy, false),
        ]
    };

    private applyBrush = (brush: d3.Selection<SVGGElement, unknown, null, undefined>, x: ScaleTime<number, number>) => {
        brush
            .call(brushX()
                .extent([[this.props.margins.left * 3, 0], [this.props.brushSize.width - 100, 100]])
                .on("end", this.brushed(x)));
    };

    private brushed = (timeScale: ScaleTime<number, number>) => {
        return () => {
            const [newMinT, newMaxT] = event.selection? event.selection.map((d: Date) => timeScale.invert(d)): [this.state.minT, this.state.maxT];
            this.onBrushChange(newMinT, newMaxT);
        };
    };

    private onBrushChange = (newMinT: Date, newMaxT: Date) => {
        this.setState({
            ...this.state,
            minT: newMinT,
            maxT: newMaxT
        })
    };

    private renderTheoreticalEnergyAreas = (areaChart: (graphData: DataPoint[]) => string | null, filteredData: InstrumentEnergy[], withLegend: boolean) => {
        return <g key={"key-energyAreas"}>
            {filteredData.map((instrument, i) => {
                return <Fragment key={`key-area-${i}`}>
                    <path id={`sum-energy-area-${i}`}
                          key={`sum-energy-area-${i}`}
                          d={areaChart(instrument.graphData)!}
                          style={{
                              fill: graphColors[i % graphColors.length].fill,
                              stroke: graphColors[i % graphColors.length].stroke,
                              strokeWidth: 3
                          }}
                    />
                    {withLegend ?
                        <DynamicLegend
                            index={i}
                            dynamicId={instrument.instrumentId}
                            text={"Sensor"}
                            x={this.props.graphSize.width - (this.props.margins.right + legendOffset.right)}
                            y={legendOffset.top}
                        /> : undefined}
                </Fragment>
            }).reverse()}
        </g>
    };

    private renderRealEnergyLines = (lineChart: (graphData: DataPoint[]) => string | null, filteredData: InverterEnergy, withLegend: boolean) => {
        return <g key={"key-energyLines"}>
            <path id={`real-energy-area`}
                  key={`real-energy-area`}
                  d={lineChart(filteredData.graphData)!}
                  strokeDasharray={"5,5"}
                  style={{
                      stroke: "#000000",
                      fillOpacity: 0,
                      strokeWidth: 3,
                  }}
            />
            {withLegend ?
                <StaticLegend
                    staticId={filteredData.inverterId} // TODO solve null energy
                    text={"Inverter"}
                    x={this.props.graphSize.width - (this.props.margins.right + legendOffset.right)}
                    y={legendOffset.top}
                    dashed={true}
                /> : undefined}
        </g>

    };

    componentDidMount() {
        this.renderGraph()
        this.renderBrush()
    }


    render() {
        return <>
            <svg ref={(graph: SVGSVGElement) => this.graph = graph} width={this.props.graphSize.width}
                 height={this.props.graphSize.height}>
                {this.renderGraph()}
                {appendY1axisDescription("Energy(kWh)", 0, this.props.graphSize.height / 2)}
            </svg>
            <svg
                ref={(brushRef: SVGSVGElement) => this.brushRef = brushRef}
                width={this.props.brushSize.width}
                height={this.props.brushSize.height}
            >
                {this.renderBrush()}
            </svg>
        </>
    }


}

export default SumEnergyChart