/*
 * #%L
 * IsisFish data
 * %%
 * Copyright (C) 2006 - 2012 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 2 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-2.0.html>.
 * #L%
 */
package rules;

import static org.nuiton.i18n.I18n._;

import java.util.HashSet;
import java.util.Set;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;

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.ResultName;
import scripts.RuleUtil;
import scripts.SiMatrix;
import fr.ifremer.isisfish.datastore.ResultStorage;
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.entities.MetierSeasonInfo;
import fr.ifremer.isisfish.entities.PopulationGroup;
import fr.ifremer.isisfish.entities.*;
import fr.ifremer.isisfish.rule.AbstractRule;
import fr.ifremer.isisfish.simulator.MetierMonitor;
import fr.ifremer.isisfish.simulator.PopulationMonitor;
import fr.ifremer.isisfish.simulator.SimulationContext;
import fr.ifremer.isisfish.types.TimeStep;
import fr.ifremer.isisfish.types.Month;
import fr.ifremer.isisfish.util.Doc;

// import rules.CapturesLongLinersNetters_Projection_Ratio;

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

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

    @Doc("Affected species")
    public Population param_population = null;
    
    @Doc("Proportion de survie")
    public double param_propSurvie = 0.45;
    @Doc(value = "param_Retention_L50")
    public double param_Retention_L50 = 29.4307;

    @Doc(value = "param_Retention_slope")
    public double param_Retention_slope = 2.02485;
    
    @Doc(value = "param_TailleMin")
    public double param_TailleMin = 27.0;
    /** TAC in tonnes */
    @Doc("TAC in tons 2010")
    public double param_tacInTons2010 =20609;
    @Doc("TAC in tons 2011")
    public double param_tacInTons2011 = 20609;
    @Doc("TAC in tons 2012")
    public double param_tacInTons2012 = 20609;
    @Doc("TAC in tons 2013")
    public double param_tacInTons2013 = 25970;
    @Doc("TAC in tons 2014")
    public double param_tacInTons2014 = 30610;
	
	public double totOtherCatchStep = 0;
	public double CatchOtherTot = 0;
	public double CatchOtherTot_Bis = 0;
	public double catchGlob = 0;
	
    boolean affectation = true; // pour ne passer qu'une seule fois dans afterSimulation (pour le premier metier)
    boolean tacAtteint = false; // pour ne passer dans beforeSimulation que si le tac est atteint

    protected String[] necessaryResult = {
    // put here all necessary result for this rule
    // example: 
    // ResultName.MATRIX_BIOMASS,
    // ResultName.MATRIX_NET_VALUE_OF_LANDINGS_PER_STRATEGY_MET,
    };

    /**
     * @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 _("Combinaison de la regle tac poids sur 2001-2011 avec ogive de tri + prop de survie des rejets.");
    }

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

		//boolean firstMet = true; // pour n'ajouter les captures Autres qu'une fois
		//log.info("firstMet_1 = " + firstMet);

        log.info("test si TAC atteint");
        // read species in current session : plus necessaire bug resolu
        //param_population = (Species) context.getDB().findByTopiaId(
        //        param_population.getTopiaId());
        TargetSpecies ts = metier.getMetierSeasonInfo(step.getMonth())
                    .getSpeciesTargetSpecies(param_population.getSpecies());
        boolean result = false;
	    tacAtteint = false; // sinon une fois le tac atteint pendant la simu cette variable reste a vrai et on passe toujours dans preAction et postAction
		
        if (ts != null) { // on n'applique la regle que si espece capturee
            double tacInTons = 0;
            
            if (step.getYear()>4) { //si on est apres 2019
                result = false;
            } else {
                result = true; // au moins ogive tri s'applique
                if (step.getYear()==0) {
                    tacInTons = param_tacInTons2010;
                } else if (step.getYear()==1) {
                    tacInTons = param_tacInTons2011;
                } else if (step.getYear()==2) {
                    tacInTons = param_tacInTons2012;
                } else if (step.getYear()==3) {
                    tacInTons = param_tacInTons2013;
                } else if (step.getYear()==4) {
                    tacInTons = param_tacInTons2014;
					context.setValue("tacInTons_" + param_population.getName() + "_2014", tacInTons);
					log.info("tacInTons2014 = " + tacInTons);
					log.info("param_tacInTons2014 = " + param_tacInTons2014);
                } 
				
				
                // test si Tac atteint
				
				double catchTons = RuleUtil.getTotalCatchTonsPop(context, param_population, step);
				log.info("[TAC] catchTons = " + catchTons + " >= tacInTons:" + tacInTons);
								
				// Attention : on boucle sur les metiers, donc il faut mettre une condition pour ne faire cette
				// operation qu'une fois, sans quoi on remplit 35 fois CatchOtherTot avant de l'ajouter aux captures.
				
				double catchPreviousStep = (Double) context.getValue("totOtherCatchStep_" + param_population.getName() + "_" + step.previous().getStep());

					if (step.getStep() == 0) {	
						totOtherCatchStep = 0;
						CatchOtherTot = 0;
					} else if (step.getStep() > 0) {
					
						// Faire boucle pour recuperer toutes les valeurs depuis le debut de l'annee en cours.
						// A un pas de temps donne on ne dispose pas encore des captures "Autres" du pas de temps,
						// mais seulement de celles jusqu'au pas de temps precedent.
					
						int stepMin = step.getYear() * 12; // Janvier de l'annee courante
						
						if (totOtherCatchStep != catchPreviousStep) {
							for (int s = stepMin; s < step.getStep(); s++) {
								totOtherCatchStep = (Double) context.getValue("totOtherCatchStep_" + param_population.getName() + "_" + s);
								log.info("totOtherCatchStep_import_2 = " + totOtherCatchStep);
								
								CatchOtherTot += totOtherCatchStep;
								log.info("CatchOtherTot_1 = " + CatchOtherTot);
								
								CatchOtherTot_Bis = CatchOtherTot;
								log.info("CatchOtherTot_Bis = " + CatchOtherTot_Bis);
							}
							catchGlob = catchTons + CatchOtherTot_Bis;
							log.info("catchGlob = " + catchGlob);
						}
					}
			
				if (catchGlob >= tacInTons) { 
					tacAtteint = true;
				}

				CatchOtherTot = 0;
				log.info("CatchOtherTot_2 = " + CatchOtherTot);
            }
					
			log.info("tacAtteint = " + tacAtteint);
        }
        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 {
    
        // la preaction ne tourne que si le TAC est atteint
        affectation = true;
        
        if (tacAtteint) {
           
            log.info("[TAC] preAction for: " + metier);
            log.info(" TAC atteint [TAC] preAction for: " + metier);
            TargetSpecies ts = metier.getMetierSeasonInfo(step.getMonth())
                    .getSpeciesTargetSpecies(param_population.getSpecies());
            if (ts != null && ts.getPrimaryCatch()) {
                // 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<Strategy>();
                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<Metier>();
                    // second cas de figure: on cherche un metier de substitution
                    // sans condition sur les engins, mais qui soit pratique
                    Set<Metier> possibleMetierCase2 = new HashSet<Metier>();
                    // 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<Metier>();

                    for (EffortDescription effort : str.getSetOfVessels()
                            .getPossibleMetiers()) {
                        Metier newMetier = effort.getPossibleMetiers();
                        if (
                        /*!aimedMetiers.contains(newMetier)
                        &&*/!newMetier.getName().equalsIgnoreCase("nonActivity")
                                && !newMetier.getName().equalsIgnoreCase("nonActivite")
                                && !newMetier.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;
                            if (somme == 0) {
                                 // cas ou aucun autre metier n'est pratique
                                 // on repartit quand meme l'effort equitablement
                                 // sur tous les autres metier
                                 newProportion = smi.getProportionMetier(metier) / possibleMetier.size();
                            } else {
                                 newProportion = smi.getProportionMetier(met)
                                    + (smi.getProportionMetier(metier)
                                            * smi.getProportionMetier(met) / somme);
                            }
                            smi.setProportionMetier(met, newProportion);

                            log.info("[TAC] set new proportion to " + newProportion + " for " + met.getName());
                        }
                        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,
                                ResultName.MATRIX_NO_ACTIVITY, 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 execute 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 {
        
        
        
        if (affectation) {
            // Application des rejets si Tac atteint et tailleMin sinon sur les captures  
            // la matrice de captures a une dimension metier - donc a cette etape, toutes 
            // les captures de tous les metiers sont connues
            // Ne doit pas s'appliquer pour chaque metier de la boucle mais une seule fois! 
            // affectation est mis 
                        // la preaction ne tourne que si le TAC est atteint
                        
            if (tacAtteint) { //postAction de la regle TAC dans ce cas TailleMin ne s'applique pas
                log.info("[TAC] postAction for: " + metier);
    
                PopulationMonitor popMon = context.getPopulationMonitor();
                    log.info("popMon biomass" + popMon.getBiomass(param_population));
                    
                        log.info("param_population : " + param_population.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, param_population);
                        //log.info("discard : " + discard);
                        if (discard != null) {
                            discard.mults(0);
                        }
                        log.info("catch = " + popMon.getCatch(param_population));
                        MatrixND discardRegle = popMon.getCatch(param_population).copy(); //nouvelle instance pour le calcul dans la regle
                        // 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 
                        discardRegle.setName(ResultName.MATRIX_DISCARDS_PER_STR_MET_PER_ZONE_POP);
                        popMon.addDiscard(step, param_population, discardRegle);
                        log.info("[TAC] add discard for " + param_population + ": "
                                + discardRegle);
                        if (param_propSurvie > 0) {
                            MatrixND eff = popMon.getN(param_population);
                            //on re
                            for (MatrixIterator i = discardRegle.iterator(); i
                                    .next();) {
                                Object[] coord = i.getSemanticsCoordinates();
                                eff.setValue(coord[2], coord[3], eff.getValue(
                                        coord[2], coord[3])
                                        + i.getValue() * param_propSurvie);
                            }
                        }
            
            } else {
                log.info("deb de affecterCaptureRejetSelonOgive");

            PopulationMonitor popMon = context.getPopulationMonitor();

                    MatrixND discard = popMon.getCatch(param_population).copy();
                    MatrixND eff = popMon.getN(param_population);
                    
                    for (MatrixIterator i = discard.iterator(); i.next();) {
                            Object[] coordonnees = i.getSemanticsCoordinates();
                               PopulationGroup group = (PopulationGroup) coordonnees[2];
                            Metier met = (Metier) coordonnees[1];
                            if (met.getName().equals("metier lang simp Sud")||met.getName().equals("metier lang simp Nord")||met.getName().equals("metier lang simple")||met.getName().equals("metier lang jum Nord")||met.getName().equals("metier lang jum Sud")||met.getName().equals("metier lang jum")) {
                                 
                                  double propTrie = 1 / (1 + Math.exp(-(group.getLength() - param_Retention_L50)/ param_Retention_slope));
                                  double propRejet = 1 - propTrie;
                                  //log.info("REJETS" + propRejet);
                                  double value = i.getValue() * propRejet;
                                  //log.info("VALUE" + value);
                                  i.setValue(value);
                                  if (param_propSurvie >0) {
                                    //ajout de la survie aux effectifs
                                    eff.setValue(coordonnees[2], coordonnees[3],
                                        eff.getValue(coordonnees[2],
                                        coordonnees[3]) + i.getValue() *
                                        param_propSurvie);
                                }
                                  
                            }else if (group.getLength() >= param_TailleMin) { 
                                   i.setValue(0); // initialise discard si pas de rejet (ie taille>=TailleMin et pas metierLangoustine)
                                   
                            } else {  //si group.getLength() < param_TailleMin
                                 if (param_propSurvie >0) {
                                    //ajout de la survie aux effectifs
                                    eff.setValue(coordonnees[2], coordonnees[3],
                                             eff.getValue(coordonnees[2],
                                             coordonnees[3]) + i.getValue() *param_propSurvie);
                                   }
                            
                             }
                    }         
                    discard.setName(ResultName.MATRIX_DISCARDS_PER_STR_MET_PER_ZONE_POP);
                    popMon.addDiscard(step, param_population, discard); 
                
            }
            // on a affecte une fois cette meta param_population au rejet il ne faut pas
                // le refaire
            affectation = false;
        }
        
    }

}
