package core;

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

import exception.OutOfBoardException;
import exception.TakenSquareException;

/**
 * Cette classe modélise le plateau sur lequel les pions peuvent se déplacer.
 * C'est un plateau de forme rectangulaire constitué de cases carrées. Une des
 * cases du plateau est une case "bonus" permettant aux pions d'être plus forts.
 */
public class Board {

	/**
	 * Le nombre de cases sur l'axe x.
	 */
	private int xSize;

	/**
	 * Le nombre de cases sur l'axe y.
	 */
	private int ySize;

	/**
	 * La position x de la case "bonus".
	 */
	private int xBonusSquare;

	/**
	 * La position y de la case "bonus".
	 */
	private int yBonusSquare;

	/**
	 * Les pions qui sont présents sur le plateau.
	 */
	private List<Pawn> pawns;

	/**
	 * Le pion dont c'est le tour de jouer.
	 */
	private Pawn currentPawn = null;

	public int getXSize() {
		return xSize;
	}

	public int getYSize() {
		return ySize;
	}

	/**
	 * Contruit un plateau avec une taille, une position de la case "bonus" et
	 * un nombre de pions donnés. Les pions sont répartis de manière aléatoire
	 * sur le plateau.
	 * 
	 * @param xSize
	 *            Le nombre de cases sur l'axe x.
	 * @param ySize
	 *            Le nombre de cases sur l'axe y.
	 * @param xBonusSquare
	 *            La position x de la case "bonus".
	 * @param yBonusSquare
	 *            La position y de la case "bonus".
	 * @param numberOfPawns
	 *            Le nombre de pions à ajouter sur le plateau.
	 */
	public Board(int xSize, int ySize, int xBonusSquare, int yBonusSquare,
			int numberOfPawns) {
		initBoard(xSize, ySize, xBonusSquare, yBonusSquare, numberOfPawns);
		Random random = new Random();
		for (int i = 1; i <= numberOfPawns; i++) {
			try {
				addPawn(new Pawn(Character.forDigit(i, 10),
						random.nextInt(xSize), random.nextInt(ySize), this));
			} catch (OutOfBoardException | TakenSquareException e) {
			}
		}
	}

	/**
	 * Contruit un plateau avec une taille et un nombre de pions donnés. Les
	 * pions sont répartis de manière aléatoire sur le plateau. La case "bonus"
	 * du plateau est choisie au hasard.
	 * 
	 * @param xSize
	 *            Le nombre de cases sur l'axe x.
	 * @param ySize
	 *            Le nombre de cases sur l'axe y.
	 * @param numberOfPawns
	 *            Le nombre de pions à ajouter sur le plateau.
	 */
	public Board(int xSize, int ySize, int numberOfPawns) {
		this(xSize, ySize, new Random().nextInt(xSize), new Random()
				.nextInt(ySize), numberOfPawns);
	}

	/**
	 * Contruit un plateau avec une taille et une position de la case "bonus"
	 * données, sans pion.
	 * 
	 * @param xSize
	 *            Le nombre de cases sur l'axe x.
	 * @param ySize
	 *            Le nombre de cases sur l'axe y.
	 * @param xBonusSquare
	 *            La position x de la case "bonus".
	 * @param yBonusSquare
	 *            La position y de la case "bonus".
	 */
	public Board(int xSize, int ySize, int xBonusSquare, int yBonusSquare) {
		this(xSize, ySize, xBonusSquare, yBonusSquare, 0);
	}

	/**
	 * Contruit un plateau avec une taille donnée, sans pion. La case "bonus" du
	 * plateau est choisie au hasard.
	 * 
	 * @param xSize
	 *            Le nombre de cases sur l'axe x.
	 * @param ySize
	 *            Le nombre de cases sur l'axe y.
	 */
	public Board(int xSize, int ySize) {
		this(xSize, ySize, new Random().nextInt(xSize), new Random()
				.nextInt(ySize), 0);
	}

	/**
	 * Initialise le plateau de jeu.
	 * 
	 * @param xSize
	 *            Le nombre de cases sur l'axe x.
	 * @param ySize
	 *            Le nombre de cases sur l'axe y.
	 * @param xBonusSquare
	 *            La position x de la case "bonus".
	 * @param yBonusSquare
	 *            La position y de la case "bonus".
	 * @param numberOfPawns
	 *            Le nombre de pions à ajouter sur le plateau.
	 * @throws IllegalArgumentException
	 *             si les paramètres du plateau ne sont pas valides.
	 */
	private void initBoard(int xSize, int ySize, int xBonusSquare,
			int yBonusSquare, int numberOfPawns) {
		if (xSize <= 0 || ySize <= 0 || xBonusSquare < 0 || yBonusSquare < 0
				|| xBonusSquare >= xSize || yBonusSquare >= ySize
				|| numberOfPawns < 0 || numberOfPawns > xSize * ySize) {
			throw new IllegalArgumentException();
		}
		this.xSize = xSize;
		this.ySize = ySize;
		this.xBonusSquare = xBonusSquare;
		this.yBonusSquare = yBonusSquare;
		this.pawns = new ArrayList<Pawn>(numberOfPawns);
	}

