import Codec from './Codec';

import { PACKET } from '../../Enumerations';

import EventEmitter from 'events';

class NetworkAdapter {
    constructor() {
        this.ping = 0;

        this.pingStart = null;
        this.pingCompletion = null;

        this.connected = false;
        this.connecting = false;

        this.codec = new Codec();

        this.emitter = new EventEmitter();
        this.emitter.setMaxListeners(50);

        this.addConnectHandler(this.sendPingIfNecessary.bind(this));
        this.addPingHandler(this.onPing.bind(this));
        
        this.emitter.on('connected', event => {
            console.log(`Websocket connection established`);

            this.connecting = false;
            this.connected = true;
        });

        this.emitter.on('close', event => {
            console.warn(`Websocket connection has been closed`);

            this.connecting = false;
            this.connected = false;
        });
    }
    
    connect(serverId) {
        if (this.connecting) {
            return;
        }

        this.connectionOptions = serverId;

        this.connected = false;
        this.connecting = true;

        const options = {
            headers: {
                'Accept-Encoding': 'gzip, deflate, br',
                'Accept-Language': 'en-US,en;q=0.9',
                'Cache-Control': 'no-cache',
                Origin: "https://zombs.io",
                Host: this.connectionOptions.hostname,
            }
        }

        console.log(`Connecting:${serverId.hostname}:${serverId.port}`)
        this.socket = new WebSocket(`wss://${serverId.hostname}:${serverId.port}`, options);
        this.socket.binaryType = 'arraybuffer';

        this.bindEventListeners();

        setTimeout(() => {
            game.ui.components.Reconnect.hide();
        }, 1000);
    }
    
    bindEventListeners() {
        this.socket.addEventListener('open', this.emitter.emit.bind(this.emitter, 'connected'));
        this.socket.addEventListener('message', this.onMessage.bind(this));
        this.socket.addEventListener('close', this.emitter.emit.bind(this.emitter, 'close'));
        this.socket.addEventListener('error', this.emitter.emit.bind(this.emitter, 'error'));
    }

    disconnect() {
        this.socket.close();
    }
    
    reconnect(allowed) {
        if (allowed) return this.connect(this.connectionOptions);
    }
    
    getPing() {
        return this.ping;
    }
    
    sendPacket(event, data) {
        if (!this.connected || !data) {
            return;
        }
        if (event == 4 || event == 5 || event == 9 || event == 3 || event == 7 || event == 6) {
            this.socket.send(this.codec.encode(event, data));
        }
    }
    
    onMessage(event) {
        this.sendPingIfNecessary();
        var message = this.codec.decode(event.data);
        this.emitter.emit(PACKET[message.opcode], message);
    }
    
    sendPingIfNecessary() {
        this.connecting = false;
        this.connected = true;
        const pingInProgress = (this.pingStart != null);
        if (pingInProgress) {
            return;
        }
        if (this.pingCompletion != null) {
            const msSinceLastPing = (new Date().getTime() - this.pingCompletion.getTime());
            if (msSinceLastPing <= 5000) {
                return;
            }
        }
        this.pingStart = new Date();

        this.sendPing({
            nonce: 0
        });
    }

    onPing() {
        const now = new Date();
        this.ping = (now.getTime() - this.pingStart.getTime()) / 2;
        this.pingStart = null;
        this.pingCompletion = now;
    }

    sendEnterWorld(data) {
        this.sendPacket(PACKET.ENTER_WORLD, data);
    }

    sendEnterWorld2() {
        this.sendPacket(PACKET.ENTER_WORLD2, {});
    }

    sendInput(data) {
        this.sendPacket(PACKET.INPUT, data);
    }

    sendPing(data) {
        this.sendPacket(PACKET.PING, data);
    }

    sendRpc(data) {
        this.sendPacket(PACKET.RPC, data);
    }

    addPreEnterWorldHandler(callback) {
        this.addPacketHandler(PACKET.PRE_ENTER_WORLD, packet => {
            callback(packet);
        });
    }

    addEnterWorldHandler(callback) {
        this.addPacketHandler(PACKET.ENTER_WORLD, packet => {
            callback(packet);
        });
    }

    addEntityUpdateHandler(callback) {
        this.addPacketHandler(PACKET.ENTITY_UPDATE, packet => {
            callback(packet);
        });
    }

    addPingHandler(callback) {
        this.addPacketHandler(PACKET.PING, response => {
            callback(response);
        });
    }

    addRpcHandler(rpcName, callback) {
        this.addPacketHandler(PACKET.RPC, rpc => {
            if (rpcName === rpc.name)
                return callback(rpc.response);
        });
    }

    addConnectHandler(callback) {
        this.emitter.on('connected', callback);
    }

    addCloseHandler(callback) {
        this.emitter.on('close', callback);
    }

    addErrorHandler(callback) {
        this.emitter.on('error', callback);
    }

    addPacketHandler(opcode, callback) {
        this.emitter.on(PACKET[opcode], callback);
    }
}

export default NetworkAdapter;

Replace EventEmitter with the JS CustomEvent API