/*
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.proc;

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.Citation;
import fr.cnrs.liris.drim.grt.modele.Passage;

/**
 *
 * @author Biblindex
 */
public class ComparateurCitations {
    /**
     * Le mode exact impose une parfaite superposition des citations (tous les
     * mots communs). C'est le mode normal de calcul.
     */
    public final static int IDENTIQUE_SI_SUPERPOSITION = 0;
    
    /**
     * Le mode chevauchement impose uniquement un mot commun de part et d'autre
     * pour donner le point.
     */
    public final static int IDENTIQUE_SI_CHEVAUCHEMENT = 1;
    
    /**
     * Quantité critique pour la multi-citation.
     * Si plusieurs citations ont un citant commun, elles seront traitées comme 
     * multi-citation : si leur quantité est sous-critique ou subcritique, elles 
     * seront toutes justes ; 
     * si elle est supercritique, elles seront toutes fausses.
     * 
     * Le raisonnement consiste à dire que si l'on ne sait pas quel est le texte 
     * cité, peu de choix est intéressant à explorer (donc pertinent), trop de 
     * choix dénote une expression à filtrer (donc même si on a trouvé la bonne, 
     * c'est par pure chance).
     * 
     * La valeur a été fixée à 5 par les praticiens.
     */
    public final static int NIVEAU_CRITIQUE_MULTICITATION = 5;
    
    /**
     * Le mode brut donne les résultats bruts.
     */
    public final static boolean METRIQUES_BRUTES = true;
    
    /**
     * Le mode épuré donne les résultats purifiés : si plusieurs citations sont 
     * trouvées pour un même texte, si leur nombre est inférieur à 
     * NIVEAU_CRITIQUE_MULTICITATION, toutes sont considérées comme justes 
     * (avis d'expert nécessaire, donc on doit les renvoyer). S'il est 
     * supérieur, toutes sont considérées fausses (même celle qui se trouvait 
     * être juste).
     */
    public final static boolean METRIQUES_EPUREES = false;
    
    /**
     * Donne le rappel d'un ensemble de résultat par rapport à un ensemble témoin.
     * Le rappel étant le nombre de résultats attendus qui apparaissent effectivement 
     * dans l'ensemble proposé.
     * @param aTrouver l'ensemble de résultats attendus
     * @param trouvees l'ensemble de résultats effectif
     * @param IDENTIQUE_SI_QQCH le IDENTIQUE_SI_QQCH de sévérité du calcul
     * @return un nombre entre 0 et 1
     */
    public static double getRappel(Citation[] trouvees , Citation[] aTrouver, int IDENTIQUE_SI_QQCH, boolean METRIQUES_QQCH) {
        int ok = 0;
        for(Citation temoin : aTrouver) {
            if(isTrouvee(temoin, trouvees, IDENTIQUE_SI_QQCH, METRIQUES_QQCH)) {
                ok++;
            }
        }
        return 1.0 * ok / aTrouver.length;
    }
    
    /**
     * Donne le nombre rappel d'un ensemble de résultat par rapport à un ensemble témoin.
     * Le rappel étant le nombre de résultats attendus qui apparaissent effectivement 
     * dans l'ensemble proposé.
     * @param aTrouver l'ensemble de résultats attendus
     * @param trouvees l'ensemble de résultats effectif
     * @param IDENTIQUE_SI_QQCH le IDENTIQUE_SI_QQCH de sévérité du calcul
     * @return un nombre entre 0 et 1
     */
    public static int getBienTrouvees(Citation[] trouvees , Citation[] aTrouver, int IDENTIQUE_SI_QQCH, boolean METRIQUES_QQCH) {
        int ok = 0;
        for(Citation temoin : aTrouver) {
            if(isTrouvee(temoin, trouvees, IDENTIQUE_SI_QQCH, METRIQUES_QQCH)) {
                ok++;
            }
        }
        return ok;
    }
    
    /**
     * Donne la précision d'un ensemble de résultat par rapport à un ensemble témoin.
     * La précision étant le nombre de résultats proposés qui sont attendus.
     * @param aTrouver l'ensemble de résultats attendus
     * @param trouvees l'ensemble de résultats effectif
     * @param IDENTIQUE_SI_QQCH le IDENTIQUE_SI_QQCH de sévérité du calcul
     * @return un nombre entre 0 et 1
     */
    public static double getPrecision(Citation[] trouvees , Citation[] aTrouver, int IDENTIQUE_SI_QQCH, boolean METRIQUES_QQCH) {
        int ok = 0;
        for(Citation proposition : trouvees) {
            if(isATrouver(proposition, aTrouver, IDENTIQUE_SI_QQCH)) {
                if(METRIQUES_QQCH) {
                    ok++;
                } else {
                    int nb = getNbMulticitationsDans(proposition, trouvees);
                    if(nb <= NIVEAU_CRITIQUE_MULTICITATION) {
                        ok+= nb;
                    } else {
                        ok += 0;
                    }
                }
            }
        }
        return 1.0 * ok / trouvees.length;
    }
    
