/*
    Owen Sullivan
 */

package cpsc2150.extendedConnectX.models;

/**
 * Interface that contains methods for implementing an entire game board for the ExtendedConnectX game.
 * <p>This interface includes methods to place tokens, check token locations,
 * and determine whether or not the game has been won or is over.</p>
 *
 * @author Owen Sullivan
 * @version 1.0
 *
 * Initialization ensures:
 *      Game board contains no tokens, i.e., all spaces are a single space character.
 *
 * Defines:
 *      number_of_rows = Z
 *      number_of_columns = Z
 *      num_to_win = Z
 *
 * Constraints:
 *      0 {@code <=} number_of_rows {@code <} MAX_ROWS
 *      0 {@code <=} number_of_columns {@code <} MAX_COLUMNS
 *      0 {@code <=} num_to_win {@code <=} NUM_TO_WIN
 *
 */
public interface IGameBoard {

    int MAX_ROWS = 9;
    int MAX_COLUMNS = 7;
    int NUM_TO_WIN = 5;

    /**
     * Function that returns true if the column can accept another
     * token; false otherwise.
     *
     * @param c Column to be checked
     * @return Whether the column can accept another token
     *
     * @pre
     *      0 {@code <=} c {@code <} MAX_COLUMNS
     *
     * @post
     *      checkIfFree = true iff ([ #self at column c and row MAX_ROWS - 1 ] = 'X' OR [ #self at column c and row MAX_ROWS - 1 ] = 'O') AND self = #self
     *      checkIfFree = false iff ([ #self at column c and row MAX_ROWS - 1 ] = ' ') AND self = #self
     */
    default boolean checkIfFree(int c) {

        // Create a BoardPosition object where the row is the top possible row and the column is the parameter c
        BoardPosition pos = new BoardPosition(MAX_ROWS - 1, c);

        // Call primary method whatsAtPos and check to see if return value is a space, indicating an empty position
        if (whatsAtPos(pos) == ' ') {

            return true;

        } else {

            return false;

        }

    }

    /**
     * Function that places the character p in column c. The token will be placed in
     * the lowest available row in column c.
     *
     * @param p Character to be placed (representing player token)
     * @param c Column to place token in
     *
     * @pre
     *      (p = 'X' OR p = 'O') AND 0 {@code <= } c {@code <} MAX_COLUMNS
     *
     * @post
     *      [ #self at column c and lowest available row ] = 'X' OR [ #self at column c and lowest available row ] = 'O' AND
     *      [ token is placed at the lowest available row ] AND
     *      [ self = #self plus the new token ]
     */
    void placeToken(char p, int c);

    /**
     * This function will check to see if the last token placed in
     * column c resulted in the player winning the game. If so it will return
     * true, otherwise false.
     *
     * @param c Column number to be checked
     * @return Whether placing a token results in a player winning the game
     *
     * @pre
     *      0 {@code <= } c {@code <} MAX_COLUMNS AND
     *      [ c is the column where the latest token was placed ]
     *
     * @post
     *      checkForWin = true iff (checkTie = false AND (checkHorizWin = true OR checkVertWin = true OR checkDiagWin = true) WHERE [ c is the column with the last placed token ] AND self = #self
     *      checkForWin = false iff (checkTie = true OR (checkHorizWin = false AND checkVertWin = false AND checkDiagWin = false) WHERE [ c is the column with the last placed token ] AND self = #self
     */
    default boolean checkForWin(int c) {

        // Call primary method checkTie to determine if there is a tie
        if (checkTie()) {

            // If so, return false immediately
            return false;

        }

        // Create a new BoardPosition object to start looking for a player token from the top of the supplied column
        BoardPosition pos = new BoardPosition(MAX_ROWS - 1, c);
        // Variable to track row subtraction
        int i = 0;
        // Loop until a player token is found in the column, or until we're at the last row
        while (whatsAtPos(pos) == ' ' && MAX_ROWS - 1 - i >= 1) {

            i++;
            // Re-assign a new BoardPosition one row down (after incrementing i)
            pos = new BoardPosition((MAX_ROWS - 1 - i), c);

        }

        // If any of the win check methods return true, the game has a winner
        // Use primary method whatsAtPos to stand in for the last placed token character
        if (checkHorizWin(pos, whatsAtPos(pos)) || checkVertWin(pos, whatsAtPos(pos)) || checkDiagWin(pos, whatsAtPos(pos))) {

            return true;

            // If none of the win check methods return true, the game does not have a winner
        } else {

            return false;

        }

    }

    /**
     * This function will check to see if the game has resulted in a
     * tie. A game is tied if there are no free board positions remaining.
     * It will return true if the game is tied and false otherwise.
     *
     * @return Whether the game has resulted in a tie
     *
     * @pre
     *      [ the game has not ended with a win yet ]
     *
     * @post
     *      checkTie = true iff ([all board positions are either 'X' or 'O'] AND self = #self
     *      checkTie = false iff ([not all board positions are either 'X' or 'O']) AND self = #self
     */
    default boolean checkTie() {

        // Loop through rows in game board
        for (int i = 0; i < MAX_ROWS; i++) {

            // Loop through columns in game board
            for (int j = 0; j < MAX_COLUMNS; j++) {

                // Create a new BoardPosition object with the current row and column
                BoardPosition pos = new BoardPosition(i, j);

                // Call primary method whatsAtPos to check if position is empty
                if (whatsAtPos(pos) == ' ') {

                    // Return false if so
                    return false;

                }

            }

        }

        // If the program has executed to this point, return true because all spaces have a token
        return true;

    }

