import {WebSocketPayload} from "../../WebSocketPayload";
import debug from "debug";
import {WebSocketHandler} from "../../WebSocketHandler";
import {iTender} from "../../iTender";
import {iTenderStatus} from "../../iTenderStatus";
import {WebSocketEvent} from "../../WebSocketEvent";
import Container from "../../database/Container";
import {SensorType} from "../../SensorType";
import {Settings} from "../../Settings";
import Ingredient from "../../database/Ingredient";
import {RequestType} from "../../RequestType";
import {IJob} from "../../database/IJob";
import {SensorHelper} from "../../SensorHelper";
import {IContainer} from "../../database/IContainer";
import {Mixer} from "../../Mixer";
import {ArduinoProxy} from "../../ArduinoProxy";
import {ContainerHelper} from "../../ContainerHelper";
import * as os from "os";
import {promisify} from "util";
import Drink from "../../database/Drink";

const exec = promisify(require('child_process').exec)

const express = require('express');
const router = express.Router();


const log = debug("itender:websocket");

router.ws('/', async (ws, req, next) => {
    log("Incoming websocket connection...");

    if (WebSocketHandler.ws) {
        WebSocketHandler.ws.close(1001);
    }
    WebSocketHandler.ws = ws;

    await WebSocketHandler.sendRunningConfig();
    await WebSocketHandler.sendContainers();
    await WebSocketHandler.sendStatus();
    await WebSocketHandler.sendDrinks();


    ws.on('message', async (raw, bool) => {
        let msg = WebSocketPayload.parseFromBase64Json(raw);
        // If message is null, close the socket because it could not be decompiled
        if (!msg) {
            ws.close(1011);
            return;
        }


        switch (msg.event) {
            case WebSocketEvent.CONTAINERS: {
                let data = msg.data as { id?: string, pumpPin: number; sensorType: SensorType; sensor1: number; sensor2: number; useProxy: boolean }[];
                // await Container.deleteMany({}); // V2: Remove this and check every container based on id if changes occurs

                let i = 0;
                for (let c of data) {
                    let container : IContainer | null = null;

                    if( c.id )
                    {
                        container = await Container.findOne( {_id: c.id } );
                    }

                    if( !container )
                        container = new Container();


                    container.slot = i;
                    //container.volume = c.volume; // V2: Removed
                    container.pumpPin = c.pumpPin;
                    container.useProxy = c.useProxy;
                    container.sensorType = c.sensorType;
                    container.sensorPin1 = c.sensor1;
                    container.sensorPin2 = c.sensor2;
                    container.enabled = true;
                    await container.save();
                    i++;
                }

                let containers : IContainer[] = await Container.find();
                for( let c of containers )
                {
                    let find = data.find( (e) => {
                        return c._id == e.id;
                    } );
                    if( !find )
                        await Container.deleteOne({_id: c._id });
                }

                break;
            }

            case WebSocketEvent.CONTAINER_UPDATE: {
                let container: IContainer | null = await Container.findById(msg.data["container"]);
                if (!container) break;

                let ingredient;
                if (msg.data["ingredient"] != null) {
                    ingredient = await Ingredient.findById(msg.data["ingredient"]);
                    if (!ingredient)
                        ingredient = undefined;
                }

                let filled: number = parseInt(msg.data["filled"]);

                container.filled = filled;
                container.volume = filled; // V2: Volume is now being updated after change of ingredient

                if (container.sensorType != SensorType.NONE) {
                    let raw = SensorHelper.measureRaw(container);
                    if (!raw) {
                        await WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.ERROR, "Der Sensor hat beim Austarieren einen ungültigen Wert zurückgegeben.<br>Dies weist auf eine Fehlkonfiguration oder kaputten Sensor hin.<br>Aus Sicherheitsgründen wurde der Sensor für diesen Behälter deaktiviert."));
                    } else {
                        container.sensorDelta = await raw - filled; // V2: Kalkuliere differenz zwischen Gewicht und gefülltem Inhalt // Todo Möglicherweise ist der "raw"-Wert nicht Gewicht
                    }
                }


                container.content = ingredient;

                await container.save();

                await ContainerHelper.measureContainers();
                await iTender.refreshDrinks();
                break;
            }

            case WebSocketEvent.CONFIG: {
                // ToDo
                console.log("New Settings:", msg.data);
                // Danach setup modus aus
                for (const [key, value] of Object.entries(msg.data)) {
                    Settings.set(key, value);
                }

                Settings.setupDone = true;
                break;
            }

            case WebSocketEvent.SETUP: {
                if ((msg.data as boolean)) {
                    iTender.setStatus(iTenderStatus.SETUP);
                } else {
                    if (Settings.setupDone) {
                        iTender.setStatus(iTenderStatus.READY);
                        await WebSocketHandler.sendRunningConfig();
                    }
                }
                await WebSocketHandler.sendContainers();
                break;
            }

            case WebSocketEvent.CANCEL: {
                await Mixer.cancelFill();
                break;
            }

            case WebSocketEvent.REQUEST: {
                log("Request to " + msg.data["type"]);

                switch (msg.data["type"] as RequestType) {
                    case RequestType.STATS: {
                        await WebSocketHandler.sendStats();
                        break;
                    }
                    case RequestType.CONTAINERS: {
                        WebSocketHandler.answerRequest(msg.data["type"] as RequestType, (await Container.find().sort({"slot": 1}).populate("content")));
                        break;
                    }
                    case RequestType.INGREDIENTS: {
                        WebSocketHandler.answerRequest(msg.data["type"] as RequestType, (await Ingredient.find().sort({"name": 1})));
                        break;
                    }
                    case RequestType.STARTFILL: {
                        let job: IJob | null = null;
                        try {
                            job = await iTender.onReceiveFill(msg.data.data);
                        } catch (e: any) {
                            console.error(e);
                        }
                        WebSocketHandler.answerRequest(msg.data["type"] as RequestType, {success: (!!job), job: job});
                        break;
                    }
                    case RequestType.JOB: {
                        WebSocketHandler.answerRequest(msg.data["type"] as RequestType, Mixer.currentJob);
                        break;
                    }
                    case RequestType.DOWNLOAD_DRINKS: {
                        await iTender.refreshFromServer();
                        WebSocketHandler.answerRequest(msg.data["type"] as RequestType, "ok");
                        break;
                    }
                    case RequestType.CHECK: {
                        let conf = msg.data.data as {
                            "led_enabled": boolean,
                            "remote_enabled": boolean,
                            "hotspot_enabled": boolean,
                            "arduino_proxy_enabled": boolean,
                            "led_gpio": number,
                            "ambient_color": string
                        }

                        await SensorHelper.clearAllRawMeasurements();

                        let content: { success: boolean, msg: string } = {
                            success: true,
                            msg: "Prüfung erfolgreich."
                        };

                        // Check config
                        /// Check Proxy
                        if (conf["arduino_proxy_enabled"]) {
                            try {
                                await ArduinoProxy.disconnect();
                            } catch( e )
                            {

                            }
                            try {
                                await ArduinoProxy.connect();
                            } catch (e) {
                                log("Checkup failed");
                                content.success = false;
                                content.msg = "Bei der Kommunikation mit dem Arduino Proxy ist ein Fehler aufgetreten.<br><br><em>Technische Details: " + e + "</em>";
                                return WebSocketHandler.answerRequest(msg.data["type"] as RequestType, content);
                            }
                        }

                        // Check measurements
                        try {
                            await SensorHelper.measureAllRaw();
                        } catch (e) {
                            content.success = false;
                            content.msg = e + "<br>Überprüfe die Einstellungen der Sensoren-Pins.";
                            return WebSocketHandler.answerRequest(msg.data["type"] as RequestType, content);
                        }

                        for (let c of await Container.find()) {
                            if (c.sensorType != SensorType.NONE && c.rawData == -1) {
                                content.success = false;
                                content.msg = "Container " + (c.slot + 1) + " weist Fehler im Sensor auf.<br>Überprüfe die Einstellungen der Sensoren-Pins.";
                                return WebSocketHandler.answerRequest(msg.data["type"] as RequestType, content);
                            }
                        }

                        return WebSocketHandler.answerRequest(msg.data["type"] as RequestType, content);
                        break;
                    }
                    case RequestType.TARE: {
                        let type = msg.data["type"];
                        // Start TARE
                        let success = true;

                        for (let c of await Container.find({})) {
                            if (c.sensorType != SensorType.NONE) {
                                c.sensorTare = 0;
                                await c.save();
                            }
                        }

                        let timeouts: NodeJS.Timer[] = [];

                        async function measureAndSafe() {
                            try {
                                await SensorHelper.measureAllRaw();
                                for (let c of await Container.find({})) {
                                    if (c.sensorType != SensorType.NONE) {
                                        c.sensorTare += c.rawData;
                                    }
                                }
                            } catch (e) {
                                // { success: boolean, msg: string }
                                WebSocketHandler.answerRequest(type, {success: false, msg: e});
                                success = false;
                                for (let t of timeouts)
                                    clearTimeout(t);
                            }
                        }

                        timeouts.push(setTimeout(measureAndSafe, 500));
                        timeouts.push(setTimeout(measureAndSafe, 1000));
                        timeouts.push(setTimeout(measureAndSafe, 2000));
                        timeouts.push(setTimeout(measureAndSafe, 3000));

                        setTimeout(async () => {
                            if (success) {
                                for (let c of await Container.find({})) {
                                    if (c.sensorType != SensorType.NONE) {
                                        c.sensorTare = c.sensorTare / 4;
                                        await c.save();
                                    }
                                }
                                WebSocketHandler.answerRequest(type, {success: true, msg: "OK"});
                            }
                        }, 4000);
                        break;

                    }

                    case RequestType.UPDATE: {
                        if( !iTender.internetConnection )
                            return WebSocketHandler.answerRequest(msg.data["type"] as RequestType, false);
                        WebSocketHandler.answerRequest(msg.data["type"] as RequestType, true);


                        try {
                            let result = await exec("/home/itender/itender/update.sh");
                            if( result.stderr )
                                await WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.ERROR, "Der iTender konnte das Update nicht installieren.<br>Möglicherweise ist die Internetverbindung nicht ausreichend oder das Update enthält Fehler.<br>"));
                        } catch( e )
                        {
                            await WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.ERROR, "Der iTender konnte das Update nicht installieren.<br>Möglicherweise ist die Internetverbindung nicht ausreichend oder das Update enthält Fehler.<br>"));
                        }

                        break;
                    }

                    case RequestType.INFO: {
                        let nets = os.networkInterfaces();
                        let net = nets["wlan0"];
                        if(!net)
                            net = nets["wlp0s20f3"];
                        let ipAddr : string = "";
                        if( net )
                            for( let addr of net )
                            {
                                if( addr.family == "IPv4" && addr.address && addr.address !== "127.0.0.1" )
                                    ipAddr = addr.address;
                            }

                        let packageJson = require('../../../package.json');

                        let wifi = (await exec("iwgetid")).stdout


                        let data = {
                            "internet": iTender.internetConnection,
                            "ip": ipAddr,
                            "network": wifi.substring(wifi.indexOf('"')+1,wifi.length-2),
                            "uptime": (await exec("uptime -p")).stdout.substring(3),
                            "version": packageJson.version,
                            "author": "Tobias Hopp",
                            "contact": "tobi@gaminggeneration.de"
                        }

                        WebSocketHandler.answerRequest(msg.data["type"] as RequestType, data);
                        break;
                    }

                    case RequestType.CLEAR_DB: {
                        await Drink.deleteMany({});
                        await Ingredient.deleteMany({});
                        for( let c of (await Container.find()) )
                        {
                            c.content = undefined;
                            c.save();
                        }

                        await iTender.refreshDrinks();
                        WebSocketHandler.answerRequest(msg.data["type"] as RequestType, true);
                        break;
                    }

                }
                break;
            }

            default: {
                log("WebSocketHandler does not know how to handle " + msg.event + " Event");
            }
        }
    });

});

module.exports = router;