    /**
     * Donne le nombre précision d'un ensemble de résultat par rapport à un ensemble témoin.
     * La précision étant le nombre de résultats proposés qui sont attendus.
     * @param aTrouver l'ensemble de résultats attendus
     * @param trouvees l'ensemble de résultats effectif
     * @param IDENTIQUE_SI_QQCH le IDENTIQUE_SI_QQCH de sévérité du calcul
     * @return un nombre entre 0 et 1
     */
    public static int getBonnesReponses(Citation[] trouvees , Citation[] aTrouver, int IDENTIQUE_SI_QQCH, boolean METRIQUES_QQCH) {
        int ok = 0;
        for(Citation proposition : trouvees ) {
            if(isATrouver(proposition, aTrouver, IDENTIQUE_SI_QQCH)) {
                if(METRIQUES_QQCH) {
                    ok++;
                } else {
                    int nb = getNbMulticitationsDans(proposition, trouvees);
                    if(nb <= NIVEAU_CRITIQUE_MULTICITATION) {
                        ok+= nb;
                    } else {
                        ok += 0;
                    }
                }
            }
        }
        return ok;
    }
    
    public static boolean[] getSpectre(Citation[] trouvees, Citation[] aTrouver, int IDENTIQUE_SI_QQCH, boolean METRIQUES_QQCH) {
        boolean[] resultat = new boolean[aTrouver.length];
        for(int i=0; i<aTrouver.length; i++) {
            resultat[i] = isTrouvee(aTrouver[i], trouvees, IDENTIQUE_SI_QQCH, METRIQUES_QQCH);
        }
        return resultat;
    }
    
    public static String getSpectreTxt(Citation[] trouvees, Citation[] aTrouver, int IDENTIQUE_SI_QQCH, boolean METRIQUES_QQCH) {
        String resultat = "";
        for(Citation temoin : aTrouver ) {
            resultat += isTrouvee(temoin, trouvees, IDENTIQUE_SI_QQCH, METRIQUES_QQCH) ? "1" : " ";
        }
        return resultat;
    }
    
    public static Passage[] getSources(Citation[] trouvees, Citation[] aTrouver, int IDENTIQUE_SI_QQCH) {
        Passage[] resultat = new Passage[aTrouver.length];
        for(int i=0; i<aTrouver.length; i++) {
            Citation temoin = aTrouver[i];
            Passage trouve = Passage.creeCollectionDePassages(new Passage[]{});
            for(Citation proposition : trouvees) {
                if(IDENTIQUE_SI_QQCH == IDENTIQUE_SI_SUPERPOSITION) {
                    if(isIdentique(temoin, proposition)) {
                        trouve = proposition.getCite();
                        break;
                    }
                } else {
                    if(isSuffisammentSemblable(temoin, proposition)) {
                        trouve = proposition.getCite();
                        break;
                    }
                }
            }
            resultat[i] = trouve;
        }
        return resultat;
    }
    
    public static Passage[] getCitants(Citation[] trouvees, Citation[] aTrouver, int IDENTIQUE_SI_QQCH) {
        Passage[] resultat = new Passage[aTrouver.length];
        for(int i=0; i<aTrouver.length; i++) {
            Citation temoin = aTrouver[i];
            Passage trouve = Passage.creeCollectionDePassages(new Passage[]{});
            for(Citation proposition : trouvees) {
                if(IDENTIQUE_SI_QQCH == IDENTIQUE_SI_SUPERPOSITION) {
                    if(isIdentique(temoin, proposition)) {
                        trouve = proposition.getCiteur();
                        break;
                    }
                } else {
                    if(isSuffisammentSemblable(temoin, proposition)) {
                        trouve = proposition.getCiteur();
                        break;
                    }
                }
            }
            resultat[i] = trouve;
        }
        return resultat;
    }
    
