GameController.java

package soen6441riskgame.controllers;

import java.util.ArrayList;
import java.util.Random;

import soen6441riskgame.enums.CommonCommandArgs;
import soen6441riskgame.enums.GamePhase;
import soen6441riskgame.models.Card;
import soen6441riskgame.models.CardSet;
import soen6441riskgame.models.Country;
import soen6441riskgame.models.Player;
import soen6441riskgame.singleton.GameBoard;
import soen6441riskgame.singleton.GameBoardPlaying;
import soen6441riskgame.utils.ConsolePrinter;
import soen6441riskgame.utils.Parser;

/**
 * Control the game
 */
public class GameController {
    private static final int NUMBER_OF_CARD_IN_ONE_SET = 3;
    private static final int MAXIMUM_ARMY_IN_ONE_ATTACK = 3;
    private static final int MINIMUM_ARMY_TO_ATTACK = 2;
    private static final int MINIMUM_NUMBER_OF_PLAYER = 2;
    private static final int MAXIMUM_NUMBER_OF_PLAYER = 6;

    /**
     * The minimum number army amount on a country
     */
    private static final int MINIMUM_NUMBER_OF_ARMY_ON_COUNTRY = 1;

    /**
     * handle <code>gameplayer</code> command
     *
     * @param args [0] -add/-remove - [1] player name
     */
    public void handleGamePlayerCommand(String[] args) {
        if (args.length == 0) {
            ConsolePrinter.printFormat("Missing parameter(s)");
            return;
        }

        CommonCommandArgs playerCommand = CommonCommandArgs.fromString(args[0]);

        switch (playerCommand) {
            case ADD: {
                GameBoard.getInstance().getGameBoardPlayer().addPlayer(args[1]);
                break;
            }
            case REMOVE: {
                GameBoard.getInstance().getGameBoardPlayer().removePlayer(args[1]);
                break;
            }
            case INVALID:
            case NONE: {
                ConsolePrinter.printFormat("Incorrect command");
                break;
            }
        }
    }

    /**
     * check if number of player is valid
     * 
     * @return if number of player is valid
     */
    public boolean isNumberOfPlayerValid() {
        ArrayList<Player> players = GameBoard.getInstance()
                                             .getGameBoardPlayer()
                                             .getPlayers();

        int numberOfPlayers = players.size();

        if (numberOfPlayers < MINIMUM_NUMBER_OF_PLAYER || numberOfPlayers > MAXIMUM_NUMBER_OF_PLAYER) {
            ConsolePrinter.printFormat("Number of player must between %d and %d, currently the game have %d players",
                                       MINIMUM_NUMBER_OF_PLAYER,
                                       MAXIMUM_NUMBER_OF_PLAYER,
                                       players.size());
            return false;
        }

        return true;
    }

    /**
     * random assign countries to players equally
     */
    public void populateCountries() {
        int totalPlayer = GameBoard.getInstance()
                                   .getGameBoardPlayer()
                                   .getPlayers()
                                   .size();
        int playerIndexToAssign = 0;

        Random random = new Random();

        ArrayList<Country> allCountries = GameBoard.getInstance().getGameBoardMap().getCountries();
        ArrayList<Country> unAssignedCountries = new ArrayList<Country>();

        for (Country country : allCountries) {
            if (!country.isConquered()) {
                unAssignedCountries.add(country);
            }
        }

        while (unAssignedCountries.size() > 0) {
            if (playerIndexToAssign == totalPlayer) {
                playerIndexToAssign = 0;
            }

            int nextIndexCountryToAssign = random.nextInt(unAssignedCountries.size());
            Country countryToAssign = unAssignedCountries.get(nextIndexCountryToAssign);

            Player player = GameBoard.getInstance()
                                     .getGameBoardPlayer()
                                     .getPlayers()
                                     .get(playerIndexToAssign);
            countryToAssign.setConquerer(player);

            // user need to place at least 1 army to the country he owned
            countryToAssign.setArmyAmount(MINIMUM_NUMBER_OF_ARMY_ON_COUNTRY);

            playerIndexToAssign++;
            unAssignedCountries.remove(countryToAssign);
        }

        ConsolePrinter.printFormat("All countries are randomly assigned to players");
    }

