/*
 * #%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 java.util.HashSet;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuiton.math.matrix.MatrixIterator;
import org.nuiton.math.matrix.MatrixND;

import scripts.RuleUtil;
import scripts.SiMatrix;
import fr.ifremer.isisfish.entities.EffortDescription;
import fr.ifremer.isisfish.entities.Metier;
import fr.ifremer.isisfish.entities.Population;
import fr.ifremer.isisfish.entities.Species;
import fr.ifremer.isisfish.entities.Strategy;
import fr.ifremer.isisfish.entities.StrategyMonthInfo;
import fr.ifremer.isisfish.entities.TargetSpecies;
import fr.ifremer.isisfish.rule.AbstractRule;
import fr.ifremer.isisfish.simulator.MetierMonitor;
import fr.ifremer.isisfish.simulator.PopulationMonitor;
import fr.ifremer.isisfish.simulator.ResultManager;
import fr.ifremer.isisfish.simulator.SimulationContext;
import fr.ifremer.isisfish.types.TimeStep;
import fr.ifremer.isisfish.types.Month;
import fr.ifremer.isisfish.annotations.Doc;
import resultinfos.MatrixDiscardsPerStrMetPerZonePop;
import resultinfos.MatrixNoActivity;

/**
 * TAC peut-etre utilise pour les differents TAC, en proportion des effectifs
 * et/ou avec survie ou non.
 * 
 * <ul>
 * <li>Pour utiliser le tac proportionnel, il faut mettre dans le parametre
 * propTac une valeur &gt; 0, le TAC sera alors recalcule a chaque mois de janvier.</li>
 * <li>Pour utiliser la survie il faut mettre dans le parametre propSurvie une
 * valeur &gt; 0, automatiquement les suvie seront ajoute aux effectifs</li>
 * </ul>
 * 
 * Created: 7 septembre 2006
 *
 * @author anonymous &lt;anonymous@labs.libre-entreprise.org&gt;
 * @version $Revision: 1.3 $
 *
 * Last update: $Date: 290607 $
 * by : $Author: sigrid $
 */
public class TACpoids extends AbstractRule {

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

    @Doc("Affected species")
    public Species param_species = null;
    @Doc("Debin date")
    public TimeStep param_beginStep = new TimeStep(0);
    @Doc("End date")
    public TimeStep param_endStep = new TimeStep(90);
    @Doc("Proportion de survie")
    public double param_propSurvie = 0;
    @Doc("Proportionnal TAC")
    public double param_propTac = 0;

    /** TAC in tonnes */
    @Doc("TAC in tons")
    public double param_tacInTons = 900;

    protected boolean affectation = false;

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

    /**
     * @return the necessaryResult
     */
    @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() {
        return "TAC weight in tons.\nIf you want survival discard use propSurvie other than 0.\nIf you wish TAC computed as a proportion of the biomass use propTac other than 0.";
    }

    /**
     * Appele au demarrage de la simulation, cette methode permet d'initialiser
     * des valeurs
     * 
     * @param context La simulation pour lequel on utilise cette regle
     */
    @Override
    public void init(SimulationContext context) throws Exception {
    }

