/*
 * Copyright (C) 2014 avigier
 *
 * 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 3 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-3.0.html>.
 */

package optimizations;

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

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

import fr.ifremer.isisfish.entities.*;
import fr.ifremer.isisfish.util.Doc;
import fr.ifremer.isisfish.IsisFishDAOHelper;
import fr.ifremer.isisfish.simulator.Optimization;
import fr.ifremer.isisfish.simulator.OptimizationContext;
import fr.ifremer.isisfish.simulator.SimulationContext;
import fr.ifremer.isisfish.simulator.SimulationParameter;
import fr.ifremer.isisfish.datastore.SimulationStorage;
import fr.ifremer.isisfish.datastore.ResultStorage;
import fr.ifremer.isisfish.entities.Metier;
import fr.ifremer.isisfish.entities.MetierSeasonInfo;
import fr.ifremer.isisfish.entities.Observation;
import fr.ifremer.isisfish.entities.Population;
import fr.ifremer.isisfish.entities.PopulationGroup;
import fr.ifremer.isisfish.entities.PopulationSeasonInfo;
import fr.ifremer.isisfish.entities.TargetSpecies;
import fr.ifremer.isisfish.types.Month;
import java.io.File;
import java.io.FileReader;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import static java.util.Arrays.asList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Comparator;
import objectives.CatchInWeightPerLengthProfileAndWeightings;
import org.apache.commons.io.FileUtils;
import org.nuiton.math.matrix.MatrixFactory;
import org.nuiton.math.matrix.MatrixIterator;
import org.nuiton.math.matrix.MatrixND;
import org.nuiton.topia.TopiaContext;
import org.nuiton.util.FileUtil;
//import scripts.ResultName;
import resultinfos.MatrixCatchPerStrategyMetPerZonePop;
import org.apache.commons.lang3.StringUtils;

/**
 * GeneticAlgorithmOptimizationGdGHake.java
 *
 * Created: 28 avril 2014
 *
 * @author avigier <user.name@vcs.hostName>
 * @version $Revision: 1545 $
 * Last update: $Date: 28 avril 2014 $
 * by : $Author: avigier $
 */
public class GeneticAlgorithmOptimizationGdGHake implements Optimization {

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

    @Doc("Path to export the historic file (trace of the algorithm). The root is the folder where the .bat is located.")
    public String param_exportPath="output/HistoricGA.csv";
    @Doc("Population")
    public Population param_Population = null;
    @Doc("File name and path of observations")
    public String param_observationFile = "S:/ApplicationsISIS/AppliMerlu_Langoustine_Sole/2017_MSE_SS3_Merlu/Calibration/CatchWeightPerLengthSemantics.csv";
    @Doc("Dimensions of the observation file")
    public String param_dimObservations = "1176;6";
    @Doc("GA parameter: Number of individuals in the population of solutions")
    public int param_taillePop = 5;
    @Doc("GA parameter: Number of parameters to calibrate = number of paremeters in a solution")
    public int param_tailleSol = 36;
    @Doc("Path to the file containing initial values for the individuals, their lower bounds and upper bounds")
    public String param_initialPopulation = "S:/ApplicationsISIS/AppliMerlu_Langoustine_Sole/2017_MSE_SS3_Merlu/Calibration/InitialPopulationSmall.csv";
    @Doc("GA parameter: Crossover method, choose between \"1X\", \"2X\", \"8X\"")
    public String param_crossoverMethod= "2X";
    @Doc("GA parameter: Crossover variations method (crossover are applied at each generation according to a probability) : \"Fixe\" or \"Adaptative\" or \"Geometric\"")
    public String param_crossoverVariationsMethod= "Geometric";
    @Doc("GA parameter: Crossover threshold, initial probability of applying crossover (between 0: never and 1: always)")
    public double param_crossoverThreshold= 1;
    @Doc("GA parameter: Mutation method, choose between \"Classic\", \"Normal\"")
    public String param_mutationMethod= "Classic";
    @Doc("GA parameter: Mutation variations method (mutations are applied at each generation according to a probability): \"Fixe\" or \"Adaptative\"")
    public String param_mutationVariationsMethod= "Fixe";
    @Doc("GA parameter: Mutation threshold, initial probability of applying mutation (between 0: never and 1: always)")
    public double param_mutationThreshold= 0.5;
    @Doc("GA parameter: Mutation rate, between 0 and 1")
    public double param_mutationRate= 0.8;
    @Doc("GA parameter: Elite number, between 1 and taillePop")
    public int param_eliteRank= 4;
    @Doc("GA parameter: Acceptation rank, between eliteRank and taillePop")
    public int param_acceptationRank= 16;
    @Doc("GA parameter: Stopping criterion, choose between \"GenerationNumber\", \"ConfidenceLevel\"")
    public String param_criterionType= "ConfidenceLevel";
    @Doc("GA parameter: level of confidence to reach before stopping the algorithm")
    public double param_criterionLevel= 1;
    @Doc("GA parameter: the alpha parameter in the objective function. The higher alpha, the higher the weight accorded to length compositions compared to total weight.")
    public double param_alphaOF= 1;

    //public long param_seed = 1;

    //Contains the parametrizations (solutions)
    protected File matrixInitFile;
    protected MatrixND matrixInit;

    //Observations
    protected File observationsFile;
    
    //Header of the trace file
    protected String exportHisto = "generation;id;status;refused;crossed;mutated;objective;objComp1;objComp2;parent1;parent2;best";

