import { SerialPort } from 'serialport'
import debug from "debug";
import {ArduinoProxyPayload} from "./ArduinoProxyPayload";
import {Utils} from "./Utils";
import {ReadlineParser} from "@serialport/parser-readline";

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

export class ArduinoProxy {

    private static serialPort;
    private static callbacks: Record<string, { resolve: Function, reject: Function }> = {};
    private static encoding: string = "utf-8";

    private static onData(data) {
        data = data.toString().trim();
        try {
            console.debug("Received ", data);
            let json = JSON.parse(data) as ArduinoProxyPayload;
            if (this.callbacks[json.id]) {
                this.callbacks[json.id].resolve(json);
                delete this.callbacks[json.id];
                log("Answered request " + json.id);
            } else {
                log("ERROR - Got an response from arduino but we are not waiting for it?");
            }
        } catch (e) {
            log("ERROR - Got an invalid response from arduino?");
        }
    }

    public static connect() {
        return new Promise<void>(async (resolve, reject) => {
            log("Connecting to arduino...");
            let list = await SerialPort.list();

            let arduino = list.find((ele) => {
                if( !ele.manufacturer ) return false;

                return ele.manufacturer.startsWith("Arduino");
            });

            if (!arduino) {
                log("No arduino found!");
                return reject("No arduino found. Is it plugged in?");
            }


            // @ts-ignore
            this.serialPort = new SerialPort({
                path: arduino.path,
                baudRate: 9600,
                autoOpen: false,
            });
            log("Opening serial port...");
            this.serialPort.open((err: Error | null | undefined) => {
                if (err) {
                    log("Error whilst connecting to proxy (open serial-connection)");
                    log(err.name + "\n" + err.message + "\n" + err.stack);
                    return reject("Could not open serial connection to arduino");
                }

                // @ts-ignore
                const parser = this.serialPort.pipe(new ReadlineParser({delimiter: '\r'}));

                parser.on('data', (data) => this.onData(data));
                resolve();
            });
        });
    }

    public static disconnect() {
        if (this.serialPort.isOpen) {
            this.serialPort.close((err) => {
                this.serialPort = null;
            });
        }
    }

    public static sendRequest(request: ArduinoProxyPayload, timeout: number = 1000) {
        return new Promise<ArduinoProxyPayload>((resolve, reject) => {
            let id = Utils.generateRandomString(8);

            request.id = id;
            this.callbacks[id] = {resolve: resolve, reject: reject};
            let formatted = {
                id: request.id,
                type: request.type,
                data: request.data
            }

            console.debug("Sending...", formatted);
            this.serialPort.write(JSON.stringify(formatted), ArduinoProxy.encoding, (err: Error | null | undefined) => {
                if (err) {
                    reject("I/O error on request " + id);
                }
            });

            setTimeout(() => {
                reject("Timeout on request " + id);
                delete this.callbacks[id];
            }, timeout);
        });
    }

}