/*
Copyright 2005-2013 Samuel Gesche

This file is part of ArcEnCiel.

ArcEnCiel is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

ArcEnCiel is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with ArcEnCiel.  If not, see <http://www.gnu.org/licenses/>.
*/

package data;

import ihm.Charte;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
/**
 * Un graphe, comme défini en Leucippe.<br>
 * Cette classe est définie pour un graphe statique, c'est à dire qu'il est
 * possible d'entrer les données concernant le graphe, mais pas de les éditer
 * (sauf par la référence bien sûr) ni de les supprimer.<br>
 *
 * @author Samuel GESCHE
 * @version 3.0
 * @since 3.0.0
 */
public class Graphe {
    private String nom;
    private Map typesNoeuds;
    private Map typesLiens;
    private Map noeuds;
    private Map liens;
    private Vector associations;
    private int tn = -1;
    private int tl = -1;
    private Set nomsNoeuds = new HashSet();

    /**
     * Crée un Graphe vide avec le nom spécifié.
     * @param nom String le nom du graphe
     * @since 3.0.0
     */
    public Graphe(String nom) {
        this.nom = nom;
        typesNoeuds = new HashMap();
        typesLiens = new HashMap();
        noeuds = new HashMap();
        liens = new HashMap();
        associations = new Vector();
    }

    /**
     * Renvoie le nom du graphe (étonnant non ?).
     * @return String le nom du graphe
     * @since 3.0.0
     */
    public String getNom() {
        return nom;
    }

    /**
     * Ajoute un Type de Noeud avec un identifiant connu à ce graphe.
     * @param id int l'identifiant du type de noeud
     * @param type TypeNoeud le TypeNoeud correspondant
     * @since 3.0.0
     */
    public void addTypeNoeud(int id, TypeNoeud type) {
        if (!(typesNoeuds.containsValue(type))) {
            typesNoeuds.put(new Integer(id), type);
        }
    }

    /**
     * Ajoute un Type de Noeud sans identifiant à ce graphe.<br>
     * L'identifiant sera assigné par le Graphe.
     * @param type TypeNoeud le TypeNoeud correspondant
     * @since 3.0.0
     */
    public void addTypeNoeud(TypeNoeud type) {
        addTypeNoeud(tn--, type);
    }

    /**
     * Renvoie le TypeNoeud ayant l'identifiant spécifié.
     * @param id int l'identifiant en question
     * @return TypeNoeud le TypeNoeud correspondant
     * @since 3.0.0
     */
    public TypeNoeud getTypeNoeud(int id) {
        return (TypeNoeud) (typesNoeuds.get(new Integer(id)));
    }

    /**
     * Renvoie l'ensemble des TypeNoeud de ce Graphe.
     * @return TypeNoeud[] l'ensemble en question
     * @since 3.0.0
     */
    public TypeNoeud[] getAllTypesNoeuds() {
        TypeNoeud[] types = new TypeNoeud[typesNoeuds.values().size()];
        typesNoeuds.values().toArray(types);
        return types;
    }

    /**
     * Ajoute un Type de Lien avec un identifiant connu à ce graphe.
     * @param id int l'identifiant du type de lien
     * @param type TypeLien le TypeLien correspondant
     * @since 3.0.0
     */
    public void addTypeLien(int id, TypeLien type) {
        if (!(typesLiens.containsValue(type))) {
            typesLiens.put(new Integer(id), type);
        }
    }

    /**
     * Ajoute un Type de Lien sans identifiant à ce graphe.<br>
     * L'identifiant sera assigné par le Graphe.
     * @param type TypeLien le TypeLien correspondant
     * @since 3.0.0
     */
    public void addTypeLien(TypeLien type) {
        addTypeLien(tl--, type);
    }

    /**
     * Renvoie le TypeLien ayant l'identifiant spécifié.
     * @param id int l'identifiant en question
     * @return TypeLien le TypeLien correspondant
     * @since 3.0.0
     */
    public TypeLien getTypeLien(int id) {
        return (TypeLien) (typesLiens.get(new Integer(id)));
    }

    /**
     * Renvoie l'ensemble des TypeLien de ce Graphe.
     * @return TypeLien[] l'ensemble en question
     * @since 3.0.0
     */
    public TypeLien[] getAllTypesLiens() {
        TypeLien[] types = new TypeLien[typesLiens.values().size()];
        typesLiens.values().toArray(types);
        return types;
    }

    /**
     * Renvoie l'ensemble des propriétés des types de liens de ce Graphe.
     * @return Propriete[] l'ensemble en question
     * @since 3.0.1
     */
    public Propriete[] getAllProprietes() {
        TypeLien[] tl = getAllTypesLiens();
        Set res = new HashSet();
        for (int i = 0; i < tl.length; i++) {
            Propriete[] p = tl[i].getProprietes();
            for (int j = 0; j < p.length; j++) {
                res.add(p[j]);
            }
        }
        Propriete[] resultat = new Propriete[res.size()];
        res.toArray(resultat);
        return resultat;
    }