    int borneInf;
    int borneSup;
    int tailleSol;
    int taillePop;
    int eliteRank;
    int acceptationRank;
    int compteurSimus;
    SecureRandom random;
    double obj;
    boolean bool;
    String crossoverMethod;
    String mutationMethod;
    double seuilCrossoverInit;
    double seuilMutationInit;
    double criterionLevel;
    double mutationRate;
    String crossoverVariationsMethod;
    String mutationVariationsMethod;
    double critAytug;
    File exportHistoric;
    MatrixND matrixTargetFactors;
    MatrixND matrixObservations;
    String [] metierString = {"Tarf.PTBV_VIIIabd","Tarf.OBTS_VIIIabd","Tarf.Metier_ChalutMixte_NordPC","Tarf.Metier_ChaluMixte_InterC","Tarf.Metier_ChalutBenth_NordAPC","Tarf.Metier_ChalutBenth_NordC","Tarf.Metier_ChalutBenth_APCS","Tarf.Metier_ChalutBenth_Inter","Tarf.Metier_ChalutBenth_S","Tarf.Metier_Lang_NordPC","Tarf.Metier_Lang_InterC","Tarf.Metier_FiletSole_NordC","Tarf.Metier_FiletSole_NordIntPC","Tarf.Metier_FiletSole_InterSudPC","Tarf.Metier_FiletSole_InterC","Tarf.Metier_ChalutSole_InterC","Tarf.Metier_PalangreMerlu_InterSudAC","Tarf.Metier_PalangreMerlu_InterSudC","Tarf.Metier_PalangreMixte_NordC","Tarf.Metier_PalangreMixte_InterC","Tarf.Metier_ChalutSole_InterSudC","Tarf.Metier_ChalutSole_NordCet","Tarf.Metier_FiletMerlu_NordC","Tarf.Metier_FiletMerlu_NordPC","Tarf.Metier_FiletMixte_InterSudC","Tarf.Metier_FiletMIxte_InterC","Tarf.Metier_FiletMerlu_InterSudAPC","Tarf.Metier_FiletMerlu_InterSudC","Tarf.Metier_ChalutMixte_NordC","Tarf.Metier_FiletMerlu_InterSudPC","Tarf.Metier_ChalutMixte_APCS","Tarf.Metier_Lang_InterPC"};
    String [] seasonString = {"Tarf.season1","Tarf.season2","Tarf.season3","Tarf.season4"};
    
    
    ArrayList<Experience> historique = new ArrayList<Experience>();
    // HashMap <List<Integer>,Experience> historique = new HashMap <List<Integer>,Experience>();
    // public List newList (int a, int b){
    // List nl = new ArrayList<Integer>();
    // nl.add(a);
    // nl.add(b);
    // return nl;
    //}

    //*** write the name of the simulated matrix that contains the data corresponding
    // to your observations (here MATRIX_CATCH_WEIGHT_PER_STRATEGY_MET_PER_ZONE_POP)
    public String[] necessaryResult = {
        MatrixCatchPerStrategyMetPerZonePop.NAME,
    };
    
    public String[] getNecessaryResult() {
        return this.necessaryResult;
    }

    /**
     * Permet d'afficher a l'utilisateur une aide sur le plan.
     * @return L'aide ou la description du plan
     */
    public String getDescription() throws Exception {
        return ("Calibration using a genetic algorithm (GA): user" +
                 "gives a file of observations (here catches)(.csv), simulated output" +
                 "will try to approach oservations by changing the values of targeting factors");
    }
    
    /**
     * Appele lors de l'initialisation.
     *
     * @param context
     */
    public void init(OptimizationContext context) throws java.io.IOException{
        random = new SecureRandom();
        //random.setSeed(param_seed);

        //Initialize metaparametrization : initial values, bounds, 
        taillePop=param_taillePop;
        tailleSol=param_tailleSol;
       
        //Get observations. Do not use ISIS interface, as it is impossible to call something from the interface here. Making this call later requires TopiaContext and causes locks issues.
        observationsFile = new File(param_observationFile);
        matrixObservations = MatrixFactory.getInstance().create(observationsFile);
        
        if(StringUtils.isNoneBlank(param_initialPopulation)){
            matrixInitFile = new File("input/InitialPopulation.csv");
            matrixInit = MatrixFactory.getInstance().create(matrixInitFile);
        }

        borneInf = 0;
        borneSup = 20;
                
        crossoverMethod=param_crossoverMethod;
        mutationMethod=param_mutationMethod;
        seuilCrossoverInit=param_crossoverThreshold;
        seuilMutationInit=param_mutationThreshold;
        eliteRank=param_eliteRank;
        acceptationRank=param_acceptationRank;
        criterionLevel = param_criterionLevel;
        mutationRate = param_mutationRate;
        mutationVariationsMethod = param_mutationVariationsMethod;
        crossoverVariationsMethod = param_crossoverVariationsMethod;
        if(param_criterionType.equals("ConfidenceLevel")){
            //critAytug = (Math.log(1-criterionLevel))/(taillePop*(Math.log(Math.min(Math.pow(mutationRate,tailleSol), Math.pow((1-mutationRate),tailleSol)))));
            critAytug = 5;
        }
        
        for (int i=0; i<tailleSol;i++){ 
            exportHisto += "param_"+i+";";
        }
        exportHisto += "\n";    
    }