    /**
     * 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 {

        log.info("test si TAC atteint");
        // read species in current session
        param_species = (Species) context.getDB().findByTopiaId(
                param_species.getTopiaId());

        // on fait le calcul du tac si necessaire
        if (param_propTac > 0 && step.getMonth().equals(Month.JANUARY)) {
            PopulationMonitor popMon = context.getPopulationMonitor();
            param_tacInTons = popMon.getBiomass(param_species) * param_propTac;
        }

        boolean result = false;
        if (step.before(param_beginStep)) {
            result = false;
        } else if (step.after(param_endStep)) {
            result = false;
        } else {
            TargetSpecies ts = metier.getMetierSeasonInfo(step.getMonth())
                    .getSpeciesTargetSpecies(param_species);
            if (ts != null) {
                double catchTons = RuleUtil.getTotalCatchTons(context,
                        param_species, step);
                log.info("[TAC] catchTons = " + catchTons
                        + " >= param_tacInTons:" + param_tacInTons);
                if (catchTons >= param_tacInTons) {
                    result = true;
                }
            }
        }
        return result;
    }

    /**
     * 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 {
        affectation = false;

        log.info("[TAC] preAction for: " + metier);
        log.info(" TAC atteint [TAC] preAction for: " + metier);
        TargetSpecies ts = metier.getMetierSeasonInfo(step.getMonth())
                .getSpeciesTargetSpecies(param_species);
        if (ts != null && ts.isPrimaryCatch()) {
            // recupere tous les metiers qui ont l'espece en capture principale =>metiers vises
            // aimedMetiers ne fonctionne pas je ne sais pas pourquoi ! mais au final forbiddenMetier aura le meme effet
            
            context.getMetierMonitor().addforbiddenMetier(metier);

            //recupere toutes les strategies pratiquant le metier et pour lesquelles la proportion !=0
            SiMatrix siMatrix = SiMatrix.getSiMatrix(context);
            Set<Strategy> strs = new HashSet<>();
            for (Strategy str : siMatrix.getStrategies(step)) {
                double prop = str.getStrategyMonthInfo(step.getMonth())
                        .getProportionMetier(metier);
                if (prop != 0) {
                    strs.add(str);
                }
            }

            for (Strategy str : strs) {
                StrategyMonthInfo smi = str.getStrategyMonthInfo(step
                        .getMonth());

                // 1er cas de figure: l'effort est reporte sur un metier de la
                // meme strategie, n'ayant pas l'espece comme capture principale
                // et pechant avec le meme engin
                Set<Metier> possibleMetierCase1 = new HashSet<>();
                // second cas de figure: on cherche un metier de substitution
                // sans condition sur les engins, mais qui soit pratique
                Set<Metier> possibleMetierCase2 = new HashSet<>();
                // 3 eme cas de figure: on cherche des metiers non vises,
                // sans consideration sur les engins, et pour lesquels la
                // proportion peut etre nulle
                Set<Metier> possibleMetierCase3 = new HashSet<>();

                for (EffortDescription effort : str.getSetOfVessels()
                        .getPossibleMetiers()) {
                    Metier newMetier = effort.getPossibleMetiers();
                    if (
                    /*!aimedMetiers.contains(newMetier)
                    &&*/!metier.getName().equalsIgnoreCase("nonActiviy")
                            && !metier.getName().equalsIgnoreCase("nonActivie")
                            && !metier.getName().equalsIgnoreCase(
                                    "non Activite")
                            && !context.getMetierMonitor().getForbiddenMetier()
                                    .contains(newMetier)) {
                        possibleMetierCase3.add(newMetier);

                        if (smi.getProportionMetier(newMetier) != 0) {
                            possibleMetierCase2.add(newMetier);

                            if (metier.getGear().equals(newMetier.getGear())) {
                                possibleMetierCase1.add(newMetier);
                            }
                        }
                    }
                }

                Set<Metier> possibleMetier = null;
                if (possibleMetierCase1.size() != 0) {
                    log.info("[TAC] Use case 1");
                    possibleMetier = possibleMetierCase1;
                } else if (possibleMetierCase2.size() != 0) {
                    log.info("[TAC] Use case 2");
                    possibleMetier = possibleMetierCase2;
                } else if (possibleMetierCase3.size() != 0) {
                    log.info("[TAC] Use case 3");
                    possibleMetier = possibleMetierCase3;
                }

                if (possibleMetier != null) {
                    // on repartit maintenant l'effort entre les differents metiers
                    // possibles dans la meme strategie si un metier possible existe
                    // bien la repartion est proportionnelle a l'effort deja alloue
                    // dans la strategie

                    double somme = 0;
                    for (Metier met : possibleMetier) {
                        somme += smi.getProportionMetier(met);
                    }
                    for (Metier met : possibleMetier) {
                        double newProportion = smi.getProportionMetier(met)
                                + (smi.getProportionMetier(metier)
                                        * smi.getProportionMetier(met) / somme);
                        smi.setProportionMetier(met, newProportion);
                    }
                    smi.setProportionMetier(metier, 0); //le metier vise a alors une proportion nulle 
                    log.info("[TAC] il y a des metiers possibles");
                } else {
                    log.info("[TAC] Use no activity");

                    // sinon on met tout dans le metier nonActivite
                    MetierMonitor metierMon = context.getMetierMonitor();
                    MatrixND mat = metierMon.getOrCreateNoActivity(step,
                            MatrixNoActivity.NAME, siMatrix
                                    .getStrategies(step), siMatrix
                                    .getMetiers(step));
                    mat.setValue(str, metier, smi.getProportionMetier(metier));

                    smi.getProportionMetier().setValue(metier, 0);
                }
            }
        }
    }

    /**
     * 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 {
        ResultManager resultmanager = context.getResultManager();
        log.info("[TAC] postAction for: " + metier);
        TargetSpecies ts = metier.getMetierSeasonInfo(step.getMonth())
                .getSpeciesTargetSpecies(param_species);
        if (ts != null) {
            if (!affectation) {
                // ATTENTION        
                // les captures pour cette metapop ne sont plus du qu'au metier
                // pour qui l'espece est secondaire: elles sont affectees aux
                // rejets

                //pb : ne se fait pas par metier
                // il faut une matrice pour chaques pas de temps qui stocke les
                // rejets par metier, par metapop et par classes d'age (comme
                // pour les captures)
                //////
                PopulationMonitor popMon = context.getPopulationMonitor();
                log.info("popMon biomass" + popMon.getBiomass(param_species));
                for (Population pop : param_species.getPopulation()) {
                    if (!pop.getName().equals("Population_new")) {
                        log.info("pop : " + pop.getName());
                        // si on a deja une matrice rejet on le vide (elle vient
                        // forcement de la regle taille minimale or si le tac est
                        // atteint, tout va dorenavent dans les rejets et on mais
                        // TOUTES les captures dans les rejets
                        MatrixND discard = popMon.getDiscard(step, pop);
                        log.info("discard : " + discard);
                        if (discard != null) {
                            discard.mults(0);
                        }
                        log.info("catch = " + popMon.getCatch(pop));
                        discard = popMon.getCatch(pop).copy();
                        // ca ne doit pas pouvoir marcher car MATRIX_DISCARDS_PER_STR_MET est de dimension pop groupe str met - et discard n'a plus la dimension pop 
                        discard.setName(MatrixDiscardsPerStrMetPerZonePop.NAME);
                        popMon.addDiscard(step, pop, discard);
                        log.info("[TAC] add discard for " + pop + ": "
                                + discard);
                        // ne manquerait-il pas
                        resultmanager.addResult(step, pop, discard);
                        if (param_propSurvie > 0) {
                            MatrixND eff = popMon.getN(pop);
                            //on réajoute les survivants aux effectifs
                            for (MatrixIterator i = discard.iterator(); i
                                    .next();) {
                                Object[] coord = i.getSemanticsCoordinates();
                                eff.setValue(coord[2], coord[3], eff.getValue(
                                        coord[2], coord[3])
                                        + i.getValue() * param_propSurvie);
                            }
                        }

                    }

                    // on a affecte une fois cette meta pop au rejet il ne faut pas
                    // le refaire
                    affectation = true;
                }
            }
        }
    }

}
