package rules;

import java.util.HashSet;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Set;
import org.nuiton.util.*;
import java.io.File;
import java.io.Writer;
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import fr.ifremer.isisfish.IsisFishDAOHelper;
import fr.ifremer.isisfish.util.Doc;
import static org.nuiton.i18n.I18n._;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.nuiton.math.matrix.*;
import org.nuiton.topia.*;
import org.nuiton.util.FileUtil;

//import scripts.ResultName;
import scripts.RuleUtil;
import scripts.SiMatrix;
import scripts.MinimisationUtil;
import scripts.ObjectiveFunctionBaranov;

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.simulator.SimulationParameter;
import fr.ifremer.isisfish.datastore.SimulationStorage;
import fr.ifremer.isisfish.datastore.ResultStorage;

import resultinfos.MatrixGrossValueOfLandingsPerSpeciesPerStrategyMet;
import resultinfos.MatrixLandingWeight;
import resultinfos.MatrixEffortPerStrategyMet;
import resultinfos.MatrixEffortNominalPerStrategyMet;

/**
 * GravityModelMixChannel.java
 *
 * Created 03/09/2013
 *
 * @author sigrid
 * @version $Revision: 3.3
 *
 * Last update: $Date: $
 * by : $Author: sigrid $
 */


public class GravityModelMixChannel extends AbstractRule {

    /** to use log facility, just put in your code: log.info("..."); */
    static private Log log = LogFactory.getLog(GravityModelMixChannel.class);
 	
	@Doc("balance tradition/opportunism")
    public double param_behaviorBalance = 0.8;
	public TimeStep param_beginDate = new TimeStep(12);
	
	protected List<Metier> metiers;
	protected List<Strategy> allStrategies;
	// interdit de faire des set sur les strategies de la map, il faut recuperer les strategies de la date courante
	
	// path matrix of vpue met other
	String param_nomfichier_vpueOthers = "InputMixChannel/Gravity_V4/ValOth_Discardless_220516.csv";
	// path matrix of distances
	String param_nomfichier_distances = "InputMixChannel/Gravity_V4/Distances_V4.csv";
	// path matrix of average fishing time
	String param_nomfichier_avFishTime = "InputMixChannel/Gravity_V4/AvFishTime_V4.csv";

	protected File VPUEotherslong;
	protected MatrixND matrixVpueOthersLong;
	protected MatrixND matrixVpueOthers;
	
	protected File Distances;
	protected MatrixND matrixDistance;
	
	protected File AvFishTime;
	protected MatrixND matrixAvFishTime;

	protected MatrixND matrixVar; 

	protected double fuelprice;
	
	static final protected String EFFORT = "Effort";
	static final protected String LVALUE = "LandingsValue";
	static final protected String LQUANT = "LandingsQuant";
	static final protected String VPUE = "VPUE";
	static final protected String COST = "fuelCosts";
	static final protected String OPP = "opportunism";
	
    protected String [] necessaryResult = {
            MatrixGrossValueOfLandingsPerSpeciesPerStrategyMet.NAME,
            MatrixLandingWeight.NAME,
            MatrixEffortPerStrategyMet.NAME,
            MatrixEffortNominalPerStrategyMet.NAME,
    };
    