    /**
     * La premiere generation doit etre construite dans cette methode
     * via des appels a context.newSimulation(...)
     *
     * @param context
     */
    public void firstSimulation(OptimizationContext context) throws Exception {
        Experience genTemp[] = new Experience [taillePop];// On cree une nouvelle generation dans la table historique, contenant autant d'Experience vides que de solutions
        compteurSimus = context.getCurrentGeneration(); //numero de la generation a venir
        
        //Get metiers list. Use a non generical approach rather than TopiaContext to avoid conflicts on database locks.
        
        //Get initial solution
        File fileTargetFactors = new File("input/InitialPopulation.csv");
        matrixTargetFactors = MatrixFactory.getInstance().create(fileTargetFactors);

        // Fill all Experiences of the first generation
        for (int solNum = 0; solNum < taillePop ; solNum++){
            //PARAMETRISATION INITIALE : the 32 METIERS (in some order), then the 4 SEASONS.
            Parameter initParam []  = new Parameter[tailleSol]; // NOT GENERICAL
            int paramNum =0;
            //Load target factors values for each metier
            for (String metier : metierString) {
                double tf = matrixTargetFactors.getValue(metier,solNum);
                initParam[paramNum] = new Parameter(tf,0,20);
                paramNum+=1;
                //System.out.println("Generation 0, experience "+exp.id+", metier "+metier.getName()+ ", nouvelle valeur ciblage "+exp.parametrisation[metierList.indexOf(metier)].value +" espece : " + ts.getSpecies().getName());
            }

            //Load target factors values for each season also
            for(String seasLabel : seasonString){
                double tf = matrixTargetFactors.getValue(seasLabel,solNum);
                initParam[paramNum] = new Parameter(tf,0,20);
                paramNum+=1;
            }

            genTemp[solNum]=createExperience(solNum,0,null,initParam);// Il faut un compteur d'experiences pour la generation.
            genTemp[solNum].refused = false; 
            SimulationStorage nextSimulation = context.newSimulation();
            changeDB(genTemp[solNum], nextSimulation);
            nextSimulation.closeStorage();
            System.out.println("GENERATION 0 SIMULATION " + solNum);
        }
    }

    /**
     * Genere une nouvelle serie de simulation suivant le context d'optimisation.
     * Pour cela vous devez appeler context.newSimulation(...) pour ajouter
     * des simulations pour la prochaine generation.
     *
     * @param context context
     */
    public void nextSimulation(OptimizationContext context) throws Exception {
            int generation = context.getCurrentGeneration();        
       //Faut-il realiser la prochaine generation?
        boolean stop = isCritereArretAtteint(generation);
        if (!stop){

            List<Experience> genNext = new ArrayList <Experience>();
            List <Experience> genPrec = getGeneration(generation-1);// historique.subList(taillePop*(generation-1),taillePop*(generation-1)+(taillePop-1));
     
            // copy ellites in decreasing order:
            int i = 0;// i sert de compteur pour savoir combien de crossover/2 il faut faire. Avec cette methode, la population non elite doit imperativement contenir un nombre pair de solutions
            List<Experience> genPrecOrd = genPrec;
            Collections.sort(genPrecOrd);
      
            for(Experience exp : genPrecOrd){
                if(exp.status.equals("Elite")) {
                    Experience [] parents = {exp,exp};
                    genNext.add(createExperience(i,generation, parents,exp.parametrisation)); // la cr��e et la stoque dans l'historique
                    i++;
                }
            }
     
           // Cross over
            genNext = crossover(i, generation, genNext); // r��alis�� pour tous les i
     
            // retour sur les mauvaises solutions
            i = acceptationRank;
            genNext = remplacement(i, genNext, generation);//Remplacement des mauvaises sol par des croisements d'elites 
     
    
            // Mutations
            if(genNext.size() >param_eliteRank){
                for(Experience exp : genNext.subList(param_eliteRank,genNext.size()-1)){// Mutations sur celles qui ne decoulent pas des elites
                    // Pour chaque parametre mutation ou pas puis modif
                    for(int k=0; k<tailleSol; k++){
                            mutateParameter(k, exp.parametrisation, generation);
                    }
                }
            }
    
            //Change database
            for (Experience exp : genNext){
                SimulationStorage nextSimulation = context.newSimulation();
                changeDB(exp, nextSimulation);//Voir comment faire reference a la bonne simulation et faire une sorte de bouscle changeDB puis appel a ISIS.
                nextSimulation.closeStorage();
            }
        }
    }

