/**
 * 
 */
package com.anemoff.android.taquin.model;

import java.util.ArrayList;
import java.util.Collections;

import android.graphics.Bitmap;
import android.graphics.Point;

/**
 * Plateau de jeu du taquin, composé d'une liste de BoardCell qui représente les
 * cases du taquin.
 * 
 * @author anemoff
 * 
 */
public class Board {

	/** Image complète */
	private Bitmap fullImage;

	/** Nombre de lignes */
	private int rowsCount;

	/** Nombre de colonnes */
	private int colsCount;

	/** Liste des cases du taquin */
	private ArrayList<Cell> cells;

	// TODO mémoriser liste d'entiers avec la config random créée, pour pouvoir
	// reset la partie en cours

	/** Position de la case vide. */
	private int emptyCellPos;

	/**
	 * Construit un nouveau plateau de taquin.
	 * 
	 * @param rows
	 *            Nombre de lignes du plateau.
	 * @param cols
	 *            Nombre de colonnes du plateau.
	 * @param bitmap
	 */
	public Board(int rows, int cols, Bitmap bitmap) {
		this.rowsCount = rows;
		this.colsCount = cols;
		this.fullImage = bitmap;
		cells = new ArrayList<Cell>();
		buildCells();
	}

	/**
	 * Retourne la case contenue à l'index donné.
	 * 
	 * @param index
	 *            L'index de la case.
	 * @return La case à l'index donné.
	 */
	public Cell getCell(int index) {
		return cells.get(index);
	}

	/**
	 * Retourne le nombre de cases du plateau de taquin.
	 * 
	 * @return Le nombre de cases du plateau de taquin.
	 */
	public int getBoardSize() {
		return cells.size();
	}

	private boolean isValidIndex(int index) {
		return index >= 0 && index < getBoardSize();
	}

	private boolean isValidPoint(Point p) {
		return p.x >= 0 && p.x < colsCount && p.y >= 0 && p.y < rowsCount;
	}

	/**
	 * Convertit une coordonnée de case P(colonne, ligne) en un index de
	 * tableau.
	 * 
	 * @param p
	 * @return
	 */
	public int pointToIndex(Point p) {
		return p.x + p.y * colsCount;
	}

	/**
	 * Converti un index de tableau en coordonnée de case P(colonne, ligne)
	 * 
	 * @param index
	 * @return
	 */
	public Point indexToPoint(int index) {
		return new Point(index / colsCount, index % rowsCount);
	}

	/**
	 * Construit la liste de cases en initialisant leur position et leur bitmap.
	 */
	private void buildCells() {
		// Découper l'image en rows * cols cases
		int cellWidth = fullImage.getWidth() / colsCount;
		int cellHeight = fullImage.getHeight() / rowsCount;
		Bitmap cellImage;
		int cellIndex;
		for (int rowIndex = 0; rowIndex < rowsCount; rowIndex++) {
			for (int colIndex = 0; colIndex < colsCount; colIndex++) {
				// Index de la case dans la liste
				cellIndex = pointToIndex(new Point(colIndex, rowIndex));

				// Découpe de l'image
				cellImage = Bitmap.createBitmap(fullImage,
						colIndex * cellWidth, rowIndex * cellHeight, cellWidth,
						cellHeight);

				cells.add(new Cell(cellIndex, cellImage));
			}
		}
	}

	/**
	 * Définit la case vide du taquin courant. Si random est true alors la case
	 * sera choisie aléatoirement, sinon ce sera la case du coin inférieur
	 * droit.
	 * 
	 * @param useRandomPosition
	 *            true pour une position aléatoire, false sinon.
	 */
	public void setEmptyCell(boolean useRandomPosition) {
		if (useRandomPosition) {
			// Case aléatoire
			emptyCellPos = Util.getRandomBoundInt(0, getBoardSize());
		} else {
			// Case du coin inférieur droit
			emptyCellPos = getBoardSize() - 1;
		}
		getCell(emptyCellPos).setEmpty(true);
	}

	/**
	 * Mélange les cases du taquin.
	 * 
	 * @param minPermutations
	 * @param maxPermutations
	 */
	public void shuffleCells(int minPermutations, int maxPermutations) {
		/*
		 * Note sur les permutations pour assurer que le taquin soit solvable :
		 * - nombre de permutations pair - toujours permuter avec la case vide
		 */

		// Entre 20 et 40 permutations
		int permutations = 2 * Util.getRandomBoundInt(minPermutations,
				maxPermutations);
		int targetPos;
		ArrayList<Integer> neighbors;
		int randomIndex;
		for (int i = 0; i < permutations; i++) {
			// Case cible à permuter : une des cases voisines de la case vide
			neighbors = getNeighbors(emptyCellPos);
			randomIndex = Util.getRandomBoundInt(0, neighbors.size());
			targetPos = neighbors.get(randomIndex);

			// Effectuer la permutation
			swapCells(emptyCellPos, targetPos);
		}

		// XXX
//		System.out.println("# Board shuffled:\n" + this);

		// TODO mémoriser l'ordre des cases pour pouvoir reset
	}
	
