const ID = window.crypto.randomUUID();
// Timeout to consider a player disconnected
const TIMEOUT = 30_000;

let players = {};
let us = null;
let currentPlayer = () => Object.values(players).filter((p) => p.isPlaying)[0];

const WAITING = 0;
const PRE_GAME = 1;
const PLAYING = 2;
const POST_GAME = 3;

let gameState = WAITING;

let socket;
let random;
let barrier;

// Not totally reliable but better than nothing.
window.addEventListener("beforeunload", () => {
    socket.emit("message", Packet.createDisconnect());
});

document.addEventListener("DOMContentLoaded", () => {
    socket = io();
    random = new Random();
    barrier = new Barrier();

    socket.on("connect", () => {
        console.log("Connected!");
        socket.emit("message", Packet.createAnnounce());
        // Create self
        players[ID] = new Player(ID, name);
        us = players[ID];
    });

    socket.on("message", async (data) => {
        switch (data.type) {
            case "ANNOUNCE":
                if (data.author === ID) {
                    return;
                }
                playerConnected(data);
                break;

            case "DISCONNECT":
                playerDisconnected(data);
                break;

            case "KEEPALIVE":
                if (data.author === ID) {
                    return;
                }
                keepAlive(data);
                break;

            case "READY":
                if (data.author === ID) {
                    return;
                }
                await setReady(data);
                break;

            case "RANDOM":
                if (data.author === ID) {
                    return;
                }
                await random.processCooperativeRandom(data);
                break;

            case "BARRIER":
                if (data.author === ID) {
                    return;
                }
                barrier.resolve(data);
                break;

            case "ACT":
                if (data.author !== currentPlayer().id) {
                    return;
                }

                if (!allRegionsClaimed()) {
                    // Claim a region in the pregame.
                    if (currentPlayer().claim(data)) {
                        // Increment to next player.
                        currentPlayer().endTurn();
                    }
                } else if (!allReinforcementsPlaced()) {
                    if (currentPlayer().reinforce(data)) {
                        currentPlayer().endTurn();
                    }
                }

                updateDom();
                break;
        }
    });

    // Emit keepalive messages to inform other players we are still here
    window.setInterval(() => {
        socket.emit("message", Packet.createKeepAlive());
    }, TIMEOUT / 5);
});

/**
 * Process player connect packets: these inform that a new player has joined.
 *
 * @param data Packet received
 */
function playerConnected(data) {
    // Block players from joining mid-game
    if (gameState !== WAITING) {
        return;
    }

    // When a new player is seen, all announce to ensure they know all players.
    if (players[data.author] === undefined) {
        players[data.author] = new Player(data.author, data.name);
        socket.emit("message", Packet.createAnnounce());
        players[data.author].resetTimeout();
    } else {
    }

    updateDom();
}

function playerDisconnected(data) {
    console.log("deleting player");
    delete players[data.author];
    updateDom();
}

/**
 * Process keep-alive packets: these are packets that check players are still online.
 *
 * @param data Packet received
 */
function keepAlive(data) {
    players[data.author].resetTimeout();
}

/**
 * Process sync packets: update player details like status and name.
 *
 * @param data Packet received
 */
async function setReady(data) {
    players[data.author].name = data.name;
    players[data.author].ready = data.ready;

    updateDom();

    if (allPlayersReady()) {
        await startPregame();
    }
}

function allPlayersReady() {
    for (let player of Object.values(players)) {
        if (!player.ready) {
            return false;
        }
    }

    return true;
}

async function startPregame() {
    console.log("all players ready.");

    gameState = PRE_GAME;

    let firstPlayerIndex = await random.get(Object.keys(players).length, "first-player");

    let firstPlayer = Object.values(players).sort((a, b) => (a.id < b.id ? -1 : 1))[
        firstPlayerIndex
    ];

    firstPlayer.isPlaying = true;
    await barrier.wait();
    updateDom();
}