    /**
     * Cette methode est appelee apres chaque serie de simulation
     * soit apres firstSimulation et nextSimulation
     * @param context
     */
    public void endSimulation(OptimizationContext context) throws Exception {
        
        int generation = context.getCurrentGeneration();// Numero de la generation qui vient de se terminer
        List<SimulationStorage>  lastGeneration = context.getLastSimulations();
        List<Experience> genCurr = getGeneration(generation);
        
        // compute and store objective fonctions
        for (Experience exp : genCurr){
            //Get estimates from a specific export. It would be betetr to implement it in a specific result, but is heavy to implement...
            //Get export location. This is tricky, since the getter for the simulation path cannot be used
            SimulationStorage simSto = lastGeneration.get(genCurr.indexOf(exp));
            String exportFilename = simSto.getSimulationLogFile();        
            exportFilename =exportFilename.substring(0,exportFilename.length()-14) + "resultExports/CapturesPoidsAgregPourOptim.csv";
            
            //Import the .csv file
            File estimatesFile = new File(exportFilename);
            MatrixND matrixEstimates = MatrixFactory.getInstance().create(estimatesFile);
            simSto.closeStorage();
            
            //Here, matrixObservations and matrixEstimates should have exactly the same semantics.
            //Convert estimates steps to seasons and aggregate them
            matrixEstimates = matrixEstimates.sumOverDim(0,0,3);
            matrixEstimates = matrixEstimates.sumOverDim(0,3,3);
            matrixEstimates = matrixEstimates.sumOverDim(0,6,3);
            matrixEstimates = matrixEstimates.sumOverDim(0,9,3);
            List<Integer> seasonNums = asList(1,2,3,4);
            matrixEstimates.setSemantic(0,seasonNums);
            
            //Here, matrixObservations and matrixEstimates should have exactly the same semantics and dimensions.
            //Compute objective function components and store them in the Experience objects
            double objectiveComponent1 =0;
            double objectiveComponent2 =0;
            double omega =1; // Work on it too!
            MatrixND matrixObservationsSumLength = matrixObservations.sumOverDim(3).reduce(); //Sum on length bins
            MatrixND matrixEstimatesSumLength = matrixEstimates.sumOverDim(3).reduce(); //Sum on length bins
            
            for(MatrixIterator matIter = matrixObservations.iterator(); matIter.hasNext();){ // For each observation.estime couple, compute objective function components
                matIter.next();
                Object[] sems = matIter.getSemanticsCoordinates();
                Object[] semsSumLength = {sems[0],sems[1],sems[2]}; // No semantics on length classes
                double obsValue = matIter.getValue();
                double estValue = matrixEstimates.getValue(sems);
                double obsValueSumLength = matrixObservationsSumLength.getValue(semsSumLength);
                double estValueSumLength = matrixEstimatesSumLength.getValue(semsSumLength);
                objectiveComponent1=+ param_alphaOF*omega*Math.pow(((obsValue/obsValueSumLength)-(estValue/estValueSumLength)),2);
                objectiveComponent2=+ omega*Math.pow(((obsValueSumLength-estValueSumLength)/obsValueSumLength),2);
            }
            double objectiveFunction = objectiveComponent1+objectiveComponent2;
            exp.objective = objectiveFunction;
            exp.objComp1 = objectiveComponent1;
            exp.objComp1 = objectiveComponent2;
        }
        
       // compute and store best solution of the generation
        Experience best = bestSolution(genCurr);
        for (Experience exp : genCurr){
            exp.best = best;
        }

       // attribute status to each simulation 
        attributionStatuts(genCurr);
        for (Experience exp : genCurr){
            exportHisto+=exp.toCSV();//Sauvegarde de tout ce qu'il s'est passe dans un fichier exterieur
            FileUtils.writeStringToFile(exportHistoric,exportHisto);
        }      
    }

    /**
     * Cette methode est appelee lorsqu'il n'y a plus de simulation a faire
     * (firstSimulation ou nextSimulation n'ont pas fait appel a context.newSimulation)
     * @param context
     */
    public void finish(OptimizationContext context) throws Exception{
        FileUtils.writeStringToFile(exportHistoric,exportHisto);       
 
    }
    /////////////////////
    /////////////////////
    ///               ///
    ///    CLASSES    ///
    ///               ///
    /////////////////////
    /////////////////////
    
    //// Class Parameter 
    class Parameter{

        double value;
        double inf;
        double sup;
        
        public Parameter (double valeur, double borneinf, double bornesup){
            /*if (borneinf >= bornesup) {
                throws SimulationException(String.format("Error: inf(%s) >= sup(%s)", borneinf, bornesup));
            }*/
            value=valeur;
            inf=borneinf;
            sup=bornesup;
        }
        
        public Parameter copy() {
            return new Parameter(value, inf, sup);
        }
    }

    
    // Class Experience
    class Experience implements Comparable<Experience>{
        
        Parameter[] parametrisation;
        Experience[] parents;
        int id;
        boolean refused;
        boolean crossed;
        boolean mutated;
        int generation;     //Variables renseignees par le constructeur

        String status;
        Experience best;
        double objective;
        double objComp1;
        double objComp2;
        int rank;


        public Experience(int id, int generation, Experience[] parents, Parameter[] parametrisation){
            this.id = id;
            this.generation = generation;
            if (parents != null){
                this.parents = parents;
                this.parametrisation = parametrisation;
            }
            this.crossed=false;
            this.mutated=false;
            this.refused=false;
            
            if ((parents == null )|| (parametrisation == null)) {
                this.parametrisation = initParam(id);
            }
        }
        
        public String toCSV() {
            String sep = ";";
            String result = "";
            String saut = "\n";
            result += generation + sep;
            result += id + sep;
            result += status + sep;
            result += refused + sep;
            result += crossed +sep;
            result += mutated +sep;
            result += objective + sep;
            result += objComp1 + sep;
            result += objComp2 + sep;
            if (parents != null){
                for(int i=0; i<parents.length; i++){
                    result += parents[i].id + sep;
                }
            }else result +=";;";
            result += best.id + sep;   
            for (int i=0; i<tailleSol; i++){
                result += parametrisation[i].value + sep;
            }
           result += saut;  
            //Il y aura des nullPointerException... 
            return result;
        }

        public Double getFO()  {
           return this.objective;
        }

        public Integer getGeneration()  {
            return this.generation;
        }
        