    /**
     * @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 ("Compute time proportion on metiers as a function of tradition and opportunism / "); 
    }
 
  

  /**
     * Appel
     * des valeurs
     * @param simulation La simulation pour lequel on utilise cette regle
     */
    public void init(SimulationContext context) throws Exception  {


    // load files
        VPUEotherslong = new File(param_nomfichier_vpueOthers);
		Distances = new File(param_nomfichier_distances);
		AvFishTime = new File(param_nomfichier_avFishTime);
	
	// reccuperation des metiers et strategies
		SiMatrix siMatrix = SiMatrix.getSiMatrix(context);
		metiers = siMatrix.getMetiers(new TimeStep(0));		
		allStrategies = siMatrix.getStrategies(new TimeStep(0));	
		List <Month> months = Month.getMonths(Month.JANUARY,Month.DECEMBER);
		
	////////////////////////
		/** Metiers for version 4*/ 
		String[] StringMetOrder = {"DRB_27E9","DRB_28E8","DRB_28E9","DRB_28F0","DRB_29F0","DRB_29F1","DRB_30F0","DRB_30F1","DRB_VIId_Nd","DRB_VIId_Nm",
		"GNS_27E9","GNS_28E8","GNS_28E9","GNS_28F0","GNS_29F0","GNS_29F1","GNS_30F1","GNS_VIId_Nd",
		"GNS_VIId_Nm",
		"GTR_27E9","GTR_28E8","GTR_28E9","GTR_28F0","GTR_29F0","GTR_29F1","GTR_30F1","GTR_VIId_Nd","GTR_VIId_Nm",
		"OTB_27E9","OTB_28E8","OTB_28E9","OTB_28F0","OTB_29E9","OTB_29F0","OTB_29F1","OTB_30F0","OTB_30F1","OTB_VIId_Nd","OTB_VIId_Nm",
		"OTH-OTH","OTH-VIId",
		"TBB_27E9","TBB_28E8","TBB_28E9","TBB_28F0","TBB_29F0","TBB_29F1","TBB_30F0","TBB_30F1","TBB_VIId_Nd","TBB_VIId_Nm"};

		String[] StringStrOrder = {"1_North_18-40","1_Norm_18-40","1_Norm_Inf12","13_North_10-12","13_North_12-18","13_North_Inf10",
		"13_Norm_10-12","13_Norm_Inf10","2_North_18-24","2_North_24-40","2_Norm_18-40","4_North_10-12",
		"4_North_12-18","4_Norm_10-12","4_Norm_12-18","4_Norm_18-24","4_Norm_Inf10"};
		
		String[] StringGearOrder = {"DRB","GNS","GTR","GTR_90","OTB","TBB","OTH"};
		
		List <String> metOrder = Arrays.asList(StringMetOrder);
		List <String> strOrder = Arrays.asList(StringStrOrder);
		List <String> geaOrder = Arrays.asList(StringGearOrder);
		List<Month> Months = Arrays.asList(Month.MONTH);

	//creation et import de la matrice des vpue other str-mois x metiers
		int longueur = 12 * allStrategies.size();
		int [] dimMatrix = {longueur,metiers.size()};	
		matrixVpueOthersLong = MatrixFactory.getInstance().create(
				"matVpueOtherslong",dimMatrix);
		matrixVpueOthersLong.importCSV(new FileReader(VPUEotherslong),new int []{0,0});
	//creation de la matrice des vpue other
		matrixVpueOthers = MatrixFactory.getInstance().create(
				"matVpueOthers",
				new List[]{Months,strOrder,metOrder},
                new String[]{"Month","Strategies","Metiers"});
		for(int mois=0; mois <12; mois++){
			for(int str=0; str< strOrder.size();str++){
				for(int met=0; met< metiers.size();met++){
					matrixVpueOthers.setValue(mois, str, met, matrixVpueOthersLong.getValue(mois*allStrategies.size()+str,met));
				}
			}
		}
			
	// Matrice of distance to port / met
		//creation et import de la matrice des distances au port [strategies x metiers]
		matrixDistance = MatrixFactory.getInstance().create(
				"matDistance",
				new List[]{strOrder,metOrder},
                new String[]{"Strategies","Metiers"});
		matrixDistance.importCSV(new FileReader(Distances),new int []{0,0});	
		
		
	// Matrice of average fishing time per str and gear
		matrixAvFishTime = MatrixFactory.getInstance().create(
				"matAvFishTime", 
				new List[]{strOrder,geaOrder}, 
				new String[]{"Strategies","Gear"});
		matrixAvFishTime.importCSV(new FileReader(AvFishTime),new int []{0,0});
	
	// fuel price
	fuelprice = 0.59;
	if(context.getValue("fuelprice") != null){
		fuelprice = (double) context.getValue("fuelprice");
	}

    // gravity
      if(context.getSimulationStorage().getParameter().getTagValue().get("gravity") != null){
          param_behaviorBalance = Double.parseDouble(context.getSimulationStorage().getParameter().getTagValue().get("gravity"));
     }
	}// fin de init		
	
	
	