    /**
     * Ajoute un Noeud avec un identifiant connu à ce graphe.
     * @param id int l'identifiant du noeud
     * @param noeud Noeud le Noeud correspondant
     * @since 3.0.0
     */
    public void addNoeud(int id, Noeud noeud) {
        while (nomsNoeuds.contains(noeud.getLabel())) {
            noeud.declareAmbigu();
        }
        nomsNoeuds.add(noeud.getLabel());
        noeuds.put(new Integer(id), noeud);
        addTypeNoeud(noeud.getType());
    }

    /**
     * Renvoie le Noeud ayant l'identifiant spécifié.
     * @param id int l'identifiant en question
     * @return Noeud le Noeud correspondant
     * @since 3.0.0
     */
    public Noeud getNoeud(int id) {
        return (Noeud) (noeuds.get(new Integer(id)));
    }

    /**
     * Renvoie le Noeud ayant le nom spécifié.
     * @param nom String le nom en question
     * @return Noeud le Noeud correspondant
     * @since 3.0.1
     */
    public Noeud trouveNoeud(String nom) {
        Noeud res = null;
        Noeud[] n = getAllNoeuds();
        for (int i = 0; i < n.length; i++) {
            if (n[i].getLabel().equals(nom)) {
                res = n[i];
                break;
            }
        }
        return res;
    }

    /**
     * Place sur le Noeud dont le nom correspond à celui qui est spécifié un
     * marqueur de désambiguation.
     * @param nom String le nom du noeud
     * @param numero int le marqueur
     * @since 3.0.4
     */
    public void desambigueHomonyme(String nom, int numero) {
        trouveNoeud(nom).setHomonyme(numero);
    }

    /**
     * Enlève sur le Noeud dont le nom correspond à celui qui est spécifié le
     * marqueur de désambiguation.
     * @param nom String le nom du noeud
     * @since 3.0.4
     */
    public void removeDesambiguation(String nom) {
        trouveNoeud(nom).setHomonyme(0);
    }

    /**
     * Renvoie le TypeLien ayant le nom spécifié.
     * @param nom String le nom en question
     * @return TypeLien le TypeLien correspondant
     * @since 3.0.4
     */
    public TypeLien trouveTypeLien(String nom) {
        TypeLien res = null;
        TypeLien[] n = getAllTypesLiens();
        for (int i = 0; i < n.length; i++) {
            if (n[i].getNom().equals(nom)) {
                res = n[i];
                break;
            }
        }
        return res;
    }

    /**
     * Place sur le TypeLien dont le nom correspond à celui qui est spécifié un
     * marqueur de désambiguation.
     * @param nom String le nom du type de lien
     * @param numero int le marqueur
     * @since 3.0.4
     */
    public void desambigueHomonymeTL(String nom, int numero) {
        trouveTypeLien(nom).setHomonyme(numero);
    }

    /**
     * Enlève sur le TypeLien dont le nom correspond à celui qui est spécifié le
     * marqueur de désambiguation.
     * @param nom String le nom du type de lien
     * @since 3.0.4
     */
    public void removeDesambiguationTL(String nom) {
        trouveTypeLien(nom).setHomonyme(0);
    }

    private Propriete[] trouveProprietes(String nom) {
        Vector res = new Vector();
        TypeLien[] n = getAllTypesLiens();
        for (int i = 0; i < n.length; i++) {
            Propriete[] p = n[i].getProprietes();
            for (int j = 0; j < p.length; j++) {
                if (p[j].getNom().equals(nom)) {
                    res.addElement(p[j]);
                    break;
                }
            }
        }
        Propriete[] pp = new Propriete[res.size()];
        res.toArray(pp);
        return pp;
    }

    /**
     * Place sur les Propriete dont le nom correspond à celui qui est spécifié
     * un marqueur de désambiguation.
     * @param nom String le nom de la propriété
     * @param numero int le marqueur
     * @since 3.0.4
     */
    public void desambigueHomonymeP(String nom, int numero) {
        Propriete[] pp = trouveProprietes(nom);
        for (int i = 0; i < pp.length; i++) {
            pp[i].setHomonyme(numero);
        }
    }

    /**
     * Enlève sur les Propriete dont le nom correspond à celui qui est spécifié
     * le marqueur de désambiguation.
     * @param nom String le nom de la propriété
     * @since 3.0.4
     */
    public void removeDesambiguationP(String nom) {
        Propriete[] pp = trouveProprietes(nom);
        for (int i = 0; i < pp.length; i++) {
            pp[i].setHomonyme(0);
        }
    }

    /**
     * Renvoie l'identifiant du Noeud ayant le nom spécifié.
     * @param label String le nom en question
     * @return int l'identifiant du Noeud correspondant
     * @since 3.0.0
     */
    public int getIdNoeud(String label) {
        Noeud[] n = getAllNoeuds();
        int i = 0;
        for (i = 0; i < n.length; i++) {
            if (n[i].getLabel().equals(label)) {
                break;
            }
        }
        return i;
    }

