package carte;
/**
 * classes pour un Jeu de 32 cartes et ses différents itérateurs
 *
 * @author Matthias Colin
 * @version 1.0 (31/05/2011)
 */

import java.util.*;

/**
 * Iterateur aléatoire pour un jeu de Carte
 * dépendant de la vue interne d'un jeu sous la forme d'une liste.
 * Positions des éléments à parcourir gérées par un tableau
 */
class ShuffleIterator1 implements Iterator<Carte> {
	// les cartes à parcourir (vue interne du jeu)
	private List<Carte> jeu;
	
	// mémorisation de la position courante dans le parcours
	// varie de 0 à taille-1 pendant le parcours
	// initialisé à -1 avant l'accès au 1er élémént
	private int indexParcours;
	
	// permutation aléatoire faisant la correspondance entre
	// l'index ci-dessus et la position de l'élément à retourner
	private Integer[] permutationAleatoire;
	
	/**
	 * constructeur de l'itérateur aléatoire
	 * - fait le lien avec le jeu à parcourir
	 * - tire au sort le futur ordre de parcours
	 * - initialise l'index du parcours
	 * package private access
	 * @param jeu liste des cartes d'un jeu
	 */
	ShuffleIterator1(List<Carte> jeu) {
		this.jeu = jeu;
		// tirage au sort d'une pemrutation aléatoire des positions à parcourir
		List<Integer> positions = new LinkedList<Integer>();
		for (int i = 0 ; i < jeu.size() ; i++) {
			positions.add(i);
		}
		// on mélange les entiers de 0 à taille-1 pour obtenir la permutation aléatoire
		Collections.shuffle(positions);
		permutationAleatoire = positions.toArray(new Integer[0]);
		// on positionne le parcours juste avant la 1ère carte
		indexParcours = -1;
	}
	
	/**
	 * @return vrai s'il reste des éléments à parcourir dans le jeu
	 */
	@Override
	public boolean hasNext() {
		return (indexParcours != (jeu.size() - 1));
	}
	
	/**
	 * @return la carte suivante dans le parcours
	 * @throws NoSuchElementException si le parcours est déjà fini
	 */
	@Override
	public Carte next() {
		if (indexParcours == (jeu.size() - 1)) {
			throw new NoSuchElementException("Plus de carte à parcourir");
		}
		indexParcours++;
		return jeu.get(permutationAleatoire[indexParcours]);
	}
	
	/**
	 * n'est pas implémentée pour cet itérateur
	 * @throws UnsupportedOperationException (always)
	 **/
	public void remove() {
		throw new UnsupportedOperationException("Le ShuffleIterator ne supporte pas l'opération de suppression");
	}
}

/**
 * Iterateur aléatoire pour un jeu de Carte
 * dépendant de la vue interne d'un jeu sous la forme d'une liste.
 * Positions des éléments à parcourir gérées par une pile.
 */
class ShuffleIterator2 implements Iterator<Carte> {
	// les cartes à parcourir (vue interne du jeu)
	private List<Carte> jeu;
	
	// permutation aléatoire des entiers de 0 taille(jeu) -1
	// faisant la correspondance avec la position de la carte
	// à retourner du jeu d'origine
	// sous la forme d'une pile, pour consommer les positions
	// au fur et à mesure
	private Deque<Integer> permutationAleatoire;
	
	/**
	 * constructeur de l'itérateur aléatoire
	 * - fait le lien avec le jeu à parcourir
	 * - tire au sort le futur ordre de parcours
	 * package private access
	 * @param jeu liste des cartes d'un jeu
	 */
	//ShuffleIterator2(List<Carte> jeu) {
	//	this.jeu = jeu;
	//	// tirage au sort d'une permutation aléatoire des positions à parcourir
	//	LinkedList<Integer> positions = new LinkedList<Integer>();
	//	for (int i = 0 ; i < jeu.size() ; i++) {
	//		positions.add(i);
	//	}
	//	// on mélange les entiers de 0 à taille-1 pour obtenir la permutation aléatoire
	//	Collections.shuffle(positions);
	//	// on place la permutation dans une pile
	//	permutationAleatoire = positions;
	//}
	
	// constructeur indépendant des choix d'implémentation de List/Deque
	ShuffleIterator2(List<Carte> jeu) {
		this.jeu = jeu;
		// tirage au sort d'une permutation aléatoire des positions à parcourir
		List<Integer> positions = new ArrayList<Integer>();
		for (int i = 0 ; i < jeu.size() ; i++) {
			positions.add(i);
		}
		// on mélange les entiers de 0 à taille-1 pour obtenir la permutation aléatoire
		Collections.shuffle(positions);
		// on place la permutation dans une pile
		permutationAleatoire = new ArrayDeque<Integer>(positions);
	}
	
	/**
	 * @return vrai s'il reste des éléments à parcourir dans le jeu
	 */
	@Override
	public boolean hasNext() {
		return (!permutationAleatoire.isEmpty());
	}
	