	// all matrix var
	protected Map<TimeStep, MatrixND> allMatrixVar = new HashMap<TimeStep, MatrixND>();
		protected MatrixND getMatrixVar(TimeStep askedDate) {
		MatrixND result = allMatrixVar.get(askedDate);
		// si on demande on matrice qui n existe pas encore : on la cree
		if (result == null) {
			MatrixND matrixVar = createMatrixVar();
			allMatrixVar.put(askedDate, matrixVar);
			result = matrixVar;
		}
		return result;
	}
	
	protected MatrixND createMatrixVar (){
		// Creation d'une nouvelle matrix qui n'existe pas encore ...
		MatrixND matrixVar = MatrixFactory.getInstance().create(
			"matrixVar",
			new List[]{allStrategies, metiers, Arrays.asList(new String[]{EFFORT, LVALUE, LQUANT,VPUE,COST,OPP})},
            new String[]{"Strategies","Metiers","Variables"});
		return matrixVar ;
	}	
	
	

	
	/**
     * 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, TimeStep step, Metier metier) throws Exception {
        return step.after(param_beginDate.previousYear());
    }

	
	/**
     * Si la condition est vrai alors cette action est execut
     * de temps de la simulation.
     * @param simulation La simulation pour lequel on utilise cette regle
     */
	