    /**
     * Renvoie l'ensemble des Noeuds de ce Graphe.
     * @return Noeud[] l'ensemble en question
     * @since 3.0.0
     */
    public Noeud[] getAllNoeuds() {
        Noeud[] n = new Noeud[noeuds.values().size()];
        noeuds.values().toArray(n);
        Arrays.sort(n, new ComparateurNoeuds());
        return n;
    }

    /**
     * Ajoute un Lien avec un identifiant connu à ce graphe.
     * @param id int l'identifiant du lien
     * @param lien Lien le Lien correspondant
     * @since 3.0.0
     */
    public void addLien(int id, Lien lien) {
        liens.put(new Integer(id), lien);
        addTypeLien(lien.getType());
    }

    /**
     * Renvoie le Lien ayant l'identifiant spécifié.
     * @param id int l'identifiant en question
     * @return Lien le Lien correspondant
     * @since 3.0.0
     */
    public Lien getLien(int id) {
        return (Lien) (liens.get(new Integer(id)));
    }

    /**
     * Renvoie l'ensemble des Liens de ce Graphe.
     * @return Lien[] l'ensemble en question
     * @since 3.0.0
     */
    public Lien[] getAllLiens() {
        Lien[] l = new Lien[liens.values().size()];
        liens.values().toArray(l);
        return l;
    }

    /**
     * Ajoute une Association à ce Graphe.
     * @param asso Association l'ssociation en question
     * @since 3.0.0
     */
    public void addAssociation(Association asso) {
        associations.add(asso);
    }

    /**
     * Ajoute à ce Graphe une Association entre un Lien et deux Noeuds déjà
     * présents dans ce Graphe.
     * @param idLien int l'identifiant du Lien entre les Noeuds
     * @param idNoeudOrigine int l'identifiant du Noeud origine du Lien
     * @param idNoeudDestination int l'identifiant du Noeud destination du Lien
     * @throws NullPointerException si l'une des entités n'est pas dans le
     * Graphe
     * @since 3.0.0
     */
    public void addAssociation(int idLien, int idNoeudOrigine,
                               int idNoeudDestination) throws
            NullPointerException {
        Lien l = (Lien) (liens.get(new Integer(idLien)));
        Noeud n1 = (Noeud) (noeuds.get(new Integer(idNoeudOrigine)));
        Noeud n2 = (Noeud) (noeuds.get(new Integer(idNoeudDestination)));
        if (l == null) {
            throw new NullPointerException(
                    Charte.getMessage("Graphe_Error_Unk_Link") + " " + idLien);
        }
        if (n1 == null) {
            throw new NullPointerException(
                    Charte.getMessage("Graphe_Error_Unk_Node") + " " +
                                           idNoeudOrigine);
        }
        if (n2 == null) {
            throw new NullPointerException(
                    Charte.getMessage("Graphe_Error_Unk_Node") + " " +
                                           idNoeudDestination);
        }
        addAssociation(new Association(l, n1, n2));
    }

    /**
     * Renvoie l'ensemble des Associations de ce Graphe.
     * @return Association[] l'ensemble en question
     * @since 3.0.0
     */
    public Association[] getAllAssociations() {
        Association[] as = new Association[associations.size()];
        associations.toArray(as);
        return as;
    }

    /**
     * Renvoie toutes les Associations de ce Graphe contenant le Noeud spécifié.
     * @param noeud Noeud le Noeud en question
     * @return Association[] les Associations qui le contiennent
     * @since 3.0.0
     */
    public Association[] getAssociationsContenant(Noeud noeud) {
        Association[] a = getAllAssociations();
        Vector res = new Vector();
        for (int i = 0; i < a.length; i++) {
            if (a[i].getOrigine().equals(noeud) ||
                a[i].getDestination().equals(noeud)) {
                res.add(a[i]);
            }
        }
        Association[] r = new Association[res.size()];
        res.toArray(r);
        return r;
    }

    private String formatage(String s) {
        s = s.toLowerCase();
        s = s.replaceAll("á|à|â|ä|å", "a");
        s = s.replaceAll("ß", "s");
        s = s.replaceAll("ç", "c");
        s = s.replaceAll("é|è|ê|ë", "e");
        s = s.replaceAll("í|ì|î|ï", "i");
        s = s.replaceAll("ñ", "n");
        s = s.replaceAll("ó|ò|ô|ö", "o");
        s = s.replaceAll("ú|ù|û|ü", "u");
        s = s.replaceAll("ÿ", "y");
        s = s.replaceAll("\\W", " ");
        return s;
    }

    /**
     * Renvoie une description textuelle de l'objet.
     * @return String une description textuelle de l'objet
     * @since 4.0.1
     */
    public String toString(){
        return "Graphe :  "+noeuds.keySet().size()+" noeuds, "+
                liens.keySet().size()+" liens, "+associations.size()+
                " associations entre eux.";
    }


    private class ComparateurNoeuds implements java.util.Comparator {
        public int compare(Object o1, Object o2) {
            int a = formatage(((Noeud) (o1)).getLabel()).compareToIgnoreCase(
                    formatage(((Noeud) (o2)).getLabel()));
            return a;
        }
    }
}