    /**
     * allocate a number of initial armies to each players, depending on number of players, from the
     * game rule
     *
     * If 2 are playing, each player counts out 40 Infantry.
     *
     * If 3 are playing, each player counts out 35 Infantry.
     *
     * If 4 are playing, each player counts out 30 Infantry.
     *
     * If 5 are playing, each player counts out 25 Infantry.
     *
     * If 6 are playing, each player counts out 20 Infantry.
     */
    public void initPlayersUnplacedArmies() {
        ArrayList<Player> players = GameBoard.getInstance()
                                             .getGameBoardPlayer()
                                             .getPlayers();

        int unplacedArmiesEachPlayer = 0;

        switch (players.size()) {
            case 2: {
                unplacedArmiesEachPlayer = 40;
                break;
            }
            case 3: {
                unplacedArmiesEachPlayer = 35;
                break;
            }
            case 4: {
                unplacedArmiesEachPlayer = 30;
                break;
            }
            case 5: {
                unplacedArmiesEachPlayer = 25;
                break;
            }
            case 6: {
                unplacedArmiesEachPlayer = 20;
                break;
            }
        }

        for (Player player : players) {
            player.setUnplacedArmies(unplacedArmiesEachPlayer);
        }
    }

    /**
     * Handle place army command for current player. The player is selected in a round-robin rule
     *
     * @param countryName name of the country to place army. The country must belong to current player
     */
    public void handlePlaceArmyCommand(String countryName) {
        Country country = GameBoard.getInstance().getGameBoardMap().getCountryFromName(countryName);
        Player currentPlayer = getCurrentPlayer();

        if (country == null) {
            ConsolePrinter.printFormat("Country %s not existed", countryName);
            return;
        }

        if (!country.getConquerer().isPlaying()) {
            ConsolePrinter.printFormat("Country not belong to the current player");
            return;
        }

        if (currentPlayer.getUnplacedArmies() != 0) {
            country.placeArmy(country.getConquerer());
        } else {
            turnToNextPlayer();
            getCurrentPlayer(true);
        }
    }

    /**
     * automatically randomly place all remaining unplaced armies for all players
     */
    public void handlePlaceAllCommand() {
        Random random = new Random();

        for (Player player : GameBoard.getInstance().getGameBoardPlayer().getPlayers()) {
            ArrayList<Country> conqueredCountries = player.getConqueredCountries();

            // if (player.getUnplacedArmies() > 0) {
            for (Country country : conqueredCountries) {
                if (player.getUnplacedArmies() > 0) {
                    int armiesToPlace = random.nextInt(player.getUnplacedArmies());
                    if (armiesToPlace == 0 && player.getUnplacedArmies() == 1) {
                        armiesToPlace = 1;
                    }

                    country.receiveArmiesFromUnPlacedArmies(armiesToPlace);
                }
            }
            // }
        }
    }

    /**
     * This get the current player without print the message
     *
     * @return current player
     */
    public Player getCurrentPlayer() {
        return getCurrentPlayer(false);
    }

    /**
     * start round-robin for list of players, exclude lost players
     *
     * @return the player in turn
     */
    public Player startRoundRobinPlayers() {
        ArrayList<Player> players = GameBoard.getInstance().getGameBoardPlayer().getPlayers();

        // set first player
        for (Player player : players) {
            if (player.getCurrentPhase() != GamePhase.LOST) {
                player.setPlaying(true);
                return player;
            }
        }

        return null;
    }

    /**
     * gives turn to the next player in list
     */
    void turnToNextPlayer() {
        Player currentPlayer = getCurrentPlayer();

        if (currentPlayer != null) {
            currentPlayer.setPlaying(false);

            if (currentPlayer.getCurrentPhase() != GamePhase.LOST) {
                currentPlayer.setCurrentPhase(GamePhase.WAITING_TO_TURN);
            }

            for (int index = 0; index < GameBoard.getInstance().getGameBoardPlayer().getPlayers().size(); index++) {
                if (currentPlayer.getNextPlayer().getCurrentPhase() != GamePhase.LOST) {
                    currentPlayer.getNextPlayer().setPlaying(true);
                    break;
                }
            }
        }
    }

