/*
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.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import fr.cnrs.liris.drim.grt.modele.listes.Lemmatiseurs;
import fr.cnrs.liris.drim.grt.modele.listes.LemmesVides;
import fr.cnrs.liris.drim.grt.proc.parsers.Textes;

/**
 *
 * @author sgesche
 */
public class Terme {
    private static Map<String, Terme> instances = new HashMap<>();
    
    private String expression = "";
    
    /**
     * Crée une instance de Terme avec l'expression demandée. S'assure qu'une 
     * instance d'exécution ne contient au maximum qu'une instance de Terme avec 
     * chaque expression.
     * @param expression la chaîne de caractère contenue dans le Terme
     * @return un Terme
     */
    public static Terme cree(String expression) {
        String xp = Textes.nettoieTerme(expression);
        if(!instances.containsKey(xp)) {
            instances.put(xp, new Terme(xp));
        }
        return instances.get(xp);
    }
    
    /**
     * Crée une instance de Terme avec l'expression et les formes demandées. S'assure qu'une 
     * instance d'exécution ne contient au maximum qu'une instance de Terme avec 
     * chaque expression.
     * 
     * Un Terme créé de cette façon n'utilisera pas les ressources de TALN pour
     * calculer ses formes normale et lemmatisée, mais utilisera les formes
     * passées en paramètre à la place. Ce comportement ne vaut que pour le Terme
     * lui-même, pas pour ses Termes dérivés (par exemple lesdites formes normale
     * et lemmatisée).
     * 
     * @param expression la chaîne de caractère contenue dans le Terme
     * @param formeNormale la forme normalisée de ladite chaîne
     * @param lemme le lemme attaché à ladite chaîne
     * @return un Terme
     */
    public static Terme cree(String expression, String formeNormale, String lemme) {
        String xp = Textes.nettoieTerme(expression);
        String norm = Textes.nettoieTerme(formeNormale);
        final Terme tNorm = Terme.cree(norm);
        String lem = Textes.nettoieTerme(lemme);
        final Lemme tLem = Lemme.cree(lem);
        
        instances.put(xp, new Terme(xp) {
            @Override
            public Terme getFormeNormale() {
                return tNorm;
            }
            
            @Override
            public Lemme getLemmePrincipal() {
                return tLem;
            }
            
            @Override
            public Lemme[] getFormesLemmatisees() {
                return new Lemme[]{tLem};
            }
            
        });
        return instances.get(xp);
    }
    
    
    protected Terme() {
        cree("");
    }
    
    protected Terme(String expression) {
        this.expression = expression;
    }
    
    public String getExpression() {
        return expression;
    }
    
    private Terme formeNormale;
    public Terme getFormeNormale() {
        if(formeNormale == null) {
            formeNormale = new Terme(Textes.normalise(expression));
        }
        return formeNormale;
    }
    
    public Lemme[] getFormesLemmatisees() {
        String[] liste = Lemmatiseurs.getToutesFormesLemmatisees(expression);
        if(liste.length==0) {
            liste = new String[]{expression};
        }
        Lemme[] resultat = new Lemme[liste.length];
        for(int i=0; i<liste.length; i++) {
            resultat[i] = Lemme.cree(liste[i]);
        }
        return resultat;
    }
    
    private Lemme lemmePrincipal;
    public Lemme getLemmePrincipal() {
        if(lemmePrincipal != null) {
            return lemmePrincipal;
        }
        String res = Lemmatiseurs.getFormeLemmatisee(expression);
        Lemme resultat;
        resultat = Lemme.cree(res);
        lemmePrincipal = resultat;
        return resultat;
    }
    
    public boolean hasLemmeCommun(Terme terme) {
        // tentative d'optimisation : on teste si l'ajout d'un des lemmes à un
        // ensemble résulte en un ajout ou non
        
        // rappel : deux termes sont considérés comme identiques d'un point de
        // vue de leurs lemmes s'ils ont une forme lemmatisée potentielle en
        // commun
        boolean resultat = false;
        Set<String> test = new HashSet<>();
        for(Lemme l: getFormesLemmatisees()) {
            test.add(l.getExpression());
        }
        for(Lemme l: terme.getFormesLemmatisees()) {
            if(!test.add(l.getExpression())) {
                resultat = true;
                break;
            }
        }
        return resultat;
    }
    
    /**
     * Détermine si oui ou non ce terme est dans la liste des mots vides.
     * 
     * Les marqueurs de fin de phrase sont des mots vides.
     * 
     * @return oui ou non
     */
    public boolean estUnMotVide() {
        return LemmesVides.contientTerme(this) || estUneFinDePhrase() || 
                LemmesVides.contientUnDe(getFormesLemmatisees());
    }
    
    public boolean estUneFinDePhrase() {
        return (expression.equals(".") || expression.equals(";"));
    }
    
    /**
     * Déclare si oui ou non deux termes sont identiques.
     * Deux termes sont identiques si et seulement s'ils ont la même expression.
     * Utiliser terme.getExpression().equals() à la place.
     * @param autreTerme un terme à comparer
     * @return oui ou non, suivant si ce Terme est égal à celui proposé
     */
    @Override
    public boolean equals(Object autreTerme) {
        if(!(autreTerme instanceof Terme)) {
            return false;
        }
        Terme autre = (Terme)autreTerme;
        return autre.expression.equals(expression);
    }

    @Override
    public int hashCode() {
        return expression.hashCode();
    }
    
    @Override
    public String toString() {
        return getExpression();
    }
}
