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

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.Lemme;
import fr.cnrs.liris.drim.grt.modele.Passage;
import fr.cnrs.liris.drim.grt.modele.Terme;
import fr.cnrs.liris.drim.grt.proc.Processus;
import fr.cnrs.liris.drim.grt.proc.Texte;

/**
 *
 * @author Sam
 */
public class AnalyseurSpectral implements Processus {
    private ArrayList<Terme[]> blocsDeMots;
    private DistanceStatisticoSemantique distance;
    private long momentDebut;
    private long momentFin;
    
    public final static int SEPARATEUR_PHRASE = 0;
    public final static int SEPARATEUR_VERSET = 1;
    public final static int SEPARATEUR_LIGNE = 2;
    public final static int SEPARATEUR_NB_MOTS = 3;
    
    public final static int TRAITEMENT_AUCUN = 0;
    public final static int TRAITEMENT_NORMALISATION = 1;
    public final static int TRAITEMENT_LEMMATISATION = 2;
    
    public final static int TERMES_TOUS_SAUFS_VIDES = 0;
    public final static int TERMES_LEMMATISES_RECURRENTS_UNIQUEMENT = 1;
    
    private double avancement = 0.0;
    private String statut = "";
    private int nbMotsParUnite = 10;
    
    public AnalyseurSpectral() {
        blocsDeMots = new ArrayList<>();
        distance = new DistanceStatisticoSemantique();
    }
    
    public void addDocuments(Map<Passage, Integer> corpusAvecSeparateurs) {
        for(Passage p: corpusAvecSeparateurs.keySet()) {
            addDocument(p, corpusAvecSeparateurs.get(p));
        }
    }
    
    public void addDocument(Passage document, Integer SEPARATEUR_QQCH) {
        System.gc();
        // Evitons les problèmes de mémoire : 50% max utilisés par l'analyseur
        // (attention : pas moyen de savoir ce que l'analyseur utilise, donc 
        // les 50% font référence au poids total de la VM
        long moUsed = (Runtime.getRuntime().totalMemory()/1024/1024) - 
                    (Runtime.getRuntime().freeMemory()/1024/1024);
        long moUsable = (Runtime.getRuntime().maxMemory()/1024/1024);
        if(moUsed < 0.75* moUsable) {
            Passage[] aAjouter;
            if(SEPARATEUR_QQCH == SEPARATEUR_PHRASE) {
                aAjouter = new Texte(document).getPhrases(false);
            } else if(SEPARATEUR_QQCH == SEPARATEUR_VERSET) {
                aAjouter = new Texte(document).getVersets();
            } else if(SEPARATEUR_QQCH == SEPARATEUR_LIGNE) {
                aAjouter = new Texte(document).getLignes();
            } else  {
                aAjouter = new Texte(document).getBlocsMots(nbMotsParUnite, false);
            }
            for(Passage p: aAjouter) {
                blocsDeMots.add(new Texte(p).getContenu());
            }
        }
    }
    
    public void addBlocDeMots(Terme[] bloc) {
        blocsDeMots.add(bloc);
    }
    
    public void setNbMotsParBloc(int quantite) {
        nbMotsParUnite = quantite;
        if(nbMotsParUnite < 2) {
            nbMotsParUnite = 2;
        }
    }
    