    /**
     * print the current player to console
     */
    public void showCurrentPlayer() {
        Player player = getCurrentPlayer();

        if (player == null) {
            ConsolePrinter.printFormat("There is no player in game. Add some!");
            return;
        }

        ConsolePrinter.printFormat("Player %s is in turn", player.getName());
        ConsolePrinter.printFormat("Current phase: %s", player.getCurrentPhase().toString());
        ConsolePrinter.printFormat("    Un-placed Armies: %d", player.getUnplacedArmies());
        ConsolePrinter.printFormat("    Countries conquered:");

        for (Country country : player.getConqueredCountries()) {
            country.viewWithoutNeighbors(2);
        }

        ArrayList<Card> cards = player.getHoldingCards();
        if (!cards.isEmpty()) {
            ConsolePrinter.printFormat("Player cards:");

            int cardPosition = 1;
            for (Card card : player.getHoldingCards()) {
                ConsolePrinter.printFormat(GameBoard.getInstance().standardPrintStream,
                                           false,
                                           String.valueOf(cardPosition));
                card.view(1);
                cardPosition++;
            }
        }
    }

    /**
     * get current player
     *
     * @param isShowMessage if true, print the current player to screen
     * @return current player
     */
    private Player getCurrentPlayer(boolean isShowMessage) {
        Player currentPlayer = null;
        ArrayList<Player> players = GameBoard.getInstance().getGameBoardPlayer().getPlayers();

        for (Player player : players) {
            if (player.isPlaying() && player.getCurrentPhase() != GamePhase.LOST) {
                currentPlayer = player;
            }
        }

        if (currentPlayer == null) {
            currentPlayer = startRoundRobinPlayers();
        }

        if (isShowMessage) {
            ConsolePrinter.printFormat("Player %s is in turn", currentPlayer.getName());
        }

        return currentPlayer;
    }

    /**
     * REINFORCEMENT PHASE enter reinforcement phase
     *
     * @return true if enter reinforcement phase successfully or the current player is already in
     *         reinforcement phase
     */
    public boolean enterReinforcement() {
        Player currentPlayer = getCurrentPlayer(true);

        if (currentPlayer.getCurrentPhase() == GamePhase.WAITING_TO_TURN) {
            currentPlayer.setCurrentPhase(GamePhase.REINFORCEMENT);
            currentPlayer.calculateReinforcementArmies();
        } else if (currentPlayer.getCurrentPhase() != GamePhase.REINFORCEMENT) {
            ConsolePrinter.printFormat("Player %s cannot reinforce armies in %s phase",
                                       currentPlayer.getName(),
                                       currentPlayer.getCurrentPhase().toString());
        }

        return currentPlayer.getCurrentPhase() == GamePhase.REINFORCEMENT;
    }

    /**
     * REINFORCEMENT PHASE handle <code>reinforce</code> command
     *
     * @param args [0] country name - [1] number of armies to reinforce
     */
    public void handleReinforceCommand(String[] args) {
        Country country = GameBoard.getInstance().getGameBoardMap().getCountryFromName(args[0]);

        int numberOfArmies = Parser.parseWithDefault(args[1], 0);

        if (country == null) {
            ConsolePrinter.printFormat("Country %s is not existed", args[0]);
            return;
        }

        Player currentPlayer = getCurrentPlayer(false);
        currentPlayer.reinforce(country, numberOfArmies);
    }

    /**
     * FORTIFICATION PHASE enter fortification phase
     *
     * @return true if enter fortification phase successfully or the current player is already in
     *         fortification phase
     */
    public boolean enterFortifyPhase() {
        Player currentPlayer = getCurrentPlayer(true);

        if (currentPlayer.getCurrentPhase() == GamePhase.ATTACK
            && !GameBoard.getInstance().getGameBoardPlaying().isAttackMoveCmdRequired()) {
            currentPlayer.setCurrentPhase(GamePhase.FORTIFICATION);
        } else if (currentPlayer.getCurrentPhase() != GamePhase.FORTIFICATION) {
            ConsolePrinter.printFormat("Player %s cannot fortify armies in %s phase",
                                       currentPlayer.getName(),
                                       currentPlayer.getCurrentPhase().toString());
        }

        return currentPlayer.getCurrentPhase() == GamePhase.FORTIFICATION;
    }

