const cbor = require('borc');

const toast = require('izitoast');
const eachSeries = require('async/eachSeries');

const actions = require('./actions');
const { setAnimations, clearAnimations } = require('./animations.utils');

const SOCKET_URL = process.env.NODE_ENV === 'production' ? 'wss://mnml.gg/api/ws' : 'ws://localhost/api/ws';

function createSocket(store) {
    let ws = null;
    // -------------
    //   Outgoing
    // -------------
    function send(msg) {
        if (msg.method !== 'ping') console.log('outgoing msg', msg);
        ws.send(cbor.encode(msg));
    }

    function sendDevResolve(skill) {
        send(['DevResolve', { skill }]);
    }

    function setGame(game) {
        store.dispatch(actions.setGame(game));
        store.dispatch(actions.setAnimating(true));
        store.dispatch(actions.setGameSkillInfo(null));
        // stop fetching the game state til animations are done
        const newRes = game.resolutions[game.resolutions.length - 1];
        return eachSeries(newRes, (r, cb) => {
            // if (r.delay === 0) return cb(); // TargetKo etc
            setAnimations(r, store);
            return setTimeout(cb, r.delay);
        }, err => {
            if (err) return console.error(err);
            clearAnimations(store);
            // set the game state so resolutions don't fire twice
            store.dispatch(actions.setGame(game));
            // ws.sendGameState(game.id);
            return true;
        });
    }

    const handlers = {
        GameState: setGame,
    };

    // decodes the cbor and
    // calls the handlers defined above based on message type
    function onMessage(event) {
        // decode binary msg from server
        const blob = new Uint8Array(event.data);
        const res = cbor.decode(blob);
        // if (res.err) return errHandler(res.err);

        const [msgType, params] = res;
        if (msgType !== 'Pong') console.log(res);

        // check for error and split into response type and data
        if (!handlers[msgType]) return console.error(`${msgType} handler missing`);
        return handlers[msgType](params);
    }

    // Connection opened
    function onOpen() {
        toast.info({
            message: 'connected',
            position: 'topRight',
        });
        return true;
    }

    function onError(event) {
        console.error('WebSocket error', event);
    }

    function onClose(event) {
        console.error('WebSocket closed', event);
        toast.warning({
            message: 'disconnected',
            position: 'topRight',
        });
        return setTimeout(connect, 5000);
    }

    function connect() {
        if (ws) {
            ws.removeEventListener('open', onOpen);
            ws.removeEventListener('message', onMessage);
            ws.removeEventListener('error', onError);
            ws.removeEventListener('close', onClose);
            ws = null;
        }

        ws = new WebSocket(SOCKET_URL);
        ws.binaryType = 'arraybuffer';

        // Listen for messages
        ws.addEventListener('open', onOpen);
        ws.addEventListener('message', onMessage);
        ws.addEventListener('error', onError);
        ws.addEventListener('close', onClose);
        return ws;
    }

    return {
        sendDevResolve,
        connect,
    };
}

module.exports = createSocket;