        @Override
        public int compareTo(Experience exp) {
            return this.getFO().compareTo(exp.getFO());
        }
       
    }
    
    /////////////////////
    /////////////////////
    ///               ///
    ///    METHODES   ///
    ///               ///
    /////////////////////
    /////////////////////
    
    /**Gives elite, non elite or refused status to each Experience of a generation following its ranking
    * @param genCurr the current generation
    */
    public void attributionStatuts(List<Experience> genCurr){// classement est le classement par ordre d'identifiant, z.B. [5, 0, 2, 6, [...]] ou chaque int = 1 numero d'identifiant, la position dans la liste est le rang-1.
        List<Experience> genCurrOrd = genCurr;
        Collections.sort(genCurrOrd);
        for(Experience exp : genCurrOrd){
            if (genCurrOrd.indexOf(exp) < eliteRank){
                exp.status = "Elite";
            } else if (genCurrOrd.indexOf(exp) < acceptationRank){
                exp.status = "Non elite";
            } else{
                exp.status = "Refused";
            }
        }
    }
   
    /**Gives best experience of the generation
    * @param genCurr the current generation
    */
    public Experience bestSolution(List<Experience> genCurr){ // Renvoie la meilleure elite de la generation, qui est la meuilleure solution jamais parcourue.
        List<Experience> genCurrOrd = genCurr;
        Collections.sort(genCurrOrd);
        Experience best = genCurrOrd.get(0);
        return best;
    }

    
    /**
     * Modify nextSimulation database with parameters in Experience exp.
     * @param exp the Experience in process
     * @param nextSimulation storage for the next simulation
     * @throws Exception
     */ 
    protected void changeDB(Experience exp, SimulationStorage nextSimulation)
            throws Exception {
        int counter = 0;
        int countSeas = 0;
        
        // Get simulationContext and alter it. DO NOT use ToPIA contexts, to much risks of locking the database for nothing.
        // Use a pre-script, that will be written by the optimization script, to change the database without risk.
        SimulationParameter params = nextSimulation.getParameter();
       
        //SimulationContext simulationContext = SimulationContext.get(); // a SimulationContext is needed to alter parameter values.
        //TopiaContext db = nextSimulation.getParameter().getRegion().getStorage().beginTransaction();//Open a context to alter the database
        
        //Change target factors values for each metier. Parameters are always stocked in the same order : first all the m��tiers, in metierList order, then the 4 seasons.
        String script = "";
        for (String metier : metierString) {
            double tf = exp.parametrisation[counter].value;
            script = script + "context.setComputeValue(\""+metier+"\", "+tf+");\n";

            //simulationContext.setValue(metier, tf);
            counter+=1;
            //System.out.println("Generation 0, experience "+exp.id+", metier "+metier.getName()+ ", nouvelle valeur ciblage "+exp.parametrisation[metierList.indexOf(metier)].value +" espece : " + ts.getSpecies().getName());
        }

        //Change target factors for each season
        for(String seasLabel : seasonString){
            String label = seasonString[countSeas];
            double tf = exp.parametrisation[counter].value;
            script = script + "context.setComputeValue(\""+seasLabel+"\", "+tf+");\n";
            //simulationContext.setValue(label, tf);
            counter+=1;
            countSeas+=1;
        }

        if (!params.getUsePreScript()) {
            params.setUsePreScript(true);
        }

        params.setPreScript(script);
        nextSimulation.setParameter(params); // on force la sauvegarde
        
        //Commit changes and close context
        //simulationContext.validateDBChanges();
        //simulationContext.closeDB();
        //Check in debug, on initial population, that the metier are assigned to their corresponding new target factor
        //db.commitTransaction(); // effectue la modification
        //db.closeContext(); // ferme le context
    }
    
    /**
    * Creates a new Experience using the appropriate constructor
    * @param parametrisation the parametrisation of the Experience to create
    * @param id the id number of the Experience to create
    * @param generation the generation number of the Experience to create
    * @param parents the parent(s) Experience(s) of the Experience to create
    * @return the created Experience
    */
    public Experience createExperience(int id, int generation, Experience [] parents, Parameter [] parametrisation){
        Experience result = new Experience (id,generation,parents, parametrisation);
        historique.add(result);
        return result;
    }


    /**
    * Implements the crossover according to chosen method, and creates 2 new individuals that are addd to the next genaration
    * Called by crossover each time two new parents have to be crossed
    * @param id, max(id of the individuals to create) + 1
    * @param generation the generation number of the Experiences to create
    * @param parents the parent(s) Experience(s) of the Experience to create
    * @return the next generation with the two new individuals created
    */
    