    /**
     * move any number of armies from one country to another if they are connected
     *
     * If last argument is "none" or "-none" then user choose not to do a move.
     *
     * The arguments can be divided by a set of 3 as below. User can enter multiple set of arguments.
     *
     * @param args [0] from country - [1] to country - [2] number of armies
     */
    public void handleMultipleFortificationCommand(String[] args) {
        int numberOfCommandSet = args.length / 3;
        int numberOfLeftOverArgs = args.length % 3;

        for (int index = 0; index < numberOfCommandSet; index++) {
            String[] commandSet = new String[] { args[(3 * index)],
                                                 args[(3 * index) + 1],
                                                 args[(3 * index) + 2] };
            handleFortifyCommand(commandSet);
        }

        if (numberOfLeftOverArgs > 0) {
            if (numberOfLeftOverArgs > 1) {
                ConsolePrinter.printFormat("You have some invalid command arguments");
            }

            String lastArg = args[args.length - 1].toLowerCase();

            if (lastArg.equals("-none") || lastArg.equals("none")) {
                turnToNextPlayer();
                getCurrentPlayer(true);
            }
        }
    }

    /**
     * move any number of armies from one country to another if they are connected
     *
     * If args[0] is "none" then user choose not to do a move
     *
     * @param args [0] from country - [1] to country - [2] number of armies
     */
    private void handleFortifyCommand(String[] args) {
        Country fromCountry = GameBoard.getInstance()
                                       .getGameBoardMap()
                                       .getCountryFromName(args[0]);
        Country toCountry = GameBoard.getInstance()
                                     .getGameBoardMap()
                                     .getCountryFromName(args[1]);

        int numberOfArmies = Parser.parseWithDefault(args[2], 0);

        if (fromCountry == null || toCountry == null) {
            ConsolePrinter.printFormat("The country name(s) not existed");
            return;
        }

        Player currentPlayer = getCurrentPlayer(false);

        if (!fromCountry.isCountryBelongToPlayer(currentPlayer)
            || !toCountry.isCountryBelongToPlayer(currentPlayer)) {
            return;
        }

        currentPlayer.fortify(fromCountry, toCountry, numberOfArmies);
    }

    /**
     * ATTACK PHASE enter attack phase
     *
     * @return true if enter attack phase successfully or the current player is already in attack phase
     */
    public boolean enterAttackPhase() {
        Player currentPlayer = getCurrentPlayer(true);

        if (currentPlayer.getCurrentPhase() == GamePhase.REINFORCEMENT) {
            currentPlayer.setCurrentPhase(GamePhase.ATTACK);
        } else if (currentPlayer.getCurrentPhase() != GamePhase.ATTACK) {
            ConsolePrinter.printFormat("Player %s cannot use attack in %s phase",
                                       currentPlayer.getName(),
                                       currentPlayer.getCurrentPhase());
        }

        return currentPlayer.getCurrentPhase() == GamePhase.ATTACK;
    }

    /**
     * it handles the attack command
     *
     * If args[0] is "-noattack" then user choose not to attack and the player moves on to next phase
     * fortification
     *
     * @param args [0] from country - [1] to country - [2] number of dice rolls or -allout option
     */
    public void handleAttackCommand(String[] args) {
        // ConsolePrinter.printFormat("attack conditions testing");

        GameBoardPlaying gameBoardPlaying = GameBoard.getInstance().getGameBoardPlaying();

        if (gameBoardPlaying.isAttackMoveCmdRequired()) {
            ConsolePrinter.printFormat("Player %s need to move armies into your conquered country %s",
                                       getCurrentPlayer(false).getName(),
                                       gameBoardPlaying.getDefendingCountry().getName());
            return;
        }

        // check if its no attack
        if (args[0].toLowerCase().equals("-noattack") || args[0].toLowerCase().equals("noattack")) {
            endAttackPhase();
            return;
        }

        Country attackingCountry = GameBoard.getInstance()
                                            .getGameBoardMap()
                                            .getCountryFromName(args[0]);
        Country defendingCountry = GameBoard.getInstance()
                                            .getGameBoardMap()
                                            .getCountryFromName(args[1]);

        // need to check whether these countries are existent or not
        gameBoardPlaying.setAttackingCountry(attackingCountry);
        gameBoardPlaying.setDefendingCountry(defendingCountry);

        if (!isAttackPreconditionsValid(attackingCountry,
                                        defendingCountry)) {
            ConsolePrinter.printFormat("Invalid Input");
            return;
        }

        if (args[2].equals("allout") || args[2].equals("-allout")) {
            gameBoardPlaying.setAttackerNumDice(Math.min((attackingCountry.getArmyAmount()
                                                          - 1),
                                                         MAXIMUM_ARMY_IN_ONE_ATTACK));
            gameBoardPlaying.setAlloutFlag(true);
            if (!isAttackValid(attackingCountry,
                               gameBoardPlaying.getAttackerNumDice(),
                               gameBoardPlaying.getDefenderNumDice(),
                               gameBoardPlaying.isAlloutFlag())) {
                gameBoardPlaying.setAlloutFlag(false);
            } else {
                gameBoardPlaying.setAlloutFlag(true);
                simulateAttack(gameBoardPlaying);
            }
        } else {
            if (Parser.checkValidInputNumber(args[2])) {
                // saving the number of dice
                gameBoardPlaying.setAttackerNumDice(Integer.parseInt(args[2]));
            } else {
                ConsolePrinter.printFormat("Invalid Input");
            }
            // check if attack is valid
            if (!isAttackValid(attackingCountry,
                               gameBoardPlaying.getAttackerNumDice(),
                               gameBoardPlaying.getDefenderNumDice(),
                               gameBoardPlaying.isAlloutFlag())) {
            }
        }
    }

