package rules;

import java.util.*;
import java.io.*;
import java.util.regex.*;

import org.nuiton.math.matrix.*;

import fr.ifremer.isisfish.simulator.SimulationContext;
import fr.ifremer.isisfish.rule.AbstractRule;
import fr.ifremer.isisfish.annotations.Doc;
import fr.ifremer.isisfish.entities.*;
import fr.ifremer.isisfish.types.*;

import scripts.SiMatrix;
import static scripts.PTAtoolbox.expandEnvirVar;

/**
 * version EMU1 de la règle Effort_2015_2017_mean_GDL
 *
 * Auteure: LEFORESTIER Sophie, 2018
 * Dernière modif: PHAN Tuan Anh, 2022:
 *   - adapter pour EMU1 2018-2020
 *   - ajouter la fonctionnalité qui permet de d’utiliser les variables d’environnement,
 *     (il faut être accompagné du script PTAtoolbox)
 *   - dapation macco : 2015-2018, forcage pour toutes les strategies,
 *     changement inactivite, effort_prop, nb vessels et prop_vessels,
 *     lecture d'un fichier par annee
 */
public class ForcageEffortMacco_2015_2018mean2010_2020_SM extends AbstractRule {

	@Doc("README")
	public String param_README = String.join(" ►►► ",
		"auteure: LEFORESTIER Sophie, 2018",
		"dernière modif: PHAN Tuan Anh, 2022",
			"adaptation macco : Stephanie M 2023 "
			
	);

	public static String param_cheminFichierAverage = "${PBS_O_WORKDIR}/forcage/" ;
    //public static String param_cheminFichierAverage = "C:/Users/aricouar/Documents/MesProjets_local/ISIS_simul/forcage/";
    //public static String param_cheminFichierAverage ="${PBS_O_WORKDIR}/forcage/";
    public static String param_cheminFichierInactivity = "inactivite/";
    public static String param_cheminFichierEffortProp = "str_prop_met/";
	//public static String param_InactivityFileName = "Total.Inactivite.effort.csv";
	//public static String param_nbVessFileName = "Total.NbBateau.effort.csv";
    public static String param_cheminFichierNbVessSov = "nb_vessels/";
    public static String param_cheminFichierPropNbVess = "nb_prop_vessels/";
	//public static String param_EffortPropFileName = "Total.Proportion.strategy.csv";
   
    public boolean param_averagefrom2010 = true;
	
	@Doc(value = "simulation starting year compared to estimation starting year (0: 2015; 1: 2016; 2: 2017; 3: 2018; 4: average 2010-20)")
	public int param_startDate = 0;
    

 
    //public int param_endDate = 0;
    private int param_year = 2015 + param_startDate; //annee de debut

	private List<Strategy> allStrategies;
    private List<Metier> allMetiers;
    private List<SetOfVessels> allSetOfVessels;
	private MatrixND matInactivity; //anneexstr
	private MatrixND matEffort; //anneexstrxmetierxmois
	private MatrixND matNbVessSov; //anneexsov
    private MatrixND matPropVess; //anneexstr

    Pattern ForeignPattern = Pattern.compile("^(ES|UK|BE)"); //pour les flottilles/str etrangeres on ne change pas nb_vessels et prop_vessells
	private boolean first = true; // ne boucler que sur un seul métier dans la préaction :

	public String[] necessaryResult = {}; // must be initialized, error if not
	public String[] getNecessaryResult() { return this.necessaryResult; }

	/** Permet d’afficher à l’utilisateur une aide sur la règle */
	public String getDescription() {
		return "Forcer chaque année et mois les % efforts et inactivité observés entre 2015 et 2018 puis leur moyenne";
	}

    protected List<SetOfVessels> getSetOfVessels(List<Strategy> strategies) {
        List<SetOfVessels> setOfVessels = new ArrayList<>();
        Set<SetOfVessels> tmp = new HashSet<>();
        for (Strategy str : strategies) {
            SetOfVessels sov = str.getSetOfVessels();
            if (tmp.add(sov)) {
                setOfVessels.add(sov);
            }
        }
        return setOfVessels;
    }
    
