package data;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.*;

/**
 * classe représentant une CDThèque avec 3 systèmes d'indexations :
 * - par numéro d'iSBN
 * - par artiste et titre
 * - par style et titre
 * 
 * @author Matthias Colin
 * @version 2.0 (14/05/2009)
 */

public class CDTheque implements Iterable<CDAudio> , Serializable{


	private static final long serialVersionUID = -1337226385286881627L;

	// la liste ordonnée des CDs
	private List<CDAudio> catalogue;

	// indexations des CDs
    private Map<Long,CDAudio> indexISBN;
    private Map<String,CDAudio> indexArtisteTitre;
    private Map<String,CDAudio> indexStyleTitre;
    
    
	/**
	 * constructeur de CDTheque
	 * initialise la liste et les index
	 */
	public CDTheque(){
    	catalogue = new ArrayList<CDAudio>();
		indexISBN = new HashMap<Long,CDAudio>();
		indexArtisteTitre = new HashMap<String,CDAudio>();
		indexStyleTitre = new HashMap<String,CDAudio>();
	}
   
	/**
 	 * ajout d'un CD dans la CDTheque
	 * @param cd le CD à ajouter
	 * @return true si le CD a bien été ajouté, false s'il y a un 
	 * doublon dans l'indexation ou un problème lors de l'ajout dans la liste
	 */
	public boolean ajouterCD(CDAudio cd) {
		boolean ajoutOK;

		// on vérifie que le CD est correct
		if (cd.getISBN() < 0) {
			// mauvais identifiant
			return false;
		}
		
		// on vérifie que le CD ne vient pas en doublon
		// sur le système de triple indexation
		if (indexISBN.containsKey(cd.getISBN()) 
				|| indexArtisteTitre.containsKey(clefArtisteTitre(cd))
				|| indexStyleTitre.containsKey(clefStyleTitre(cd))) {
			// problème de collision, on refuse l'enregistrement du CD
			return false;
		}

		// ajout du CD dans le catalogue			
		ajoutOK = catalogue.add(cd);
		if (ajoutOK) {
			// indexation du CD
			indexISBN.put(cd.getISBN(), cd);
			indexArtisteTitre.put(clefArtisteTitre(cd), cd);
			indexStyleTitre.put(clefStyleTitre(cd), cd);
		}
		return ajoutOK;
	}
   
	/**
 	 * ajout d'un CD dans la CDTheque à partir de ses propriétés
	 * @param iSBN le numéro ISBN du CD à ajouter
	 * @param artiste l'artiste du CD à ajouter
	 * @param titre le titre du CD à ajouter
	 * @param style le style ISBN du CD à ajouter
	 * @param nbrePistes le nombre de psites du CD à ajouter
	 * @return true si le CD a bien été ajouté, false s'il y a un 
	 * doublon dans l'indexation ou un problème lors de l'ajout dans la liste
	 */
	public boolean ajouterCD(long iSBN, String artiste, String titre, String
			style, int nbrePistes){
		CDAudio cd = new CDAudio(iSBN, artiste, titre, style, nbrePistes);
		return this.ajouterCD(cd);
	}

	/**
	 * suppression d'un CD dans la CDTheque à partir de sa référence
	 * @param cd le CD à supprimer
	 * @return true si le CD était présent dans la CDTheque et a bien été
	 * supprimé
	 */
	public boolean supprimerCD(CDAudio cd) {
		boolean supprimerOK;
		
		supprimerOK = catalogue.remove(cd);
		if (supprimerOK) {
			// le CD était bien présent dans le catalogue
			// donc il est indexé
			indexISBN.remove(cd.getISBN());
			indexArtisteTitre.remove(clefArtisteTitre(cd));
			indexStyleTitre.remove(clefStyleTitre(cd));
		}
		return supprimerOK;
	}
			
	/**
	 * suppression d'un CD dans la CDTheque à partir de son numéro ISBN
	 * @param iSBN le numéro ISBN du CD à supprimer
	 * @return true si le CD était présent dans la CDTheque et a bien été
	 * supprimé
	 */
	public boolean supprimerCD(long iSBN) {
		// suppression du CD en se servant de son indexation par ISBN
		return supprimerCD(iSBN, indexISBN);
	}
		
	/**
	 * suppression d'un CD dans la CDTheque à partir de son artiste
	 * et de son titre
	 * @param artiste l'artiste du CD à supprimer
	 * @param titre le titre du CD à supprimer
	 * @return true si le CD était présent dans la CDTheque et a bien été
	 * supprimé
	 */
	public boolean supprimerCDparArtisteTitre(String artiste, String titre) {
		// suppression du CD en se servant de son indexation par artiste et
		// titre
		return supprimerCD(clef(artiste, titre), indexArtisteTitre);
	}

	/**
	 * suppression d'un CD dans la CDTheque à partir de son style
	 * et de son titre
	 * @param style le style du CD à supprimer
	 * @param titre le titre du CD à supprimer
	 * @return true si le CD était présent dans la CDTheque et a bien été
	 * supprimé
	 */
	public boolean supprimerCDparStyleTitre(String style, String titre) {
		// suppression du CD en se servant de son indexation par style et
		// titre
		return supprimerCD(clef(style, titre), indexStyleTitre);
	}

	/**
	 * recherche d'un CD par son numéro ISBN
	 * @param iSBN le numéro ISBN du CD à rechercher
	 * @return le CD correspondant au numéro ISBN s'il est présent dans la
	 * CDThèque, null sinon
	 */
	public CDAudio rechercherCD(long iSBN) {
		// NB : si le CD n'est pas indexé, renvoie null
		return indexISBN.get(iSBN);
	}

