/*
Copyright 2012-2014 Samuel Gesche

This file is part of the Greek Reuse Toolkit.

The Greek Reuse Toolkit 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.

The Greek Reuse Toolkit 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 the Greek Reuse Toolkit.  If not, see <http://www.gnu.org/licenses/>.
*/

package fr.cnrs.liris.drim.grt.modele;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import fr.cnrs.liris.drim.grt.modele.exceptions.ListeStatistiqueNonInitialiseeException;
import fr.cnrs.liris.drim.grt.modele.listes.LemmesStatistiquementVides;
import fr.cnrs.liris.drim.grt.modele.listes.LemmesVides;
import fr.cnrs.liris.drim.grt.proc.Texte;
import fr.cnrs.liris.drim.grt.proc.parsers.Textes;

/**
 * La classe Passage encapsule toute notion de passage comme contenant de 
 * terme ou (exclusif) d'autres passages.
 * 
 * La structure en passages d'un document est une structure hiérarchique : un passage
 * peut contenir zéro, un ou plusieurs autres passages, et faire partie d'autant.
 * 
 * Un Passage peut être un document (il a un ou plusieurs auteurs, mais pas de 
 * passage parent), un mot (il a un contenu, mais pas de sousè-passage), ou un 
 * passage quelconque (sans contenu, et sans auteur).
 * 
 * @author sgesche
 */
public class Passage {
    private ArrayList<Personne> auteurs;
    private Map<Coordonnee, Passage> souspassages;
    private Terme contenu = null;
    private String titre;
    private ArrayList<Passage> parents;
    
    private Map<Passage, Passage> indexMotsSuivants = new HashMap<>();
    private Map<Passage, Passage> indexMotsPrecedents = new HashMap<>();
    private Map<Passage, Passage> indexMotsSuivantsSansMotsVides = new HashMap<>();
    private Map<Passage, Passage> indexMotsPrecedentsSansMotsVides = new HashMap<>();
    private Map<Passage, Passage> indexMotsSuivantsSansMotsStatistiquementVides = new HashMap<>();
    private Map<Passage, Passage> indexMotsPrecedentsSansMotsStatistiquementVides = new HashMap<>();
    
    
    /**
     * Crée un Passage de manière générique. Prévu pour un processus qui ne sait 
     * pas a priori ce que contiennent réellement les paramètres. La présence de 
     * sous-passages (tableau non vide) écrase le contenu.
     * @param parents les passages qui contiennent ce passage
     * @param titre le titre
     * @param contenu le contenu textuel
     * @param sousPassages les sous-divisions
     */
    private Passage(String titre, Passage[] parents, Terme contenu, Map<Coordonnee, Passage> sousPassages, Personne[] auteurs) {
        this.titre = titre;
        this.souspassages = new HashMap<>();
        this.auteurs = new ArrayList<>();
        this.parents = new ArrayList<>();
        // les sous-passages sont prioritaires et écrasent le contenu
        setContenu(contenu);
        addSousPassages(sousPassages);
        // les parents sont prioritaires et écrasent les auteurs
        setAuteurs(auteurs);
        setParents(parents);
    }
    
    /**
     * Crée un Passage textuel à partir d'un texte brut. Le texte sera découpé et 
     * divisé en Termes, stockés dans des Passages mots inclus dans le résultat.
     * 
     * Ce type de passage est un passage quelconque, et donc sous-passage d'au 
     * moins un autre passage.
     * 
     * Le titre des mots sera leur index (en partant de 1) dans le texte.
     * 
     * @param parents les passages qui contiennent ce passage
     * @param titre le titre
     * @param texte le texte brut
     */
    public Passage(String titre, Passage[] parents, String texte) {
        this(titre, parents, Terme.cree(""), new HashMap<Coordonnee, Passage>(), new Personne[]{});
        Terme[] termes = Textes.decoupeEnTermes(texte);
        Map<Coordonnee, Passage> mots0 = new HashMap<>();
        for(int i=0 ; i<termes.length; i++) {
            Terme t = termes[i];
            mots0.put(new Coordonnee(OrdreStrict.getInstance(OrdreStrict.MOTS), ""+(i+1)), 
                     new Passage("", new Passage[]{this}, t));
        }
        
        this.titre = titre;
        this.souspassages = new HashMap<>();
        this.auteurs = new ArrayList<>();
        this.parents = new ArrayList<>();
        addSousPassages(mots0);
        setParents(parents);
    }
    
    /**
     * Crée un Passage textuel à partir de Termes. Ces Termes seront stockés 
     * dans des Passages mots inclus dans le résultat. 
     * 
     * Ce type de passage est un passage quelconque, et donc sous-passage d'au 
     * moins un autre passage.
     * 
     * Le titre des mots sera leur index (en partant de 1) dans le texte.
     * 
     * @param parents les passages qui contiennent ce passage
     * @param titre le titre
     * @param termes les termes du passage
     */
    public Passage(String titre, Passage[] parents, Terme[] termes) {
        this(titre, parents, Terme.cree(""), new HashMap<Coordonnee, Passage>(), new Personne[]{});
        
        Map<Coordonnee, Passage> mots0 = new HashMap<>();
        for(int i=0 ; i<termes.length; i++) {
            Terme t = termes[i];
            mots0.put(new Coordonnee(OrdreStrict.getInstance(OrdreStrict.MOTS), ""+(i+1)), 
                     new Passage("", new Passage[]{this}, t));
        }
        
        this.titre = titre;
        this.souspassages = new HashMap<>();
        this.auteurs = new ArrayList<>();
        this.parents = new ArrayList<>();
        addSousPassages(mots0);
    }
    
    /**
     * Crée un Passage de type mot à partir d'un Terme.
     * 
     * Ce type de passage est un mot, donc sous-passage d'au moins un autre passage.
     * 
     * @param parents les passages qui contiennent ce passage
     * @param titre le titre
     * @param contenu le terme utilisé par le mot
     */
    public Passage(String titre, Passage[] parents, Terme contenu) {
        this(titre, parents, contenu, new HashMap<Coordonnee, Passage>(), new Personne[]{});
    }
    
    /**
     * Crée un Passage de type mot à partir d'un Terme.
     * 
     * Ce type de passage est un mot, donc sous-passage d'au moins un autre passage.
     * 
     * @param parents les passages qui contiennent ce passage
     * @param contenu le terme utilisé par le mot
     * @return un Passage contenant le Termes
     */
    public static Passage creeMot(Passage[] parents, Terme contenu) {
        return new Passage("", parents, contenu);
    }
    
    /**
     * Crée un Passage contenant des mots qui à leur tour contiennent les Termes
     * proposés.
     * Ce Passage et ses mots sont complètement indépendants de tout autre contexte.
     * @param mots des Termes à mettre ensemble
     * @return un Passage contenant les Termes
     */
    public static Passage creeBlocDeMotsIndependants(Terme[] mots) {
        Passage p = new Passage("", new Passage[]{}, new HashMap<Coordonnee, Passage>());
        for(int i=0; i<mots.length; i++) {
            p.addSousPassage(
                    new Coordonnee(OrdreStrict.getInstance(OrdreStrict.NUMERIQUE_ARABE), i),
                    creeMot(new Passage[]{p}, mots[i]));
        }
        return p;
    }
    
    /**
     * Crée un Passage structurel à partir de sous-passages pré-existants.
     * Ce type de passage est sous-passage d'un autre passage.
     * @param parents les passages qui contiennent ce passage
     * @param titre le titre
     * @param sousPassages les divisions du Passage
     */
    public Passage(String titre, Passage[] parents, Map<Coordonnee, Passage> sousPassages) {
        this(titre, parents, Terme.cree(""), sousPassages, new Personne[]{});
    }
    
    /**
     * Crée un Passage de type document avec un titre et des auteurs.
     * 
     * Ce type de passage ne peut pas avoir de parent.
     * 
     * @param titre le titre
     * @param sousPassages les divisions du Passage
     * @param auteurs les auteurs
     */
    public Passage(String titre, Map<Coordonnee, Passage> sousPassages, Personne[] auteurs) {
        this(titre, new Passage[]{}, Terme.cree(""), sousPassages, auteurs);
    }
    
    /**
     * Crée un Passage de type document avec un titre et des auteurs.
     * 
     * Ce type de passage ne peut pas avoir de parent.
     * 
     * @param titre le titre
     * @param sousPassages les divisions du Passage
     * @param auteurs les auteurs
     */
    public static Passage creeDocument(String titre, Map<Coordonnee, Passage> sousPassages, Personne[] auteurs) {
        return new Passage(titre, sousPassages, auteurs);
    }
    
    /**
     * Crée un simple Passage contenant les Passages proposés. 
     * 
     * Ce Passage est un document avec une liste d'auteurs vide.
     * 
     * @param sousPassages un groupe de passages
     */
    public static Passage creeCollectionDePassages(Passage[] sousPassages) {
        Map<Coordonnee, Passage> sousPassages2 = new HashMap<>();
        for(int i=0; i<sousPassages.length; i++) {
            sousPassages2.put(new Coordonnee(OrdreStrict.getInstance(OrdreStrict.NUMERIQUE_ARABE), i), sousPassages[i]);
        }
        return creeDocument("", sousPassages2, new Personne[]{});
    }
    
    /**
     * Crée un Passage de type document avec un titre uniquement.
     * 
     * Ce type de passage ne peut pas avoir de parent.
     * 
     * @param titre le titre
     */
    public static Passage creeDocument(String titre) {
        return new Passage(titre, new HashMap<Coordonnee, Passage>(), new Personne[]{});
    }
    