	/**
	 * Appelée au démarrage de la simulation, cette méthode permet d’initialiser des valeurs.
	 *
	 * @param context la simulation pour lequel on utilise cette règle
	 */
	public void init(SimulationContext context) throws Exception {

		// récupération des set of vessels, stratégies
		SiMatrix siMatrix = SiMatrix.getSiMatrix(context);
		TimeStep step = new TimeStep(0);

		// récupération des stratégies
		allStrategies = siMatrix.getStrategies(step);
		allMetiers = siMatrix.getMetiers(step);
        allSetOfVessels = getSetOfVessels(allStrategies);
		List<Month>allMonths=Arrays.asList(Month.MONTH);
        

        // (0: 2015; 1: 2016; 2: 2017; 3: 2018; 4: average 2010-20)
        //List<Integer> annees = List.of(0, 1, 2, 3, 4);
		List<Integer> annees = List.of(0, 1, 2, 3);
        // init all matrix
        matInactivity = MatrixFactory.getInstance().create(
                "matInactivity", new List[] { annees, allStrategies, allMonths },
                new String[] { "Annee", "Strategies","Months" });
        matEffort = MatrixFactory.getInstance().create(
                "matEffort", new List[] { annees, allStrategies, allMetiers, allMonths },
                new String[] { "Annee", "Strategies", "Metiers","Months" });      
        matNbVessSov = MatrixFactory.getInstance().create(
                "matNbVessSov", new List[] { annees, allSetOfVessels },
                new String[] { "Annee", "Flottille" });
        matPropVess = MatrixFactory.getInstance().create(
                "matPropVess", new List[] { annees, allStrategies },
                new String[] { "Annee", "Strategies" });

		String param_StringYear;
		//chargement dans l'init des matrices pour toutes les annnees 
		// Boucle sur les annees (year) de startDate a endDate  pour remplir les matrices
        // Import des matrices 2015,2016,2017,2018,moyenne2010-2020

       param_cheminFichierAverage = expandEnvirVar.replace(param_cheminFichierAverage);
  
        for (Integer annee : annees) {

			param_year = 2015 + annee;
           System.out.println("---------------------- Annee =  " + param_year);
            // NEW : nb vessels de set of vessels :fichier avec semantic des setof vessels et annee de 0 =2015 à ...

		    File nbVessSovFile = new File(param_cheminFichierAverage + param_cheminFichierNbVessSov + param_year + "_allSetofVessels.txt");
	        MatrixND matNbVessSovForYear = MatrixFactory.getInstance().create(nbVessSovFile);
            for (SetOfVessels sov : allSetOfVessels) {
                String sovName=sov.getName();
                 Matcher ForeignMatch = ForeignPattern.matcher(sovName);
                if (!ForeignMatch.find()) {
                    // if(matNbVessSovForYear.getValue(sov)==null) mettre 0 sinon remplir
                 if (matNbVessSovForYear.getSemantic(0).contains(sov)) {    
                    matNbVessSov.setValue(annee, sov, matNbVessSovForYear.getValue(sov));
                 }  
                 //else {
                 //    matNbVessSov.setValue(annee, sov, 0);
                 // }
                }
            }
		    System.out.println("matrice nb navires Set of Vessels " + matNbVessSov);


            // Inactivity
			File inactivityFile = new File(param_cheminFichierAverage + param_cheminFichierInactivity + param_year + "_allStrategy.txt");
            MatrixND matInactivityForYear = MatrixFactory.getInstance().create(inactivityFile);
            for (Strategy str : allStrategies) {
                String strName = str.getName();
                Matcher ForeignMatch = ForeignPattern.matcher(strName);
                if (!ForeignMatch.find()) {
                for (Month month : allMonths){
                       // if(matInactivityForYear.getValue(str,month)==null) mettre 0 sinon remplir
                   if (matInactivityForYear.getSemantic(0).contains(str)) {  
                    matInactivity.setValue(annee, str, month,matInactivityForYear.getValue(str,month));
                   }
                   //else {
                   // matInactivity.setValue(annee, str, month,0);
                   //}
                }
                }
            }
            System.out.println("matrice inactivite " + matInactivity);


            // propVess ?
			File propVessFile = new File(param_cheminFichierAverage + param_cheminFichierPropNbVess + param_year + "_allStrategy.txt"); //double nbv = matNbVess.getValue(str, ts.getYear()); donc fichier avec semantic des stratégies et annee de 0 =2015 à ...
            MatrixND matPropVessForYear = MatrixFactory.getInstance().create(propVessFile);
            for (Strategy str : allStrategies) {
                String strName = str.getName();

                Matcher ForeignMatch = ForeignPattern.matcher(strName);
                if (!ForeignMatch.find()) {
					System.out.println("strategie = " +strName);
                  if (matPropVessForYear.getSemantic(0).contains(str)) {  
                    // if matPropVessForYear.getValue(str)=null mettre 0
                   matPropVess.setValue(annee, str, matPropVessForYear.getValue(str));
                  } 
                  //else {
                  // matPropVess.setValue(annee, str, 0);  
                  // }
                }
            }
            System.out.println("matrice prop navires Set of Vessels " + matPropVess);


            // Effort
			File effortFile;
			MatrixND matEffortForYear;
              for (Strategy str : allStrategies) {
				  String strName = str.getName();
				  System.out.println("strategie = " +strName);
				  effortFile = new File(param_cheminFichierAverage + param_cheminFichierEffortProp + param_year + "_"+ str + ".txt"); // double newProp = matEffort.getValue(str, strMetier, ts); ; donc fichier avec semantic des stratégies, des metiers et annee de 0 =2015 à ...
				  matEffortForYear = MatrixFactory.getInstance().create(effortFile);
				  Collection<EffortDescription> strMet = str.getSetOfVessels().getPossibleMetiers() ;
				  for (EffortDescription ed : strMet) {
					  Metier met = ed.getPossibleMetiers();
					  String metName = met.getName();
					  System.out.println("metier = " +metName);
					  for (Month month : allMonths) {
							matEffort.setValue(annee, str, met, month, matEffortForYear.getValue(met, month));
					  }
                }
            }
			System.out.println("matrice effort " + matEffort);
        }
	}

