/*
 * #%L
 * IsisFish data
 * %%
 * Copyright (C) 2006 - 2015 Ifremer, CodeLutin
 * %%
 * This program 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.
 * 
 * This program 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 this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 * #L%
 */
package rules;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import scripts.SiMatrix;

import java.util.Arrays;
import java.util.List;
import java.util.Collection;

import org.nuiton.math.matrix.*;

import fr.ifremer.isisfish.simulator.SimulationContext;
import fr.ifremer.isisfish.types.TimeStep;
import resultinfos.MatrixEffortNominalPerStrategyMet;
import resultinfos.MatrixGrossValueOfLandingsPerStrategyMet;
import fr.ifremer.isisfish.types.Month;
import fr.ifremer.isisfish.entities.*;
import fr.ifremer.isisfish.rule.AbstractRule;
import fr.ifremer.isisfish.simulator.ResultManager;

/**
 * GraviteVPUE1.java
 *
 * Created: 26 aout 2008
 *
 * @author anonymous &lt;anonymous@labs.libre-entreprise.org&gt;
 * @version $Revision$
 *
 * Last update: $Date$
 * by : $Author: 2SY- Sigrid+Youen+Stephanie $
 */
public class GraviteVPUE1 extends AbstractRule {

    /** to use log facility, just put in your code: log.info("..."); */
    static private Log log = LogFactory.getLog(GraviteVPUE1.class);

    /** stocke la somme des prop initiales [str x month] */
    protected MatrixND SommePropInitial = null;

    /** permet de stocker les CPUE nominales [str x met] */
    protected MatrixND valuePerUnitOfEffort = null;

    // Booleen permettant que ne boucler que sur un seul metier dans la preaction :
    protected boolean first = true;

    protected String[] necessaryResult = {
        // put here all necessary result for this rule
        // example: 
        // MatrixBiomass.NAME,
        // MatrixNetValueOfLandingsPerStrategyMet.NAME
        MatrixGrossValueOfLandingsPerStrategyMet.NAME,
        MatrixEffortNominalPerStrategyMet.NAME
    };

    @Override
    public String[] getNecessaryResult() {
        return this.necessaryResult;
    }

    /**
     * Permet d'afficher a l'utilisateur une aide sur la regle.
     * @return L'aide ou la description de la regle
     */
    @Override
    public String getDescription() throws Exception {
        return "Calcule les proportion par métier chaque mois en fonction de la VPUE du métier l'année précédante";
        /*"HYPOTHESES GRAVITE"
        " attention cette règle doit toujours être mise avant les mesures de gestion"+
        " si pour un metier Effort (métier annee-1,mois) = 0 et propinitiale (métier, mois) !=0 (ie métier potentiellement pratiqué), alors "+
        on remet propInitiale pour tous les métiers (premiere vue complete pour tous les metiers de  la strategie - graviteVPUE1-, une alternative
        pourrait être de chercher lapremiere année avant année -1 pour laquelle le métier, ayant une propInitiale non nulle , aurait une VPUE (metier,mois) non nulle
        et recuperer la propStr (metier,mois) pour cette année et on l'affecte année courante - mois, les autres métiers se partageant la proportion d'effort restante 
        en fonction de leur VPUE  - graviteVPUE2-, une alternative pourrait etre de chercher la premiere année avant année -1 pour laquelle tous les métiers, 
         ayant une propInitiale non nulle, auraient eu une propStr non nulle, (surement difficile à trouver) -  - graviteVPUE3- d'autres hypothèses pourraient etre envisagées)
        "*/
    }

    /**
     * function used to initialise MatrixND to NaN double
     */
    private MapFunction nanFunction = new MapFunction() {
        @Override
        public double apply(double value) {
            return Double.NaN;
        }
    };