    /**
     * Crée un Passage vide.
     * Attention à remplir le Passage après : un Passage ne doit pas être vide.
     */
    private Passage() {
        this("", new Passage[]{}, Terme.cree(""), new HashMap<Coordonnee, Passage>(), new Personne[]{});
    }
    
    
    /**
     * Dit si oui ou non ce Passage est de type mot.
     * 
     * Un passage de type mot a un Terme contenu (et pas de sous-passage).
     * 
     * @return 
     */
    public boolean estUnMot() {
        return contenu != null;
    }
    
    /**
     * Dit si oui ou non ce Passage est de type document.
     * 
     * Un passage de type document a des auteurs (et pas de passage parent).
     * 
     * @return oui ou non
     */
    public boolean estUnDocument() {
        return auteurs.size() > 0;
    }
    
    /**
     * Dit si oui ou non ce Passage fait partie d'un autre passage.
     * @return oui ou non
     */
    public boolean hasParents() {
        return parents.size()>0;
    }
    
    /**
     * Donne les passages parents de ce passage.
     * @return lesdits passages parents
     */
    public Passage[] getParents() {
        Passage[] res = new Passage[parents.size()];
        parents.toArray(res);
        return res;
    }
    
    /**
     * Change les passages parents de ce passage.
     * 
     * Ecrase les auteurs, puisqu'un document n'a pas de passage parent. Sous 
     * réserve que la liste ne soit pas vide.
     * 
     * Une liste vide videra la liste des parents sans toucher aux auteurs.
     * 
     * @param liste les nouveaux parents
     */
    public final void setParents(Passage[] liste) {
        parents.clear();
        if(liste.length==0) {return;}
        auteurs.clear();
        parents.addAll(Arrays.asList(liste));
    }
    
    /**
     * Ajoute un passage parent à ce passage.
     * 
     * Ecrase les auteurs, puisqu'un document n'a pas de passage parent. Sous 
     * réserve que le parent ne soit pas null.
     * 
     * @param nouveauParent le nouveau parent
     */
    public void addParent(Passage nouveauParent) {
        if(nouveauParent != null) {
            parents.add(nouveauParent);
            auteurs.clear();
        }
    }
    
    /**
     * Donne les auteurs de ce passage.
     * @return lesdits auteurs
     */
    public Personne[] getAuteurs() {
        Personne[] res = new Personne[auteurs.size()];
        auteurs.toArray(res);
        return res;
    }
    
    /**
     * Change les auteurs de ce passage.
     * 
     * Ecrase les passages parents, puisqu'un document n'a pas de passage parent. 
     * Sous réserve que la liste ne soit pas vide.
     * 
     * @param liste les nouveaux auteurs
     */
    public final void setAuteurs(Personne[] liste) {
        auteurs.clear();
        if(liste.length==0) {return;}
        parents.clear();
        auteurs.addAll(Arrays.asList(liste));
    }
    
    /**
     * Donne le titre de ce passage.
     * @return ledit titre
     */
    public String getTitre() {
        return titre;
    }
    
    /**
     * Change le titre de ce passage.
     * @param titre le nouveau titre
     */
    public void setTitre(String titre) {
        this.titre = titre;
    }
    
    /**
     * Donne le numéro de ce passage.
     * (ex : chapitre 1)
     * 
     * Le numéro est celui chez le premier parent, si parent il y a.
     * 
     * @return ledit numéro
     */
    public String getNumero() {
        if(!hasParents()) {
            return titre;
        }
        Coordonnee resultat = getParents()[0].getCoordonnee(this);
        if(resultat == null) {
            return "num introuvable";
        }
        return resultat.getSysteme().toString() + " " + resultat.getExpression();
    }
    
    /**
     * Donne le numéro de ce passage en tenant compte de tous ses sur-passages.
     * (ex : chapitre 1 paragraphe 3 ligne 6)
     * 
     * @return ledit numéro
     */
    public String getNumeroComplet() {
        String resultat = "";
        Passage p = this;
        while(p.hasParents()) {
            resultat = p.getNumero() + " " + resultat;
            p = p.getParents()[0];
        }
        return resultat;
    }
    
    /**
     * Donne les numéros du premier et du dernier sous-passage de ce passage
     * (ex : chapitre 1 paragraphe 3 ligne 6 - chapitre 1 paragraphe 3 ligne 9).
     * Ne présage aucunement d'une continuité à l'intérieur de l'intervalle fourni.
     * 
     * @return lesdits numéros
     */
    public String getNumerosCompletsSousPassages() {
        if(getNbSousPassages() == 0) {
            return "";
        } else {
            return getSousPassages()[0].getNumeroComplet() + " - " + 
                    getSousPassages()[getSousPassages().length-1].getNumeroComplet();
        }
    }
    
    /**
     * Indique si oui ou non ce Passage a un contenu.
     * 
     * @see estUnMot()
     * 
     * @return oui ou non
     */
    public boolean hasContenu() {
        return estUnMot();
    }
    
    /**
     * Donne le Terme qui constitue ce Passage, le cas échéant.
     * @return un terme (null s'il n'y en a pas)
     */
    public Terme getContenu() {
        return contenu;
    }
    
    /**
     * Définit le contenu textuel de ce Passage. Supprime tout sous-passage 
     * pouvant exister dans ce Passage si le terme n'est pas vide.
     * 
     * Si le terme proposé est vide ou null, le contenu est supprimé.
     * @param termes un Terme
     */
    public final void setContenu(Terme terme) {
        if(terme == null) {
            contenu = null;
            return;
        }
        if(!terme.getExpression().isEmpty()) {
            souspassages.clear();
            contenu = terme;
        } else {
            contenu = null;
        }
    }
    
    /**
     * Donne le nombre de sous-passages de ce Passage.
     * @return le nombre en question
     */
    public int getNbSousPassages() {
        return souspassages.size();
    }
    
    /**
     * Remplace les sous-passages existants par ceux proposés.
     * @param sousPassages les nouveaux sous-passages
     */
    public void setSousPassages(Map<Coordonnee, Passage> sousPassages) {
        souspassages.clear();
        addSousPassages(sousPassages);
    }
    
    /**
     * Donne les sous-passages inclus dans ce Passage. La liste est triée par 
     * coordonnées.
     * 
     * @return un tableau ordonné de Passages
     */
    public Passage[] getSousPassages() {
        ArrayList<Passage> resultat = new ArrayList<>();
        Set<Coordonnee> liste = souspassages.keySet();
            if(liste.size()>0) {
                Coordonnee[] coo = new Coordonnee[liste.size()];
                liste.toArray(coo);
                coo = coo[0].getSysteme().getListeOrdonnee(liste);
                for(Coordonnee c: coo) {
                    resultat.add(souspassages.get(c));
                }
            }
        Passage[] res = new Passage[resultat.size()];
        resultat.toArray(res);
        return res;
    }
    
    private Coordonnee sousCoordonneeMaximale;
    /**
     * Donne la coordonnée la plus élevée des sous-passages de ce passage.
     * @return ladite coordonnee (null en l'absence de sous-passages)
     */
    public Coordonnee getSousCoordonneeMaximale() {
        if(getNbSousPassages() == 0){
            return null;
        }
        if(sousCoordonneeMaximale == null) {
            Passage[] sp = getSousPassages();
            Coordonnee maximale = null;
            for(Passage p: sp) {
                if(maximale == null) {
                    maximale = getCoordonnee(p);
                } else if(maximale.getDistance(getCoordonnee(p)) > 0) {
                    maximale = getCoordonnee(p);
                }
            }
            sousCoordonneeMaximale = maximale;
        }
        return sousCoordonneeMaximale;
    }
    
    private Coordonnee sousCoordonneeMinimale;
    /**
     * Donne la coordonnée la moins élevée des sous-passages de ce passage.
     * @return ladite coordonnee (null en l'absence de sous-passages)
     */
    public Coordonnee getSousCoordonneeMinimale() {
        if(getNbSousPassages() == 0){
            return null;
        }
        if(sousCoordonneeMinimale == null) {
            Passage[] sp = getSousPassages();
            Coordonnee minimale = null;
            for(Passage p: sp) {
                if(minimale == null) {
                    minimale = getCoordonnee(p);
                } else if(minimale.getDistance(getCoordonnee(p)) < 0) {
                    minimale = getCoordonnee(p);
                }
            }
            sousCoordonneeMinimale = minimale;
        }
        return sousCoordonneeMinimale;
    }
    
    public Map<Coordonnee, Passage> getSousPassagesAvecCoordonnees() {
        return souspassages;
    }
    
    /**
     * Donne la Coordonnee du sous-passage demandé dans ce passage.
     * 
     * Le sous-passage doit se trouver directement dans le passage (pas de 
     * recherche en profondeur).
     * 
     * @param sousPassage une sous-passage à chercher
     * @return la coordonnée trouvée (null si le sous-passage n'est pas trouvé)
     */
    public Coordonnee getCoordonnee(Passage sousPassage) {
        Coordonnee resultat = null;
        for(Coordonnee prospect: souspassages.keySet()) {
            if(souspassages.get(prospect).equals(sousPassage)) {
                resultat = prospect;
                break;
            }
        }
        return resultat;
    }
    