    /**
     * it simulates the attack command for -allout option
     *
     * @param gameBoardPlaying the singleton class holding playing data
     */
    private void simulateAttack(GameBoardPlaying gameBoardPlaying) {
        while (gameBoardPlaying.isAlloutFlag()) {
            gameBoardPlaying.setAttackerNumDice(Math.min((gameBoardPlaying.getAttackingCountry()
                                                                          .getArmyAmount()
                                                          - 1),
                                                         MAXIMUM_ARMY_IN_ONE_ATTACK));
            int defender_dice = Math.min(gameBoardPlaying.getDefendingCountry()
                                                         .getArmyAmount(),
                                         2);
            if (gameBoardPlaying.isAttackMoveCmdRequired()) {
                gameBoardPlaying.setAlloutFlag(false);
            } else if (isAttackValid(gameBoardPlaying.getAttackingCountry(),
                                     gameBoardPlaying.getAttackerNumDice(),
                                     gameBoardPlaying.getDefenderNumDice(),
                                     gameBoardPlaying.isAlloutFlag())) {
                handleDefendCommand(new String[] { Integer.toString(defender_dice) });
            } else {
                gameBoardPlaying.setAlloutFlag(false);
                gameBoardPlaying.reset();
                ConsolePrinter.printFormat("The attack has ended as no other move is possible.");
            }
        }
    }

    /**
     * it handles the defend command and executes the entire attack and calls the dice roll method.
     *
     * @param args [0] number of dice that defender rolls
     *
     */
    public void handleDefendCommand(String[] args) {
        GameBoardPlaying gameBoardPlaying = GameBoard.getInstance().getGameBoardPlaying();
        Country defendingCountry = gameBoardPlaying.getDefendingCountry();
        Country attackingCountry = gameBoardPlaying.getAttackingCountry();
        int attackerNumDice = gameBoardPlaying.getAttackerNumDice();

        if (gameBoardPlaying.isAttackMoveCmdRequired()) {
            ConsolePrinter.printFormat("Player %s need to move armies into your conquered country %s",
                                       getCurrentPlayer(false).getName(),
                                       defendingCountry
                                                       .getName());
            return;
        }
        if (!Parser.checkValidInputNumber(args[0])) {
            ConsolePrinter.printFormat("Invalid Input");
            return;
        }

        gameBoardPlaying.setDefenderNumDice(Integer.parseInt(args[0]));
        int defenderNumDice = gameBoardPlaying.getDefenderNumDice();

        if (attackingCountry == null
            || defendingCountry == null) {
            ConsolePrinter.printFormat("You cannot use defend command without attack command.");
            return;
        }

        if (!isAttackValid(attackingCountry,
                           attackerNumDice,
                           defenderNumDice,
                           gameBoardPlaying.isAlloutFlag())) {
            ConsolePrinter.printFormat("Defend command not allowed as attack is invalid");
            return;
        } else if (defenderNumDice > 2) {
            ConsolePrinter.printFormat("You can only defend with a maximum of 2 armies at a time");
            return;
        } else if (defenderNumDice > defendingCountry.getArmyAmount()) {
            ConsolePrinter.printFormat("Error. Number of dice rolls should be less than or equal to the number of armies in the defending country but atmost 2.");
            return;
        }

        Player currentPlayer = getCurrentPlayer(false);
        currentPlayer.attack(attackingCountry,
                             defendingCountry,
                             attackerNumDice,
                             defenderNumDice);

        attackResult(defendingCountry, attackingCountry);
    }