    public List<Experience> croiser (int i, int generation, List<Experience> genNext, Experience parent1, Experience parent2){

        List<Experience> genPrec = historique.subList(taillePop*(generation-1),taillePop*(generation-1)+(taillePop-1));
        /*Experience [] genPrec = new Experience[taillePop];
        for(int j=0; j<taillePop; j++){
            genPrec[j]=historique.get(taillePop*(generation-1)+j);
        }*/
        int localisation;
        Parameter[] nouvelleParametrisation1 = new Parameter[tailleSol]; 
        Parameter[] nouvelleParametrisation2 = new Parameter[tailleSol];

        // Methodes ou on coupe 1,2, 8 fois
        if (crossoverMethod.equals("1X")||crossoverMethod.equals("2X")||crossoverMethod.equals("8X")){//Avec des points de croisement (1X,2X,8X)
            int aPlacer = 0;
            if (crossoverMethod.equals("1X")){
                aPlacer=1;
            }
            if (crossoverMethod.equals("2X")){
                aPlacer=2;
            }
            if (crossoverMethod.equals("8X")){
                aPlacer=8;
            }
           
            ArrayList <Integer> crossLocations = new ArrayList<Integer>();
            while (aPlacer>0){
                localisation=hasard1(tailleSol);
                while (crossLocations.contains(localisation)){
                    localisation=hasard1(tailleSol);
                }
                crossLocations.add(localisation);
                aPlacer--;
            }
            //----------------------------------------------
            Collections.sort(crossLocations);
            Experience parentA = parent1;
            Experience parentB = parent2;
            Experience parentTemp;
          
            for (int k=0; k<tailleSol; k++){// On recopie l'int��gralit�� des parents.....
                nouvelleParametrisation1[k] = parentA.parametrisation[k].copy();
                nouvelleParametrisation2[k] = parentB.parametrisation[k].copy();
            }

           //----------------------------------------------

            for(int cl : crossLocations){ // for each cross locations, the entire part of the chromosome between the cross loc and the end is exchanged
                for (int j=cl ; j<tailleSol ; j++){
                //if (crossLocations.contains(genPrec.get(j).id)){ ???
                        nouvelleParametrisation1[j] = parentB.parametrisation[j].copy();
                        nouvelleParametrisation2[j] = parentA.parametrisation[j].copy();
                }
                   // parents are then inverted, to alternate origine of the parts of chromosome that are exchanged
                    parentTemp=parentA;
                    parentA=parentB;
                    parentB=parentTemp;
            }       
            
        }

/*       // Methode : TODO
        if (crossoverMethod.equals("UX")){//UX REPRENDRE LA MANIERE DE COPIER DE 1X
            int crossNumbers;
            for (int j=0; j<taillePop; j++){
                crossNumbers=hasard2();
                if (crossNumbers==0){
                    nouvelleParametrisation1[j] = parent1.parametrisation[j].copy();
                    nouvelleParametrisation2[j] = parent2.parametrisation[j].copy();
                }else{
                    nouvelleParametrisation1[j] = parent2.parametrisation[j].copy();
                    nouvelleParametrisation2[j] = parent1.parametrisation[j].copy();                
                }
            }
        }

       // Methode : TODO
        if (crossoverMethod.equals("AX")){//AX REPRENDRE LA MANIERE DE COPIER DE 1X
            int crossNumbers;
            double valeur1;
            double valeur2;
            double rand;
            nouvelleParametrisation1 = parent1.parametrisation;
            nouvelleParametrisation2 = parent2.parametrisation;// Question de facilite. Les valeurs vont etre changees juste paers.
            for (int j=0; j<taillePop; j++){
                rand=hasardUniforme();
                nouvelleParametrisation1[j].value = rand*parent1.parametrisation[j].value+(1-rand)*parent2.parametrisation[j].value;
                nouvelleParametrisation2[j].value = rand*parent2.parametrisation[j].value+(1-rand)*parent1.parametrisation[j].value;
            }
        }
*/
       
        Experience [] parents = {parent1,parent2};
        if(i-2 < taillePop) {genNext.add(i-2,createExperience(i-2, generation, parents, nouvelleParametrisation1));}
        if(i-1 < taillePop) {genNext.add(i-1,createExperience(i-1, generation, parents, nouvelleParametrisation2));}
        return genNext;
    }
   
    
    /**
    * Does the crossovers on the current generation non elites parametrisations, following the method the user indicated
    * @param i the number of children solutions to generate by crossover
    * @param generation the current generation number
    * @param genNext a temporary generation used to stock the children parametrisations and Experience
    * @return the temporary generation with the children parametrisations generated by crossover
    */
    public List<Experience> crossover(int i, int generation, List<Experience> genNext){
        boolean doCrossover = isCrossover(generation);
        //if(doCrossover){     
            Experience parent1;     
            Experience parent2;
            List<Experience> genPrec = getGeneration(generation-1);
            int id1, id2;
            while (i < acceptationRank){
                id1=hasard1(taillePop); //Tirer 2 numeros d'id au hasard(id1 et id2)
                id2=hasard1(taillePop);   
                while (!(genPrec.get(id1).status.equals("Non elite"))||genPrec.get(id1).crossed==true){
                    id1=hasard1(taillePop);
                }
                while (!(genPrec.get(id2).status.equals("Non elite"))||(id1==id2)||(genPrec.get(id2).crossed==true)){
                    id2=hasard1(taillePop);
                }
                genPrec.get(id1).crossed = true;//Marquer les parents comme ayant ete croises
                genPrec.get(id2).crossed = true;
                i += 2;
             // TO DO changer croiser pour renvoyer uniquement les 2 experiences?
                genNext = croiser(i, generation, genNext, genPrec.get(id1), genPrec.get(id2));
            }
        //}
            return genNext;
    }

    /** Methods to draw random number
     * 
     */
    public int hasard1(int i){
        int result = random.nextInt(i);
        return result;
    }

    public int hasard2(){
        int result = random.nextInt(2);
        return result;
    }
    
    public double hasard4(){
        double result = 1 + random.nextDouble()*0.1-0.05;
        return result;
    }   
    
    public double hasardNormal(){
        double result = random.nextGaussian();
        return result;
    }
        
