'use strict'
// Remember : the whole app context is in another parallel & inacessible universe !

if(typeof(crypto.randomUUID)!='function'){
    crypto.randomUUID = ()=>{   var buf = new Uint8Array(14);
                                crypto.getRandomValues(buf);
                                var uuid = Array.from(buf, byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
                                return(uuid.substr(0,8)+'-'+uuid.substr(10,4)+'-'+uuid.substr(14,4)+'-'+uuid.substr(18,4)+'-'+uuid.substr(22));
                            }
}

/**
 * 
 * 
 * @author Nicolas Stein
 * @category Core
 * @subcategory Libraries
 * @requires MessageBus
 */
class MessageBusWorker {

    /**
     * 
     * @param {*} config 
     * @param {*} userInfo 
     */
    constructor(config, userInfo){
        this.config = config;
        this.userInfo = userInfo;
        this.wsurl = this.config.protocol+this.config.hostname;
        if(('port' in this.config) && (this.config.port!='')) this.wsurl += ':'+this.config.port;
        this.wsurl += this.config.path ;
        this.keepAlive = true;
        this.curReconnectTime = 0; 
        this.ConnectTimeout = null
        this.token = false       
        this.stateMachine = 'DISCONNECTED'
        this.noReconnect = false
        this.connect();
    }

    /**
     * 
     */
    connect(){
        this.socket = new WebSocket(this.wsurl);
        this.ConnectTimeout = setTimeout(() => { 
            if((this.socket) && (close in this.socket)) this.socket.close(null); 
        }, this.config.connectTimeout*1000);
        this.socket.onopen = this.WSonOpen.bind(this);
        this.socket.onmessage = this.WSonMessage.bind(this);
        this.socket.onclose = this.WSonClose.bind(this);
        this.socket.onerror = this.WSonError.bind(this);
    }

    /**
     * 
     * @param {*} data 
     */
    clientActionDispatch(data){
        if(this.socket.readyState != 1) {
            var state = [ 'Connecting', '', 'Closing', 'Closed'];
            console.warn(`Attempt to send to ${state[this.socket.readyState]} Websocket !`);
            return;
        }
        if(typeof(data)!='string') data=JSON.stringify(data);
        this.socket.send(data);
    }

    /**
     * 
     * @param {*} e 
     */
    WSonOpen(e){
        this.stateMachine = 'READY'
        clearTimeout( this.ConnectTimeout);
        console.log('Websocket connection established');
        this.curReconnectTime = 0;
        postMessage({'event': 'connected' });
    }

    /**
     * 
     * @param {*} e 
     */
    WSonMessage(e){
        if(e.data.toLowerCase()=='unauthorized'){ // Do not spam if session is lost
            this.noReconnect = true
            if(this.config.debug) console.log(`Received MSG unauthorized !?`)
            return;
        }

        // We're supposed to receive JSON only !
        try{
            var data = JSON.parse(e.data);
        } catch(e){
            console.warn('WSS: Received garbage :'+e.data);
            return;
        }

        if((data.action=='PING') && this.keepAlive){ // Keep Alive is managed here
            this.clientActionDispatch({'action':'PONG', 'reqid':data.reqid});
            return
        }             

        // All other messages are the upper-layer's business !
        postMessage({'event': 'ReceiveFromServer', 'data':e.data});    
    }

    /**
     * 
     * @param {*} e 
     */
    WSonClose(e){
        clearTimeout( this.ConnectTimeout);
        console.warn(`Websocket connection has closed ! [${(new Date()).toISOString()}]`);
        postMessage({'event': 'closed' });  
        this.socket.close();
        if(this.noReconnect) return

        var reconnectTime = parseFloat(this.config.autoReconnect);
        var reconnectTimeFactor = parseFloat(this.config.autoReconnectTimeFactor);
        var reconnectTimeMax = parseFloat(this.config.autoReconnectTimeMax);
        var reconnectJitterPercent = parseFloat(this.config.autoReconnectJitterPercent);
        if( (!isNaN(reconnectTime)) && (!isNaN(reconnectTimeFactor)) && (!isNaN(reconnectTimeMax)) && (!isNaN(reconnectJitterPercent)) ) {
            if(this.curReconnectTime==0) this.curReconnectTime = reconnectTime;
            else {
                this.curReconnectTime *= reconnectTimeFactor;
                if(this.curReconnectTime>reconnectTimeMax) this.curReconnectTime = reconnectTimeMax;
            }
            var rjit = (Math.random()*reconnectJitterPercent)-(reconnectJitterPercent/2);
            this.curReconnectTime += (this.curReconnectTime*(rjit/100));
            // Reconnect in curReconnectTime 
            setTimeout(this.connect.bind(this), Math.floor(1000*this.curReconnectTime));
        }
    }

    /**
     * 
     * @param {*} e 
     */
    WSonError(e){
        //console.warn('Websocket error:', e.message);
    }

}


var msgbus = null;
onmessage = (e) => { // message from client
    if (e.data.action=='start') {
        if(!msgbus) msgbus = new MessageBusWorker(e.data.config, e.data.userInfo);
    } else {
        if(msgbus) msgbus.clientActionDispatch(e.data);
    }
}