    public static Map<Citation, ArrayList<Citation>> getCitationsTrouvees(Citation[] trouvees, Citation[] aTrouver, int IDENTIQUE_SI_QQCH) {
        Map<Citation, ArrayList<Citation>> resultat = new HashMap<>();
        for(Citation temoin : aTrouver ) {
            ArrayList<Citation> trouvee = null;
            for(Citation proposition : trouvees) {
                if(IDENTIQUE_SI_QQCH == IDENTIQUE_SI_SUPERPOSITION) {
                    if(isIdentique(temoin, proposition)) {
                        trouvee.add(proposition);
                        break;
                    }
                } else if(isSuffisammentSemblable(temoin, proposition)) {
                    if(trouvee == null) {
                        trouvee = new ArrayList<>();
                        trouvee.add(proposition);
                    } else {
                        trouvee.add(proposition);
                    }
                }
            }
            if(trouvee != null) {
                resultat.put(temoin, trouvee);
            }
        }
        return resultat;
    }
    
    public static Citation getCitationAttendue(Citation trouvee, Citation[] aTrouver, int IDENTIQUE_SI_QQCH) {
        Citation resultat = null;
        System.err.println("Demande\n"+trouvee);
        for(Citation temoin : aTrouver ) {
            if(IDENTIQUE_SI_QQCH == IDENTIQUE_SI_SUPERPOSITION) {
                if(isIdentique(temoin, trouvee)) {
                    resultat = temoin;
                    break;
                }
            } else if(isSuffisammentSemblable(temoin, trouvee)) {
                resultat = temoin;
                System.err.println("Trouvé !");
                break;
            }
        }
        return resultat;
    }
    
    public static Citation[] getCitationsNonTrouvees(Citation[] trouvees, Citation[] aTrouver, int IDENTIQUE_SI_QQCH) {
        ArrayList<Citation> resultat = new ArrayList<>();
        for(Citation temoin : aTrouver ) {
            boolean trouvee = false;
            for(Citation proposition : trouvees) {
                if(IDENTIQUE_SI_QQCH == IDENTIQUE_SI_SUPERPOSITION) {
                    if(isIdentique(temoin, proposition)) {
                        trouvee = true;
                        break;
                    }
                } else if(isSuffisammentSemblable(temoin, proposition)) {
                    trouvee = true;
                    break;
                }
            }
            if(!trouvee) {
                resultat.add(temoin);
            }
        }
        Citation[] res = new Citation[resultat.size()];
        resultat.toArray(res);
        return res;
    }
    
    /**
     * Donne la liste des citations trouvées qui n'étaient pas à trouver (et qui 
     * ne comptent donc pas pour la précision).
     * param aTrouver l'ensemble de résultats attendus
     * @param trouvees l'ensemble de résultats effectif
     * @param IDENTIQUE_SI_QQCH le IDENTIQUE_SI_QQCH de sévérité du calcul
     * @return ladite liste
     */
    public static Citation[] getNouvellesCitations(Citation[] trouvees , Citation[] aTrouver, int IDENTIQUE_SI_QQCH) {
        ArrayList<Citation> resultat = new ArrayList<>();
        for(Citation proposition : trouvees) {
            boolean listee = false;
            for(Citation temoin : aTrouver ) {
                if(IDENTIQUE_SI_QQCH == IDENTIQUE_SI_SUPERPOSITION) {
                    if(isIdentique(temoin, proposition)) {
                        listee = true;
                        break;
                    }
                } else if(isSuffisammentSemblable(temoin, proposition)) {
                    listee = true;
                    break;
                }
            }
            if(!listee) {
                resultat.add(proposition);
            }
        }
        Citation[] res = new Citation[resultat.size()];
        resultat.toArray(res);
        return res;
    }
    
    /**
     * Donne parmi la liste des nouvelles citations (@see getNouvellesCitations) 
     * celles qui ont comme texte citant un texte citant à trouver mais qui
     * n'ont pas un texte citeur correspondant.
     * param aTrouver l'ensemble de résultats attendus
     * @param trouvees l'ensemble de résultats effectif
     * @param IDENTIQUE_SI_QQCH le IDENTIQUE_SI_QQCH de sévérité du calcul
     * @return ladite liste
     */
    public static Citation[] getCitationsSurnumeraires(Citation[] trouvees , Citation[] aTrouver, int IDENTIQUE_SI_QQCH) {
        ArrayList<Citation> nouvelles = new ArrayList<>(Arrays.asList(getNouvellesCitations(trouvees, aTrouver, IDENTIQUE_SI_QQCH)));
        ArrayList<Citation> resultat = new ArrayList<>();
        for(Citation proposition : nouvelles) {
            //System.err.println("Citation nouvelle évaluée");
            boolean listee = false;
            for(Citation temoin : aTrouver ) {
                if(IDENTIQUE_SI_QQCH == IDENTIQUE_SI_SUPERPOSITION) {
                    if(isCitantIdentique(temoin, proposition)) {
                        listee = true;
                        break;
                    }
                } else if(isCitantSuffisammentSemblable(temoin, proposition)) {
                    listee = true;
                    break;
                }
            }
            if(listee) {
                //System.err.println(" Citation surnuméraire trouvée !");
                resultat.add(proposition);
            }
        }
        Citation[] res = new Citation[resultat.size()];
        resultat.toArray(res);
        return res;
    }
    
