const { toast } = require('bulma-toast');
const cbor = require('borc');

const actions = require('./actions');

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

function errorToast(err) {
    console.error(err);
    return toast({
        message: err,
        type: 'is-warning',
        duration: 5000,
    });
}

// Create WebSocket connection.
// requires the redux store in order to push updates
// to components
function createSocket(store) {
    let ws;

    let gameStateTimeout;

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

        // Connection opened
        ws.addEventListener('open', (event) => {
            send({ method: 'account_login', params: { name: 'ntr', password: 'grepgrepgrep' } });
        });

        // Listen for messages
        ws.addEventListener('message', onMessage);

        ws.addEventListener('error', (event) => {
            console.error('WebSocket error', event);
            account = null;
            // return setTimeout(connect, 5000);
        });

        ws.addEventListener('close', (event) => {
            console.error('WebSocket closed', event);
            account = null;
            return setTimeout(connect, 5000);
        });

        return ws;
    }

    // handle account auth within the socket itself
    // https://www.christian-schneider.net/CrossSiteWebSocketHijacking.html
    let account = null;

    // -------------
    //   Incoming
    // -------------
    function accountLogin(res) {
        const [struct, login] = res;

        account = login;
        store.dispatch(actions.setAccount(login));
        // send({ method: 'cryp_spawn', params: { name: 'bees' } });
        send({ method: 'account_cryps', params: {} });
        send({ method: 'item_list', params: {} });
        console.log(account);
    }

    function accountCryps(response) {
        const [structName, cryps] = response;
        store.dispatch(actions.setCryps(cryps));
        console.log('got my cryps', cryps);
    }

    function gameState(response) {
        const [structName, game] = response;
        clearInterval(gameStateTimeout);
        gameStateTimeout = setTimeout(() => sendGameState(game.id), 1000);
        store.dispatch(actions.setGame(game));
    }

    function crypSpawn(response) {
        const [structName, cryp] = response;
        console.log('got a new cryp', cryp);
    }

    function gamePve(response) {
        const [structName, game] = response;
        console.log('got a new game', game);
    }

    function itemList(response) {
        const [structName, items] = response;
        store.dispatch(actions.setItems(items));
    }

    // -------------
    //   Outgoing
    // -------------
    function send(msg) {
        console.log('outgoing msg', msg);
        msg.token = account && account.token;
        ws.send(cbor.encode(msg));
    }

    function sendAccountLogin(name, password) {
        send({ method: 'account_login', params: { name, password } });
    }

    function sendAccountRegister(name, password) {
        send({ method: 'account_create', params: { name, password } });
    }

    function sendCrypSpawn(name) {
        send({ method: 'cryp_spawn', params: { name } });
    }

    function sendCrypLearn(id, skill) {
        send({ method: 'cryp_learn', params: { id, skill } });
    }

    function sendCrypForget(id, skill) {
        send({ method: 'cryp_forget', params: { id, skill } });
    }

    function sendGameState(id) {
        send({ method: 'game_state', params: { id } });
    }

    function sendGamePve(id) {
        send({ method: 'game_pve', params: { id } });
    }

    function sendGamePvp(crypIds) {
        send({ method: 'game_pvp', params: { cryp_ids: crypIds } });
    }

    function sendGameJoin(gameId, crypIds) {
        send({ method: 'game_join', params: { game_id: gameId, cryp_ids: crypIds } });
    }

    function sendGameSkill(gameId, crypId, targetTeamId, skill) {
        send({
            method: 'game_skill',
            params: {
                game_id: gameId, cryp_id: crypId, target_team_id: targetTeamId, skill,
            },
        });
        store.dispatch(actions.setActiveSkill(null));
    }

    function sendGameTarget(gameId, crypId, skillId) {
        send({ method: 'game_target', params: { game_id: gameId, cryp_id: crypId, skill_id: skillId } });
        store.dispatch(actions.setActiveIncoming(null));
    }

    function sendItemUse(item, target) {
        console.log(item, target);
        send({ method: 'item_use', params: { item, target } });
        store.dispatch(actions.setActiveItem(null));
    }


    // -------------
    //   Events
    // -------------

    function clearGameStateInterval() {
        clearInterval(gameStateTimeout);
    }

    // -------------
    //   Handling
    // -------------
    // when the server sends a reply it will have one of these message types
    // this object wraps the reply types to a function
    const handlers = {
        cryp_spawn: crypSpawn,
        game_pve: gamePve,
        game_state: gameState,
        account_login: accountLogin,
        account_create: accountLogin,
        account_cryps: accountCryps,
        item_list: itemList,
    };

    // 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);
        console.log(res);

        // check for error and split into response type and data
        if (res.err) return errorToast(res.err);
        const { method, params } = res;
        if (!handlers[method]) return errorToast(`${method} handler missing`);
        return handlers[method](params);
    }

    return {
        clearGameStateInterval,
        sendAccountLogin,
        sendAccountRegister,
        sendGameState,
        sendGamePve,
        sendGamePvp,
        sendGameJoin,
        sendGameSkill,
        sendGameTarget,
        sendCrypSpawn,
        sendCrypLearn,
        sendCrypForget,
        sendItemUse,
        connect,
    };
}

module.exports = createSocket;
