class DevicePubSub {
    constructor(onMessage) {
        this.TAG = '[DevicePubSub]';
        return this.createInstance(onMessage);
    }
    createInstance(onMessage) {
        if (this.isTizen()) {
            console.log(`${this.TAG} Create local client instance`);
            const pkgId = tizen.application.getCurrentApplication().appInfo.packageId;
            return new Host(onMessage, pkgId);
        } else {
            console.log(`${this.TAG} Create remote client instance`);
            const urlParams = new URLSearchParams(window.location.search);
            const pkgId = urlParams.get('pkgId');
            return new Client(onMessage, pkgId);
        }
    }
    isTizen() {
        return typeof tizen === 'object' && typeof xwalk === 'object';
    }
}

class Node {
    constructor(onMessage, pkgId) {
        this.TAG = '[DevicePubSub]';
        this.pkgId = pkgId;
        this.onMessage = onMessage ? onMessage : () => {};
        this.TO_ALL = 100;
        this.SERVER = -1;
    }
    send(type, data, whom = this.TO_ALL) {}
}

class Host extends Node {
    constructor(onMessage, pkgId) {
        super(onMessage, pkgId);
        this.TAG = `${this.TAG}[Host]`;
        this.SERVICE_DAEMON_APP_ID = 'org.tizen.chromium-efl.wrt-service';
        this.appId = tizen.application.getCurrentApplication().appInfo.id;
        this.sender = {};
        this.receiver = {};
        this.initialize();
    }
    onReceived(msg) {
        console.log(`${this.TAG} Received Data in Web App : ${msg[0].value}`);
        this.onMessage(JSON.parse(msg[0].value));
    };
    initialize() {
        try {
            // Message port receiver
            console.log(`${this.TAG} Initialize msg port recevier : ${this.pkgId}.Companion`);
            this.receiver.messagePort = tizen.messageport.requestLocalMessagePort(`${this.pkgId}.Companion`);
            this.receiver.listener = this.receiver.messagePort.addMessagePortListener(this.onReceived.bind(this));
            // Message port sender
            console.log(`${this.TAG} Initialize msg port sender : ${this.SERVICE_DAEMON_APP_ID}`);
            this.sender.messagePort = tizen.messageport.requestRemoteMessagePort(this.SERVICE_DAEMON_APP_ID, `${this.pkgId}.Companion`);
            console.log(`${this.TAG} Send appId : ${this.appId}`);
            this.sender.messagePort.sendMessage([{
                key: this.pkgId,
                value: JSON.stringify({type: 'AppId', data: this.appId}),
            }]);
        } catch (err) {
            console.log(`${err}`);
        }
    }
    doSend(type, data, secure, whom) {
        var packet = {type: type, data: data, secure: secure, id: whom};
        console.log(`${this.TAG} Send message : ${JSON.stringify(packet)}`);
        this.sender.messagePort.sendMessage([{
            key: this.pkgId,
            value: JSON.stringify(packet),
        }]);
    }
    send(type, data, secure = true, whom = this.TO_ALL) {
        this.doSend(type, data, secure, whom);
    }
}

class Client extends Node {
    constructor(onMessage, pkgId) {
        super(onMessage, pkgId);
        this.TAG = `${this.TAG}[Client]`;
        this.xhr = new XMLHttpRequest();
        this.publicKey = '';
        this.serverCrypto = new JSEncrypt();
        // Generate client RSA keys
        this.clientCrypto = new JSEncrypt();
        this.clientCrypto.getPublicKey();
        this.clientCrypto.getPrivateKey();
        this.sendClientPublicKey();
        this.initialize();
    }
    initialize() {
        this.id;
        this.wsUri = `${window.location.origin}/${this.pkgId}`;
        this.websocket = null;
        this.websocket = io.connect(this.wsUri);
        this.websocket.on('d2d_message', function(evt) {
            console.log(`${this.TAG} Received Data in Web Browser : ${evt}`);
            if (evt === undefined)
                return;
            try {
                JSON.parse(evt);
            } catch (e) {
                evt = this.clientCrypto.decrypt(evt);
            }
            const msg = JSON.parse(evt);
            if (msg.id === this.SERVER && msg.type === "id")
                this.id = msg.data;
            this.onMessage(msg);
        }.bind(this));
        this.websocket.on("connect", () => {
            console.log(`${this.TAG} connection : ${this.websocket.connected}`);
        });
    }
    async doSend(type, data, secure) {
        let msg = JSON.stringify({type: type, data: data, id: this.id});
        if (secure) {
            if (this.publicKey === '')
                await this.promiseGetServerPublicKey();
            msg = this.serverCrypto.encrypt(msg);
        }
        this.websocket.emit('d2d_message', msg);
    }
    send(what, data, secure = true) {
        this.doSend(what, data, secure);
    }
    getServerPublicKey(resolve) {
        this.xhr.onload = function() {
            if (this.xhr.status === 200 || this.xhr.status === 201) {
                this.publicKey = this.xhr.responseText;
                this.serverCrypto.setPublicKey(this.publicKey);
                resolve(true);
            } else {
                console.log(`${this.TAG} ${this.xhr.responseText}`);
                resolve(false);
            }
        }.bind(this);
        this.xhr.open('GET', `${window.location.origin}/pincode/getServerPublicKey`);
        this.xhr.send();
    }
    promiseGetServerPublicKey() {
        return new Promise(resolve => {
            this.getServerPublicKey(resolve);
        })
    }
    sendClientPublicKey() {
        this.xhr.open('POST', `${window.location.origin}/pincode/publicKeyToServer`);
        this.xhr.setRequestHeader("Content-Type", "application/json");
        this.xhr.send(JSON.stringify({
            publicKey: this.clientCrypto.getPublicKey(),
            pkgId : this.pkgId
        }));
    }
}