    /**
     * Ajoute un sous-passage à ce Passage. Si ce sous-passage n'est pas nul, 
     * efface le contenu textuel éventuellement présent.
     * @param p un Passage
     * @param c la coordonnée du Passage dans ce Passage
     */
    public void addSousPassage(Coordonnee c, Passage p){
        if(p != null) {
            contenu = null;
        }
        /*if(souspassages.get(c) != null) {
            throw new IllegalArgumentException("Ajout de sous-passage à la Coordonnée " + c + " en écrasement dans le passage " + getNumeroComplet() + ".");
        }*/
        souspassages.put(c, p);
        if(sousCoordonneeMaximale != null) {
            if(sousCoordonneeMaximale.getDistance(c) > 0) {
                sousCoordonneeMaximale = c;
            }
        }
        if(sousCoordonneeMinimale != null) {
            if(sousCoordonneeMinimale.getDistance(c) < 0) {
                sousCoordonneeMinimale = c;
            }
        }
        resetIndex();
    }
    
    /**
     * Ajoute des sous-passages à ce Passage. S'il y a au moins un sous-passage 
     * dans le tableau, efface le contenu textuel éventuellement présent.
     * @param pp des Passages à inclure dans ce Passage
     */
    public final void addSousPassages(Map<Coordonnee, Passage> pp) {
        if(!pp.isEmpty()) {
            contenu = null;
        }
        souspassages.putAll(pp);
        sousCoordonneeMaximale = null;
        sousCoordonneeMinimale = null;
        resetIndex();
    }
    
    /**
     * Donne la référence de ce Passage dans un autre passage plus global.
     * 
     * Cherche un chemin dans le graphe de Passage entre les deux Passages et 
     * renvoie ce chemin sous la forme d'une Reference.
     * 
     * Ne marche que sur les éléments structurants (à savoir, quand 'this' est
     * un noeud d'un graphe de Passages contenant 'tout' dans une même composante 
     * connexe. Ne marche pas, typiquement, sur les résultats de getAllMots() ou 
     * rassemblePassage(), puisque les résultats ne font pas partie de la structure 
     * du document et ne sont pas inscrits en tant que parents/sous-passages.
     * 
     * @param tout un passage contenant ce passage
     * @return ladite référence (null si un chemin n'est pas trouvé)
     */
    public Reference getReferenceInitialeDans(Passage tout) {
        // attention : ne renvoie que la référence du premier élément...
        // suffisant pour les utilisations actuelles, mais gare au futur.
        Reference resultat = null;
        for(Coordonnee c: tout.souspassages.keySet()) {
            Reference trouvee = trouvePassageDans(this, tout.souspassages.get(c), c);
            if(trouvee != null) {
                resultat = trouvee;
                break;
            }
        }
        return resultat;
    }
    
    private Reference trouvePassageDans(Passage aTrouver, Passage ouTrouver, Coordonnee aStocker) {
        Reference resultat = new Reference();
        resultat.setBorne(Reference.COTE_DEBUT, aStocker);
        if(aTrouver == ouTrouver) {
            return resultat;
        }
        boolean trouvee = false;
        for(Coordonnee c: ouTrouver.souspassages.keySet()) {
            Reference prospect = trouvePassageDans(aTrouver, ouTrouver.souspassages.get(c), c);
            if(prospect != null) {
                resultat.addSousReference(Reference.RELATION_PRECISION, prospect);
                trouvee = true;
                break;
            }
        }
        if(!trouvee) {
            resultat = null; // on renvoie null si on ne trouve pas de chemin.
        }
        return resultat;
    }
    
    /**
     * Etend le sous-passage proposé de manière à ce qu'il soit composé de blocs
     * entiers dont la granularité est celle proposée.
     * Le calcul se fait à partir des mots du Passage et en remontant dans la structure.
     * @param aEtendre un Passage
     * @param granularite la granularité des blocs à constituer
     * @return un Passage contenant les blocs numérotés à partir de 1
     */
    public static Passage etend(Passage aEtendre, OrdreStrict granularite) {
        ArrayList<Passage> blocsAFormer = new ArrayList<>();
        for(Passage mot: aEtendre.getAllMots()) {
            Passage blocDuMot = trouveBloc(mot, granularite);
            if(blocDuMot != null) {
                if(!blocsAFormer.contains(blocDuMot)) {
                    //System.err.println("Mot " + mot.getContenu().getExpression() + " : trouvé " + blocDuMot.getNumeroComplet());
                    blocsAFormer.add(blocDuMot);
                }
            }
        }
        Map<Coordonnee, Passage> structure = new HashMap<>();
        for(int i=0; i<blocsAFormer.size(); i++) {
            structure.put(
                    new Coordonnee(OrdreStrict.getInstance(OrdreStrict.NUMERIQUE_ARABE), ""+(i+1)), 
                    blocsAFormer.get(i));
        }
        Passage resultat = new Passage("", new Passage[]{}, structure);
        return resultat;
    }
    
    private static Passage trouveBloc(Passage aExplorer, OrdreStrict ordreATrouver) {
        Passage bloc = null;
        for(Passage parent: aExplorer.getParents()) {
            if(parent.getCoordonnee(aExplorer).getSysteme().equals(ordreATrouver)) {
                bloc = aExplorer;
                break;
            } else {
                bloc = trouveBloc(parent, ordreATrouver);
                if(bloc != null) {
                    break;
                }
            }
        }
        return bloc;
    }
    
    
    /**
     * Donne le mot suivant de ce Passage. Le mot proposé doit faire partie du 
     * passage.
     * @param mot un mot de ce Passage
     * @return le mot suivant (null en son absence)
     */
    public Passage getMotSuivant(Passage mot) {
        /*Passage reponse = null;
        Passage[] listeMots = getAllMots();
        for(int i=0; i<listeMots.length; i++) {
            if(listeMots[i] == mot && i < listeMots.length-1) {
                reponse = listeMots[i+1];
            }
        }
        return reponse;*/
        if(indexMotsSuivants.isEmpty()) {
            indexe();
        }
        return indexMotsSuivants.get(mot);
    }
    
    /**
     * Donne le mot précédent de ce Passage. Le mot proposé doit faire partie du 
     * passage.
     * @param mot un mot de ce Passage
     * @return le mot précédent (null en son absence)
     */
    public Passage getMotPrecedent(Passage mot) {
        /*Passage reponse = null;
        Passage[] listeMots = getAllMots();
        for(int i=1; i<listeMots.length; i++) {
            if(listeMots[i] == mot) {
                reponse = listeMots[i-1];
            }
        }
        return reponse;*/
        if(indexMotsPrecedents.isEmpty()) {
            indexe();
        }
        return indexMotsPrecedents.get(mot);
    }
    
    /**
     * Donne le mot non vide précédent de ce Passage. Le mot proposé doit faire partie du 
     * passage.
     * @param mot un mot non vide de ce Passage
     * @return le mot non vide précédent (null en son absence)
     */
    public Passage getMotNonVidePrecedent(Passage mot) {
        if(indexMotsPrecedentsSansMotsVides.isEmpty()) {
            indexe();
        }
        return indexMotsPrecedentsSansMotsVides.get(mot);
    }
    
    /**
     * Donne le mot non vide suivant de ce Passage. Le mot proposé doit faire partie du 
     * passage.
     * @param mot un mot non vide de ce Passage
     * @return le mot non vide suivant (null en son absence)
     */
    public Passage getMotNonVideSuivant(Passage mot) {
        if(indexMotsSuivantsSansMotsVides.isEmpty()) {
            indexe();
        }
        return indexMotsSuivantsSansMotsVides.get(mot);
    }
    
    /**
     * Donne le mot non vide précédent de ce Passage. Le mot proposé doit faire partie du 
     * passage.
     * @param mot un mot non vide de ce Passage
     * @return le mot non vide précédent (null en son absence)
     */
    public Passage getMotNonStatistiquementVidePrecedent(Passage mot) throws ListeStatistiqueNonInitialiseeException {
        if(indexMotsPrecedentsSansMotsStatistiquementVides.isEmpty()) {
            indexe();
        }
        if(listeNonInitialisee) {
            throw new ListeStatistiqueNonInitialiseeException();
        }
        return indexMotsPrecedentsSansMotsStatistiquementVides.get(mot);
    }
    
    /**
     * Donne le mot non vide suivant de ce Passage. Le mot proposé doit faire partie du 
     * passage.
     * @param mot un mot non vide de ce Passage
     * @return le mot non vide suivant (null en son absence)
     */
    public Passage getMotNonStatistiquementVideSuivant(Passage mot) throws ListeStatistiqueNonInitialiseeException {
        if(indexMotsSuivantsSansMotsStatistiquementVides.isEmpty()) {
            indexe();
        }
        if(listeNonInitialisee) {
            throw new ListeStatistiqueNonInitialiseeException();
        }
        return indexMotsSuivantsSansMotsStatistiquementVides.get(mot);
    }
    
    /**
     * Détermine si ce Passage contient le mot proposé
     * @param mot un mot à chercher
     * @return oui ou non
     */
    public boolean contientMot(Passage mot) {
        if(!mot.estUnMot()) {
            return false;
        }
        Passage[] tous = getAllMots();
        boolean reponse = false;
        for(Passage essai : tous) {
            if(essai.equals(mot)) {
                reponse = true;
                break;
            }
        }
        return reponse;
    }
    