    /**
     * This function checks to see if the last token placed (which was placed in
     * position pos by player p) resulted in 5 in a row horizontally. Returns
     * true if it does, otherwise false.
     *
     * @param pos BoardPosition pair of row and column
     * @param p Player to check with
     * @return Whether the last token placed resulted in 5 in a row horizontally
     *
     * @pre
     *      (0 {@code >=} pos.row {@code <} MAX_ROWS AND 0 {@code >=} pos.column {@code <} MAX_COLUMNS) AND
     *      (p = 'X' OR p = 'O') AND [ pos is the game board position on the latest play ] AND
     *      [ p is the token located at position pos ]
     *
     * @post
     *      checkHorizWin = true iff ([5 horizontal positions are p]) AND self = #self
     *      checkHorizWin = true iff ([5 horizontal positions are not p]) AND self = #self
     */
    boolean checkHorizWin(BoardPosition pos, char p);

    /**
     * This function checks to see if the last token placed (which was placed in
     * position pos by player p) resulted in 5 in a row vertically. Returns
     * true if it does, otherwise false.
     *
     * @param pos BoardPosition pair of row and column
     * @param p Player to check with
     * @return Whether the last token placed resulted in 5 in a row vertically
     *
     * @pre
     *      (0 {@code >=} pos.row {@code <} MAX_ROWS AND 0 {@code >=} pos.column {@code <} MAX_COLUMNS) AND
     *      (p = 'X' OR p = 'O') AND [ pos is the game board position on the latest play ] AND
     *      [ p is the token located at position pos ]
     *
     * @post
     *      checkVertWin = true iff ([5 vertical positions are p]) AND self = #self
     *      checkVertWin = true iff ([5 vertical positions are not p]) AND self = #self
     */
    boolean checkVertWin(BoardPosition pos, char p);

    /**
     * This function checks to see if the last token placed (which was placed in
     * position pos by player p) resulted in 5 in a row diagonally. Returns
     * true if it does, otherwise false.
     *
     * @param pos BoardPosition pair of row and column
     * @param p Player to check with
     * @return Whether the last token placed resulted in 5 in a row diagonally
     *
     * @pre
     *      (0 {@code >=} pos.row {@code <} MAX_ROWS AND 0 {@code >=} pos.column {@code <} MAX_COLUMNS) AND
     *      (p = 'X' OR p = 'O') AND [ pos is the game board position on the latest play ] AND
     *      [ p is the token located at position pos ]
     *
     * @post
     *      checkVertWin = true iff ([5 diagonal positions are p]) AND self = #self
     *      checkVertWin = true iff ([5 diagonal positions are not p]) AND self = #self
     */
    boolean checkDiagWin(BoardPosition pos, char p);

    /**
     * Function that returns what is in the GameBoard at position pos.
     * If no marker is there, it returns a blank space char.
     *
     * @param pos BoardPosition pair of row and column
     * @return Character representing player token or space
     *
     * @pre
     *      (0 {@code >=} pos.row {@code <} MAX_ROWS AND 0 {@code >=} pos.column {@code <} MAX_COLUMNS)
     *
     * @post
     *      whatsAtPos = [ board at row pos.row and column pos.col ] AND self = #self
     */
    char whatsAtPos(BoardPosition pos);

    /**
     * This function returns true if the player is at pos; otherwise, it returns
     * false.
     *
     * @param pos BoardPosition pair of row and column
     * @param player Player to check with
     * @return Whether a player's token is at a position
     *
     * @pre
     *      (0 {@code >=} pos.row {@code <} MAX_ROWS AND 0 {@code >=} pos.column {@code <} MAX_COLUMNS) AND
     *      (player = 'X' OR player = 'O')
     *
     * @post
     *      isPlayerAtPos = true iff ([ board at row pos.row and column pos.col ] = player) AND self = #self
     *      isPlayerAtPos = false iff ([ board at row pos.row and column pos.col ] != player) AND self = #self
     */
    default boolean isPlayerAtPos(BoardPosition pos, char player) {

        // Call primary method whatsAtPos and check the result against player parameter
        if (whatsAtPos(pos) == player) {

            return true;

        } else {

            return false;

        }

    }

    /**
     * Returns the number of rows in the GameBoard.
     *
     * @return The number of rows
     *
     * @post
     *      getNumRows = MAX_ROWS
     */
    int getNumRows();

    /**
     * Returns the number of columns in the GameBoard.
     *
     * @return The number of columns
     *
     * @post
     *      getNumColumns = MAX_COLUMNS
     */
    int getNumColumns();

    /**
     * Returns the number of tokens in a row needed to win the game.
     *
     * @return The number of tokens
     *
     * @post
     *      getNumToWin = NUM_TO_WIN
     */
    int getNumToWin();
    
}