    /**
     * Appelé au démarrage de la simulation, cette méthode permet d'initialiser
     * des valeurs
     * @param context La simulation pour lequel on utilise cette regle
     */
    @Override
    public void init(SimulationContext context) throws Exception {
        TimeStep step = new TimeStep(0);

        List<Strategy> strs = SiMatrix.getSiMatrix(context).getStrategies(step);
        List<Metier> metiers = SiMatrix.getSiMatrix(context).getMetiers(step);
        List<Month> months = Arrays.asList(Month.MONTH);

        SommePropInitial = MatrixFactory.getInstance().create(
                "SommePropInitial", new List[] { strs, months },
                new String[] { "Strategies", "Months" });
        SommePropInitial.map(nanFunction);

        valuePerUnitOfEffort = MatrixFactory.getInstance().create(
                "ValuePerUnitOfEffort", new List[] { strs, metiers },
                new String[] { "Strategies", "Metiers" });
        valuePerUnitOfEffort.map(nanFunction);

        for (Strategy str : strs) {
            List<Metier> strMetiers = SiMatrix.getSiMatrix(context).getMetiers(
                    str, step);
            List<StrategyMonthInfo> infos = str.getStrategyMonthInfo();
            for (StrategyMonthInfo info : infos) {
                double somme = 0;
                for (Metier strMetier : strMetiers) {
                    somme += info.getProportionMetier(strMetier);
                }
                // FIXME soit on somme pour toutes les str le meme metier; donc pas de notion de str.getName dans la cle
                // soit pour une str on somme tous ces metiers (mais ce doit etre 1; donc pas de notion de metier.getName dans la cle
                SommePropInitial.setValue(str, info.getMonth(), somme);
            }
        }
    }

    /**
     * La condition qui doit etre vrai pour faire les actions.
     * 
     * @param context la simulation pour lequel on utilise cette regle
     * @param step le pas de temps courant
     * @param metier le metier concerné
     * @return vrai si on souhaite que les actions soit faites
     */
    @Override
    public boolean condition(SimulationContext context, TimeStep step, Metier metier)
            throws Exception {
        // Il faut etre au moins au deuxieme pas de temps. 
        return step.getYear() > 0;
    }