    /**
     * Détermine si ce Passage chevauche le Passage proposé, à savoir s'il 
     * contient au moins un mot de ce Passage.
     * @param autrePassage un Passage
     * @return oui ou non
     */
    public boolean chevauche(Passage autrePassage) {
        Set<Passage> s1 = new HashSet<>(Arrays.asList(getAllMots()));
        Set<Passage> s2 = new HashSet<>(Arrays.asList(autrePassage.getAllMots()));
        s1.retainAll(s2);
        
        return s1.size() > 0;
    }
    
    /**
     * Détermine si ce Passage a les mêmes mots que le Passage proposé.
     * @param autrePassage un Passage
     * @return oui ou non
     */
    public boolean aLesMemesMotsQue(Passage autrePassage) {
        Set<Passage> s1 = new HashSet<>(Arrays.asList(getAllMots()));
        Set<Passage> s2 = new HashSet<>(Arrays.asList(autrePassage.getAllMots()));
        s1.removeAll(s2);
        
        return s1.isEmpty();
    }
    
    
    
    /**
     * Renvoie la distance entre deux fragments de ce Passage. Les fragments 
     * peuvent être des Passages créés pour l'occasion (et ne faisant donc pas 
     * partie de ce Passage), mais ils doivent être composés (uniquement, du 
     * moins au niveau des feuilles du graphe) de mots appartenant à ce Passage.
     * 
     * On suppose que les mots sont dans l'ordre dans les passages.
     * 
     * La distance peut être négative si c'est fragment1 qui suit fragment2. 
     * Elle est nulle si les deux passages se touchent ou se chevauchent.
     * 
     * @param fragment1 un Passage contenant des mots de ce Passage
     * @param fragment2 un Passage contenant des mots de ce Passage
     * @return le nombre de mots que ce Passage contient entre fragment1 et 
     * fragment2
     * @throws IllegalArgumentException si l'un des fragments contient un mot 
     * qui n'est pas dans ce Passage
     */
    public int getDistance(Passage fragment1, Passage fragment2) throws IllegalArgumentException {
        int motMinF1 = getIndexMot(fragment1.getAllMots()[0]);
        int motMaxF1 = getIndexMot(fragment1.getAllMots()[fragment1.getAllMots().length-1]);
        int motMinF2 = getIndexMot(fragment2.getAllMots()[0]);
        int motMaxF2 = getIndexMot(fragment2.getAllMots()[fragment2.getAllMots().length-1]);
        if(motMinF1 == -1 || motMaxF1 == -1) {
            throw new IllegalArgumentException("Le premier fragment contient un mot qui ne fait pas partie de ce Passage.");
        }
        if(motMinF2 == -1 || motMaxF2 == -1) {
            throw new IllegalArgumentException("Le second fragment contient un mot qui ne fait pas partie de ce Passage.");
        }
        // Est-ce que les deux passages se chevauchent ou se touchent (plus généralement, ne sont pas disjoints) ?
        if(motMinF1 <= motMaxF2 && motMinF2 <= motMaxF1) {
            return 0;
        } else {
            return (motMinF2 - motMaxF1 > 0) ? (motMinF2 - motMaxF1) : (motMaxF2 - motMinF1);
        }
    }
    
    // optimisation nécessaire : va être appelé des millions de fois
    Map<Passage, Integer> index;
    /**
     * Détermine l'index du mot proposé dans l'ensemble des mots du passage 
     * (indépendamment de la structure).
     * Rappel : il s'agit d'un mot (Passage) et non d'un terme (Terme), et il 
     * doit appartenir à ce Passage (et non, par exemple, à un autre Passage
     * contenant le même texte).
     * @param mot un mot du passage
     * @return l'index du mot, -1 s'il n'est pas dans ce passage
     */
    public int getIndexMot(Passage mot) {
        //long tps1 = System.currentTimeMillis();
        int resultat = -1;
        if(index == null) {
            index = new HashMap<>();
            for(int i=0; i<getAllMots().length; i++) {
                index.put(getAllMots()[i], i);
            }
        }
        if(index.containsKey(mot)) {
            resultat = index.get(mot);
        }
        //System.err.println("  index de mot récupéré en "+(System.currentTimeMillis()-tps1)+" ms");
        return resultat;
    }
    
    
    /**
     * Trie les mots du sous-passage proposé. Ce sous-passage n'est pas forcément 
     * techniquement un sous-passage de ce Passage, mais il doit contenir uniquement
     * des mots de ce sous-passage.
     * Si des mots sont en double, les doubles seront supprimés.
     * 
     * @param tasDeMots un Passage contenant uniquement des mots de ce Passage
     */
    public void trieMots(Passage tasDeMots) {
        ArrayList<Integer> indexPresents0 = new ArrayList<>();
        Map<Integer, Passage> motsPresents = new HashMap<>();
        for(Passage mot: tasDeMots.getAllMots()) {
            indexPresents0.add(getIndexMot(mot));
            motsPresents.put(getIndexMot(mot), mot);
        }
        
        Integer[] indexPresents = new Integer[indexPresents0.size()];
        indexPresents0.toArray(indexPresents);
        Arrays.sort(indexPresents);
        
        int coo = 0, precedent = Integer.MIN_VALUE;
        Map<Coordonnee, Passage> motsTries = new HashMap<>();
        for(Integer i: indexPresents) {
            if(precedent != i) {
                coo++;
                motsTries.put(
                        new Coordonnee(OrdreStrict.getInstance(OrdreStrict.MOTS), coo),
                        motsPresents.get(i));
                precedent = i;
            }
        }
        tasDeMots.setSousPassages(motsTries);
    }
    
    /**
     * Fabrique un passage à partir de ce passage, ne comportant que les fragments 
     * décrits par la requête.
     * 
     * Contrat : la référence donnée contient la même structure que ce passage, 
     * et commence au même niveau de granularité (livre, chapitre, paragraphe...)
     * que les sous-passages de ce passage.
     * (par exemple, si la référence est un paragraphe, ce passage doit 
     * contenir des paragraphes)
     * 
     * Un tel Passage ne contient que des Mots, et ne garde pas la structure du 
     * passage source. Il n'est pas considéré comme parent des mots.
     * 
     * La structure est toujours récupérable par le biais de getParents(), 
     * indépendamment pour chaque mot.
     * 
     * @param requete la référence du passage à délimiter, ne comprenant que des 
     * sous-passages de ce passage
     * @return le passage délimité (null si aucun n'est faisable)
     */
    public Passage rassemblePassage(Reference requete) {
        //System.err.println("Recherche de " + requete.toString() + " dans " + titre);
        ArrayList<Passage> mots = new ArrayList<>();
        addMots(mots, requete);
        Passage resultat = new Passage();
        for(int i=0; i<mots.size(); i++) {
            resultat.addSousPassage(new Coordonnee(OrdreStrict.getInstance(OrdreStrict.MOTS), ""+i), mots.get(i));
        }
        //System.err.println(" " + resultat.getNumerosCompletsSousPassages());
        //System.err.println(resultat.getTexteCompletAvecRefs());
        return resultat;
    }
    