    public int hasardPoisson(int lambda){ // See http://stackoverflow.com/questions/1241555/algorithm-to-generate-poisson-and-binomial-random-numbers (17th March 2014)
      double L = Math.exp(-lambda);
      double p = 1.0;
      int k = 0;

      do {
        k++;
        p *= Math.random();
      } while (p > L);

      return k - 1;
    }

    public double hasardUniforme(){
        double result = random.nextDouble();
        return result;
    }


    
    /**
    * Called by Experience constructor if the Experience to create has no parameters values. Reads the input file created by LHS.
    * @return the parametrisation
    */
    public Parameter [] initParam(int id){
        Parameter M1[] = new Parameter[tailleSol];
            for (int i=0; i<tailleSol; i++){
                double inf = borneInf;
                double sup = borneSup;
                double val = 0;
                double uni = 0;
                if(StringUtils.isNoneBlank(param_initialPopulation)){
                    val = matrixInit.getValue(i,id);
                }else{      
                    uni=hasardUniforme();
                    val= inf+uni*(sup-inf); 
                }
                Parameter param = new Parameter(val, inf, sup);
                M1[i]=param;
            }
        
        return M1;
    }
   
    /** Stopping criteria
    * 
    */
    public boolean isCritereArretAtteint(int generation){
        boolean bool = false;
        if(param_criterionType.equals("GenerationNumber") && generation >= (int)criterionLevel){
            bool = true;
        }else if(param_criterionType.equals("ConfidenceLevel") && generation < critAytug){
            // Ce que dit Aytug ATTENTION, une fois le probleme du critere resolu, il faudra bien re-indiquer bool=true
            System.out.println("Generation " + generation + " et critere d'Aytug " + critAytug);
            bool = true;
        }
        log.error("bool = " + bool);
        return bool;
    }
    
    /**
    * Checks if the algorithm must do crossovers or not for the current generation.
    * @param generation the current generation (iteration)
    * @return true if crossovers will be done, false otherwise 
    */
    public boolean isCrossover(int generation){
        boolean bool = true;
        double seuilCrossover = 0;
     /* if (crossoverVariationsMethod.equals("Adaptative")&&(isFrozen(generation))){
            int genMemeFO = sameValue(generation);
            seuilCrossover = seuilCrossoverInit*((genMemeFO-nbGenLimite)/nbGenLimite)*(alpha-seuilCrossoverInit);//Formule de seuilCrossover adaptatif, alpha a determiner a l'avance (utilisatuer), nbGenLimite a fixer par l'utilisateur
        }else if(crossoverVariationsMethod.equals("Adaptative")&&(!isFrozen(generation))){
            seuilCrossover = seuilCrossoverInit;}*/
        if(crossoverVariationsMethod.equals("Geometric")){
        //Methode 1 : on fait des crossovers ou non avec un seuil
            seuilCrossover = Math.pow(0.9975,generation)*seuilCrossoverInit;//Cas de diminution geometrique du seuil de refus (voir les autres methodes aussi)
        }else if (crossoverVariationsMethod.equals("Fixe")){
        //Methode 2 : fixe
            seuilCrossover = seuilCrossoverInit;
        }   //Autres methodes
        
        double rand=hasardUniforme();
        if (rand > seuilCrossover){
            bool = false;
        }
        return bool;
    }
    /*
    /**
    * Checks if the algorithm is "frozen", i.e. is exploring solutions with the same best objective function values for a given number of iterations
    * @param generation the current generation (iteration) number
    * @return true if the algorithm is frozen, false otherwise
    * @see sameValue
    *
    public boolean isFrozen(int generation){
        boolean bool=false;
        int memeFO = sameValue(generation);//nbGenLimite a������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ fixer par l'utilisateur
        if (memeFO > nbGenLimite){// Si ca fait longtemps que la valeur de FO est la meme (fait-on une variante avec "la meilleure solution est la meme depuis longtemps"?)
            bool= true;
        }
        return bool;
    }
    */


    /**
    *Modifie un parametre selon une certaine methode, c est l operateur de mutation
    *@param parametrisation la parametrisation contenant l'Experience sur laquelle on travaille
    *@param num la position dans la parametrisation du parametre a modifier
    */
    public void mutateParameter(int num, Parameter[] parametrisation, int generation){

        // Should mutation be applied ?
        double seuilMutation = 0;
        //Meme principe qu'isCrossover
        /*if ((mutationVariationsMethod.equals("Adaptative"))&&(isFrozen(generation))){
            int genMemeFO = sameValue(generation);
            seuilMutation = seuilMutationInit*((genMemeFO-nbGenLimite)/nbGenLimite)*(beta-seuilMutationInit);//Formule de seuilMutation adaptatif, beta a determiner a l'avance (utilisatuer), nbGenLimite a fixer par l'utilisateur
        }else if ((mutationVariationsMethod.equals("Adaptative"))&&(!isFrozen(generation))){
            seuilMutation = seuilMutationInit;}*/
        if(crossoverVariationsMethod.equals("Geometric")){
        //Methode 1 : on fait des crossovers ou non avec un seuil
            seuilMutation = Math.pow(1.002506,generation)*seuilMutationInit;//Cas de diminution geometrique (voir les autres methodes aussi)
        }else if(mutationVariationsMethod.equals("Fixe")){
        //Methode 1 : fixe
            seuilMutation = seuilMutationInit;
        }//Autres methodes
        //Cas ou : s'il n'y a pas eu crossover, alors isMutation renvoie true
        //Autres

        // al��a de mutation
        double rand = hasardUniforme();
        boolean bool = (rand < seuilMutation);

        // If yes, Apply mutation
        if(bool){
            double amplitude = 1;
            //Methode 1
            if (mutationMethod.equals("Classic")){
                amplitude = hasard4();
            }
            else if (mutationMethod.equals("Normal")){
                amplitude = hasardNormal() + 1;
            }
            double valeur=parametrisation[num].value;
            parametrisation[num].value=amplitude*valeur;
            int MAX = 10000;
            while (MAX > 0 && (parametrisation[num].value<parametrisation[num].inf || parametrisation[num].value>parametrisation[num].sup)){    //Tant qu'on est en dehors du domaine de definiton du parametre, on modifie la valeur
                amplitude = hasard4();
                parametrisation[num].value=amplitude*valeur;
                MAX--;
            }
            /*
            if (MAX <= 0) {
                throws SimulationException("Can't find new value");
            }*/
            //Methode 2
            //etc. L'idee est de faire varier la maniere de faire varier delta en  fcontion de l'avancement de l'algorithme.
        }
    }
    