    /**
     * Donne parmi la liste des nouvelles citations (@see getNouvellesCitations) 
     * celles qui ont comme texte citant un texte citant à trouver mais qui
     * n'ont pas un texte citeur correspondant. Donne pour chacune la citation
     * à trouver qui a le même texte citant.
     * param aTrouver l'ensemble de résultats attendus
     * @param trouvees l'ensemble de résultats effectif
     * @param IDENTIQUE_SI_QQCH le IDENTIQUE_SI_QQCH de sévérité du calcul
     * @return un dictionnaire avec pour clé les citations surnuméraires, 
     * en valeur les citations qui étaitent à trouver
     */
    public static Map<Citation, Citation> getCitationsSurnumerairesEtAttendues(Citation[] trouvees , Citation[] aTrouver, int IDENTIQUE_SI_QQCH) {
        Map<Citation, Citation> resultat = new HashMap<>();
        ArrayList<Citation> nouvelles = new ArrayList<>(Arrays.asList(getNouvellesCitations(trouvees, aTrouver, IDENTIQUE_SI_QQCH)));
        for(Citation proposition : nouvelles) {
            Citation listee = null;
            for(Citation temoin : aTrouver ) {
                if(IDENTIQUE_SI_QQCH == IDENTIQUE_SI_SUPERPOSITION) {
                    if(isCitantIdentique(temoin, proposition)) {
                        listee = temoin;
                        break;
                    }
                } else if(isCitantSuffisammentSemblable(temoin, proposition)) {
                    listee = temoin;
                    break;
                }
            }
            if(listee != null) {
                resultat.put(proposition, listee);
            }
        }
        return resultat;
    }
    
    /**
     * Donne parmi la liste des nouvelles citations (@see getNouvellesCitations) 
     * celles qui n'ont pas comme texte citant un texte citant à trouver.
     * param aTrouver l'ensemble de résultats attendus
     * @param trouvees l'ensemble de résultats effectif
     * @param IDENTIQUE_SI_QQCH le IDENTIQUE_SI_QQCH de sévérité du calcul
     * @return ladite liste
     */
    public static Citation[] getCitationsInedites(Citation[] trouvees , Citation[] aTrouver, int IDENTIQUE_SI_QQCH) {
        Set<Citation> nlles = new HashSet<>(Arrays.asList(getNouvellesCitations(trouvees, aTrouver, IDENTIQUE_SI_QQCH)));
        Set<Citation> surn = new HashSet<>(Arrays.asList(getCitationsSurnumeraires(trouvees, aTrouver, IDENTIQUE_SI_QQCH)));
        nlles.removeAll(surn);
        
        Citation[] res = new Citation[nlles.size()];
        nlles.toArray(res);
        return res;
    }
    
    /**
     * Détermine si deux citations sont suffisamment proches, en termes de 
     * mots contenus, pour être jugées semblables.
     * 
     * Pour cela, en première approche, il suffit qu'il y ait un mot commun de 
     * chaque côté (mot au sens de @see Passage.creeMot(), et non d'ensemble de 
     * lettres).
     * Le terme 'commun' est ici à comprendre au sens de @see Passage.equals().
     * 
     * @param c1 une citation
     * @param c2 une autre citation
     * @return vrai ou faux
     */
    private static boolean isSuffisammentSemblable(Citation c1, Citation c2) {
        return c1.getCite().chevauche(c2.getCite()) && c1.getCiteur().chevauche(c2.getCiteur());
        
        /*boolean cite = false;
        boolean citeur = false;
        // Test sur le cité
        ArrayList<Passage> m = new ArrayList<>();
        m.addAll(Arrays.asList(c1.getCite().getSousPassages()));
        for(Passage p: c2.getCite().getSousPassages()) {
            if(m.contains(p)) {
                cite = true;
                break;
            }
        }
        if(cite) {
            // Test sur le citeur
            m.clear();
            m.addAll(Arrays.asList(c1.getCiteur().getSousPassages()));
            for(Passage p: c2.getCiteur().getSousPassages()) {
                if(m.contains(p)) {
                    citeur = true;
                    break;
                }
            }
        }
        return cite && citeur;*/
    }
    