    private void addMots(ArrayList<Passage> mots, Reference aInclure) {
        //System.err.println(" Recherche de mots dans " + aInclure.toString());
        
        // Est-ce une référence non vide ?
        if(aInclure.hasBorne(Reference.COTE_DEBUT)) {
            //System.err.println("  La référence n'est pas vide.");
            // Est-ce un simple mot (ou plusieurs) ?
            if(aInclure.getBorne(Reference.COTE_DEBUT).getSysteme().equals(
                    OrdreStrict.getInstance(OrdreStrict.MOTS))) {
                //System.err.println("  C'est un ou plusieurs mot(s).");
                // Oui ; on liste les coordonnées à inclure
                ArrayList<Coordonnee> coordMots = new ArrayList<>();
                // Un seul mot ou un intervalle de mots ?
                if(aInclure.hasBorne(Reference.COTE_FIN)) { // Un intervalle
                    //System.err.println("  Plusieurs, en fait.");
                    Coordonnee debut = aInclure.getBorne(Reference.COTE_DEBUT);
                    Coordonnee fin = aInclure.getBorne(Reference.COTE_FIN);
                    if(fin.isFinale()) {
                        fin = new Coordonnee(fin.getSysteme(), souspassages.size());
                    }
                    if(debut.getDistance(fin) < 0) {
                        // Peut cacher des erreurs préoccupantes, mais dures à pister
                        // On remplace la coordonnée de début qui est après la fin par
                        // la coordonnée de fin
                        // (c'est mieux qu'un message d'erreur)
                        //System.err.println("   Argh, début est plus grand que fin ; on remplace par fin");
                        coordMots.add(fin);
                        //System.err.println("   Mot rajouté : "+fin);
                    } else {
                        Coordonnee courante = debut;
                        coordMots.add(courante);
                        //System.err.println("   Mot rajouté : "+souspassages.get(courante));
                        while(!courante.equals(fin) && courante.getSysteme().hasSuivant(courante)) {
                            // on suppose que les deux bornes sont dans le même système
                            // et que début est inférieure ou égale à fin
                            // (Reference s'en occupe)
                            courante = courante.getSysteme().getSuivant(courante);
                            //System.err.println("   Mot rajouté : "+souspassages.get(courante));
                            coordMots.add(courante);
                        }
                    }
                } else { // Un seul mot
                    //System.err.println("  Un seul, en fait.");
                    coordMots.add(aInclure.getBorne(Reference.COTE_DEBUT));
                        //System.err.println("   Mot rajouté : "+souspassages.get(aInclure.getBorne(Reference.COTE_DEBUT)));
                }
                // chercher les mots correspondants à ces coordonnées dans ce passage
                for(Coordonnee recherche: coordMots) {
                    //System.err.println("  Recherche de la coordonnee : " + recherche.getExpression());
                    if(souspassages.containsKey(recherche)) {
                        //System.err.println("   Mot trouvé : " + souspassages.get(recherche).contenu.getExpression());
                        mots.add(souspassages.get(recherche));
                    } else {
                        //System.err.println("   Pas de mot trouvé.");
                        //System.err.println("   Coordonnees disponibles : " + souspassages.keySet().toString());
                    }
                }
            } else {
                //System.err.println("  C'est une référence à creuser");
                // Ceci n'est pas un Mot
                if(aInclure.hasSousReference(Reference.RELATION_PRECISION)) {
                    //System.err.println("  Et on peut creuser ; on va le faire");
                    // on peut creuser : on délègue au sous-passage
                    Reference sousRef = aInclure.getSousReferences(Reference.RELATION_PRECISION)[0];
                    Passage sousPassage = souspassages.get(aInclure.getBorne(Reference.COTE_DEBUT));
                    if(sousPassage != null)  {
                        //System.err.println("   Recherche de mots dans " + sousRef.toString());
                        sousPassage.addMots(mots, sousRef);
                    }
                    //System.err.println("  Au fait, c'est un passage ou un intervalle ?");
                    if(aInclure.hasBorne(Reference.COTE_FIN)) {
                        //System.err.println("  Ah, un intervalle.");
                        Coordonnee debut = aInclure.getBorne(Reference.COTE_DEBUT);
                        Coordonnee fin = aInclure.getBorne(Reference.COTE_FIN);
                        if(fin.isFinale()) {
                            fin = new Coordonnee(fin.getSysteme(), souspassages.size());
                        }
                        //System.err.println("  Récupération des mots aux coordonnées "+debut+" à "+fin+".");
                        for(Coordonnee c = debut.getSuivante() ; !c.equals(fin.getSuivante()); c = c.getSuivante()) {
                            Passage sousPassage2 = souspassages.get(c);
                            mots.addAll(Arrays.asList(sousPassage2.getAllMots()));
                            //System.err.println("   Mots rajoutés : " + Arrays.toString(sousPassage2.getAllMots()));
                        }
                    } else {
                        //System.err.println("  Un seul passage. OK, rien de plus à faire.");
                    }
                } else {
                    //System.err.println("  Mais on ne peut pas creuser ; tant pis, on rajoute tous les mots du passage");
                    //System.err.println("  Au fait, c'est un passage ou un intervalle ?");
                    // on ne peut pas creuser : on ajoute tous les mots de ce Passage
                    if(aInclure.hasBorne(Reference.COTE_FIN)) {
                        //System.err.println("  Ah, un intervalle.");
                        Coordonnee debut = aInclure.getBorne(Reference.COTE_DEBUT);
                        Coordonnee fin = aInclure.getBorne(Reference.COTE_FIN);
                        if(fin.isFinale()) {
                            fin = new Coordonnee(fin.getSysteme(), souspassages.size());
                        }
                        for(Coordonnee c = debut ; !c.equals(fin.getSuivante()); c = c.getSuivante()) {
                            Passage sousPassage = souspassages.get(c);
                            if(sousPassage != null) {
                                mots.addAll(Arrays.asList(sousPassage.getAllMots()));
                                //System.err.println("   Mots rajoutés : " + Arrays.toString(sousPassage.getAllMots()));
                            }
                        }
                    } else {
                        //System.err.println("  Un seul passage.");
                        Passage sousPassage = souspassages.get(aInclure.getBorne(Reference.COTE_DEBUT));
                        if(sousPassage != null) {
                            mots.addAll(Arrays.asList(sousPassage.getAllMots()));
                            //System.err.println("   Mots rajoutés : " + Arrays.toString(sousPassage.getAllMots()));
                        }
                    }
                }
            }
        }
        
        // ajouter la partie correspondant aux inclusions
        //System.err.println("  Traitement des références supplémentaires incluses dans celle-ci");
        for(Reference inclusion: aInclure.getSousReferences(Reference.RELATION_INCLUSION)) {
            addMots(mots, inclusion);
        }
        // enlever la partie correspondant aux exclusions
        //System.err.println("  Traitement des références supplémentaires exclues de celle-ci");
        for(Reference exclusion: aInclure.getSousReferences(Reference.RELATION_EXCLUSION)) {
            removeMots(mots, exclusion);
        }
    }
    
    private void removeMots(ArrayList<Passage> mots, Reference aExclure) {
        // enlever les Mots correspondant à cette Référence s'il y en a
        if(aExclure.hasBorne(Reference.COTE_DEBUT)) {
            if(aExclure.getBorne(Reference.COTE_DEBUT).getSysteme().equals(
                    OrdreStrict.getInstance(OrdreStrict.MOTS))) {
                ArrayList<Coordonnee> coordMots = new ArrayList<>();
                // il y a des mots ; combien ?
                if(aExclure.hasBorne(Reference.COTE_FIN)) {
                    // plusieurs
                    Coordonnee debut = aExclure.getBorne(Reference.COTE_DEBUT);
                    Coordonnee fin = aExclure.getBorne(Reference.COTE_FIN);
                    
                    Coordonnee courante = debut;
                    coordMots.remove(courante);
                    while(!courante.equals(fin)) {
                        // on suppose que les deux bornes sont dans le même système
                        // et que début est inférieure à fin
                        // (Reference s'en occupe)
                        courante = courante.getSysteme().getSuivant(courante);
                        coordMots.remove(courante);
                    }
                } else {
                    // un seul
                    coordMots.remove(aExclure.getBorne(Reference.COTE_DEBUT));
                }
                // chercher les mots correspondants à ces coordonnées dans ce passage
                for(Coordonnee recherche: coordMots) {
                    for(Coordonnee prospect: souspassages.keySet()) {
                        if(prospect.equals(recherche)) {
                            mots.add(souspassages.get(prospect));
                        }
                    }
                }
            }
        }
        
        // enlever la partie correspondant à l'éventuelle précision
        for(Reference precision: aExclure.getSousReferences(Reference.RELATION_PRECISION)) {
            removeMots(mots, precision);
        }
        
        // enlever la partie correspondant aux inclusions
        for(Reference inclusion: aExclure.getSousReferences(Reference.RELATION_INCLUSION)) {
            removeMots(mots, inclusion);
        }
        // ajouter la partie correspondant aux exclusions
        for(Reference exclusion: aExclure.getSousReferences(Reference.RELATION_EXCLUSION)) {
            addMots(mots, exclusion);
        }
        sousCoordonneeMaximale = null;
        sousCoordonneeMinimale = null;
        resetIndex();
    }
    
    /**
     * Insère le Passage proposé comme sous-passage du Passage trouvé à la 
     * référence donnée.
     * 
     * Cette méthode prend en charge le rajout du Passage trouvé comme parent 
     * du Passage donné.
     * 
     * Contrat : la Reference donnée ne contient que des sous-références de type 
     * Reference.RELATION_PRECISION (en d'autres termes : on vise un sous-passage 
     * précis et non pas une plage).
     * 
     * @param lieu la référence d'un Passage déjà présent
     * @param coordonnee la coordonnée du sous-passage là où on le rangera
     * @param aRanger un sous-passage à ranger à cette référence
     * @return si oui ou non le passage a été rajouté
     */
    public boolean addPassageAt(Reference lieu, Coordonnee coordonnee, Passage aRanger) {
        // On vérifie si on peut suivre la référence
        boolean reussi = false;
        for(Coordonnee prospect: souspassages.keySet()) {
            if(prospect.equals(lieu.getBorne(Reference.COTE_DEBUT))) {
                if(lieu.hasSousReference(Reference.RELATION_PRECISION)) {
                    reussi = souspassages.get(prospect).addPassageAt(
                            lieu.getSousReferences(
                            Reference.RELATION_PRECISION)[0], coordonnee, aRanger);
                } else {
                    aRanger.addParent(souspassages.get(prospect));
                    souspassages.get(prospect).addSousPassage(coordonnee, aRanger);
                    reussi = true;
                    break;
                }
            }
        }
        sousCoordonneeMaximale = null;
        sousCoordonneeMinimale = null;
        resetIndex();
        return reussi;
    }
    