	/**
	 * @return la carte suivante dans le parcours
	 * @throws NoSuchElementException si le parcours est déjà fini
	 */
	@Override
	public Carte next() {
		if (permutationAleatoire.isEmpty()) {
			throw new NoSuchElementException("Plus de carte à parcourir");
		}
		// on consomme une position dans la permutation et on revoie la carte correspondante du jeu
		return jeu.get(permutationAleatoire.pollFirst());
	}
	
	/**
	 * n'est pas implémentée pour cet itérateur
	 * @throws UnsupportedOperationException (always)
	 **/
	public void remove() {
		throw new UnsupportedOperationException("Le ShuffleIterator ne supporte pas l'opération de suppression");
	}
}

/**
 * classe Jeu représentant un jeu de 32 cartes classiques
 */
public class Jeu implements Iterable<Carte> {

	// vue interne des cartes pour gérer le paquet de cartes
	// accès uniquement par le dessus ou le dessous
	private Deque<Carte> paquet;
	
	// vue interne des cartes pour gérer un parcours personnalisé des cartes (nécessité de connaître le nième élément)
	// ou pour mélanger le jeu
	private List<Carte> cartes;
	
	// NB : les 2 vues ci-dessus référencent la même collection
	
	/**
	 * constructeur d'un jeu neuf de 32 cartes avec cartes mélangées
	 */
	public Jeu() {
		// création des 32 cartes
		// NB : on peut remplacer la LinkedList par une autre collection
		// qui implémente les 2 interfaces List et Deque
		LinkedList<Carte> tmp32cartes = new LinkedList<Carte>();
		for (Famille famille : Famille.values()) {
			for (Valeur valeur : Valeur.values()) {
				tmp32cartes.add(new Carte(valeur, famille));
			}
		}
		// partage des références pour les 2 vues
		paquet = tmp32cartes;
		cartes = tmp32cartes;
		// mélange du jeu
		this.melanger();
	}
	
	/**
	 * retourne la taille du jeu (en nombre de cartes)
	 * @return le nombre de cartes du jeu
	 */
	public int taille() {
		return paquet.size();
	}
	
	/**
	 * Iterateur : parcours le jeu du sommet du paquet vers le bas
	 * @return l'itérateur
	 */
	public Iterator<Carte> iterator() {
		return paquet.iterator();
	}
	
	/**
	 * Iterateur : parcours le jeu du dessous du paquet vers le haut
	 * @return l'itérateur
	 */
	public Iterator<Carte> descendingIterator() {
		return paquet.descendingIterator();
	}
	
	/**
	 * Iterateur : parcours le jeu de manière aléatoire
	 * @return l'itérateur
	 */
	public Iterator<Carte> shuffleIterator() {
		// return new ShuffleIterator1(this.cartes);
		return new ShuffleIterator2(this.cartes);
	}
	
	/**
	 * distribuer nombreCartes à la main d'un joueur
	 * @param main main receptrice des cartes à distribuer
	 * @param nombreCartes nombre de cartes à distribuer
	 * @throws IllegalArgumentException si la main n'est pas valide (null)
	 * ou s'il n'y a pas assez de cartes à distribuer
	 */
	public void distribuer(Main main, int nombreCartes) {
		if (main == null) {
			throw new IllegalArgumentException("main invalide");
		}
		if (nombreCartes > this.taille()) {
			throw new IllegalArgumentException(
				"Le jeu contient " + this.taille()
				+ ". Impossible de distribuer" + nombreCartes
				+ " cartes.");
		}
		for (int i = 0 ; i < nombreCartes ; i++) {
			main.donner(paquet.pollFirst());
		}
	}
	
	/**
	 * retourner la carte au sommet du paquet
	 * @return la carte du sommet du paquet (enlevée de ce dernier)
	 * @throws IllegalArgumentException si le paquet de cartes est vide
	 */
	public Carte retournerCarte() {
		if (paquet.isEmpty()) {
			throw new IllegalArgumentException("paquet de cartes vide");
		}
		return (paquet.pollFirst());
	}
	
	/**
	 * remettre une carte au sommet du paquet
	 * @param carte la carte à ajouter
	 */
	public void remettreCarteDessus(Carte carte) {
		paquet.offerFirst(carte);
	}
	
	/**
	 * remettre une carte en dessous du paquet
	 * @param carte la carte à ajouter
	 */
	public void remettreCarteDessous(Carte carte) {
		paquet.offerLast(carte);
	}
	
	/**
	 * coupe le jeu
	 * consiste à faire passer n (paramètre coupe) cartes du dessus du paquet en dessous
	 * @param coupe nombre de cartes à faire passer sous le paquet
	 * @throws IllegalArgumentException si la coupe ne prend pas au moins une carte (d'un des 2 côtés)
	 */
	public void couper(int coupe) {
		if ((coupe < 1) || (coupe >= paquet.size())) {
			throw new IllegalArgumentException("La coupe doit prendre au moins une carte");
		}
		// on enlève les cartes du haut pour les remettre en dessous
		for (int i=0; i<coupe; i++) {
			paquet.offerLast(paquet.pollFirst());
		}
	}
	
	/**
	 * mélanger le jeu
	 */
	public void melanger() {
		Collections.shuffle(cartes);
	}
	
}