	/**
	 * recherche d'un CD par son artiste et titre
	 * @param artiste l'artiste du CD à rechercher
	 * @param titre le titre du CD à rechercher
	 * @return le CD correspondant aux critères de recherche s'il est présent dans la
	 * CDThèque, null sinon
	 */
	public CDAudio getCDparArtisteTitre(String artiste, String titre) {
		// NB : si le CD n'est pas indexé, renvoie null
		return indexArtisteTitre.get(clef(artiste, titre));
	}
	
	/**
	 * recherche d'un CD par son style et titre
	 * @param style l'artiste du CD à rechercher
	 * @param titre le titre du CD à rechercher
	 * @return le CD correspondant aux critères de recherche s'il est présent 
	 * dans la CDThèque, null sinon
	 */
	public CDAudio getCDparStyleTitre(String style, String titre) {
		// NB : si le CD n'est pas indexé, renvoie null
		return indexStyleTitre.get(clef(style, titre));
	}

	/**
	 * construction d'un itérateur pour parcourir la CDThèque
	 * @return un itérateur pour parcourir la CDThèque
	 */
	public Iterator<CDAudio> iterator() {
		return catalogue.iterator();
	}

	/**
	 * construction d'un itérateur ordonné pour parcourir la CDThèque
	 * @return un itérateur ordonné pour parcourir la CDThèque
	 */
	public ListIterator<CDAudio> listIterator() {
		return catalogue.listIterator();
	}

	/**
	 * sauvegarde d'une CDTheque dans un fichier
	 * TODO
	 * @param nomFichier le nom du fichier contenant la sauvegarde de la
	 * CDThèque
	 */
    public void sauvegarder(String nomFichier){
    
    	File f = new File(nomFichier);
    	try {
			FileOutputStream fos = new FileOutputStream(f);
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			oos.writeObject(this);
			fos.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
   	}

	/**
	 * chargement d'une CDTheque à partir d'un fichier
	 * TODO (pour l'instant chargement en dur)
	 * @param nomFichier le nom du fichier contenant la sauvegarde de la
	 * CDThèque
	 */
  	public static CDTheque charger(String nomFichier) {
  		File f = new File(nomFichier);
    	try {
			FileInputStream fos = new FileInputStream(f);
			ObjectInputStream oos = new ObjectInputStream(fos);

			CDTheque tmp = (CDTheque)oos.readObject();
			fos.close();
			return tmp;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		return null;
	}

	/**
	 * suppression d'un CD à partir d'une clef d'indexation et d'un index
	 * @param clef la clef d'indéxation
	 * @param index l'index dans lequel chercher le CD à partir de la clef
	 * @return true si le CD était présent dans la CDTheque et a bien été
	 * supprimé
	 */
	private <K> boolean supprimerCD(K clef, Map<K,CDAudio> index) {
		CDAudio cdASupprimer;
		if (!index.containsKey(clef)) {
			// Le CD n'est pas référencé
			return false;
		}
		cdASupprimer = index.get(clef);
		return supprimerCD(cdASupprimer);
	}

	/**
	 * fabrication d'une clef normalisée à partir d'un tuple de chaînes
	 * de caractères
	 * @param parties le tuple à partir duquel fabriquer la clef
	 * @return la clef constitués des différentes parties (normalisée en
	 * majuscule) et séparées par le caractère '-'
	 * @throws IllegalArgumentException le tuple doit contenir au moins un
	 * élément
	 */
	private static String clef(String... parties) {
		String clefResultat;

		if (parties.length < 1) {
			// NB : ne doit pas arriver ici
			throw new IllegalArgumentException("clé d'indexation vide");
		}
		clefResultat = parties[0].toUpperCase();
		for (int i = 1; i < parties.length; i++) {
			clefResultat += "-" + parties[i].toUpperCase();
		}
		return clefResultat;
	}

	/**
	 * fabrication d'une clef à partir des propriétés artiste et titre d'un CD
	 * @param cd le CD dont on veut extraire la clef
	 * @return la clef constituée à partir du nom d'artiste et du titre du CD
	 */
	private static String clefArtisteTitre(CDAudio cd) {
		return clef(cd.getArtiste(),cd.getTitre());
	}

	/**
	 * fabrication d'une clef à partir des propriétés style et titre d'un CD
	 * @param cd le CD dont on veut extraire la clef
	 * @return la clef constituée à partir du style et du titre du CD
	 */
	private static String clefStyleTitre(CDAudio cd) {
		return clef(cd.getStyle(),cd.getTitre());
	}


//	/**
//	 * Pour debugger une représentation complète de la CDTheque 
//	 * (CDs + 3 index)
//	 */
//	public String toString() {
//		String res = "Les CDS : \n";
//		for (CDAudio cd : catalogue) {
//			res += cd + "\n";
//		}
//		res += indexToString(indexISBN, " Index ISBN ");
//		res += indexToString(indexArtisteTitre, " Index Artiste/Titre ");
//		res += indexToString(indexStyleTitre, " Index Style/Titre ");
//		return res;
//	}
//
//	/**
//	 * Pour debugger, représentation textuelle d'un index sur les CD Audios
//	 */
//	private <K> String indexToString(Map<K,CDAudio> index, String message) {
//		String res = message + "\n";
//		for (K key : index.keySet()) {
//			res += key + " : " + index.get(key) + "\n";
//		}
//		return res;
//	}
}
