import { App } from "./Components.js";
import { render } from "./SJDON.js";
import { State } from "./State.js";

/**
 * Represents a game.
 */
export class Game
{
    /**
     * The number of chips required for a win.
     */
    static #count = 4;

    /**
     * The width of the board.
     */
    static #width = 7;

    /**
     * The height of the board.
     */
    static #height = 6;

    /**
     * The state of the game.
     *
     * @type {State}
     */
    #state;

    /**
     * The id of the element to add the board to.
     *
     * @type {string}
     */
    id;

    /**
     * Initializes a new instance of the {@link Game `Game`} class.
     *
     * @param {string} id
     * The id of the element to add the board to.
     */
    constructor(id)
    {
        this.id = id;
        this.#state = new State(Game.#width, Game.#height);
    }

    /**
     * Gets the state of the game.
     */
    get state()
    {
        return this.#state;
    }

    /**
     * Gets the board of the game.
     */
    get board()
    {
        return this.state.board;
    }

    /**
     * Gets the current player.
     */
    get currentPlayer()
    {
        return this.state.currentPlayer;
    }

    /**
     * Gets the id of the player that is winning.
     *
     * @type {CellOwner}
     */
    get winner()
    {
        for (let yOffset = 0; yOffset <= 1; yOffset++)
        {
            for (let xOffset = (yOffset === 1) ? -1 : 1; xOffset <= 1; xOffset++)
            {
                let lowerBound = Math.max(0, xOffset * (Game.#count - 1) * -1);
                let upperBound = Math.min(Game.#width, Game.#width - (xOffset * (Game.#count - 1)));

                for (let y = 0; y < (Game.#height - yOffset * (Game.#count - 1)); y++)
                {
                    for (let x = lowerBound; x < upperBound; x++)
                    {
                        /**
                         * @type {CellOwner[]}
                         */
                        let tokens = [];

                        for (let i = 0; i < Game.#count; i++)
                        {
                            tokens.push(this.board[y + i * yOffset][x + i * xOffset]);
                        }

                        let player = tokens[0];

                        if (
                            player !== "" &&
                            tokens.every((token) => token === player))
                        {
                            return player;
                        }
                    }
                }
            }
        }

        return null;
    }

    /**
     * Dumps the state of the game.
     *
     * @returns {IState}
     * The JSON string representing the state.
     */
    dump()
    {
        return {
            turnCount: this.state.turnCount,
            board: {
                ...this.state.board
            }
        };
    }

    /**
     * Loads the game from the specified {@link data `data`}.
     *
     * @param {IState} data
     * The data to load.
     */
    load(data)
    {
        this.state.turnCount = data.turnCount;
        this.state.board.splice(0);
        this.#state = new State(Game.#width, Game.#height);
        Object.assign(this.state.board, data.board);
        this.draw();
    }

    /**
     * Initializes the game.
     */
    initialize()
    {
        this.draw();
    }

    /**
     * Resets the game.
     */
    reset()
    {
        this.#state = new State(Game.#width, Game.#height);
        this.draw();
    }

    /**
     * Replaces the content of the board with the current state.
     */
    draw()
    {
        let container = document.getElementById(this.id);
        container.innerHTML = "";
        render([App, this], container);
    }

    /**
     * Adds a chip to the board indicated by the {@link x `x`} and the {@link y `y`} coordinate.
     *
     * @param {number} x
     * The x coordinate to add the chip to.
     *
     * @param {number} y
     * The y coordinate to add the chip to.
     *
     * @returns {boolean}
     * A value indicating whether the chip could be added.
     */
    addChip(x, y)
    {
        if (!this.winner)
        {
            for (let i = Game.#height - 1; i >= 0; i--)
            {
                if (this.board[i][x] === "")
                {
                    this.board[i][x] = this.state.currentPlayer;
                    return true;
                }
            }
        }

        return false;
    }
}