import ReconnectingWebSocket from 'reconnecting-websocket';
import * as msgpack from 'msgpack-lite';


const OpCodes = {
    // from swim tracker device to frontend
    ERROR: 1,

    INITIAL_INFO: 2,
    SESSION_STARTED: 3,
    SESSION_STOPPED: 4,
    SESSION_NEW_DATA: 5,
    ANSWER_USER_LIST: 6,
    ANSWER_SESSION_LIST: 7,
    WIFI_STATE_RESPONSE: 8,
    WIFI_SCAN_RESPONSE: 9,

    // from frontend to device
    START_SESSION: 128,
    STOP_SESSION: 129,
    TARE: 130,
    QUERY_USER_LIST: 131,
    QUERY_SESSION_LIST: 132,
    WIFI_STATE_SET: 133,
    WIFI_STATE_GET: 134,
    WIFI_TRIGGER_SCAN: 135,
};


export default class SwimTrackerWebsocketConnection {
    constructor(swimTrackerHost, onData, onStarted, onStopped, onWifiStateInfo, onConnect, onDisconnect) {
        this.swimTrackerHost = swimTrackerHost;

        this.onData = onData;
        this.onStarted = onStarted;
        this.onStopped = onStopped;
        this.onWifiStateInfo = onWifiStateInfo;
        this.onConnect = onConnect;
        this.onDisconnect = onDisconnect;

        const wsOptions = {
            maxReconnectionDelay: 4000
        };
        this.ws = new ReconnectingWebSocket(`ws://${swimTrackerHost}:81`, [], wsOptions);
        this.ws.onmessage = this._onMessage;
        this.ws.onopen = this.onConnect;
        this.ws.onclose = this.onDisconnect;
        this.ws.onerror = this._onError;
        this.ws.binaryType = 'arraybuffer';

        this.msgpackCodec = msgpack.createCodec();
        this.msgpackCodec.addExtUnpacker(205, function (byteArr) {
            const buffer = byteArr.buffer.slice(byteArr.byteOffset, byteArr.byteLength + byteArr.byteOffset);
            const result = new Int16Array(buffer);
            return result;
        });

        this._wifiScanPromises = [];
    }

    sendStartCommand() {
        this._sendMsg(OpCodes.START_SESSION);
    }

    sendStopCommand() {
        this._sendMsg(OpCodes.STOP_SESSION);
    }

    sendTareCommand() {
        this._sendMsg(OpCodes.TARE);
    }

    scanWifiNetworks() {
        console.log("Trigger wifi scan");
        this._sendMsg(OpCodes.WIFI_TRIGGER_SCAN);

        let conn = this;
        return new Promise((resolve, reject) => {
            conn._wifiScanPromises.push({ resolve: resolve, reject: reject });
        });

    }

    wifiResetToProvisioning() {
        this._sendMsg(OpCodes.WIFI_STATE_SET, {
            "reset_to_provisioning": true,
        });

    }

    wifiSetModeAP(password) {
        this._sendMsg(OpCodes.WIFI_STATE_SET, {
            "ap_password": password,
        });
    }

    wifiSetModeSTA(ssid, password) {
        this._sendMsg(OpCodes.WIFI_STATE_SET, {
            "sta_ssid": ssid,
            "sta_password": password,
        });
    }

    _sendMsg(code, data) {
        let msg = undefined;
        if (data) {
            const serializedData = msgpack.encode(data);
            msg = new Uint8Array([code, ...serializedData]);
        } else {
            msg = new Uint8Array(1);
            msg[0] = OpCodes.WIFI_TRIGGER_SCAN;
        }
        this.ws.send(msg);
    }

    _onMessage = (e) => {
        const dv = new DataView(e.data);
        const opCode = dv.getInt8(0);
        const payload = new Uint8Array(e.data).slice(1);

        if (opCode === OpCodes.INITIAL_INFO) {
            const headerSize = 6;
            const running = Boolean(dv.getInt8(1));
            const sessionId = dv.getUint32(2);
            if (running && e.data.byteLength > headerSize) {
                const data = new Uint16Array(e.data.slice(headerSize));
                this.onStarted(sessionId);
                this.onData(data);
            } else
                this.onStopped();
        } else if (opCode === OpCodes.SESSION_STARTED) {
            const sessionId = dv.getUint32(1);
            this.onStarted(sessionId);
        } else if (opCode === OpCodes.SESSION_STOPPED) {
            this.onStopped();
        } else if (opCode === OpCodes.SESSION_NEW_DATA) {
            const data = new Uint16Array(e.data.slice(1));
            this.onData(data);
        } else if (opCode === OpCodes.WIFI_SCAN_RESPONSE) {
            const scanResult = msgpack.decode(payload, { codec: this.msgpackCodec });

            for (let i = 0; i < this._wifiScanPromises.length; ++i) {
                this._wifiScanPromises[i].resolve(scanResult);
            }
            this._wifiScanPromises.length = 0;
        } else if (opCode === OpCodes.WIFI_STATE_RESPONSE) {
            const wifiInfo = msgpack.decode(payload, { codec: this.msgpackCodec });
            this.onWifiStateInfo(wifiInfo);
        }
    }

    _onError = (ev) => {
        console.log("Websocket error", ev);
    }
};