    public void calculeDistance(int nbFrequents, int TRAITEMENT_QQCH, int TERMES_QQCH) {
        momentDebut = System.currentTimeMillis();
        avancement = 0.0;
        // Lister les textes à étudier et en faire un unique grand texte
        ArrayList<Terme> blocs = new ArrayList<>();
        for(Terme[] bloc: blocsDeMots) {
            blocs.addAll(Arrays.asList(bloc));
        }
        Terme[] blocs2 = new Terme[blocs.size()];
        blocs.toArray(blocs2);
        Texte masseTextuelle = new Texte(blocs2);
        avancement = 0.04;
        
        // Lister les nbLemmesFrequents termes les plus fréquents et la totalité des autres lemmes
        Terme[] termesLesPlusFrequents;
        Set<Terme> termesAEtudier;
        if(TERMES_QQCH == TERMES_TOUS_SAUFS_VIDES) {
            switch(TRAITEMENT_QQCH) {
                case TRAITEMENT_NORMALISATION:
                    termesLesPlusFrequents = masseTextuelle.getTermesNormalisesLesPlusFrequents(nbFrequents, false);
                    termesAEtudier = masseTextuelle.getTousTermesNormalises();
                    break;
                case TRAITEMENT_LEMMATISATION:
                    termesLesPlusFrequents = masseTextuelle.getLemmesLesPlusFrequents(nbFrequents, false);
                    Set<Lemme> lae = masseTextuelle.getTousLemmes();
                    termesAEtudier = new HashSet<Terme>(lae);
                    break;
                default:
                    termesLesPlusFrequents = masseTextuelle.getTermesLesPlusFrequents(nbFrequents, false);
                    termesAEtudier = masseTextuelle.getTousTermes();
                    break;
            }
        } else { // TERMES_QQCH == TERMES_LEMMATISES_RECURRENTS_UNIQUEMENT
            termesLesPlusFrequents = masseTextuelle.getLemmesRecurrentsLesPlusFrequents(nbFrequents);
            Set<Lemme> lae = masseTextuelle.getTousLemmes();
            termesAEtudier = new HashSet<Terme>(lae);
        }
        // Enlever les termes fréquents eux-mêmes ne fait que rajouter des problèmes dans l'étude des textes ensuite
        /*Set<Terme> termesAEnlever = new HashSet<>(Arrays.asList(termesLesPlusFrequents));
        termesAEtudier.removeAll(termesAEnlever);*/
        
        avancement = 0.06;
        
        // Indexer les apparitions de lemmes dans les phrases ou versets
        Map<Terme, Set<Integer>> occurrencesTermesFrequents = new HashMap<>();
        Map<Terme, Set<Integer>> occurrencesTermesNonFrequents = new HashMap<>();
        Set<Terme> frequents = new HashSet<>(Arrays.asList(termesLesPlusFrequents));
        for(int i=0; i<blocsDeMots.size(); i++) {
            avancement = 0.06 + 0.24*i/blocsDeMots.size();
            Texte sujetEtude = new Texte(blocsDeMots.get(i));
            Set<Terme> listeTermes;
            switch(TRAITEMENT_QQCH) {
                case TRAITEMENT_NORMALISATION:
                    listeTermes = sujetEtude.getTousTermesNormalises();
                    break;
                case TRAITEMENT_LEMMATISATION:
                    Set<Lemme> ll = sujetEtude.getTousLemmes();
                    listeTermes = new HashSet(ll);
                    break;
                default:
                    listeTermes = sujetEtude.getTousTermes();
                    break;
            }
            for(Terme l: listeTermes) {
                if(frequents.contains(l)) {
                    if(!occurrencesTermesFrequents.containsKey(l)) {
                        occurrencesTermesFrequents.put(l, new HashSet<Integer>());
                    }
                    occurrencesTermesFrequents.get(l).add(i);
                }
                if(!occurrencesTermesNonFrequents.containsKey(l)) {
                    occurrencesTermesNonFrequents.put(l, new HashSet<Integer>());
                }
                occurrencesTermesNonFrequents.get(l).add(i);
            }
        }
        
        avancement = 0.30;
        
        // Pour chaque lemme à étudier et chaque lemme fréquent, lister le nombre de lieux où ils co-occurrent
        Map<Terme, int[]> cooccurrences = new HashMap<>();
        ArrayList<Terme> termesAEtudier2 = new ArrayList<>(termesAEtudier);
        for(int i=0; i<termesAEtudier2.size(); i++) {
            avancement = 0.30 + 0.50*i/termesAEtudier2.size();
            Terme aEtudier = termesAEtudier2.get(i);
            int[] coocs = new int[termesLesPlusFrequents.length];
            Set<Integer> occurrences = occurrencesTermesNonFrequents.get(aEtudier);
            for(int j=0; j<termesLesPlusFrequents.length; j++) {
                Set<Integer> a = new HashSet<>(occurrences);
                Set<Integer> b = occurrencesTermesFrequents.get(termesLesPlusFrequents[j]);
                a.retainAll(b); // intersection des passages ayant le terme fréquent et de ceux ayant le terme non fréquent
                coocs[j] = a.size();
            }
            cooccurrences.put(aEtudier, coocs);
        }
        
        avancement = 0.80;
        
        // Normaliser les spectres de co-occurrence
        ArrayList<Terme> ls = new ArrayList<>(cooccurrences.keySet());
        for(int i=0; i<ls.size(); i++) {
            avancement = 0.80 + 0.20*i/ls.size();
            Terme t = ls.get(i);
            int[] scores = cooccurrences.get(t);
            int qt = 0;
            for(int s: scores) {
                qt += s;
            }
            double[] scoresNorm = new double[scores.length];
            if(qt>0) {
                for(int j=0; j<scoresNorm.length; j++) {
                    scoresNorm[j] = 1.0 * scores[j] / qt;
                }
            }
            distance.addTerme(t, scoresNorm);
        }
        
        // Fini !
        avancement = 1.0;
    }
    
    public DistanceStatisticoSemantique getDistance() {
        return distance;
    }

    @Override
    public double getAvancement() {
        return avancement;
    }

    @Override
    public String getStatut() {
        return statut;
    }
    
    @Override
    public long getTempsExecution() {
        if(avancement == 1.0) {
            return momentFin - momentDebut;
        } else {
            return System.currentTimeMillis() - momentDebut;
        }
    }
}