    /**
     * it handles the attack move command and moves the army from attacking country to defending country
     * after the attacker has won the defending country.
     *
     * @param args [0] number of armies to move from attacking country to defending country
     *
     */
    public void handleAttackMoveCommand(String[] args) {
        GameBoardPlaying gameBoardPlaying = GameBoard.getInstance().getGameBoardPlaying();
        if (gameBoardPlaying.isAttackMoveCmdRequired()) {
            if (Parser.checkValidInputNumber(args[0])) {
                int army_to_be_moved = Integer.parseInt(args[0]);
                Country attackingCountry = gameBoardPlaying.getAttackingCountry();
                int attackerNumDice = gameBoardPlaying.getAttackerNumDice();
                if (army_to_be_moved < attackerNumDice
                    && attackingCountry.getArmyAmount()
                       - 1 > army_to_be_moved) {
                    ConsolePrinter.printFormat("You need to move atleast %s army to the conquered country.",
                                               attackerNumDice);
                } else if (attackingCountry.getArmyAmount()
                           - 1 < army_to_be_moved) {
                    ConsolePrinter.printFormat("You only have %s army available to move. You cannot move more armies than what you have.",
                                               attackerNumDice);
                } else {
                    Player currentPlayer = getCurrentPlayer(false);
                    currentPlayer.attackMove(attackingCountry,
                                             gameBoardPlaying.getDefendingCountry(),
                                             army_to_be_moved);
                    // attackingCountry.moveArmies(defendingCountry, army_to_be_moved);
                    // reinitialize variables to null
                    gameBoardPlaying.reset();
                    gameBoardPlaying.setAlloutFlag(false);
                    gameBoardPlaying.setAttackMoveCmdRequired(false);

                    if (currentPlayer.furtherAttackPossible()) {
                        ConsolePrinter.printFormat("The attack has ended. You can continue to attack other countries or type attack -noattack to end attack phase.");
                    } else {
                        ConsolePrinter.printFormat("No other attack is possible from any country.");
                        endAttackPhase();
                    }
                }

            } else {
                ConsolePrinter.printFormat("Invalid Input");
            }
        }
    }

    /**
     * it checks whether the game has ended or not.
     *
     * @return boolean if game has ended, then true , else false
     *
     */
    private boolean isGameEnded() {
        // check whether this player has won the game
        Player currentPlayer = getCurrentPlayer(false);
        boolean gameEnded = true;
        ArrayList<Country> countries = GameBoard.getInstance()
                                                .getGameBoardMap()
                                                .getCountries();
        for (Country country : countries) {
            if (country.getConquerer() != currentPlayer) {
                gameEnded = false;
                break;
            }
        }

        return gameEnded;
    }

    /**
     * check if attack pre-conditions valid. The conditions are:
     *
     * valid attacking country and defending country
     *
     * attacking country belong to attacker
     *
     * defending country does not belong to attacker
     *
     * attacking country and defending country is neighbor
     *
     * 
     * @param attackingCountry attacking country
     * @param defendingCountry defending country
     * 
     * @return is valid or not
     */
    private boolean isAttackPreconditionsValid(Country attackingCountry, Country defendingCountry) {
        Player currentPlayer = getCurrentPlayer(false);

        if (attackingCountry == null
            || defendingCountry == null) {
            ConsolePrinter.printFormat("The countries you have entered is non-existent");
            return false;
        }

        // check if attacking country belongs to the current player
        if (!attackingCountry.getConquerer()
                             .equals(currentPlayer)) {
            ConsolePrinter.printFormat("The country %s does not belong to %s",
                                       attackingCountry.getName(),
                                       currentPlayer.getName());
            return false;
        }

        // check if the defending country does not belong to the current player
        if (defendingCountry.getConquerer().equals(currentPlayer)) {
            ConsolePrinter.printFormat("The country %s belongs to %s. You cannot attack your own country.",
                                       defendingCountry.getName(),
                                       currentPlayer.getName());
            return false;
        }

        // the countries have to be neighbours
        if (!attackingCountry.isNeighboringCountries(defendingCountry)) {
            ConsolePrinter.printFormat("The countries %s and %s are not neighbouring countries. You can only attack neighbouring countries.",
                                       attackingCountry.getName(),
                                       defendingCountry.getName());
            return false;
        }

        return true;
    }

