package rules;

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

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

import scripts.GravityModel;
import scripts.ResultName;
import scripts.SiMatrix;

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

import org.codelutin.math.matrix.*;

import fr.ifremer.isisfish.simulator.SimulationContext;
import fr.ifremer.isisfish.types.Date;
import fr.ifremer.isisfish.types.Month;
import fr.ifremer.isisfish.entities.*;
import fr.ifremer.isisfish.rule.AbstractRule;
import fr.ifremer.isisfish.datastore.ResultStorage;

/**
 * GraviteCPUE.java
 *
 * Created: 30 novembre 2006
 *
 * @author anonymous <anonymous@labs.libre-entreprise.org>
 * @version $Revision: 1.2 $
 *
 * Last update: $Date: 2007-07-18 08:59:38 $
 * by : $Author: hilaire $
 */
public class GraviteCPUE extends AbstractRule {

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

    /** stocke la somme des prop initiales [str x month] */
    private MatrixND SommePropInitial = null;
    /** stocke par strat*met, la dernière attractivite pour chaque mois
     *  [str x met x month] */
    private MatrixND HistoriqueCPUE = null;
    
    /** va permettre de stocker a chaque pas de temps la somme des attractivite [str] */
    MatrixND SommeCPUEstrat = null;
    /** va permettre de stocker a chaque pas de temps la somme des proportions
    * pour les metiers n'ayant pas d'attractivite [str] */
    MatrixND SommeProp = null;

    /** permet de stocker les CPUE standardiser [str x met] */
    private MatrixND catchPerUnitOfEffort = null;

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

    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() throws Exception {
        // TODO
        return _("GraviteCPUE");
    }
     
    /**
     * function used to initialise MatrixND to NaN double
     */
    private MapFunction nanFunction = new MapFunction() {
        public double apply(double value) {
            return Double.NaN;
        }
    };
    