    /**
     * Insère le Passage proposé comme sous-passage du Passage trouvé à la 
     * référence donnée, ainsi que, s'ils n'existent pas, les passages menant 
     * à celui-ci.
     * 
     * Cette méthode prend en charge le rajout du Passage trouvé comme parent 
     * du Passage donné, et de même pour les passages intermédiaires rajoutés.
     * 
     * Contrat : la Reference donnée ne contient que des sous-références de type 
     * Reference.RELATION_PRECISION (en d'autres termes : on vise un sous-passage 
     * précis et non pas une plage).
     * 
     * @param lieu la référence d'un Passage déjà présent
     * @param coordonnee la coordonnée du sous-passage là où on le rangera
     * @param aRanger un sous-passage à ranger à cette référence
     */
    public void addPassagesUpTo(Reference lieu, Coordonnee coordonnee, Passage aRanger) {
        // On vérifie si on peut suivre la référence
        boolean reussi = false;
        for(Coordonnee prospect: souspassages.keySet()) {
            if(prospect.equals(lieu.getBorne(Reference.COTE_DEBUT))) {
                if(lieu.hasSousReference(Reference.RELATION_PRECISION)) {
                    souspassages.get(prospect).addPassagesUpTo(
                            lieu.getSousReferences(
                            Reference.RELATION_PRECISION)[0], coordonnee, aRanger);
                    reussi = true;
                } else {
                    aRanger.addParent(souspassages.get(prospect));
                    souspassages.get(prospect).addSousPassage(coordonnee, aRanger);
                    reussi = true;
                    break;
                }
            }
        }
        if(!reussi) {
            // il faut rajouter des passages intermédiaires
            // on en rajoute un, on dépile et on propage
            Coordonnee c = lieu.getBorne(Reference.COTE_DEBUT);
            Passage intermediaire = new Passage("", new Passage[]{this}, "");
            this.addSousPassage(c, intermediaire);
            if(lieu.hasSousReference(Reference.RELATION_PRECISION)) {
                intermediaire.addPassagesUpTo(lieu.getSousReferences(
                        Reference.RELATION_PRECISION)[0], coordonnee, aRanger);
            } else {
                // plus rien à rajouter, on retente, normalement ça marche
                addPassagesUpTo(lieu, coordonnee, aRanger);
            }
        }
        sousCoordonneeMaximale = null;
        sousCoordonneeMinimale = null;
        resetIndex();
    }
    
    private Passage[] mots;
    /**
     * Renvoie l'ensemble des Passages de type mot présents dans ce Passage. Il 
     * est possible d'accéder aux parents de ces mots par la méthode getParents().
     * 
     * Les mots sont donnés dans l'ordre où ils apparaissent dans le texte 
     * (parcours séquentiel de l'arbre).
     * 
     * @return les mots de ce Passage
     */
    public Passage[] getAllMots() {
        if(estUnMot()) {
            mots = new Passage[]{this};
        }
        if(mots == null) {
            ArrayList<Passage> resultat = new ArrayList<>();
            for(Passage p: getSousPassages()) {
                if(p.estUnMot()) {
                    resultat.add(p);
                } else {
                    resultat.addAll(Arrays.asList(p.getAllMots()));
                }
            }
            mots = new Passage[resultat.size()];
            resultat.toArray(mots);
        }
        return mots;
    }
    
    private Passage[] motsNonVides;
    /**
     * Renvoie un ensemble de Passages de type mot présents dans ce Passage. Il 
     * est possible d'accéder aux parents de ces mots par la méthode getParents().
     * 
     * Les mots sont donnés dans l'ordre où ils apparaissent dans le texte 
     * (parcours séquentiel de l'arbre).
     * 
     * Les mots ayant un contenu défini comme mot vide par @see modele.listes.LemmesVides
     * ne sont pas renvoyé
     * 
     * @return les mots de ce Passage
     */
    public Passage[] getAllMotsNonVides() {
        if(estUnMot() && !LemmesVides.contientTerme(getContenu())) {
            motsNonVides = new Passage[]{this};
        }
        if(motsNonVides == null) {
            ArrayList<Passage> resultat = new ArrayList<>();
            for(Passage p: getSousPassages()) {
                if(p.estUnMot() && !LemmesVides.contientTerme(p.getContenu())) {
                    resultat.add(p);
                } else {
                    resultat.addAll(Arrays.asList(p.getAllMotsNonVides()));
                }
            }
            motsNonVides = new Passage[resultat.size()];
            resultat.toArray(motsNonVides);
        }
        return motsNonVides;
    }
    
    private Passage[] motsNonStatistiquementVides;
    /**
     * Renvoie un ensemble de Passages de type mot présents dans ce Passage. Il 
     * est possible d'accéder aux parents de ces mots par la méthode getParents().
     * 
     * Les mots sont donnés dans l'ordre où ils apparaissent dans le texte 
     * (parcours séquentiel de l'arbre).
     * 
     * Les mots ayant un contenu défini comme mot vide par 
     * @see modele.listes.LemmesStatistiquementVides
     * ne sont pas renvoyé
     * 
     * @return les mots de ce Passage
     */
    public Passage[] getAllMotsNonStatistiquementVides() throws ListeStatistiqueNonInitialiseeException {
        if(estUnMot() && !LemmesStatistiquementVides.contientTerme(getContenu())) {
            motsNonStatistiquementVides = new Passage[]{this};
        }
        if(motsNonStatistiquementVides == null) {
            ArrayList<Passage> resultat = new ArrayList<>();
            for(Passage p: getSousPassages()) {
                if(p.estUnMot()) {
                    if(!LemmesStatistiquementVides.contientTerme(p.getContenu())) {
                        resultat.add(p);
                    }
                } else {
                    resultat.addAll(Arrays.asList(p.getAllMotsNonStatistiquementVides()));
                }
            }
            motsNonStatistiquementVides = new Passage[resultat.size()];
            resultat.toArray(motsNonStatistiquementVides);
        }
        return motsNonStatistiquementVides;
    }
    
    /*public boolean contientUnLemmeDe(Terme terme) {
        boolean resultat = false;
        for(Terme t: contenu) {
            if(t.hasLemmeCommun(terme)) {
                resultat = true;
                break;
            }
        }
        return resultat;
    }*/
    
    /*public boolean contientTerme(Terme terme) {
        return contenu.contains(terme);
    }*/
    
    /*public int getNbOccurrencesTerme(Terme terme) {
        int resultat = 0;
        for(Terme t: contenu) {
            if(terme.equals(t)) {
                resultat++;
            }
        }
        return resultat;
    }*/
    
    /*public int[] getIndexOccurrencesTerme(Terme terme) {
        int[] resultat = new int[getNbOccurrencesTerme(terme)];
        int i = 0;
        for(int n = 0; n<contenu.size(); n++) {
            Terme t = contenu.get(n);
            if(t.equals(terme)) {
                resultat[i] = n;
                i++;
            }
        }
        return resultat;
    }*/
    
    
    /*public void addContenu(Terme terme) {
        contenu.add(terme);
    }*/
    
    /**
     * Définit le contenu textuel de ce Passage. Supprime tout sous-passage 
     * pouvant exister dans ce Passage si le texte n'est pas vide. 
     * Le texte brut sera découpé en Termes.
     * @param texte le texte à découper
     */
    /*private void setContenu(String texte) {
        if(texte.length() >0) {
            passages.clear();
        }
        contenu.clear();
        Terme[] tt = Textes.decoupeEnTermes(texte);
        contenu.addAll(Arrays.asList(tt));
    }*/
    
    /**
     * Donne sous une forme textuelle le contenu complet de ce Passage et de 
     * tous ses sous-passages.
     * @return le texte complet de ce Passage
     */
    public String getTexteComplet() {
        
        /* 
         * Pourquoi faire simple quand on peut faire compliqué ? 
         * Parce que 'simple' pour le développeur est ici 'lent'
         * pour la machine.
         */
        /*
         * Donc pas ce qui suit, et qui oblige à faire beaucoup de concaténations 
         * qui sont lourdes : 
         */
        /*String res = "";
        if(texte.equals("")) {
            for(Passage p: passages) {
                res += p.getTexteComplet();
            }
        } else {
            res = texte + "\n";
        }*/
        /*
         * Mais ceci qui les évite : 
         */
        ArrayList<Character> res = texteComplet();
        char[] cc = new char[res.size()];
        for(int i=0; i<cc.length; i++) {
            cc[i] = res.get(i).charValue();
        }
        String s = new String(cc);
        s = s.replaceAll(" <", "<");
        s = s.replaceAll(">\\\n", ">");
        s = s.replaceAll("><", ">\n<");
        return s;
    }
    
    /**
     * Calcule récursivement le texte de ce Passage.
     * @return une structure contenant ledit texte
     */
    private ArrayList<Character> texteComplet() {
        ArrayList<Character> res = new ArrayList<>();
        if(contenu == null) {
            String tag = getNumero().split(" ")[0];
            String id = "N/A";
            if(getNumero().split(" ").length > 1) {
                id = getNumero().split(" ")[1];
            }
            char[] cc = ("<" + tag + " id=\"" + id + "\">\n").toCharArray();
            for(char c: cc) {
                res.add(c);
            }
            for(Passage p: getSousPassages()) {
                res.addAll(p.texteComplet());
            }
            cc = ("</" + tag + ">\n").toCharArray();
            for(char c: cc) {
                res.add(c);
            }
        } else {
            return texteCompletMot();
        }
        return res;
    }
    private ArrayList<Character> texteCompletMot() {
        ArrayList<Character> res = new ArrayList<>();
            String tag = getNumero().split(" ")[0];
            String id = getNumero().split(" ")[1];
            char[] cc = ("<" + tag + " id=\"" + id + "\">" + 
                    getContenu().getExpression() + "</"  + tag + "> ").toCharArray();
            for(char c: cc) {
                res.add(c);
            }
        return res;
    }
    
    
    /**
     * Donne sous une forme textuelle le contenu complet de ce Passage et de 
     * tous ses sous-passages.
     * @return le texte complet de ce Passage
     */
    public String getTexteCompletAvecRefs() {
        ArrayList<Character> res = texteCompletAvecRefs();
        char[] cc = new char[res.size()];
        for(int i=0; i<cc.length; i++) {
            cc[i] = res.get(i);
        }
        String s = new String(cc);
        return s;
    }
    /**
     * Calcule récursivement le texte de ce Passage.
     * @return une structure contenant ledit texte
     */
    private ArrayList<Character> texteCompletAvecRefs() {
        ArrayList<Character> res = new ArrayList<>();
        if(contenu == null) {
            char[] cc = ("[" + getNumeroComplet() + (getTitre().isEmpty()?"":" - " + getTitre()) + "]" + 
                    "\n").toCharArray();
            for(char c: cc) {
                res.add(c);
            }
            for(Passage p: getSousPassages()) {
                if(p.estUnMot()) {
                    res.addAll(p.texteCompletMotAvecRefs());
                } else {
                    res.addAll(p.texteCompletAvecRefs());
                }
            }
        } else {
            char[] cc = ("[" + getNumeroComplet() + (getTitre().isEmpty()?"":" - " + getTitre()) + "] " + 
                    getContenu().getExpression() + "\n").toCharArray();
            for(char c: cc) {
                res.add(c);
            }
        }
        return res;
    }
    
