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

type NonNullEnergyAndIrradianceData = {
    instrumentId: number,
    minT: Date,
    maxT: Date,
    irradiance: DataPoint[],
    monitoredAreaEnergy: MonitoredAreaEnergy[]
}


type Props = {
    instrumentEnergyAndIrradiance: EnergyAndIrradianceData,
    graphSize: Size
    brushSize: Size
    margins: Margins
    graphIndex: number
}

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


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

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

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

        const filteredData = this.filterData();

        const x = this.computeXaxis(filteredData.minT, filteredData.maxT);
        const y1 = this.computeY1axis(filteredData.irradiance, this.props.graphSize.height);
        const irradianceLineChart = transformXYvaluesToLines(x, y1);
        appendY1AxisTicks(graph, y1, this.props.margins);
        appendXAxisTicks(graph, x, this.props.graphSize, this.props.margins);

        if (filteredData.monitoredAreaEnergy) {
            const y2 = this.computeY2axis(filteredData.monitoredAreaEnergy, this.props.graphSize.height);
            const energyAreaChart = transformXYvaluesToAreas(x, y2, this.props.graphSize.height - this.props.margins.bottom);
            appendY2AxisTicks(graph, y2, this.props.graphSize.width - (this.props.margins.right * 5 + legendOffset.right));
            return [
                this.renderEnergyAreas(energyAreaChart, filteredData.monitoredAreaEnergy, true),
                this.renderIrradianceAreas(irradianceLineChart, filteredData.irradiance)
            ]
        }


        return [
            this.renderIrradianceAreas(irradianceLineChart, filteredData.irradiance)
        ]


    };

    private filterData = (): EnergyAndIrradianceData => {
        return {
            ...this.props.instrumentEnergyAndIrradiance,
            minT: this.state.minT,
            maxT: this.state.maxT,
            irradiance: filterData(this.props.instrumentEnergyAndIrradiance.irradiance, this.state.minT, this.state.maxT),
            monitoredAreaEnergy: this.props.instrumentEnergyAndIrradiance.monitoredAreaEnergy?.map((d): MonitoredAreaEnergy => {
                return {
                    ...d,
                    graphData: filterData(d.graphData, this.state.minT, this.state.maxT)
                }
            })
        };
    };

    renderBrush = () => {
        const brush = select(this.brushRef!);
        const x = this.computeXaxis(this.props.instrumentEnergyAndIrradiance.minT, this.props.instrumentEnergyAndIrradiance.maxT);
        const y1 = this.computeY1axis(this.props.instrumentEnergyAndIrradiance.irradiance, this.props.brushSize.height);
        const irradianceLineChart = transformXYvaluesToLines(x, y1);

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

        if (this.props.instrumentEnergyAndIrradiance.monitoredAreaEnergy) {
            const y2 = this.computeY2axis(this.props.instrumentEnergyAndIrradiance.monitoredAreaEnergy, this.props.brushSize.height);
            const energyAreaChart = transformXYvaluesToAreas(x, y2, this.props.brushSize.height - this.props.margins.bottom);
            return [
                this.renderEnergyAreas(energyAreaChart, this.props.instrumentEnergyAndIrradiance.monitoredAreaEnergy, false),
                this.renderIrradianceAreas(irradianceLineChart, this.props.instrumentEnergyAndIrradiance.irradiance)
            ]
        }

        return [
            this.renderIrradianceAreas(irradianceLineChart, this.props.instrumentEnergyAndIrradiance.irradiance)
        ]
    };

    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 - (this.props.margins.right * 5 + legendOffset.right), 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 computeXaxis = (minT: Date, maxT: Date) => {
        return d3.scaleTime()
            .domain([minT, maxT])
            .range([this.props.margins.left * 3, this.props.graphSize.width - (this.props.margins.right * 5 + legendOffset.right)]);
    };

    private computeY1axis = (data: DataPoint[], height: number) => {
        return d3.scaleLinear()
            .domain([0, d3.max(data.map(ir => ir.value))!])
            .range([height - this.props.margins.bottom, 0]);
    };

    private computeY2axis = (data: MonitoredAreaEnergy[], height: number) => {
        return d3.scaleLinear()
            .domain([0, d3.max(data.flatMap(ir => ir.graphData).flatMap(p => p.value))!]) // TODO solve null values of mae
            .range([height - this.props.margins.bottom, 0]);
    };

    private renderIrradianceAreas = (lineChart: (graphData: DataPoint[]) => string | null, filteredData: DataPoint[]) => {
        return <g key={"key-irradianceAreas"}>
            <path id={`instrument-irradiance-area-${this.props.graphIndex}`}
                  key={`instrument-irradiance-area-${this.props.graphIndex}`}
                  d={lineChart(filteredData)!}
                  style={{
                      fill: graphColors[this.props.graphIndex % graphColors.length].fill,
                      stroke: graphColors[this.props.graphIndex % graphColors.length].stroke,
                      fillOpacity: 0,
                      strokeWidth: strokeWidth
                  }}/>
            )}
        </g>
    };

    private renderEnergyAreas = (areaChart: (graphData: DataPoint[]) => string | null, filteredData: MonitoredAreaEnergy[], withLegend: boolean) => {
        return <g key={"key-energyAreas"}>
            {filteredData.map((maE, i) => {
                return <Fragment key={`key-area-${i}`}>
                    <path id={`monitored-area-energy-${i}`} key={"area"}
                          d={areaChart(maE.graphData)!} style={{
                        fill: graphColors[i % graphColors.length].fill,
                        stroke: graphColors[i % graphColors.length].stroke,
                        strokeWidth: strokeWidth
                    }}/>
                    {withLegend ?
                        <DynamicLegend
                            index={i}
                            dynamicId={maE.monitoredAreaId}
                            text={"Area"}
                            x={this.props.graphSize.width - (this.props.margins.right + legendOffset.right)}
                            y={legendOffset.top}
                        /> : undefined}
                </Fragment>
            }).reverse()}
            {withLegend ?
                <StaticLegend
                    index={this.props.graphIndex}
                    staticId={this.props.instrumentEnergyAndIrradiance.instrumentId}
                    text={"Sensor"}
                    x={this.props.graphSize.width - (this.props.margins.right + legendOffset.right)}
                    y={legendOffset.top}
                /> : 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("Irradiance (W/m2)", 0, this.props.graphSize.height / 2)}
                {this.props.instrumentEnergyAndIrradiance.monitoredAreaEnergy ?
                    appendY1axisDescription("Energy (kWh)",
                        this.props.graphSize.width - (this.props.margins.right * 3 + legendOffset.right),
                        this.props.graphSize.height / 2
                    ) : undefined}

            </svg>
            <svg
                ref={(brushRef: SVGSVGElement) => this.brushRef = brushRef}
                width={this.props.brushSize.width}
                height={this.props.brushSize.height}
            >
                {this.renderBrush()}
            </svg>
        </>
    }


}

export default InstrumentEnergyAndIrradianceChart