    /**
     * 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 {
        Date date = new Date(0);
        
        List<Strategy> strs = SiMatrix.getSiMatrix(context).getStrategies(date);
        List<Metier> metiers = SiMatrix.getSiMatrix(context).getMetiers(date);
        List<Month> months = Arrays.asList(Month.MONTH);
 
        SommePropInitial = MatrixFactory.getInstance().create("SommePropInitial",
                new List[]{strs, months}, new String[]{"Strategies", "Months"});
        SommePropInitial.map(nanFunction);
        
        HistoriqueCPUE = MatrixFactory.getInstance().create("HistoriqueCPUE",
                new List[]{strs, metiers, months}, new String[]{"Strategies", "Metiers", "Months"});
        HistoriqueCPUE.map(nanFunction);

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

        catchPerUnitOfEffort = MatrixFactory.getInstance().create("ValuePerUnitOfEffort",
                new List[]{strs, metiers}, new String[]{"Strategies", "Metiers"});
        catchPerUnitOfEffort.map(nanFunction);
        
        for (Strategy str : strs) {
            List<Metier> strMetiers = SiMatrix.getSiMatrix(context).getMetiers(str, date);
            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);
            }
        }
        
/*
var allStr = SiMatrice.getListStrategies();

for(var i=0; i<allStr.size(); i++){
	var str=allStr.get(i);
	var metiers=str.getSetOfVessels().getMetiers();
	var ListeStrMonthInfos=str.getStrategyMonthInfos();
	for (var j=0; j<ListeStrMonthInfos.size();j++){
		var somme=0;
		var StrMonthInfo=ListeStrMonthInfos.get(j);
		for (var k=0;k<metiers.size();k++){
			var metier=metiers.get(k);
			somme+=StrMonthInfo.getProportionMetier(metier);
		}
		this.p.SommePropInitial.put(new Chaine(str.getName()+"-"+metier.getNom()+"-"+StrMonthInfo.getMonth().getNumMois()),somme);
	}
}

	//permet de stocker les CPUE standardiser
CatchperUnitOfEffort = MatrixFactory.create("ValuePerUnitOfEffort",new Array(allStr,SiMatrice.getListMetiers()),new Array("Strategies","Metiers"));
*/
    }
    /**
     * 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 {
        // Il faut etre au moins au deuxieme pas de temps. 
        // FIXME 2eme pas de temps ou 2eme annee ? ici c 2eme annee et non pas 2eme mois
        return date.getYear() > 0;
    }
 
    /**
     * 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 {
        if (log.isDebugEnabled()) {
            log.debug("Gravite Action avant metier:"+ metier + "date:"+ date);
        }
        
        List<Strategy> strs = SiMatrix.getSiMatrix(context).getStrategies(date);
        List<Population> populations = SiMatrix.getSiMatrix(context).getPopulations(date);

        double newPropDiv = 0;
        
        ResultStorage resultmanager = context.getSimulationStorage().getResultStorage();

        //on commence par creer une matrice de capture par strategie met
        MatrixND catchWeigthPerStrMet = null;
        for (Population pop : populations){
            MatrixND temp = resultmanager.getMatrix(
                    date.previousYear(),
                    pop,
                    ResultName.MATRIX_CATCH_WEIGHT_PER_STRATEGY_MET);
            // on somme sur les zones
            temp=temp.sumOverDim(3);
            // on somme sur les groupes
            temp=temp.sumOverDim(2);
            if (catchWeigthPerStrMet == null) {
                catchWeigthPerStrMet = temp;
            } else {
                catchWeigthPerStrMet.add(temp);
            }
        }
                
        MatrixND effortPerStrMet = resultmanager.getMatrix(
                date.previousYear(),
                ResultName.MATRIX_EFFORT_PER_STRATEGY_MET);
        
        for (Strategy str : strs){
            List<Metier> strMetiers = SiMatrix.getSiMatrix(context).getMetiers(str, date);
            for (Metier strMetier : strMetiers){
                double effort = effortPerStrMet.getValue(str, strMetier);
                if (effort > 0){
                    EffortDescription effortdesc = str.getSetOfVessels().getPossibleMetiers(strMetier);
                    int nombreop=effortdesc.getFishingOperation();
                    int nombregear=effortdesc.getGearsNumberPerOperation();
                    double param=strMetier.getGearParameterValueAsDouble();
                    double facteur = param * nombreop * nombregear*strMetier.getGear().getStandardisationFactor() / 24.0;    
                    //on restandardise l'effort
                    effort /= facteur;
                    //on recupere la capture tot
                    Object group = catchWeigthPerStrMet.getSemantics(2).get(0);
                    Object zone = catchWeigthPerStrMet.getSemantics(3).get(0);
                    
                    double capture = catchWeigthPerStrMet.getValue(str, strMetier, group, zone);
                    
                    catchPerUnitOfEffort.setValue(str, strMetier, capture/effort);
                }
            }
        }



        for (Strategy str : strs){
            List<Metier> strMetiers = SiMatrix.getSiMatrix(context).getMetiers(str, date);
            StrategyMonthInfo info = str.getStrategyMonthInfo(date.getMonth());

            if(strMetiers.contains(metier)){
                // on verifie que le metier est bien un metier possible
                double CPUE = Double.NaN;
                if (SommeCPUEstrat.getValue(str) == Double.NaN){
                    if (effortPerStrMet.getValue(str, metier) > 0){
                        CPUE = catchPerUnitOfEffort.getValue(str, metier);
                    }
                }
                
                if (CPUE != Double.NaN){
                    if (log.isDebugEnabled()) {
                        log.debug("on a une CPUE l'année n-1, on la met dans l'historique\n" + info + " " + metier);
                    }
                    HistoriqueCPUE.setValue(str, metier, info.getMonth(), CPUE);
                } else {
                    if (log.isDebugEnabled()) {
                        log.debug("ou calcul déjà fait ou pas de CPUE à l'année n-1");
                    }
                    CPUE = HistoriqueCPUE.getValue(str, metier, info.getMonth());
                    if (CPUE != Double.NaN) {
                        if (log.isDebugEnabled()) {
                            log.debug("on a une CPUE dans le passé: " + CPUE);
                        }
                    } else{
                        if (log.isDebugEnabled()) {
                            log.debug("on a pas de CPUE dans le passé, on remet donc la poportion initiale");
                        }
                    }
                }
                if (CPUE != Double.NaN) {
                    // si on a une CPUE, il faut aller voir l'attractivite des autres metiers: 2 cas de figure:
                    // soit on en retrouve une dans le passe qui est son attractivite
                    // soit on en retrouve pas et dans ce cas le, la proportion d'effort sera celle initiale de la db
                    // on a alors newProp=(1-sum(pmet1))*CPUE/somme(CPUEmet2)
                    // avec pmet1 la proprtion initiale pour les metiers pour lesquels on a pas retrouve de CPUE, CPUEmet2, la CPUE pour les 
                    // metiers qui ont eu une CPUE dans le passe, et CPUE la CPUE du metier courant
                    if (SommeCPUEstrat.getValue(str) == Double.NaN){
                        double SommeCPUE=0;
                        double PropTot=0;
                        for (Metier strMetier : strMetiers){
                            double temp = Double.NaN;
                            if (effortPerStrMet.getValue(str, strMetier) > 0){
                                temp = catchPerUnitOfEffort.getValue(str, strMetier);
                            }   
                            if (temp != Double.NaN){
                                if (log.isDebugEnabled()) {
                                    log.debug("on a une CPUE l'année n-1, on l'ajoute à p.SommeCPUEstrat");
                                }
                                HistoriqueCPUE.setValue(str, metier, info.getMonth(), temp);
                                SommeCPUE += temp;
                            }
                            else{
                                if (log.isDebugEnabled()) {
                                    log.debug("on a pas de CPUE l'année n-1, on regarde si il y en a dans le passé");
                                }
                                temp = HistoriqueCPUE.getValue(str, metier, info.getMonth());
                                if (temp != Double.NaN){
                                    if (log.isDebugEnabled()) {
                                        log.debug("on a une CPUE dans le passé: "+temp);
                                    }
                                    SommeCPUE+=temp;
                                }
                                else{
                                    if (log.isDebugEnabled()) {
                                        log.debug("on a pas de CPUE dans le passé, on l'ajoute donc à p.SommeProp");
                                    }
                                    PropTot += info.getProportionMetier(strMetier);
                                }
                            }
                        }
                        SommeCPUEstrat.setValue(str, SommeCPUE);
                        SommeProp.setValue(str, PropTot);
                    }

                    if (SommeCPUEstrat.getValue(str) > 0){
                        newPropDiv = (SommePropInitial.getValue(str, info.getMonth()) - SommeProp.getValue(str))
                                * CPUE / SommeCPUEstrat.getValue(str);
                        if (log.isDebugEnabled()) {
                            log.debug("newpropdiv: " + newPropDiv);
                        }
                        info.setProportionMetier(metier, newPropDiv);
                    }
                    else { // c'est le cas e priori quasi impossible oe y a toujours eu une CPUE nulle quelquesoit le metier, dans ce cas le on remet aussi la prop iniitiale
                        if (log.isDebugEnabled()) {
                            log.debug("aucun metier n'a d'attractivite, on remet la prop initiale");
                        }
                    }
                }
            }
        }

        if (log.isDebugEnabled()) {
            log.debug("fin Gravite CPUEAction avant");
        }
        
/*
writeln("Gravite Action avant metier:"+p.metier+ "date:"+ p.date);
var Chaine=Packages.java.lang.String;



var month=p.date.getMois();
var allStr = SiMatrice.getListStrategies();
var newPropDiv=0.0;

if (!p.CalculCatch){
	var resultmanager=control.getResultatManager();

	//on commence par creer une matrice de capture par strategie met
	var ListePopulation=SiMatrice.getListPopulations();
	pop=ListePopulation.get(0);
	var CaptureParStrMet=resultmanager.getMatrix(new Packages.fr.ifremer.nodb.Date(p.date.getDate()-12),pop,"matriceCatchWeightPerStrategyMet");
	CaptureParStrMet=CaptureParStrMet.sumOverDim(3);
	CaptureParStrMet=CaptureParStrMet.sumOverDim(2);
	writeln(CaptureParStrMet);
	for (var ipop=1;ipop<ListePopulation.size();ipop++){
		var pop=ListePopulation.get(ipop);
		var temp=resultmanager.getMatrix(new Packages.fr.ifremer.nodb.Date(p.date.getDate()-12),pop,"matriceCatchWeightPerStrategyMet");
		temp=temp.sumOverDim(3);
		temp=temp.sumOverDim(2);
		writeln(temp);
		CaptureParStrMet.add(temp);
		writeln(CaptureParStrMet);
	}
			
	var Effort=resultmanager.getMatrix(new Packages.fr.ifremer.nodb.Date(p.date.getDate()-12),"matriceEffortPerStrategyMet");
	writeln("on récupère la matrice d'effort");
	for (var iStrategie=allStr.iterator();iStrategie.hasNext();){
		var Strategie=iStrategie.next();
		var listeMet=Strategie.getSetOfVessels().getMetiers();
		for (imetier=listeMet.iterator();imetier.hasNext();){
			var metier=imetier.next();
			var effort=Effort.getValue(Strategie,metier);
			if (effort>0){
				var effortdesc=Strategie.getSetOfVessels().getEffort(metier);
				var nombreop=effortdesc.getFishingOperation();
				var nombregear=effortdesc.getGearsNumberPerOperation();
				var param=metier.getValeurParamControlable();
				var facteur=metier.getValeurParamControlable()*nombreop*nombregear/24.0;	
				//on restandardise l'effort
				effort/=facteur;
				//on recupere la capture tot
				var dim3=new Packages.java.util.ArrayList(CaptureParStrMet.getSemantics(3));
				var dim2=new Packages.java.util.ArrayList(CaptureParStrMet.getSemantics(2));
				
				var capture=CaptureParStrMet.getValue(Strategie,metier,dim2.get(0),dim3.get(0));
				
				p.CatchperUnitOfEffort.setValue(Strategie,metier,capture/effort);
			}
		}
	}
	p.CalculCatch=true;
}



for(var i=0; i<allStr.size(); i++){
	var str = allStr.get(i);
	var strMonthInfo = str.getStrategyMonthInfo(month);
	var propStrMet = strMonthInfo.getPropStrMetier(p.metier);

	if(str.getSetOfVessels().getMetiers().contains(p.metier)){
//on verifie que le metier est bien un metier possible
		var CPUE=null;
		if (!p.SommeCPUEstrat.containsKey(new Chaine(str.getName()))){
			if (GravityModel.getEffortPerStrategyMet(str, p.metier, new Packages.fr.ifremer.nodb.Date(p.date.getDate()-12))>0){
				CPUE=p.CatchperUnitOfEffort.getValue(str,p.metier);
			}
		}
		if (CPUE!=null){
			writeln("on a une CPUE l'année n-1, on la met dans l'historique");
			writeln(strMonthInfo+" "+p.metier);
			p.HistoriqueCPUE.put(new Chaine(str.getName()+"-"+p.metier.getNom()+"-"+strMonthInfo.getMonth().getNumMois()),CPUE);
		}
		else{
			writeln ("ou calcul déjà fait ou pas de CPUE à l'année n-1");
			CPUE=p.HistoriqueCPUE.get(new Chaine(str.getName()+"-"+p.metier.getNom()+"-"+strMonthInfo.getMonth().getNumMois()));
			if (CPUE!=null){
				writeln ("on a une CPUE dans le passé: "+CPUE);
			}
			else{
				writeln ("on a pas de CPUE dans le passé, on remet donc la poportion initiale");
			}
		}
		if (CPUE!=null) {
//si on a une CPUE, il faut aller voir l'attractivite des autres metiers: 2 cas de figure:
// soit on en retrouve une dans le passe qui est son attractivite
// soit on en retrouve pas et dans ce cas le, la proportion d'effort sera celle initiale de la db
//on a alors newProp=(1-sum(pmet1))*CPUE/somme(CPUEmet2)
//avec pmet1 la proprtion initiale pour les metiers pour lesquels on a pas retrouve de CPUE, CPUEmet2, la CPUE pour les 
//metiers qui ont eu une CPUE dans le passe, et CPUE la CPUE du metier courant
			if (!p.SommeCPUEstrat.containsKey(str.getName())){
				var SommeCPUE=0;
				var PropTot=0;
				var metiers=str.getSetOfVessels().getMetiers();
				for (var imetiers=metiers.iterator();imetiers.hasNext();){
					var metier=imetiers.next();
					var temp=null;
					if (GravityModel.getEffortPerStrategyMet(str, metier, new Packages.fr.ifremer.nodb.Date(p.date.getDate()-12))>0){
						temp=p.CatchperUnitOfEffort.getValue(str,metier);
					}	
					if (temp!=null){
						writeln("on a une CPUE l'année n-1, on l'ajoute à p.SommeCPUEstrat");
						p.HistoriqueCPUE.put(new Chaine(str.getName()+"-"+metier.getNom()+"-"+strMonthInfo.getMonth().getNumMois()),CPUE);
						SommeCPUE+=temp;	
					}
					else{
						writeln ("on a pas de CPUE l'année n-1, on regarde si il y en a dans le passé");
						temp=p.HistoriqueCPUE.get(new Chaine(str.getName()+"-"+metier.getNom()+"-"+strMonthInfo.getMonth().getNumMois()));
						if (temp!=null){
							writeln ("on a une CPUE dans le passé: "+temp);
							SommeCPUE+=temp;
						}
						else{
							writeln ("on a pas de CPUE dans le passé, on l'ajoute donc à p.SommeProp");
							PropTot+=strMonthInfo.getProportionMetier(metier);
						}
					}
				}
				p.SommeCPUEstrat.put(new Chaine(str.getName()),SommeCPUE);
				p.SommeProp.put(new Chaine(str.getName()),PropTot);
			}
			if (p.SommeCPUEstrat.get(new Chaine(str.getName()))>0){
				newPropDiv = (p.SommePropInitial.get(new Chaine(str.getName()+"-"+strMonthInfo.getMonth().getNumMois()))-p.SommeProp.get(new Chaine(str.getName())))*CPUE/ p.SommeCPUEstrat.get(new Chaine(str.getName()));
				writeln("newpropdiv: "+newPropDiv);
				strMonthInfo.setProportionMetier(p.metier, new Packages.java.lang.Float(newPropDiv));
			}
			else { // c'est le cas e priori quasi impossible oe y a toujours eu une CPUE nulle quelquesoit le metier, dans ce cas le on remet aussi la prop iniitiale
				writeln("aucun metier n'a d'attractivite, on remet la prop initiale");
			}
		}
	}
}

writeln("fin Gravite CPUEAction avant");
return p.gestionMetier;
*/
    }
 
    /**
     * 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 {
        SommeCPUEstrat.map(nanFunction);
        SommeProp.map(nanFunction);
        catchPerUnitOfEffort.map(nanFunction);
    }

}