	/**
	 * La condition qui doit être vraie pour faire les actions.
	 *
	 * @param context la simulation pour laquelle on utilise cette règle
	 * @param step le pas de temps courant
	 * @param metier le métier concerné
	 * @return vrai si on souhaite que les actions soit faites
	 */
	public boolean condition(SimulationContext context, TimeStep step, Metier metier) { 
       
        int start = param_startDate * 12; // If startDate != 0
        TimeStep ts = step.add(start);
        boolean before2019 = (ts.getStep() < 48);
        if (before2019){ // before 201, do preaction
             return true; 
        } else {
	       if (param_averagefrom2010) { 
            return false; // Do not change prop_str : on utlise la moyenne2010-2020 qui est par defaut dans la base
	       } else {
	        return true; // use 2018 values
	       }
	    }

	}

	/** Si la condition est vraie alors cette action est exécutée avant le pas de temps de la simulation. */
	public void preAction(SimulationContext context, TimeStep step, Metier metier) {

		if (first) {
			System.out.println("Oui, preaction : ");

			// If startDate != 0
			int start = param_startDate * 12;
			TimeStep ts = step.add(start); //time step relative to 2015
         
			//if (step.getStep() + 12 > 47) ts = new TimeStep(ts.getMonth().getMonthNumber() + 36);
		    //if (step.getStep() + 12 > 59) ts = new TimeStep(ts.getMonth().getMonthNumber() + 48); // une annee de plus - ne marchera que quand on aura une moyenne
            // if 
			if (ts.getStep() > 47) ts = new TimeStep(ts.getMonth().getMonthNumber() + 36); // ie on simule avec valeur de 2018
			System.out.println("Timestep : " + ts);


			//Boucle sur les Set of Vessels : a verifier
			for (SetOfVessels sovIndex : allSetOfVessels){
				String sovName=sovIndex.getName();
				System.out.println("Set of Vessels : "+sovName);

				// interdit de faire des set sur les stratégies de la sémantique de la matrice, il faut récupérer les stratégies de la date courante ?? pour sov
		        SetOfVessels sov=(SetOfVessels)context.getDB().findByTopiaId(sovIndex.getTopiaId());

				// Change vessel number
				Matcher ForeignMatch = ForeignPattern.matcher(sovName);
				if (ForeignMatch.find()) {
					System.out.println("    foreign, don’t change nb vessells");
				} else {
					int nbvSov=(int)matNbVessSov.getValue(ts.getYear(), sov);
					sovIndex.setNumberOfVessels(nbvSov);
					System.out.println("Nb bateaux : "+nbvSov);
				}
			}
			// Boucle sur les stratégies
			for (Strategy strIndex : allStrategies) {
				String strName = strIndex.getName();
				System.out.println("Strategie : " + strName);

				// interdit de faire des set sur les stratégies de la sémantique de la matrice, il faut récupérer les stratégies de la date courante
				Strategy str = (Strategy) context.getDB().findByTopiaId(strIndex.getTopiaId());

				// Change prop_vessel number
				Matcher ForeignMatch = ForeignPattern.matcher(strName);
				if (ForeignMatch.find()) {
					System.out.println("    foreign, don’t change prop vessells");
				} else {
					double nbv = matPropVess.getValue(ts.getYear(), str);
					str.setProportionSetOfVessels(nbv);
					System.out.println("Nb bateaux : " + nbv);
				}

				// Change Effort proportion
				StrategyMonthInfo smi = str.getStrategyMonthInfo(ts.getMonth());
				Collection<EffortDescription> strMet = str.getSetOfVessels().getPossibleMetiers() ;
				for (EffortDescription ed : strMet) {
					Metier strMetier = ed.getPossibleMetiers();
					double newProp = matEffort.getValue(ts.getYear(), str, strMetier,ts.getMonth()); 
					smi.setProportionMetier(strMetier,newProp);
					System.out.println("proportion : " + strMetier + " " + newProp);
					System.out.println(smi.getProportionMetier(strMetier));

				}

				// Change Inactivity équation
				double newInactivity = matInactivity.getValue(ts.getYear(), str, ts.getMonth());
				//double newInactivity = 20;
				System.out.println("newInactivity :" + newInactivity);
				Equation eqInac = str.getInactivityEquation();
				eqInac.setContent("return " + newInactivity + " ;");
				str.setInactivityEquationUsed(true) ;
				System.out.println("Inactivity :" + eqInac.getContent());

			}
			first = false;
		}

	}

	/** Si la condition est vraie alors cette action est exécutée après le pas de temps de la simulation. */
	public void postAction(SimulationContext context, TimeStep step, Metier metier) { first = true; }

}