	public void shuffleCells2(int minPermutations, int maxPermutations) {
		
		int permutations = 2 * Util.getRandomBoundInt(minPermutations, maxPermutations);
		System.out.println("permuts: " + permutations);
		int sourcePos, targetPos;
		for (int i = 0; i < permutations; i++) {
			// Case source
			sourcePos = Util.getRandomBoundInt(0, getBoardSize());
			
			// Case cible
			do {
				targetPos = Util.getRandomBoundInt(0, getBoardSize());
			} while (targetPos == sourcePos);

			// Effectuer la permutation
			swapCells(sourcePos, targetPos);
		}

		// XXX
//		System.out.println("# Board shuffled:\n" + this);

		// TODO mémoriser l'ordre des cases pour pouvoir reset
	}

	public void resetBoard() {
		// TODO reset board
		// TODO reset la case vide avec son image
	}

	public void resetShuffle() {
		// TODO reset shuffle
	}

	/**
	 * Echange la case source avec la case cible.
	 * 
	 * @param sourcePos
	 *            Position de la case source.
	 * @param targetPos
	 *            Position de la case cible.
	 */
	private void swapCells(int sourcePos, int targetPos) {
		// Actualiser la position de la case vide : la case cible devient la
		// case vide
		emptyCellPos = sourcePos == emptyCellPos ? targetPos : sourcePos;

		// Actualiser la position courante de la case
		getCell(sourcePos).setCurrentPosition(targetPos);
		getCell(targetPos).setCurrentPosition(sourcePos);

		// Echanger position
		Collections.swap(cells, sourcePos, targetPos);

		// XXX
//		System.out.println("# Cells swapped:  " + sourcePos + " <-> "
//				+ targetPos);
//		System.out.println(this);
	}

	/**
	 * Retourne true si la case en question peut être déplacé dans une des
	 * directions, ie. si une de ses cases voisines est libre.
	 * 
	 * @param index
	 *            L'index de la case.
	 * @return true si elle peut bouger, false sinon.
	 */
	public boolean canMove(int index) {
		if (isValidIndex(index))
			return getFreeNeighbor(index) != -1;
		else
			return false;
	}

	/**
	 * Vérifie que la case peut être déplacée, et si c'est le cas déplace la
	 * case vers la (seule) case voisine libre.
	 * 
	 * @param index
	 *            Index de la case à déplacer.
	 * @return true si la case a été déplacée, false sinon.
	 */
	public boolean checkAndMoveCell(int index) {
		if (canMove(index)) {
			swapCells(index, getFreeNeighbor(index));
			return true;
		} else
			return false;
	}

	/**
	 * Retourne la liste des index des cases voisines de la case. Au moins 2,
	 * maximum 4.
	 * 
	 * @param index
	 *            Index de la case dont on veut connaître les voisines.
	 * @return Liste des index des cases voisines.
	 */
	private ArrayList<Integer> getNeighbors(int index) {
		ArrayList<Integer> neighbors = new ArrayList<Integer>();

		int col = index % rowsCount;
		int row = index / colsCount;

		// Calcul des coordonnées des 4 voisins possibles
		Point possibleNeighbors[] = new Point[] {
				// Voisin du haut
				new Point(col, row - 1),
				// Voisin du bas
				new Point(col, row + 1),
				// Voisin gauche
				new Point(col - 1, row),
				// Voisin droit
				new Point(col + 1, row) };

		// Les seuls voisins valides sont ceux aux coordonnées valides
		for (Point p : possibleNeighbors) {
			if (isValidPoint(p)) {
				neighbors.add(pointToIndex(p));
			}
		}

		return neighbors;
	}

	/**
	 * Retourne l'index de la case voisine libre, ou -1 si aucune.
	 * 
	 * @param index
	 *            L'index de la case dont on veut connaitre la voisinne libre.
	 * @return L'index de la case voisine libre, ou -1 si aucune.
	 */
	private int getFreeNeighbor(int index) {
		int freeNeighborIndex = -1;
		for (int neighborIndex : getNeighbors(index)) {
			if (getCell(neighborIndex).isEmpty()) {
				freeNeighborIndex = neighborIndex;
				break;
			}
		}
//		System.out.println("Voisin libre de " + index + ": "
//				+ freeNeighborIndex);
		return freeNeighborIndex;
	}

	/**
	 * Réinitialise le bitmap d'une case en fonction de son index. Utile
	 * notamment si on veut pouvoir changer la case vide (noire).
	 * 
	 * @param index
	 *            Index de la case.
	 */
	private void resetCellImage(int index) {
		// TODO resetCellImage
	}

	@Override
	public String toString() {
		String string = new String();
		string += "Board: size=" + rowsCount + "x" + colsCount + ", "
				+ getBoardSize() + " cells\n";

		// Affichage du taquin sous forme de tableau d'entiers
		Cell cell;
		for (int rowIndex = 0; rowIndex < rowsCount; rowIndex++) {
			for (int colIndex = 0; colIndex < colsCount; colIndex++) {
				cell = getCell(pointToIndex(new Point(colIndex, rowIndex)));
				string += cell.getTargetPosition() + "("
						+ cell.getCurrentPosition() + ")";

				if (cell.isEmpty())
					string += "*";

				string += "\t";
			}
			string += "\n";
		}
		return string;
	}
}
