package cpsc2150.extendedConnectX.models;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Class that represents an entire game board for the ExtendedConnectX game using a
 * more memory-efficient map instead of an array.
 * <p>This class 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
 *
 * @invariant
 *      board.containsKey(' ') = false AND
 *      MIN_ROWS {@code <=} numRows {@code <} MAX_ROWS AND
 *      MIN_COLUMNS {@code <=} numColumns {@code <} MAX_COLUMNS
 *      MIN_NUM_TO_WIN {@code <=} numToWin {@code <=} MAX_NUM_TO_WIN
 *
 * @correspondence
 *      number_of_rows = numRows AND
 *      number_of_columns = numColumns AND
 *      num_to_win = numToWin AND
 *      self = board<Character, List<BoardPosition>>
 */
public class GameBoardMem extends AbsGameBoard implements IGameBoard {

    private int numRows;
    private int numColumns;
    private int numToWin;
    private Map<Character, List<BoardPosition>> board;

    /**
     * Constructor for GameBoardMem that takes parameters for the number of rows, the number of columns, and
     * the number needed to win, and initializes a HashMap for the game board.
     *
     * @param rows Number of rows
     * @param columns Number of columns
     * @param win Number needed to win
     *
     * @pre
     *      MIN_ROWS {@code <=} rows {@code <} MAX_ROWS AND
     *      MIN_COLUMNS {@code <=} columns {@code <} MAX_COLUMNS AND
     *      MIN_NUM_TO_WIN {@code <=} win {@code <=} MAX_NUM_TO_WIN
     *
     * @post
     *      board = [ empty HashMap with key type Character and value type BoardPosition ] AND
     *      numRows = rows AND
     *      numColumns = columns AND
     *      numToWin = win
     */
    public GameBoardMem(int rows, int columns, int win) {

        // Update private variables
        numRows = rows;
        numColumns = columns;
        numToWin = win;

        // Initialize board
        board = new HashMap<Character, List<BoardPosition>>();

    }

    public void placeToken(char p, int c) {

        // Loop through rows (starting from the bottom)
        for (int i = 0; i < numRows; i++) {

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

            // If the current value at current row and column indexes is a space (not a token)
            if (whatsAtPos(pos) == ' ') {

                // If the board already contains the key p, add the new position to the key's value list
                if (board.containsKey(p)) {

                    board.get(p).add(pos);

                    // If the board does not contain the key p
                } else {

                    // Create a new list of type BoardPosition
                    List<BoardPosition> newList = new ArrayList<BoardPosition>();

                    // Add the new position to the empty list
                    newList.add(pos);

                    // Add the new list to the board map at key p
                    board.put(p, newList);

                }

                // Return early
                return;

            }

        }

    }

    public char whatsAtPos(BoardPosition pos) {

        // Loop through each entry in the map
        for (Map.Entry<Character, List<BoardPosition>> entry : board.entrySet()) {

            // Check if specified position is present in the list of values at the current key
            if (entry.getValue().contains(pos)) {

                // If so, return the current entry's key
                return entry.getKey();

            }

        }

        // If the board does not contain the specified position, return a space character
        return ' ';

    }

    /**
     * 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 <} numRows AND 0 {@code >=} pos.column {@code <} numColumns) AND
     *      player != ' '
     *
     * @post
     *      isPlayerAtPos = true iff (board.containsKey(player) = true AND board.get(player).contains(pos) = true) AND board = #board
     *      isPlayerAtPos = false iff (board.containsKey(player) = false) AND board = #board
     *      isPlayerAtPos = false iff (board.containsKey(player) = true AND board.get(player).contains(pos) = false) AND board = #board
     */
    @Override
    public boolean isPlayerAtPos(BoardPosition pos, char player) {

        // Check that the board map actually contains the key player, return false if not
        if (!board.containsKey(player)) {

            return false;

        }

        // Check if the list of BoardPosition objects that correspond to the key player
        // contains the position supplied by the pos parameter, return true if so
        if (board.get(player).contains(pos)) {

            return true;

            // Otherwise, return false
        } else {

            return false;

        }

    }

    public int getNumRows() {

        return numRows;

    }

    public int getNumColumns() {

        return numColumns;

    }

    public int getNumToWin() {

        return numToWin;

    }

}