    private static boolean isCitantSuffisammentSemblable(Citation c1, Citation c2) {
        return c1.getCiteur().chevauche(c2.getCiteur());
        /*boolean citeur = false;
        // Test sur le cité
        ArrayList<Passage> m = new ArrayList<>();
        // Test sur le citeur
        m.addAll(Arrays.asList(c1.getCiteur().getSousPassages()));
        for(Passage p: c2.getCiteur().getSousPassages()) {
            if(m.contains(p)) {
                citeur = true;
                break;
            }
        }
        return citeur;*/
    }
    
    private static boolean isIdentique(Citation c1, Citation c2) {
        return c1.getCite().aLesMemesMotsQue(c2.getCite()) && c1.getCiteur().aLesMemesMotsQue(c2.getCiteur());
        /*if(c1.getCite().getNbSousPassages() != c2.getCite().getNbSousPassages()) {
            return false;
        }
        if(c1.getCiteur().getNbSousPassages() != c2.getCiteur().getNbSousPassages()) {
            return false;
        }
        boolean resultat = true;
        for(int i=0; i<c1.getCite().getNbSousPassages(); i++) {
            if(!(c1.getCite().getSousPassages()[i].equals(c2.getCite().getSousPassages()[i]))) {
                resultat = false;
                break;
            }
        }
        if(resultat) {
            for(int i=0; i<c1.getCiteur().getNbSousPassages(); i++) {
                if(!(c1.getCiteur().getSousPassages()[i].equals(c2.getCiteur().getSousPassages()[i]))) {
                    resultat = false;
                    break;
                }
            }
        }
        return resultat;*/
    }
    
    private static boolean isCitantIdentique(Citation c1, Citation c2) {
        return c1.getCiteur().aLesMemesMotsQue(c2.getCiteur());
        /*if(c1.getCiteur().getNbSousPassages() != c2.getCiteur().getNbSousPassages()) {
            return false;
        }
        boolean resultat = true;
        for(int i=0; i<c1.getCiteur().getNbSousPassages(); i++) {
            if(!(c1.getCiteur().getSousPassages()[i].equals(c2.getCiteur().getSousPassages()[i]))) {
                resultat = false;
                break;
            }
        }
        return resultat;*/
    }
    
    private static int getNbMulticitationsDans(Citation c, Citation[] liste) {
        int resultat = 0;
        for(Citation cc: liste) {
            // Multicitation = même citant, différents cités
            if(isCitantSuffisammentSemblable(c, cc) && !isSuffisammentSemblable(c, cc)) {
                resultat ++;
            }
        }
        return resultat;
    }
    
    private static boolean isATrouver(Citation proposition, Citation[] aTrouver, int IDENTIQUE_SI_QQCH) {
        boolean ok = false;
        for(Citation temoin : aTrouver) {
            if(IDENTIQUE_SI_QQCH == IDENTIQUE_SI_SUPERPOSITION) {
                if(isIdentique(temoin, proposition)) {
                    ok = true;
                    break;
                }
            } else {
                if(isSuffisammentSemblable(temoin, proposition)) {
                    ok = true;
                    break;
                }
            }
        }
        return ok;
    }
    
    private static boolean isTrouvee(Citation temoin, Citation[] trouvees, int IDENTIQUE_SI_QQCH, boolean METRIQUES_QQCH) {
        boolean ok = false;
        for(Citation proposition : trouvees) {
            if(IDENTIQUE_SI_QQCH == IDENTIQUE_SI_SUPERPOSITION) {
                if(isIdentique(temoin, proposition)) {
                    if(METRIQUES_QQCH || getNbMulticitationsDans(proposition, trouvees) <= NIVEAU_CRITIQUE_MULTICITATION) {
                        ok = true;
                    }
                    break;
                }
            } else {
                if(isSuffisammentSemblable(temoin, proposition)) {
                    if(METRIQUES_QQCH || getNbMulticitationsDans(proposition, trouvees) <= NIVEAU_CRITIQUE_MULTICITATION) {
                        ok = true;
                    }
                    break;
                }
            }
        }
        return ok;
    }
}
