package rules;

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

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

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

import scripts.ResultName;
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.SimulationContext;
import fr.ifremer.isisfish.types.Date;
import fr.ifremer.isisfish.types.Month;

import fr.ifremer.isisfish.util.Doc; // pour pouvoir afficher une aide contextuelle (BUG#1605)

/**
 * TAC.java
 *
 * Created: 7 septembre 2006
 *
 * @author anonymous <anonymous@labs.libre-entreprise.org>
 * @version $Revision: 1.5 $
 *
 * Last update: $Date: 2007-11-20 15:50:09 $
 * by : $Author: bpoussin $
 */

/**
 * TAC peut-etre utilisé pour les différents 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 recalculé a chaque mois de janvier.
 * <li>Pour utiliser la survie il faut mettre dans le paramètre propSurvie
 * une valeur > 0, automatiquement les suvie seront ajouté aux effectifs
 *  
 */
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(value="do the doc of param species")
    public Species param_species = null;
    @Doc(value="do the doc of param beginDate")
    public Date param_beginDate = new Date(0);
    @Doc(value="do the doc of param endDate")
    public Date param_endDate = new Date(22);
    @Doc(value="do the doc of param propSurvie")
    public double param_propSurvie = 0;
    @Doc(value="do the doc of param propTac")
    public double param_propTac = 0;

    /** TAC in tonne */
    public double param_tacInTons = 0;
    
    boolean affectation = false;
    
    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
     */
    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
     */
    public String getDescription() {
        return _("TAC weight in tons.\nIf you want survival discard use propSurvie other than 0.\nIf you wish TAC computed use propTac other than 0.");
    }
 
    /**
     * Appelé au démarrage de la simulation, cette méthode permet d'initialiser
     * des valeurs
     * @param simulation La simulation pour lequel on utilise cette regle
     */
    public void init(SimulationContext context) throws Exception  {
    }
    
    /**
     * La condition qui doit etre vrai pour faire les actions
     * @param simulation La simulation pour lequel on utilise cette regle
     * @return vrai si on souhaite que les actions soit faites
     */
    public boolean condition(SimulationContext context, Date date, Metier metier) throws Exception {
        // read species in current session
        param_species = (Species)context.getDB().findByTopiaId(param_species.getTopiaId());
        
        // on fait le calcul du tac si nécessaire
        if (param_propTac > 0 && date.getMonth().equals(Month.JANUARY)){
            PopulationMonitor popMon = context.getPopulationMonitor();
            param_tacInTons = popMon.getBiomass(param_species) * param_propTac;
        }   
        
        boolean result = false;
        if(date.before(param_beginDate)) {
            result = false;
        } else if(date.after(param_endDate)) {
          result = false;
        } else {
            TargetSpecies ts = metier.getMetierSeasonInfo(date.getMonth()).getSpeciesTargetSpecies(param_species);
            if (ts != null) {
                double catchTons = RuleUtil.getTotalCatchTons(context, param_species, date);
                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 executée avant le pas
     * de temps de la simulation.
     * @param simulation La simulation pour lequel on utilise cette regle
     */
    public void preAction(SimulationContext context, Date date, Metier metier) throws Exception {
        affectation = false;
        log.info("[TAC] preAction for: " + metier);

        TargetSpecies ts = metier.getMetierSeasonInfo(date.getMonth()).getSpeciesTargetSpecies(param_species);
        if(ts != null && ts.getPrimaryCatch()) {
            // recupere tous les metiers qui ont l'espece en capture principale =>metiers vises
            List<Metier> aimedMetiers = context.getDB().find("select distinct(metierSeasonInfo.metier) " +
                    "from fr.ifremer.isisfish.entities.TargetSpecies " +
                    "where species=? and primaryCatch=true", param_species);

            log.info("aimed Metier: " + aimedMetiers);
            context.getMetierMonitor().addforbiddenMetier(metier);
            
            //récupere toutes les stratégies pratiquant le métier et pour lesquelles la proportion !=0
            SiMatrix siMatrix = SiMatrix.getSiMatrix(context);
            Set<Strategy> strs = new HashSet<Strategy>();
            for (Strategy str : siMatrix.getStrategies(date)) {
                double prop =  str.getStrategyMonthInfo(date.getMonth()).getProportionMetier(metier);
                if (prop != 0) {
                    strs.add(str);
                }
            }
            
            for (Strategy str : strs){
                StrategyMonthInfo smi = str.getStrategyMonthInfo(date.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)
                            && !metier.getName().equalsIgnoreCase("nonActiviy")
                            && !metier.getName().equalsIgnoreCase("nonActivie")
                            && !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 : possibleMetierCase1) {
                        somme += smi.getProportionMetier(met);
                    }
                    for (Metier met : possibleMetierCase1) {
                        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                    
                } else{
                    log.info("[TAC] Use no activity");
                    
                    // sinon on met tout dans le metier nonActivite
                    MetierMonitor metierMon = context.getMetierMonitor();
                    MatrixND mat = metierMon.getOrCreateNoActivity(date,
                            ResultName.MATRIX_NO_ACTIVITY,
                            siMatrix.getStrategies(date),
                            siMatrix.getMetiers(date));
                    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 simulation La simulation pour lequel on utilise cette regle
     */
    public void postAction(SimulationContext context, Date date, Metier metier) throws Exception {
        log.info("[TAC] postAction for: " + metier);
        TargetSpecies ts = metier.getMetierSeasonInfo(date.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();
                for (Population pop : param_species.getPopulation()) {
                    // 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(date, pop);
                    if (discard != null) {
                        discard.mults(0);
                    }
                    
                    discard = popMon.getCatch(pop).copy();
                    discard.setName(ResultName.MATRIX_DISCARDS_PER_STR_MET);
                    popMon.addDiscard(date, pop, discard);
                    log.info("[TAC] add discard for " + 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;
            }
        }
    }

}