    private ArrayList<Character> texteCompletMotAvecRefs() {
        ArrayList<Character> res = new ArrayList<>();
            char[] cc = ("[" + getNumeroComplet() + (getTitre().isEmpty()?"":" - " + getTitre()) + "] " + 
                    getContenu().getExpression() + " ").toCharArray();
            for(char c: cc) {
                res.add(c);
            }
        return res;
    }
    
    
    /**
     * Donne sous une forme textuelle le contenu complet de ce Passage et de 
     * tous ses sous-passages, mais n'affiche pas la structure des mots.
     * @return le texte complet de ce Passage
     */
    public String getTexteCompletSansMots() {
        ArrayList<Character> res = texteCompletSansMots();
        char[] cc = new char[res.size()];
        for(int i=0; i<cc.length; i++) {
            cc[i] = res.get(i);
        }
        String s = new String(cc);
        s = s.replaceAll(" <", "<");
        s = s.replaceAll(">\\\n", ">");
        s = s.replaceAll("><", ">\n<");
        return s;
    }
    
    /**
     * Calcule récursivement le texte de ce Passage.
     * @return une structure contenant ledit texte
     */
    private ArrayList<Character> texteCompletSansMots() {
        ArrayList<Character> res = new ArrayList<>();
        if(contenu == null) {
            char[] cc = ("<" + getNumero() + (getTitre().isEmpty()?"":" - " + getTitre()) + ">\n").toCharArray();
            for(char c: cc) {
                res.add(c);
            }
            for(Passage p: getSousPassages()) {
                res.addAll(p.texteCompletSansMots());
            }
            cc = ("</" + getNumero() + (getTitre().isEmpty()?"":" - " + getTitre()) + ">\n").toCharArray();
            for(char c: cc) {
                res.add(c);
            }
        } else {
            return texteCompletMotSansMots();
        }
        return res;
    }
    private ArrayList<Character> texteCompletMotSansMots() { // drôle de nom, je sais
        ArrayList<Character> res = new ArrayList<>();
            char[] cc = (getContenu().getExpression() + " ").toCharArray();
            for(char c: cc) {
                res.add(c);
            }
        return res;
    }
    
    
    /**
     * Donne sous une forme textuelle le contenu complet de ce Passage et de 
     * tous ses sous-passages, mais n'affiche pas la structure ; au lieu de cela, 
     * utilise des passages à la ligne.
     * @return le texte complet de ce Passage
     */
    public String getTexteCompletMisEnForme() {
        ArrayList<Character> res = texteCompletMisEnForme();
        char[] cc = new char[res.size()];
        for(int i=0; i<cc.length; i++) {
            cc[i] = res.get(i).charValue();
        }
        String s = new String(cc);
        return s;
    }
    
    /**
     * Calcule récursivement le texte de ce Passage.
     * @return une structure contenant ledit texte
     */
    private ArrayList<Character> texteCompletMisEnForme() {
        ArrayList<Character> res = new ArrayList<>();
        if(contenu == null) {
            Coordonnee coo = null;
            if(hasParents()) {
                coo = getParents()[0].getCoordonnee(this);
            }
            if(coo != null) {
                if(coo.getSysteme().equals(OrdreStrict.getInstance(OrdreStrict.LIGNES)) || 
                        coo.getSysteme().equals(OrdreStrict.getInstance(OrdreStrict.VERSETS))) {
                    char[] cc = ("\n").toCharArray();
                    for(char c: cc) {
                        res.add(new Character(c));
                    }
                } else {
                    char[] cc = ("\n\n" + getNumero()).toCharArray();
                    for(char c: cc) {
                        res.add(new Character(c));
                    }
                }
            } else {
                char[] cc = getTitre().toCharArray();
                for(char c: cc) {
                    res.add(new Character(c));
                }
            }
            for(Passage p: getSousPassages()) {
                res.addAll(p.texteCompletMisEnForme());
            }
        } else {
            return texteCompletMotMisEnForme();
        }
        return res;
    }
    private ArrayList<Character> texteCompletMotMisEnForme() {
        ArrayList<Character> res = new ArrayList<>();
            char[] cc = (getContenu().getExpression() + " ").toCharArray();
            for(char c: cc) {
                res.add(new Character(c));
            }
        return res;
    }
    
    /**
     * Donne la liste de tous les termes présents dans le texte de ce Passage 
     * et de ses sous-passages. La liste ne présente aucun doublon.
     * @return ladite liste
     */
    public Terme[] getEnsembleTermes() {
        ArrayList<Terme> resultat = new ArrayList<>();
        resultat.addAll(Arrays.asList(new Texte(this).getContenu()));
        Terme[] res = new Terme[resultat.size()];
        resultat.toArray(res);
        return res;
    }
    
    /**
     * Donne la liste des Phrases présentes dans ce passage. 
     * Une phrase ne contient que des passages de type mot.
     * @return ladite liste
     */
    public Passage[] getPhrases() {
        ArrayList<Passage> resultat = new ArrayList<>();
        Passage phraseCourante = new Passage();
        Passage[] listeMots = getAllMots();
        //System.err.println(" "+listeMots.length+" mots trouvés");
        int idMot = 1;
        for(int i=0; i<listeMots.length ; i++) {
            Passage p = listeMots[i];
            if (p.getContenu().estUneFinDePhrase()) {
                resultat.add(phraseCourante);
                phraseCourante = new Passage();
                idMot = 1;
            } else {
                phraseCourante.addSousPassage(new Coordonnee(OrdreStrict.getInstance(OrdreStrict.MOTS), idMot), p);
                idMot++;
            }
        }
        if(phraseCourante.getNbSousPassages() > 0) {
            resultat.add(phraseCourante);
        }
        Passage[] res = new Passage[resultat.size()];
        resultat.toArray(res);
        return res;
    }
    
    /**
     * Donne la liste des Phrases présentes dans ce passage. 
     * Une phrase ne contient que des passages de type mot.
     * @return ladite liste
     */
    public Passage[] getPhrasesSansMotsVides() {
        ArrayList<Passage> resultat = new ArrayList<>();
        Passage phraseCourante = new Passage();
        Passage[] listeMots = getAllMotsNonVides();
        int idMot = 1;
        for(int i=0; i<listeMots.length ; i++) {
            Passage p = listeMots[i];
            if (p.getContenu().estUneFinDePhrase()) {
                resultat.add(phraseCourante);
                phraseCourante = new Passage();
                idMot = 1;
            } else {
                phraseCourante.addSousPassage(new Coordonnee(OrdreStrict.getInstance(OrdreStrict.MOTS), idMot), p);
                idMot++;
            }
        }
        Passage[] res = new Passage[resultat.size()];
        resultat.toArray(res);
        return res;
    }
    
    /**
     * Donne la liste des n-lexes présentes dans ce passage. 
     * Un n-lexe ne contient que des passages de type mot.
     * Le résultat comporte toutes les suites possibles de n mots dans ce Passage.
     * @param n le nombre de mots par n-lexe
     * @return ladite liste
     */
    public Passage[] getNLexes(int n) {
        ArrayList<Passage> resultat = new ArrayList<>();
        Passage[] listeMots = getAllMots();
        for(int i=0; i<listeMots.length - n ; i++) {
            Passage nLexe = new Passage();
            for(int j=0; j<n; j++) {
                nLexe.addSousPassage(
                        new Coordonnee(OrdreStrict.getInstance(OrdreStrict.MOTS), j+1), 
                        listeMots[i+j]);
            }
            resultat.add(nLexe);
        }
        Passage[] res = new Passage[resultat.size()];
        resultat.toArray(res);
        return res;
    }
    
    /**
     * Donne la liste des n-lexes présentes dans ce passage. 
     * Un n-lexe ne contient que des passages de type mot.
     * Le résultat comporte toutes les suites possibles de n mots dans ce Passage.
     * Les mots dont le contenu est défini comme mot vide par @see modele.listes.LemmesVides
     * sont filtrés.
     * @param n le nombre de mots par n-lexe
     * @return ladite liste
     */
    public Passage[] getNLexesSansMotsVides(int n) {
        ArrayList<Passage> resultat = new ArrayList<>();
        Passage[] listeMots = getAllMotsNonVides();
        for(int i=0; i<listeMots.length - n ; i++) {
            Passage nLexe = new Passage();
            for(int j=0; j<n; j++) {
                nLexe.addSousPassage(
                        new Coordonnee(OrdreStrict.getInstance(OrdreStrict.MOTS), j+1), 
                        listeMots[i+j]);
            }
            resultat.add(nLexe);
        }
        Passage[] res = new Passage[resultat.size()];
        resultat.toArray(res);
        return res;
    }
    
