package sudoku.structure;

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

import sudoku.io.SudokuSerializer;

/**
 * @class MultiDoku
 * @brief Représente une grille de Multidoku.
 *        Une grille de sudoku est un multidoku avec un seul sous-sudoku
 */
public class MultiDoku {

	/**
	 * Liste des sous-Sudoku contenue dans le multidoku.
	 */
	private final List<Sudoku> subGrids;

	private final StateManager stateManager;

	public MultiDoku(List<Sudoku> subGrids) {
		this.subGrids = subGrids;
		this.stateManager = new StateManager(this);
	}

	/**
	 * Renvoie le nombre de sudoku contenu dans ce MultiDoku.
	 * 
	 * @return int
	 */
	public int getNbSubGrids() {
		return subGrids.size();
	}

	/**
	 * Renvoie la ie sudoku contenue dans ce MultiDoku.
	 * 
	 * @param i int, indice du sudoku à renvoyer.
	 * @return Sudoku, ie Sudoku
	 */
	public Sudoku getSubGrid(int i) {
		return subGrids.get(i);
	}

	/**
	 * Renvoie la liste des Cells contenue dans ce MultiDoku,
	 * soit les Cells contenues de chaques sous-Sudoku.
	 * 
	 * @return List<Cell>
	 */
	public List<Cell> getCells() {
		Set<Cell> cellsSet = new HashSet<>();
		for (Sudoku sudoku : subGrids) {
			cellsSet.addAll(sudoku.getCells());
		}
		return new ArrayList<>(cellsSet);
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("Multidoku {");
		for (Sudoku sudoku : subGrids) {
			sb.append("\n\t").append(sudoku.toString());
		}
		sb.append("\n}");
		return sb.toString();
	}

	/**
	 * Renvoie les sous-Sudoku
	 * 
	 * @return List<Sudoku>
	 */
	public List<Sudoku> getSubGrids() {
		return this.subGrids;
	}

	/**
	 * Check si le MultiDoku est résolu, c'est à dire complet et cohérent avec ses contraintes. 
	 * 
	 * @return boolean, true s'il est résolu et false sinon.
	 */
	public boolean isSolved() {
		for (Sudoku sudoku : this.subGrids) {
			if (!sudoku.isSolved())
				return false;
		}
		return true;
	}

	/**
	 * Renvoie la 1re Cell vide des sous-Sudoku.
	 * 
	 * @return Cell, une Cell vide, ou null s'il n'y en a pas.
	 */
	public Cell getFirstEmptyCell() {
		for (Sudoku sudoku : this.subGrids) {
			Cell cellTmp = sudoku.getFirstEmptyCell();
			if (cellTmp != null) {
				return cellTmp;
			}
		}
		return null;
	}

	/**
	 * Renvoie la liste des Cells préalablement remplies du MultiDoku.
	 * 
	 * @return List<Cell>, vide si aucune Cell n'est remplie.
	 */
	public List<Cell> getFilledCells() {
		List<Cell> result = new ArrayList<>();
		for (Cell cell : this.getCells()) {
			if (!cell.isEmpty()) {
				result.add(cell);
			}
		}
		return result;
	}

	/**
	 * Renvoie la liste des Cells vides du MultiDoku.
	 * 
	 * @return List<Cell>, vide si aucune Cell ne l'est.
	 */
	public List<Cell> getEmptyCells() {
		List<Cell> result = new ArrayList<>();
		for (Cell cell : this.getCells()) {
			if (cell.isEmpty()) {
				result.add(cell);
			}
		}
		return result;
	}

	/**
	 * Vide une Cell donnée.
	 * 
	 * @param cell Cell, à vider.
	 */
	public void empty(Cell cell) {
		List<Cell> cells = getCells();
		Cell cellToEmpty = cells.get(cells.indexOf(cell));
		cellToEmpty.setSymbolIndex(Cell.NOSYMBOL);
	}

	/**
	 * Renvoie le nombre de Cell contenue dans le MultiDoku.
	 * 
	 * @return int, nombre de Cell dans le MultiDoku.
	 */
	public int getNbCells() {
		return getCells().size();
	}

	/**
	 * Change les Cells de ce MultiDoku avec des symboles, en Cells immuables.
	 */
	public void setFilledCellsImmutable() {
		for (Cell filledCell : getFilledCells()) {
			filledCell.setImmutable();
		}
	}

	public StateManager getStateManager() {
		return stateManager;
	}

	/**
	 * Renvoie une Cell vide choisie aléatoirement.
	 *
	 * @param rand Random, pour le choix aléatoire.
	 * @return Cell, une Cell vide.
	 */
	public Cell getRandomEmptyCell(Random rand) {
		List<Cell> emptyCells = getEmptyCells();
		int randomIndex = rand.nextInt(emptyCells.size());
		return emptyCells.get(randomIndex);
	}

	public void clearMutableCells() {
		for (Sudoku s : getSubGrids()) {
			for (Cell cell : s.getCells()) {
				if (cell.isMutable())
					cell.clearCurrentSymbol();
			}
		}
	}

	public MultiDoku clone() {
		// TODO: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah
		return SudokuSerializer.deserializeSudoku(SudokuSerializer.serializeSudoku(this));
	}
}