    /**
     *
     * it returns whether the attack is valid (true) or not (false)
     *
     * @return boolean it checks whether attack is valid or not and returns true or false based on that.
     * @param attackingCountry attacking country
     * @param attackerNumDice  attacker number of dice
     * @param defenderNumDice  defender number of dice
     * @param alloutFlag       is all out
     */
    boolean isAttackValid(Country attackingCountry,
                          int attackerNumDice,
                          int defenderNumDice,
                          boolean alloutFlag) {
        // check if you have more than 2 army in attacking country
        if (attackingCountry.getArmyAmount() < MINIMUM_ARMY_TO_ATTACK) {
            ConsolePrinter.printFormat("The country %s has less than 2 armies. You need at least 2 armies to attack a country.",
                                       attackingCountry.getName());
            return false;
        }
        // check if the player has more armies than the numDice. numDice can be at most 3.
        else if ((attackingCountry.getArmyAmount()
                  - 1) < attackerNumDice) {
            ConsolePrinter.printFormat("The number of armies available to attack are less than %s",
                                       attackerNumDice);
            return false;
        }
        // also numdice has to be less than 3
        else if (attackerNumDice > MAXIMUM_ARMY_IN_ONE_ATTACK) {
            ConsolePrinter.printFormat("You can only attack with a maximum of %d armies at a time",
                                       MAXIMUM_ARMY_IN_ONE_ATTACK);
            return false;
        }

        // attack execution
        else {
            if (defenderNumDice == 0
                && !alloutFlag) {
                ConsolePrinter.printFormat("Enter defend command");
            }
            return true;
        }
    }

    /**
     * it prints the result of the attack.
     * 
     * @param defendingCountry attacking country
     * @param attackingCountry defending country
     */
    private void attackResult(Country defendingCountry, Country attackingCountry) {
        // now check if defender's armies left is 0, set conquerer as attacker
        Player attacker = attackingCountry.getConquerer();
        Player defender = defendingCountry.getConquerer();

        if (defendingCountry.getArmyAmount() == 0) {
            ConsolePrinter.printFormat("The attacker %s has conquered the country %s successfully. He has %s army available to move.",
                                       attacker.getName(),
                                       defendingCountry.getName(),
                                       attackingCountry.getArmyAmount()
                                                                   - 1);

            defendingCountry.setConquerer(attacker);

            // set attacker can be reward a card when attack phase end
            attacker.setPlayerBeAwardCard(true);

            // check if defender has any countries that he has conquered. If not, the player lost the game.
            if (defender.getConqueredCountries().isEmpty()) {
                defender.setCurrentPhase(GamePhase.LOST);
                // GameBoard.getInstance()
                // .getGameBoardPlayer()
                // .removePlayer(defendingCountry.getConquerer().getName());
            }

            // check if player has conquered entire continent
            if (isGameEnded()) {
                GameBoard.getInstance()
                         .getGameBoardPlaying()
                         .setAlloutFlag(false);
                GameBoard.getInstance()
                         .getGameBoardPlaying()
                         .reset();
                GameBoard.getInstance()
                         .getGameBoardPlaying()
                         .setAttackMoveCmdRequired(false);
                setEndOfGamePhase();
            } else {
                // move armies
                ConsolePrinter.printFormat("Player %s needs to move armies into your conquered country %s",
                                           attacker.getName(),
                                           defendingCountry.getName());
                GameBoard.getInstance()
                         .getGameBoardPlaying()
                         .setAttackMoveCmdRequired(true);
            }
        } else {

            if (!GameBoard.getInstance()
                          .getGameBoardPlaying()
                          .isAlloutFlag()) {
                // reinitialize variables to null
                GameBoard.getInstance()
                         .getGameBoardPlaying()
                         .reset();
            }
        }

        Player currentPlayer = getCurrentPlayer(false);

        if (!isGameEnded()
            && !currentPlayer.furtherAttackPossible()
            && !GameBoard.getInstance().getGameBoardPlaying()
                         .isAttackMoveCmdRequired()) {
            if (GameBoard.getInstance()
                         .getGameBoardPlaying()
                         .isAlloutFlag()) {
                isAttackValid(attackingCountry,
                              GameBoard.getInstance().getGameBoardPlaying().getAttackerNumDice(),
                              GameBoard.getInstance().getGameBoardPlaying().getDefenderNumDice(),
                              GameBoard.getInstance().getGameBoardPlaying().isAlloutFlag());
            }

            ConsolePrinter.printFormat("No other attack is possible from any country.");
            GameBoard.getInstance()
                     .getGameBoardPlaying()
                     .reset();
            GameBoard.getInstance()
                     .getGameBoardPlaying()
                     .setAlloutFlag(false);
            endAttackPhase();
        }
    }