    /**
     * Donne la liste des n-lexes présentes dans ce passage. 
     * Un n-lexe ne contient que des passages de type mot.
     * Le résultat comporte toutes les suites possibles de n mots dans ce Passage.
     * Les mots dont le contenu est défini comme mot vide par @see modele.listes.LemmesVides
     * sont filtrés.
     * @param n le nombre de mots par n-lexe
     * @return ladite liste
     */
    public Passage[] getNLexesSansMotsStatistiquementVides(int n) throws ListeStatistiqueNonInitialiseeException {
        ArrayList<Passage> resultat = new ArrayList<>();
        Passage[] listeMots = getAllMotsNonStatistiquementVides();
        for(int i=0; i<listeMots.length - n ; i++) {
            Passage nLexe = new Passage();
            for(int j=0; j<n; j++) {
                nLexe.addSousPassage(
                        new Coordonnee(OrdreStrict.getInstance(OrdreStrict.MOTS), j+1), 
                        listeMots[i+j]);
            }
            resultat.add(nLexe);
        }
        Passage[] res = new Passage[resultat.size()];
        resultat.toArray(res);
        return res;
    }
    
    /**
     * Donne la liste des caractères présents dans ce document.
     * @return ladite liste
     */
    public String getListeCaracteres() {
        Map<Integer, String> res = new HashMap<>();
        for(Character c: listeCaracteresTotale()) {
            String uni = c.toString();
            String beta = Textes.betacodifie(uni);
            String reuni = Textes.unicodifie(beta);
            Character c2 = reuni.charAt(0);
            String s = ((int)(c.charValue())) + " - " + uni + 
                    " - " + beta + " -> " + reuni + " - "+
                    (int)(c2.charValue()) + "  (" + 
                    (uni.equals(reuni)?"OK":"ERREUR") + ")\n";
            /*String s = "s0 = s0.replaceAll(\"\"+(char)" + ((int)(c.charValue())) + 
                    ", \"" + beta + "\"); //" + uni + "\n";*/
            res.put(new Integer(c.charValue()), s);
        }
        Integer[] keys = new Integer[res.keySet().size()];
        res.keySet().toArray(keys);
        Arrays.sort(keys);
        String resultat = "";
        for(Integer i: keys) {
            resultat += res.get(i);
        }
        return resultat;
    }
    
    /**
     * Calcule récursivement l'ensemble des caractères présents dans ce Passage.
     * @return une structure contenant lesdits caractères
     */
    Set<Character> listeCaracteresTotale() {
        Set<Character> res = new HashSet<>();
        if(contenu == null) {
            for(Passage p: getAllMots()) {
                res.addAll(p.listeCaracteresTotale());
            }
        } else {
            for (int j = 0; j < contenu.getExpression().length(); j++) {
                res.add(new Character(contenu.getExpression().charAt(j)));
            }
        }
        return res;
    }
    
    private void resetIndex() {
        mots = null;
        motsNonVides = null;
        indexMotsSuivants.clear();
        indexMotsPrecedents.clear();
        indexMotsSuivantsSansMotsVides.clear();
        indexMotsPrecedentsSansMotsVides.clear();
        indexMotsSuivantsSansMotsStatistiquementVides.clear();
        indexMotsPrecedentsSansMotsStatistiquementVides.clear();
    }
    
    private boolean listeNonInitialisee = false;
    private void indexe() {
        resetIndex();
        Passage[] pmots = getAllMots();
        for(int i=0; i<pmots.length-1; i++) {
            indexMotsSuivants.put(pmots[i], pmots[i+1]);
        }
        for(int i=1; i<pmots.length; i++) {
            indexMotsPrecedents.put(pmots[i], pmots[i-1]);
        }
        pmots = getAllMotsNonVides();
        for(int i=0; i<pmots.length-1; i++) {
            indexMotsSuivantsSansMotsVides.put(pmots[i], pmots[i+1]);
        }
        for(int i=1; i<pmots.length; i++) {
            indexMotsPrecedentsSansMotsVides.put(pmots[i], pmots[i-1]);
        }
        try {
            pmots = getAllMotsNonStatistiquementVides();
            for(int i=0; i<pmots.length-1; i++) {
                indexMotsSuivantsSansMotsStatistiquementVides.put(pmots[i], pmots[i+1]);
            }
            for(int i=1; i<pmots.length; i++) {
                indexMotsPrecedentsSansMotsStatistiquementVides.put(pmots[i], pmots[i-1]);
            }
        } catch(ListeStatistiqueNonInitialiseeException lnie) {
            listeNonInitialisee = true;
        }
    }
    
    /**
     * Ecrit le XML correspondant à ce Passage dans le flux de sortie spécifié.
     * @param ici où écrire
     * @throws IOException en cas d'erreur d'écriture
     */
    /*public void exportXML(OutputStream ici) throws IOException {
        ici.write(Textes.ligne("  <passage>"));
        ici.write(Textes.ligne("    <titre>" + Textes.echappeXML(titre) + "</titre>"));
        if(contenu.isEmpty()) {
            for(Passage p: getSousPassages()) {
                p.exportXML(ici);
            }
        } else {
            ici.write(Textes.ligne("    <texte>"));
            for(Terme t: contenu) {
                if(t instanceof FinDePhrase) {
                    ici.write(Textes.ligne("     <findephrase/>"));
                } else {
                    ici.write(Textes.ligne("     <terme>" + Textes.echappeXML(t.getExpression()) + "</terme>"));
                }
            }
            ici.write(Textes.ligne("    </texte>"));
        }
        ici.write(Textes.ligne("  </passage>"));
        ici.flush();
    }*/
    
    /**
     * Lit le XML correspondant à un Passage dans le Node XML fourni.
     * @param passage une structure permettant de naviguer dans le XML
     * @return un Passage tout neuf
     */
    /*public static Passage chargementXML(Node passage) {
        Passage resultat = new Passage();
        NodeList elements = passage.getChildNodes();
        for(int i=0; i<elements.getLength(); i++) {
            Node passageOuTexte = elements.item(i);
            switch (passageOuTexte.getNodeName()) {
                case "titre":
                    String t = Textes.desechappeXML(passageOuTexte.getChildNodes().item(0).getNodeValue().trim());
                    resultat.titre = t;
                    break;
                case "texte":
                    String s = Textes.desechappeXML(passageOuTexte.getChildNodes().item(0).getNodeValue().trim());
                    if(s.length()>0) {
                        // comptabilité ascendante
                        resultat.setContenu(s);
                    } else {
                        NodeList termes = passageOuTexte.getChildNodes();
                        for(int j=0; j<termes.getLength(); j++) {
                            if(termes.item(j).getNodeName().equals("terme")) {
                                try {
                                    String terme = Textes.desechappeXML(termes.item(j).getChildNodes().item(0).getNodeValue().trim());
                                    resultat.contenu.add(Terme.cree(terme));
                                } catch(NullPointerException npe) {
                                    //System.err.println("terme vide");
                                }
                            } else if(termes.item(j).getNodeName().equals("findephrase")) {
                                resultat.contenu.add(new FinDePhrase());
                            }
                        }
                    }
                    break;
                case "passage":
                    resultat.addSousPassage(Passage.chargementXML(passageOuTexte));
                    break;
            }
        }
        return resultat;
    }*/
    
    // Versions Document
    /*@Override
    public void exportXML(OutputStream ici) throws IOException {
        // xml header
        ici.write(Textes.ligne("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"));
        // document metadata
        ici.write(Textes.ligne("<document>"));
        ici.write(Textes.ligne("  <titre>" + Textes.echappeXML(titre) + "</titre>"));
        ici.write(Textes.ligne("  <auteurs>"));
        ici.write(Textes.ligne("    <auteur>" + Textes.echappeXML(auteur.toString()) + "</auteur>"));
        ici.write(Textes.ligne("  </auteurs>"));
        // text
        super.exportXML(ici);
        // fin
        ici.write(Textes.ligne("</document>"));
        ici.flush();
        ici.close();
    }*/
    
    /*public static Document lectureXML(String fichier) throws DOMCreationFailureException {
        Document resultat = new Document("");
        File fLoad = new File(fichier);
        org.w3c.dom.Document doc = XMLReader.getDOM(fLoad);
        NodeList document = doc.getElementsByTagName("document");
        NodeList infosEtTexte = document.item(0).getChildNodes();
        for (int i = 0; i < infosEtTexte.getLength(); i++) {
            Node infoOuTexte = infosEtTexte.item(i);
            switch (infoOuTexte.getNodeName()) {
                case "titre":
                    resultat.titre = Textes.desechappeXML(infoOuTexte.getChildNodes().item(0).getNodeValue().trim());
                    break;
                case "auteurs":
                    int nbAuteurs = infoOuTexte.getChildNodes().getLength();
                    if(nbAuteurs > 0) {
                        resultat.auteur = new Personne(Textes.desechappeXML(infoOuTexte.getChildNodes().item(0).getNodeValue().trim()));
                        System.out.println(resultat.auteur.toString());
                    }
                    break;
                case "passage":
                    resultat.addSousPassage(Passage.chargementXML(infoOuTexte));
                    break;
            }
        }
        return resultat;
    }*/
    
    
    @Override
    public String toString() {
        String s = getTitre();
        if(hasContenu()) {
            s += " " + getContenu();
        } else if(hasParents()) {
            s += " " + getParents()[0].getCoordonnee(this);
        }
        return s;
    }
    
    @Override
    public boolean equals(Object o) {
        return o == this; // pointeurs égaux, passages égaux
    }
}