    boolean first = true ;
	public void preAction(SimulationContext context, TimeStep step, Metier metier) throws Exception {
	
		// la preaction n est realisee qu une seule fois pour tous les metiers		
		if (first == true){
			first = false ;
			SiMatrix siMatrix = SiMatrix.getSiMatrix(context);
			ResultStorage Result = context.getSimulationStorage().getResultStorage();
			MatrixND currentMatrixVar = getMatrixVar(step);
			List<Strategy> strategies = (List<Strategy>)currentMatrixVar.getSemantic(0);
			List<Metier> metiers = (List<Metier>)currentMatrixVar.getSemantic(1);
		 	List<Population> Pops = context.getPopulationMonitor().getPopulations();
		
			// 1)  reccupere valeurs effort et debarquement du mois precedant -> vpue

				MatrixND valueMat = Result.getMatrix(step.previous(), MatrixGrossValueOfLandingsPerSpeciesPerStrategyMet.NAME);
				valueMat = valueMat.sumOverDim(2).reduceDims(2); // sum pop ([str x met x pop])
				MatrixND EffortNominalPerStrMet_pm = Result.getMatrix(step.previous(),MatrixEffortNominalPerStrategyMet.NAME);
				
				List[] sems = valueMat.getSemantics();
				MatrixND quantMat = MatrixFactory.getInstance().create(sems);
				for(Population pop:Pops){
					MatrixND tmp = Result.getMatrix(step.previous(), pop, MatrixLandingWeight.NAME);
					tmp = tmp.sumOverDim(3);
					tmp = tmp.sumOverDim(2);
					tmp = tmp.reduceDims(2,3);
					quantMat.add(tmp);
				}
			
				List<Strategy> str = (List<Strategy>)valueMat.getSemantic(0);

				for(Strategy strIndex : str){ // boucle sur les str 
					Strategy strCourante = (Strategy)context.getDB().findByTopiaId(strIndex.getTopiaId());
					Collection<EffortDescription> strMet = strCourante.getSetOfVessels().getPossibleMetiers() ;
					for(EffortDescription effort : strMet){
						Metier metIndex = effort.getPossibleMetiers();
						double valOTH = matrixVpueOthers.getValue(step.previous().getMonth(),strCourante.getName(),metIndex.getName());
						double val = valueMat.getValue(strCourante,metIndex);
						getMatrixVar(step.previous()).setValue(strCourante,metIndex,LVALUE,val);
						double eff = EffortNominalPerStrMet_pm.getValue(strCourante,metIndex);
						getMatrixVar(step.previous()).setValue(strCourante,metIndex,EFFORT,eff);
						double quant = quantMat.getValue(strCourante,metIndex);
						getMatrixVar(step.previous()).setValue(strCourante,metIndex,LQUANT,quant);
						double vpue = 0;
						if(eff != 0.0){
							vpue = val / eff + valOTH;
						}else {
							vpue = 0;
						}
						getMatrixVar(step.previous()).setValue(strCourante,metIndex,VPUE,vpue);						
						
						double A = strIndex.getSetOfVessels().getVesselType().getUnitFuelCostOfTravel()* fuelprice; // * FuelPrice
						double cost = A * 2 * matrixDistance.getValue(strIndex.getName(),metIndex.getName())/ matrixAvFishTime.getValue(strIndex.getName(),metIndex.getGear().getName());
						getMatrixVar(step.previous()).setValue(strCourante,metIndex,COST,cost);		

						double opp;
						if(quant !=0){
							opp = Math.max((vpue - cost)/quant,0);
						}else opp = 0;
						getMatrixVar(step.previous()).setValue(strCourante,metIndex,OPP,opp);		

					}
				}
				
				if(step.afterOrEquals(param_beginDate)){
					// Compute new effort percentages
					for(Strategy strIndex : str){ // boucle sur les str 
						Strategy strCourante = (Strategy)context.getDB().findByTopiaId(strIndex.getTopiaId());
						Collection<EffortDescription> strMet = strCourante.getSetOfVessels().getPossibleMetiers() ;
						StrategyMonthInfo smi = strCourante.getStrategyMonthInfo(step.getMonth());	
						Map<Metier, Double> perc_met = new HashMap<Metier, Double>();
						TimeStep previousStep = step.previous();
						MatrixND matrixVarPreviousMonth = getMatrixVar(previousStep);
						
						double cum_opp = matrixVarPreviousMonth.getSubMatrix(2,OPP).getSubMatrix(0,strCourante).sumAll();
						double cum_perc = 0;
						
						for (EffortDescription effort : strMet){ 		
							Metier met = effort.getPossibleMetiers() ;									    			
							double opp = matrixVarPreviousMonth.getValue(strCourante,met,OPP);
							double sumEff =getMatrixVar(step.previousYear()).getSubMatrix(2,EFFORT).getSubMatrix(0,strCourante).sumAll() ;
							double pEffMoy = 0;
							if(sumEff != 0){
								pEffMoy = getMatrixVar(step.previousYear()).getValue(strCourante,met,EFFORT) / sumEff;
							}
							double percent = 0;
							
							// if metier forbidden, percent = 0; if strategy made no value, percent depends on effort; 
							// else percent = a * tradition + (1-a) * opportunism
							if(context.getMetierMonitor().getForbiddenMetier().contains(met)){
								percent = 0;
							}else if(cum_opp == 0){
								percent = param_behaviorBalance * pEffMoy;
							}else{
								percent = param_behaviorBalance * pEffMoy + (1-param_behaviorBalance)*opp/cum_opp;
							}
							perc_met.put(met,percent);
							cum_perc += percent;
						}
						
						// Fill in strategy table
						for (EffortDescription effort : strMet){
							Metier met = effort.getPossibleMetiers() ;
							double pp = 0;
							if(cum_perc != 0){	
								pp = perc_met.get(met) / cum_perc;
							}
							smi.setProportionMetier(met,pp);
						}
					}
				}
		}
	}
	
 
    /**
     * Si la condition est vrai alors cette action est execute
     * de temps de la simulation.
     * @param simulation La simulation pour lequel on utilise cette regle
     */
    public void postAction(SimulationContext context, TimeStep step, Metier metier) throws Exception {
    first = true ;   
      
    }

}