    /**
     * it ends the attack phase and sets the current game phase to fortification
     */
    private void endAttackPhase() {
        GameBoard.getInstance()
                 .getGameBoardPlaying()
                 .reset();
        Player currentPlayer = getCurrentPlayer(false);
        ConsolePrinter.printFormat("End of attack Phase. Player %s has entered fortification phase",
                                   currentPlayer.getName());
        currentPlayer.setCurrentPhase(GamePhase.FORTIFICATION);
    }

    /**
     * it sets the game phase to end of game when a player has won the game
     */
    void setEndOfGamePhase() {
        Player currentPlayer = getCurrentPlayer(false);
        ConsolePrinter.printFormat("Congratulations, The player %s has won the game.", currentPlayer.getName());
        currentPlayer.setCurrentPhase(GamePhase.END_OF_GAME);
    }

    /**
     * build set a card
     *
     * @param args {@value #NUMBER_OF_CARD_IN_ONE_SET} number as position of card in a set, -none at the
     *             end to not exchange
     * @return a set of card
     */
    private CardSet buildCardSet(String[] args) {
        Player currentPlayer = getCurrentPlayer();

        if (currentPlayer.getCurrentPhase() != GamePhase.REINFORCEMENT
            && currentPlayer.getCurrentPhase() != GamePhase.WAITING_TO_TURN) {
            ConsolePrinter.printFormat("Cannot exchange cards in %s phase",
                                       currentPlayer.getCurrentPhase().toString());
            return null;
        }

        if (args.length > NUMBER_OF_CARD_IN_ONE_SET) {
            ConsolePrinter.printFormat("A set of card only have %d card", NUMBER_OF_CARD_IN_ONE_SET);
            return null;
        }

        Card[] cards = new Card[NUMBER_OF_CARD_IN_ONE_SET];

        for (int index = 0; index < NUMBER_OF_CARD_IN_ONE_SET; index++) {
            if (Parser.checkValidInputNumber(args[index])) {
                int cardPosition = Parser.parseWithDefault(args[index], 0);
                cards[index] = currentPlayer.getHoldingCard(cardPosition);

                if (cards[index].isExchanged()) {
                    ConsolePrinter.printFormat("Card(s) you want to trade is already exchanged for armies");
                    return null;
                }
            }
        }

        return new CardSet(cards[0], cards[1], cards[2]);
    }

    /**
     * handle exchange card command
     *
     * @param args {@value #NUMBER_OF_CARD_IN_ONE_SET} number as position of card in a set, -none at the
     *             end to not exchange
     */
    public void exchangeCard(String[] args) {
        Player currentPlayer = getCurrentPlayer();

        int paramLeftOver = args.length % NUMBER_OF_CARD_IN_ONE_SET;
        int size = args.length - paramLeftOver;

        ArrayList<CardSet> cardSets = new ArrayList<>();

        for (int index = 0; index < size; index = index + NUMBER_OF_CARD_IN_ONE_SET) {

            CardSet cardSet = buildCardSet(new String[] {
                                                          args[index],
                                                          args[index + 1],
                                                          args[index + 2]
            });

            cardSets.add(cardSet);
        }

        currentPlayer.exchangeCardSets(cardSets);
    }
}