	/**
	 * Détermine si une case est une case "bonus" ou non.
	 * 
	 * @param x
	 *            La valeur de l'axe x.
	 * @param y
	 *            La valeur de l'axe y.
	 * @return true si la case est la case "bonus", false sinon.
	 */
	public boolean isBonusSquare(int x, int y) {
		return x == xBonusSquare && y == yBonusSquare;
	}

	/**
	 * Donne le contenu d'une case.
	 * 
	 * @param x
	 *            La valeur de l'axe x.
	 * @param y
	 *            La valeur de l'axe y.
	 * @return Le pion trouvé ou null s'il n'y a aucun pion.
	 */
	public Pawn getSquareContent(int x, int y) {
		for (Pawn p : this.pawns) {
			if ((p.getX() == x) && (p.getY() == y)) {
				return p;
			}
		}
		return null;
	}

	/**
	 * Ajoute un pion sur le plateau.
	 * 
	 * @param pawn
	 *            Le pion à ajouter.
	 * @throws TakenSquareException
	 *             si la case est déjà occupée par un autre pion.
	 * @throws OutOfBoardException
	 *             si le pion à ajouter est hors du plateau.
	 */
	public void addPawn(Pawn pawn) throws TakenSquareException,
			OutOfBoardException {
		if (pawn.getX() < 0 || pawn.getX() >= this.getXSize()
				|| pawn.getY() < 0 || pawn.getY() >= this.getYSize()) {
			throw new OutOfBoardException(pawn.getX(), pawn.getY());
		} else if (getSquareContent(pawn.getX(), pawn.getY()) != null) {
			throw new TakenSquareException(pawn.getX(), pawn.getY());
		} else {
			this.pawns.add(pawn);
			if (numberOfPawns() == 1)
				this.currentPawn = pawn;
		}
	}

	/**
	 * Supprime un pion du plateau.
	 * 
	 * @param pawn
	 *            Le pion à supprimer.
	 */
	public void removePawn(Pawn pawn) {
		this.pawns.remove(pawn);
	}

	/**
	 * Supprime tous les pions du plateau.
	 */
	public void removeAllPawns() {
		this.pawns.clear();
		this.currentPawn = null;
	}

	/**
	 * Donne le nombre de pions sur le plateau.
	 * 
	 * @return Le nombre de pions sur le plateau.
	 */
	public int numberOfPawns() {
		return this.pawns.size();
	}

	/**
	 * Calcule le nombre maximum de pièces d'or qu'un pion a.
	 * 
	 * @return Le nombre maximum de pièces d'or qu'un pion a.
	 */
	public int maxGold() {
		int max = 0;
		for (Pawn p : this.pawns) {
			max += Math.max(max, p.getGold());
		}
		return max;
	}

	/**
	 * Choisit le prochain pion qui est autorisé à jouer.
	 * 
	 * @return Le prochain pion qui est autorisé à jouer.
	 */
	public Pawn getNextPawn() {
		Pawn result = this.currentPawn;
		currentPawn = this.pawns.get((this.pawns.indexOf(this.currentPawn) + 1)
				% this.numberOfPawns());
		return result;
	}

	/**
	 * Donne le caractère qui doit être affiché pour représenter la case ou son
	 * contenu
	 * 
	 * @param x
	 *            La valeur de l'axe x.
	 * @param y
	 *            La valeur de l'axe y.
	 * @return '#' pour la case "bonus", '.' pour une case vide, 'x' pour une
	 *         case contenant le pion dont c'est le tour, un nombre
	 *         correspondant au numéro des autres pions.
	 */
	public char squareContentSprite(int x, int y) {
		char result;
		Pawn content = getSquareContent(x, y);
		if (content == null) {
			if (isBonusSquare(x, y)) {
				result = '#';
			} else
				result = '.';
		} else {
			if (content == this.currentPawn) {
				result = 'x';
			} else
				result = content.getLetter();
		}
		return result;
	}

	/**
	 * @return Une chaîne de caractères représentant tout le plateau.
	 */
	public String toString() {
		String result = "";
		for (int y = ySize - 1; y >= 0; y--) {
			for (int x = 0; x < xSize; x++) {
				result += squareContentSprite(x, y);
			}
			result += "\n";
		}
		return result;
	}

}
