import Chart, {ChartPoint} from "chart.js";
import {generateClimateChartConfig} from "./climateChartConfig";

interface Snapshot {
    id: number,
    temp: number,
    humidity: number,
    co2: number,
    time: string,
}

interface SnapshotRecords {
    snapshots: Snapshot[]
}

class ClimateChart {
    private readonly urlEndpoint: string;
    private chart: Chart | null;
    private latestSnapshot: Snapshot | null;
    private onLoadedCallback: () => void = () => {};
    constructor(
        private readonly rootUrl: string,
        private readonly canvasId: string,
        private readonly minutesDisplayed: number = 60
    ) {
        if (minutesDisplayed > 0 && Math.floor(minutesDisplayed) == minutesDisplayed) {
            this.minutesDisplayed = minutesDisplayed;
        } else {
            throw new Error(`invalid minutes passed to display in chart: ${minutesDisplayed}`);
        }
        this.urlEndpoint = this.rootUrl + "data/";
        this.urlEndpoint += "since/" + this.minutesDisplayed;
        this.initChart()
    }

    onLoaded(callback: () => void) {
        this.onLoadedCallback = callback;
    }

    private static isCanvas(el: HTMLElement): el is HTMLCanvasElement {
        return el.tagName === "canvas";
    }

    private async getInitialDataBlob(): Promise<SnapshotRecords> {
        const data = await fetch(this.urlEndpoint);
        return await data.json();
    }

    private async initChart() {
        const canvasElement = document.getElementById(this.canvasId);
        let ctx: CanvasRenderingContext2D;
        if (ClimateChart.isCanvas(canvasElement)) {
            ctx = canvasElement.getContext('2d');
        } else {
            throw new Error(`improper HTML element passed, needed type canvas, got ${canvasElement.tagName}`);
        }
        const payload = await this.getInitialDataBlob();
        this.latestSnapshot = payload.snapshots[payload.snapshots.length - 1];
        this.chart = new Chart(ctx, generateClimateChartConfig(this.jsonToChartPoints(payload)));
        setInterval(() => this.updateFromServer(), 30 * 1000);
        this.onLoadedCallback();
    }

    private jsonToChartPoints(json: SnapshotRecords): {humidity: ChartPoint[], temp: ChartPoint[], co2: ChartPoint[]} {
        const humidity = [];
        const co2 = [];
        const temp = [];
        for (let i = 0; i < json.snapshots.length; i++) {
            const snapshot = json.snapshots[json.snapshots.length - i - 1];
            co2.push({x: snapshot.time, y: snapshot.co2});
            temp.push({x: snapshot.time, y: snapshot.temp});
            humidity.push({x: snapshot.time, y: snapshot.humidity});
        }
        return {humidity, co2, temp};
    }

    private async updateFromServer() {
        const currentTime = (new Date(this.latestSnapshot.time)).toISOString();
        const url = "/" + this.rootUrl + "data?since=" + currentTime;
        const payload: SnapshotRecords = await (await fetch(url)).json();
        if (payload.snapshots.length > 0) {
            this.latestSnapshot = payload.snapshots[payload.snapshots.length - 1];
            this.removeExpiredData(currentTime);
            this.insertSnapshots(...payload.snapshots);
            this.chart.update();
        }
    }

    private insertSnapshots(...snapshots: Snapshot[]) {
        for (const snapshot of snapshots) {
            this.chart.data.datasets[0].data.push(snapshot.humidity);
            this.chart.data.datasets[1].data.push(snapshot.temp);
            this.chart.data.datasets[2].data.push(snapshot.co2);
        }
    }

    private removeExpiredData(latestTime: string) {
        for (let i = 0; i < this.chart.data.datasets[0].data.length; i++) {
            const timeOnPoint = (this.chart.data.datasets[0].data[i] as ChartPoint).x as string;
            const timeElapsedSincePoint = Date.parse(latestTime) - Date.parse(timeOnPoint);
            if (timeElapsedSincePoint > this.minutesDisplayed * 60000) {
                this.chart.data.datasets[0].data.splice(i, 1);
                this.chart.data.datasets[1].data.splice(i, 1);
                this.chart.data.datasets[2].data.splice(i, 1);
            }
        }
    }
}

export default ClimateChart;