    /**
     * Si la condition est vrai alors cette action est executee avant le pas
     * de temps de la simulation.
     * 
     * @param context la simulation pour lequel on utilise cette regle
     * @param step le pas de temps courant
     * @param metier le metier concerné
     */
    @Override
    public void preAction(SimulationContext context, TimeStep step, Metier metier)
            throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("first = " + first + "step:" + step);
        }
        //log.info("first = " + first + " ,on passe dans la preaction ?");
        if (first) { // on passe dans preaction pour la premiere fois
            //log.info("Oui, preaction : ");

            List<Strategy> strs = SiMatrix.getSiMatrix(context).getStrategies(
                    step);
            //List<Population> populations = SiMatrix.getSiMatrix(context)
            //        .getPopulations(date);
            ResultManager resultmanager = context.getResultManager();

            ////////Initialisation des matrices qui resultent de la simulation////////////////////////////////////////////////////////////

            //Calcul de l effort nominal par strategy met    
            MatrixND EffortNominalPerStrMet;
            EffortNominalPerStrMet = resultmanager.getMatrix(step.previousYear(),
                    MatrixEffortNominalPerStrategyMet.NAME);
            //System.out.println("EffortNominalPerStrMet calculee "
            //        + EffortNominalPerStrMet);

            //on commence par creer une matrice de valeurs (somme sur ttes les especes capturees) par strategie met
            MatrixND GrossValuePerStrMet;
            GrossValuePerStrMet = resultmanager.getMatrix(step.previousYear(),
                    MatrixGrossValueOfLandingsPerStrategyMet.NAME);

            //System.out.println("GrossValuePerStrMet calculee " + GrossValuePerStrMet);

            //ajouter pour tous les métiers les valeurs liées qux autres espèces calculées par modele lineaire

            //////////////////////////////////////////////////////////////////////////////////////////////////////////
            valuePerUnitOfEffort.map(nanFunction); //réinitialisation avant calcul pour date

            for (Strategy str : strs) {
                //log.info("INFO: Boucle creation valuePerUnitEffort : " + str.getName());
                // log.info("Boucle creation catchperuniteffort : "+ str.getName());
                StrategyMonthInfo smi = str.getStrategyMonthInfo(step.getMonth());
                Collection<EffortDescription> strMet = str.getSetOfVessels()
                        .getPossibleMetiers();
                //SiMatrix.getSiMatrix(context).getMetiers(str, date);

                // boucle pour tester s'il existe un metier pour lequel effort(metier)=0 et PropInitiale(metier)=0 (Condition)
                boolean testCondition = false;
                double somme = 0;//initialisation de la somme des VPUE des metiers de la strategie
                for (EffortDescription ed : strMet) {
                    Metier strMetier = ed.getPossibleMetiers();
                    //log.info("Pour str=" + str.getName()
                    //        + " et metier=" + strMetier.getName());
                    double effort = EffortNominalPerStrMet.getValue(str,
                            strMetier);
                    // on teste effort pour le calcul des VPUE 
                    // si effort != 0 , valeur/effort
                    //sinon (effort =0) , deux cas de figure : 
                    //    1. soit propInitiale =0 pour ce métier et dans ce cas VPUE =0 et ca ne doit pas impacter le calcul de la gravite pour les autres metiers de str
                    //   2. soit propInitiale! =0 et dans ce cas, on mettra PropInitiale pour tous les metiers de str
                    if (effort > 0) {// a peché au mois, annee-1
                        //on recupere la capture tot                   
                        double value = GrossValuePerStrMet.getValue(str,
                                strMetier);
                        //log.debug("DEBUG: value : " + value);
                        //log.debug("DEBUG: effort : " + effort);
                        double vpue = value / effort;
                        valuePerUnitOfEffort.setValue(str, strMetier, vpue);
                        //log.info("value/effort= " + vpue);
                        somme += value / effort;
                    } else if ((effort == 0)
                            && (smi.getProportionMetier(strMetier) == 0)) {// n'a jamais pêche avec ce metier
                        valuePerUnitOfEffort.setValue(str, strMetier, 0);
                        //log.info("n'a jamais pêche avec ce metier");
                    } else {// n'a pas peche au mois, annee -1, mais avait une prop d'effort non nul a l'annee=0
                        testCondition = true;// ie somme est incomplète mais pas grave car on mettra PropInitiale à tous les metiers
                        //log.info("n'a pas peche au mois, annee -1, mais avait une prop d'effort non nul a l'annee=0");
                    }
                }
                //log.info("testCondition pour str" + str.getName()
                //        + ":" + testCondition);
                // A partir des VPUE stockees dans valuePerUnitOfEffort, on calcule la gravité
                double newProp;
                //log.info("A partir des VPUE stockees dans valuePerUnitOfEffort, on calcule la gravité");
                if (!testCondition) {
                    double SommeVPUEstrat = somme;
                    //System.out.println("SommeVPUEstrat=" + SommeVPUEstrat);
                    for (EffortDescription ed : strMet) {
                        Metier strMetier = ed.getPossibleMetiers();
                        //System.out.println("PropStrInitiale(metier="
                        //        + strMetier.getName() + ")"
                        //        + smi.getProportionMetier(strMetier));
                        //System.out.println("SommePropInitial.getValue(str, date.getMonth()="
                        //                + SommePropInitial.getValue(str, date
                        //                        .getMonth()));
                        //System.out.println("valuePerUnitOfEffort.getValue(str, strMetier)"
                        //                + valuePerUnitOfEffort.getValue(str,
                        //                        strMetier));
                        if (SommeVPUEstrat == 0) {
                            newProp = 0;
                        } else {
                            newProp = SommePropInitial.getValue(str, step
                                    .getMonth())
                                    * valuePerUnitOfEffort.getValue(str,
                                            strMetier) / SommeVPUEstrat;
                        }
                        //System.out.println("newProp(metier="
                        //        + strMetier.getName() + ")" + newProp);
                        smi.setProportionMetier(strMetier, newProp);
                        //System.out.println("PropStrNouvelle(metier="
                        //        + strMetier.getName() + ")"
                        //        + smi.getProportionMetier(strMetier));
                    }
                }
                // else  ie on met propInitiale dans PropStr(str,annee,mois)
                // rien n'a faire car au debut de chaque pas de temps, PropStr
                // est par défaut initialisé à la valeur de la base de données (val initiales)

            }//fin de boucle sur strategy    

            first = false;

        }// fin de first= true

        if (log.isDebugEnabled()) {
            log.debug("fin Gravite CPUEAction avant");
        }

    }

    /**
     * Si la condition est vrai alors cette action est executée apres le pas
     * de temps de la simulation.
     * 
     * @param context La simulation pour lequel on utilise cette regle
     * @param step le pas de temps courant
     * @param metier le metier concerné
     */
    @Override
    public void postAction(SimulationContext context, TimeStep step, Metier metier)
            throws Exception {
        first = true;
    }
}