    /**
    * Ranks the Experience contained in a generation by objective function value
    * @param genCurr the current generation
    * @return classement a list containing the id numbers of the Experience, ordered by objective function value
    */
/*  public double [] ranking (Experience[] genCurr){//Tri des solutions en fonction de leur valeur de fonction d'objectif, attribution d'un rang a chacune.
        //Pour la generation en cours, copie de la valeur de FO et des identifiants de toutes les solutions, tab_copie
        double tabCopie [][] = new double [taillePop][2];
        for (int j=0; j<taillePop; j++){
            tabCopie[j][0] = genCurr[j].objective;
            tabCopie[j][1] = genCurr[j].id;
        }
        double tabRank[][] = new double [taillePop][2];//creation d'un tableau qui va contenir le rang et les identifiants de chacune, tab_rank
        int rang=1;
        int min=0;
        while (rang<=taillePop){
            for (int j=0; j<taillePop; j++){
                if ((tabCopie[j][0]<tabCopie[min][0])&&(tabCopie[j][0]>=0)){
                    min=j;
                }
            }//Chercher son minimum
            tabRank[rang-1][0]=min;
            tabRank[rang-1][1]=rang;//Copier son identifant dans tab_rank avec le rang rang
            tabCopie[min][0]=-1;//Les valeurs negetives ne sont pas parcourues dabs la boucle du dessus.
            rang++;
            min=0;
        }
        double [] classement = new double [taillePop];
        for (int j=0;j<taillePop;j++){
            classement[j] = tabRank[j][0];
        }
        return classement;
    }*/


    public List<Experience> rank (List<Experience> genCurr){//Tri des solutions en fonction de leur valeur de fonction d'objectif, attribution d'un rang a chacune.
       
        List <Experience> genCurrOrd = genCurr;
        Collections.reverse(genCurrOrd);
        
        return genCurrOrd;
    }
    
        /**
    * Does the crossovers on the current generation elite parametrsisations to replace refused parametrisations.
    * @param i the number of children solutions to generate by crossover
    * @param generation the current generation number
    * @param genTemp a temporary generation used to stock the children parametrisations and Experience
    * @return the temporary generation with the children parametrisations generated by crossover
    */
    public List<Experience> remplacement(int i, List<Experience> genNext, int generation){
        int localisation;
        List<Experience> genPrec = getGeneration(generation -1);//historique.subList(taillePop*(generation-1),taillePop*(generation-1)+(taillePop-1));
        /*Experience[] genPrec = new Experience[taillePop];
        for(int j=0;j<taillePop; j++){
            genPrec[j]=historique.get(taillePop*(generation-1)+j);
        }*/
        int id1, id2, k;
        k=0;
        while ((i < taillePop)&&(k<1000)){
            id1=hasard1(taillePop); //Tirer 2 numeros d'id au hasard(id1 et id2)
            id2=hasard1(taillePop);
            while (!(genPrec.get(id1).status.equals("Elite"))){
                id1=hasard1(taillePop);
                k++;
            }
            while (!(genPrec.get(id2).status.equals("Elite"))||(id1==id2)){
                id2=hasard1(taillePop);
                k++;
            }
            genPrec.get(id1).crossed = true;//Marquer les parents comme ayant ete croises
            genPrec.get(id2).crossed = true;
            i+= 2;
            genNext = croiser (i, generation, genNext, genPrec.get(id1), genPrec.get(id2));
        }
        return genNext;
    }
    
    /**
    * Calculates for how long the best objective function value is the same, in number of generations (iterations)
    * @param generation the current generation (iteration) number
    * @return the number of generations (iterations) with the same best objective function value
    */
    public int sameValue (int generation){
        double value = historique.get(taillePop*(generation)+1).best.objective;
        int sameValueNumber = 1; //Combien de generations, la generation en cours comprise, ont la meme valeur de meilleure FO?
        double valuePrec = historique.get(taillePop*(generation-1)+1).best.objective;
        while (valuePrec == value){
            sameValueNumber++;
            valuePrec = historique.get(taillePop*(generation-sameValueNumber-1)+1).best.objective;
        }
        return sameValueNumber;
    }


    List<Experience> getGeneration(int generation){
        List<Experience> gen = new ArrayList<Experience>();
        for(Experience e : historique){
            if(e.getGeneration().equals(generation)) gen.add(e);
        }
        return gen;
    }

   
}