package sfs3.extensions.examples.tictactoe.game

import com.smartfoxserver.entities.data.ISFSArray
import com.smartfoxserver.entities.data.SFSArray

enum Mark
{
    EMPTY(0), CROSS(1), RING(2);

    int id
    private Mark(int id)
    {
        this.id = id
    }
}

enum State
{
    RUNNING,
    END_WITH_WINNER,
    END_WITH_TIE;
}

class Board
{
    private final Mark[][] board
    private int winner

    /**
     * Initialize the board.
     * We use a 4x4 matrix, so we can have 1-based indexes (rows and columns)
     */
    Board()
    {
        board = new Mark[4][4]

        // Add empty mark in all board slots
        reset()
    }

    /**
     * Return the mark contained in a board slot.
     */
    Mark getMarkAt(int row, int col)
    {
        return board[row][col]
    }

    /**
     * Set the mark in a board slot.
     */
    void setMarkAt(int row, int col, Mark mark)
    {
        // Validate coordinates
        checkCoords(row, col)

        // Set mark
        board[row][col] = mark
    }

    /**
     * Return the last winner:
     * - 0: no winner (tie or game still in progress)
     * - 1: player 1 was the winner
     * - 2: player 2 was the winner
     */
    int getWinner()
    {
        return winner
    }

    /**
     * Get the current board state.
     */
    State getState(int moveCount)
    {
        Iterator<String> iter = getAllSolutions().iterator()
        State state = State.RUNNING

        while(iter.hasNext())
        {
            String solution = iter.next()
            iter.remove()

            // Player 1 wins
            if (solution.equals("111"))
            {
                state = State.END_WITH_WINNER
                winner = 1
                break
            }

            // Player 2 wins
            else if (solution.equals("222"))
            {
                state = State.END_WITH_WINNER
                winner = 2
                break
            }
        }

        if (winner == 0 && moveCount == 9)
            state = State.END_WITH_TIE

        return state
    }

    /**
     * Reset the board adding an empty mark in all board slots.
     */
    void reset()
    {
        winner = 0

        for (int r = 1; r <= 3; r++)
        {
            Mark[] boardRow = board[r]

            for (int c = 1; c <= 3; c++)
            {
                boardRow[c] = Mark.EMPTY
            }
        }
    }

    /**
     * Validate the passed board slot coordinates.
     */
    private void checkCoords(int row, int col)
    {
        if (row < 1 || row > 3)
            throw new IllegalArgumentException("Mark row position out of range: " + row)

        if (col < 1 || col > 3)
            throw new IllegalArgumentException("Mark column position out of range: " + col)
    }

    /**
     * Get all completed rows, columns and diagonals to check if the game ended.
     */
    private List<String> getAllSolutions()
    {
        def solutions = new ArrayList<String>()

        //--- All rows ------------------------------------------------
        for (int r = 1; r <= 3; r++)
        {
            solutions.add(String.format("%s%s%s", String.valueOf(board[r][1].getId()), String.valueOf(board[r][2].getId()), String.valueOf(board[r][3].getId())))
        }

        //--- All columns ---------------------------------------------
        for (int c = 1; c <= 3; c++)
        {
            solutions.add(String.format("%s%s%s", String.valueOf(board[1][c].getId()), String.valueOf(board[2][c].getId()), String.valueOf(board[3][c].getId())))
        }

        //--- All diagonals ------------------------------------------
        solutions.add(String.format("%s%s%s", String.valueOf(board[1][1].getId()), String.valueOf(board[2][2].getId()), String.valueOf(board[3][3].getId())))
        solutions.add(String.format("%s%s%s", String.valueOf(board[1][3].getId()), String.valueOf(board[2][2].getId()), String.valueOf(board[3][1].getId())))

        return solutions
    }

    /**
     * Return the game board as a SFSArray.
     * As SFSArrays use 0-based indexes and we can't add entries starting from index 1,
     * the client receiving the data will need to transform it to rows/columns.
     */
    ISFSArray toSFSArray()
    {
        def sfsa = new SFSArray()

        for (int r = 1; r <= 3; r++)
            sfsa.addIntArray(getRowAsList(r))

        return sfsa
    }

    private List<Integer> getRowAsList(int r)
    {
        def row = new ArrayList<Integer>()

        for (int c = 1; c <= 3; c++)
            row.add( board[r][c].getId() )

        return row
    }
}
