/*
 * 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.util.Doc;
import fr.ifremer.isisfish.simulator.Optimization;
import fr.ifremer.isisfish.simulator.OptimizationContext;
import fr.ifremer.isisfish.datastore.SimulationStorage;
import fr.ifremer.isisfish.datastore.ResultStorage;
import fr.ifremer.isisfish.entities.Population;
import fr.ifremer.isisfish.entities.PopulationGroup;
import fr.ifremer.isisfish.entities.PopulationSeasonInfo;
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 java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;
import java.util.TreeSet;
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;

/**
 * GeneticAlgorithmOptimization.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 GeneticAlgorithmMultiSpecies implements Optimization {

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

	@Doc("Path to export the historic file. The root is the folder where the .bat is located.")
	public String param_exportPath="HistoricGA.csv";
    protected String exportHisto = "population;generation;id;status;crossed;mutated;objective;parent1;parent2;best;sim\n";
    
    @Doc("Populations which parameters are calibrated separated by ;")
    //public Population param_Population = null;
	public String param_populations = "Sole;Plaice;";
	protected List<Population> pops;
	
	
    // ***put here the path and name of the file containing the data used to calibrate
       // your fishery ( here observed landings per season and age groups)
    @Doc(value = "pop names ; file name and path of observed landings, seperated by \"|\"")
    public String param_nomfichier_debarquements = "Sole;InputMixChannel/Calibrations/InitialPop_sol.csv:Plaice;InputMixChannel/Calibrations/InitialPop_ple.csv:RedMullet;InputMixChannel/Calibrations/InitialPop_mur.csv";
    protected File debarquementsObserves;
    public int param_dimObservations = 7;
    
	protected File matrixInitFile;
    protected MatrixND matrixInit;
	protected Map<Population,MatrixND> mapMatrixInit;
	
	@Doc("GA parameter: Number of individuals in the population of solutions")
	public int param_taillePop = 10;
    
    @Doc("GA parameter: Lower values of parameters, separated with semicolons: de la forme(\"pop1;xx1;xx2;xx3|pop2;yy1;yy2;\")")
    public String param_borneInf = "0;0;0";// devient un parametre du plan d analyse/// Rentrer ici les bornes inferieruers de chaque parametre.
    @Doc("GA parameter: Upper values of parameters, separated with semicolons: de la forme(\"pop1;xx1;xx2;xx3|pop2;yy1;yy2;\")")
    public String param_borneSup = "pop1;10;10;10|pop2;";// devient un parametre du plan d analyse/// Rentrer ici les bornes superieruers de chaque parametre.
	@Doc("Path to the file containing initial values for the individuals")
	public String param_initialPopulation = "pop1;path|pop2;path2;";

    
	@Doc("GA parameter: Crossover method, choose between \"1X\", \"2X\", \"8X\", \"UX\", \"AX\"")
	public String param_crossoverMethod= "1X";
	@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= 0.5;	

    @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= 2;
	@Doc("GA parameter: Acceptation rank, between eliteRank and taillePop")
	public int param_acceptationRank= 8;

    @Doc("GA parameter: Stopping criterion, choose between \"GenerationNumber\", \"ConfidenceLevel\"")
    public String param_criterionType= "GenerationNumber";
	@Doc("GA parameter: number to reach before stopping the algorithm")
	public double param_criterionLevel= 200;

	//public long param_seed = 1;
	
	protected Map<String, Population> mapPops ;
	protected Map<Population,MatrixND> mapMatrixDebarquement;
    protected MatrixND matrixDebarquement;
	protected Map<Population,List<String>> mapPopBoundsInf;
	protected Map<Population,List<String>> mapPopBoundsSup;

	SecureRandom random;
	double obj;
	boolean bool;
	//int nbGenLimite;
	double critAytug;
	File exportHistoric;
	
	protected Historique historique;
    protected Ellites ellites; 

	//*** 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 of one or several populations using a genetic algorithm (GA): user" +
                 "gives files of observations (here catches)(.csv), simulated output" +
                 "will try to approach oservations by changing the values of catchability per group");
    }
	
    /**
     * Appele lors de l'initialisation.
     *
     * @param context
     */
    public void init(OptimizationContext context) throws java.io.IOException {
        if (param_taillePop % 2 != 0) {
            throw new RuntimeException("Idiot, il faut un nombre taillePop pair !!!!");
        }
		
        exportHistoric = new File(param_exportPath);
		
				historique = new Historique();
		ellites = new Ellites(param_eliteRank);
    	random = new SecureRandom();
		//random.setSeed(param_seed);
		pops = new ArrayList<Population>();
        mapPops = new HashMap<String, Population>();
     //System.out.println("pops size:"+pops.size());
		String[] popnames = param_populations.split(";");
		for(String popname : popnames){
			for(Population pop: context.getParam().getPopulations()){
				if(pop.getName().equals(popname)){
					pops.add(pop);
                  //System.out.println(popname + pop.getName());
					mapPops.put(popname,pop);
				}
			}
		}
		
		// matrix of catch
		mapMatrixDebarquement = new HashMap<Population, MatrixND>();
		String[] listPopNomfichier = param_nomfichier_debarquements.split(":");
		for(String deb : listPopNomfichier){
		//System.out.println(deb);
			String[]  listPopNom = deb.split(";");
				debarquementsObserves = new File(listPopNom[1]);
				Population pop = mapPops.get(listPopNom[0]);
			// ***Create the matrix named matrixDebarquement that will contain your observed landings
				//int[] dimMatrix = {context.getParam().getNumberOfYear(),pop.sizePopulationGroup()};
				int[] dimMatrix = {param_dimObservations,pop.sizePopulationGroup()};
				matrixDebarquement=MatrixFactory.getInstance().create(dimMatrix); //date est un int donnant le nombre de mois sur lequel on a simule.
			// ***Then import your file in it
				try (FileReader in = new FileReader(debarquementsObserves)){
					matrixDebarquement.importCSV(in,new int []{0,0});
				}		
				matrixDebarquement= matrixDebarquement.reduce(); //Indispensable pour pouvoir travailler sur la matrice et avoir les memes dimensions que les simulations.
			//log.error("MatrixDebarquement : " + matrixDebarquement);
				mapMatrixDebarquement.put(pop,matrixDebarquement);
		}
		
		// parameter bounds
		mapPopBoundsInf = new HashMap<Population, List<String>>();	
		mapPopBoundsSup = new HashMap<Population, List<String>>();	
		String[] listBorneInf=param_borneInf.split(":");
		String[] listBorneSup=param_borneSup.split(":");
		for(String bi : listBorneInf){
			List<String> popBorneinf = Arrays.asList(bi.split(";"));
            int pbis = popBorneinf.size() ;
			mapPopBoundsInf.put(mapPops.get(popBorneinf.get(0)),popBorneinf.subList(1,pbis));
		}
		for(String bs : listBorneSup){
			List<String> popBornesup = Arrays.asList(bs.split(";"));
            int pbss = popBornesup.size();
			mapPopBoundsSup.put(mapPops.get(popBornesup.get(0)),popBornesup.subList(1,pbss));
		}
		
		// initial pop
		if(StringUtils.isNoneBlank(param_initialPopulation)){
			mapMatrixInit = new HashMap<Population, MatrixND>();				
			String[] listPopMatInit = param_initialPopulation.split(":");
			for(String init : listPopMatInit){
				String[]  listPopNom = init.split(";");
				matrixInitFile = new File(listPopNom[1]);
				Population pop = mapPops.get(listPopNom[0]);
				int tailleSol = pop.sizePopulationGroup();

				// ***Create the matrix that contains initial parameter values for individuals
				int[] dimMatrixInit = {param_taillePop,tailleSol};
				matrixInit=MatrixFactory.getInstance().create(dimMatrixInit); //date est un int donnant le nombre de mois sur lequel on a simule.
				// ***Then import your file in it
				try (FileReader in = new FileReader(matrixInitFile)){
					matrixInit.importCSV(in,new int []{0,0});
				}
				matrixInit = matrixInit.reduce(); //Indispensable pour pouvoir travailler sur la matrice et avoir les memes dimensions que les simulations.
				mapMatrixInit.put(pop,matrixInit);
			}	
		}
		
		//nbGenLimite = 2;
      // Aytung seulement si une seule pop
        int tailleSol = listBorneInf[0].split(";").length - 1;
        if(param_criterionType == "GenerationNumber") critAytug = (Math.log(1-param_criterionLevel))/(param_taillePop*(Math.log(Math.min(Math.pow(param_mutationRate,tailleSol), Math.pow((1-param_mutationRate),tailleSol)))));
    }

    /**
     * La premiere generation doit etre construite dans cette methode
     * via des appels a context.newSimulation(...)
     *
     * @param context
     */
    public void firstSimulation(OptimizationContext context) throws Exception {
		for (int i = 0; i < param_taillePop ; i++){
			SimulationStorage nextSimulation = context.newSimulation();
			for (Population pop : pops) {
				Experience exp = createExperience(pop, i, 0, null, null);// Il faut un compteur d'experiences pour la generation.
				exp.simulationNumber = nextSimulation.getParameter().getSimulationPlanNumber();
				changeDB(exp, nextSimulation);
			} 
		}
    }

    /**
     * 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 {
        log.info("enter next simu");
            int generation = context.getCurrentGeneration();        
       //Faut-il realiser la prochaine generation?
        boolean stop = isCritereArretAtteint(generation);
        if (!stop){
         
			for (Population pop: pops) {
		log.info("boucle pop "+pop.getName());
				List <Experience> genPrec = historique.getGeneration(generation-1).get(pop);// historique.subList(taillePop*(generation-1),taillePop*(generation-1)+(taillePop-1));
		 
				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
				
				for(Experience exp : genPrec){
                    if (exp == null || exp.status == null) {
                        throw new RuntimeException("!!!! ca marche pô: " + exp + "\n" + genPrec);
                    }
					if(exp.status.equals("Elite")) {
						Experience [] parents = {exp, exp};
						createExperience(pop, i, generation, parents, exp.parametrisation); // la crée et la stoque dans l'historique
						i++;
					}
				}
		 log.info("crossOver");
			   // Cross over
				crossover(i, generation, pop); // réalisé pour tous les i
		 log.info("replaceemnt");
				// retour sur les mauvaises solutions
				i = param_acceptationRank;
				remplacement(i, generation, pop);//Remplacement des mauvaises sol par des croisements d'elites 
log.info("Mutations");
				// Mutations
				ArrayList<Experience> genNext = historique.getGeneration(generation).get(pop);
				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 chaquce parametre mutation ou pas puis modif
						for(int k=0; k<pop.sizePopulationGroup(); k++){
							mutateParameter(k, exp.parametrisation, generation);
						}
					}
				}   
			}    

            log.info("entre creation des nelles simu");
			Map<Population, ArrayList<Experience>> mapPopExp = historique.getGeneration(generation);
			for (int i = 0; i < param_taillePop; i++) {
				SimulationStorage nextSimulation = context.newSimulation();
				for (ArrayList<Experience> exps : mapPopExp.values()) {
					Experience exp = exps.get(i);
					exp.simulationNumber = nextSimulation.getParameter().getSimulationPlanNumber();
					changeDB(exp, nextSimulation);//Voir comment faire reference a la bonne simulation et faire une sorte de boucle changeDB puis appel a ISIS.
				}
    		}
		}
       log.info("exit next simu");
    }

    /**
     * Cette methode est appelee apres chaque serie de simulation
     * soit apres firstSimulation et nextSimulation
     * @param context
     */
    public void endSimulation(OptimizationContext context) throws Exception {
		log.info("enter end simu");
		int generation = context.getCurrentGeneration();// Numero de la generation qui vient de se terminer
		List<SimulationStorage>  lastGeneration = context.getLastSimulations();
		Map<Population, ArrayList<Experience>> mapPopExp = historique.getGeneration(generation);
		
		for (Population pop: mapPopExp.keySet()){
        log.info("boucle pop"+pop.getName());
			List<Experience> genCurr = mapPopExp.get(pop); 

		  // compute and store objective fonctions
			for (int i = 0, maxi = genCurr.size(); i<maxi; i++){ 
				Experience exp = genCurr.get(i);
				SimulationStorage simSto = lastGeneration.get(i);
				ResultStorage result = simSto.getResultStorage();//Reference a la generation qui vient de se terminer. Voir s'il y a une appelleation pour faire reference a une simuilation de la derniere generation et boucler dessus
				exp.objective = calculFonctionObjectif(pop, result);//Voir comment faire reference a la bonne simulation.
				log.info("Objective computed: " + exp);
				simSto.closeStorage();
			}
		}
		 log.info("Elites");
		ellites.put(historique.getGeneration(generation));
 log.info("export histo");
		for (List<Experience> genCurr: mapPopExp.values()){
			for (Experience exp : genCurr){
				exportHisto+=exp.toCSV();//Sauvegarde de tout ce qu'il s'est passe dans un fichier exterieur
				FileUtils.writeStringToFile(exportHistoric,exportHisto);
			}
		}
       log.info("exit end simu");
				
   }

    /**
     * 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{

	}
	/////////////////////
	/////////////////////
	///		 		  ///
	///    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>{
		
		Population pop;
		Parameter[] parametrisation;
		Experience[] parents;
		int id;
		boolean crossed;
		boolean mutated;
		int generation;		//Variables renseignees par le constructeur
		int simulationNumber;

		String status;
		int best = -1;
		double objective;
		int rank;


		public Experience(Population pop, int id, int generation, Experience[] parents, Parameter[] parametrisation){
			this.pop = pop;
			this.id = id;
			this.generation = generation;
			if (parents != null){
				this.parents = parents;
				this.parametrisation = parametrisation;
                if (parents.length != 2 || parents[0] == null || parents[1] == null) {
                    throw new RuntimeException("C pas possible, il ne doit pas y avoir de parent null :D");
                }
			}
			this.crossed=false;
			this.mutated=false;
			
			if ((parents == null )|| (parametrisation == null)) {
				this.parametrisation = initParam(id,pop);
			}
		}
		
		public String toCSV() {
			String sep = ";";
			String result = "";
			String saut = "\n";
			result += pop.getName() + sep;
			result += generation + sep;
			result += id + sep;
			result += status + sep;
			result += crossed +sep;
			result += mutated +sep;
			result += objective + sep;
            if (parents != null){
                for(int i=0; i<parents.length; i++){
                    result += parents[i].id + sep;
                }
            }else result +=";;";
            log.info("ds toCSV "+ pop.getName() + " best id" + best);
            result += best + sep;   
            result += simulationNumber + sep;
			for (int i=0; i<pop.sizePopulationGroup(); 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) {
            int result = this.getFO().compareTo(exp.getFO());
            if (result == 0) {
                result = exp.simulationNumber - this.simulationNumber;
            }
            return result;
        }
        
        @Override
        public String toString() {
            return "Exp n°" + id + 
                " pop:" + pop +
                " parent:" + parents +
                " crossed:" + crossed +
                " mutated:" + mutated +
                " generation:" + generation +
                " simulationNumber" + simulationNumber +
                " status:" + status +
                " best:" + best +
                " objective:" + objective +
                " rank:" + rank;
        }
	}
	
	
	
	class Historique {
		protected Map <Integer, Map<Population, ArrayList<Experience>>> historique = new HashMap<Integer,Map<Population, ArrayList<Experience>>>();
		public void add(Experience exp) {
		
			Map<Population, ArrayList<Experience>> mapPopExp = historique.get(exp.generation);
			if(mapPopExp == null){
				mapPopExp = new HashMap<Population, ArrayList<Experience>>();
				historique.put(exp.generation, mapPopExp);
			}
			ArrayList <Experience> listExp = mapPopExp.get(exp.pop);
			if(listExp == null){
				listExp = new ArrayList<Experience>();
				mapPopExp.put(exp.pop,listExp);
			}
			listExp.add(exp);
		}
		
		public Map<Population, ArrayList<Experience>> getGeneration(int n){
			Map<Population, ArrayList<Experience>> result = historique.get(n);
			/*if(mapPopExp == null){
				mapPopExp = new HashMap<Population, ArrayList<Experience>>();
				historique.put(exp.generation, mapPopExp);
			}*/
			return result;
		}
	}
	
	
	class Ellites {
		protected int max;
		protected Map <Population, TreeSet<Experience>> ellites = new HashMap<Population, TreeSet<Experience>>();
		public Ellites(int max) {
			this.max = max; 
		}
		public Map <Population, TreeSet<Experience>> get() {
			return ellites;
		}
		
		public void put(Map<Population, ArrayList<Experience>> mapPopExp) {
		// comparer avec les vielles et remplacer la moins bonne si nouvelle meilleure 
			for (Population pop : mapPopExp.keySet()){
				TreeSet<Experience> ellitePop = ellites.get(pop);
				if(ellitePop == null || true){
                    // on force la recreation des elites car actuellement on resimule
                    // a chaque generation les elites. Il ne faut pas se retrouver 
                    // plusieurs fois la meme elite. Lorsqu'on ne simulera plus
                    // les elites a chaque generation, il ne faudra pas recréer
                    // ellitePop, mais l'utiliser pour afficher la génération courante.
					ellitePop = new TreeSet<Experience>();
					ellites.put(pop, ellitePop);
				}
				
				TreeSet<Experience> exps = new TreeSet<Experience>(mapPopExp.get(pop));
				Experience thebest = exps.first();
				log.info(pop.getName() +" best:"+thebest.id + "; "+thebest.simulationNumber);
				int pos = 0;
				for (Experience e : exps) {
                log.info("boucle status exp is :"+e.id);
					e.best = thebest.id;
                    if (ellitePop.size() < max) {// ca marche car exps est trié
                        e.status = "Elite";
                        ellitePop.add(e);
                   
                    }else if (e.compareTo(ellitePop.last()) < 0) {
						e.status = "Elite";
                        ellitePop.pollLast();
						ellitePop.add(e);
					} else {
						if ( pos   < param_acceptationRank){
							e.status = "Non elite";
						} else{
							e.status = "Refused";
						}
					}
					pos++;
				}				
			}
		}
	}
	
	/////////////////////
	/////////////////////
	///		 		  ///
	///    METHODES	  ///
	///		 		  ///
	/////////////////////
	/////////////////////
	
	/**
	*Calcule la valeur de la fonction d'objectif de la parametrisation utilisee pour la simulation qui vient de s'achever.
	*Pour la methode de calcul, voir ce qui a le mieux marche pour le recuit.
	*@param result le numero de la simulation venant de s'achever
	*@return la valeur de fonction d'objectif
	*/
	public double calculFonctionObjectif(Population pop, ResultStorage result){// A adapter a l'aspect muklti-solutions d'une generation
        /////***import the matrix of simulated data (here landings) from the simulation result
		MatrixND L = result.getMatrix(pop,
                MatrixCatchPerStrategyMetPerZonePop.NAME);
        /////*** extract, sum, etc to obtain the same format/data as your observation matrix
        // useful methods to work on matrix : sumOverDim(), getSubMatrix(), reduce()
		//Somme sur les mois, strategies, metiers et zones
        L= L.sumOverDim(0,12);
		L= L.sumOverDim(1);
		L= L.sumOverDim(2);
		L= L.sumOverDim(4);
		L= L.reduce(); //Indispensable pour pouvoir travailler sur la matrice et avoir les memes dimensions que les observations.
        
        ///////////////////Calcul du critere//////////////////
        double obj = 0;
        // *** using the matrixIterator assumes that the observation and simulated
        // matrix are organised in the exact same way ( columns and rows corresponding in each of them)
        //log.info("simulated landings :"+pop.getName()+L.getDim(0)+""+L.getDim(1));
        for (MatrixIterator g = L.iterator(); g.hasNext();) {
            g.next();
            int[] dim = g.getCoordinates();
            matrixDebarquement = mapMatrixDebarquement.get(pop);
        //log.info("obs landings :"+pop.getName()+matrixDebarquement.getDim(0)+""+matrixDebarquement.getDim(1));
            double obs = matrixDebarquement.getValue(dim);
            if(obs == 0){
                obs =1;
            }
            double simules = g.getValue();
            obj += Math.pow((obs - simules)/1000.0, 2);// On prend le millieme au carre pour eviter les valeurs trop grandes
		}
		return obj;
	}
	
	/**
     * 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 {
        // methode appelee dans before simualtion
		Population pop = exp.pop;
        TopiaContext db = nextSimulation.getStorage().beginTransaction();//ouvrir un context pour modifier les donnees
        pop = (Population) db.findByTopiaId(pop.getTopiaId()); //reccupere la pop ciblee
        MatrixND c = pop.getCapturability(); // recupere la matrice de capturabilite
        // *** that is where you explain how to fill the catchability matrix with q1 and q2 
        for (MatrixIterator i = c.iterator(); i.hasNext();) {
            i.next();
            Object[] sem = i.getSemanticsCoordinates();
            PopulationGroup group = (PopulationGroup) sem[0];

            i.setValue(exp.parametrisation[group.getId()].value);
        
        }//fin du for

        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(Population pop, int id, int generation, Experience [] parents, Parameter [] parametrisation){
		Experience result = new Experience (pop, 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 generation
    * 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 void croiser (Population pop, int i, int generation, Experience parent1, Experience parent2){
//log.info("entre croiser");
		List<Experience> genPrec = historique.getGeneration(generation-1).get(pop); //subList(param_taillePop*(generation-1),param_taillePop*(generation-1)+(param_taillePop-1));
        //log.info("size genPrec = "+genPrec.size());
        /*Experience [] genPrec = new Experience[param_taillePop];
		for(int j=0; j<param_taillePop; j++){
			genPrec[j]=historique.get(param_taillePop*(generation-1)+j);
		}*/
		int localisation;
		Parameter[] nouvelleParametrisation1 = new Parameter[pop.sizePopulationGroup()]; 
		Parameter[] nouvelleParametrisation2 = new Parameter[pop.sizePopulationGroup()];

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

            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<pop.sizePopulationGroup() ; 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;
			}      
         //log.info("cross locations bouclees"); 
			
	    }

/*       // Methode : TODO
		if (param_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 (param_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;
			}
		}
*/
       //log.info("i avant creation exp");
		Experience [] parents = {parent1,parent2};
		if(i-2 < param_taillePop) {
			createExperience(pop, i-2, generation, parents, nouvelleParametrisation1);
		}
		if(i-1 < param_taillePop) {
			createExperience(pop, i-1, generation, parents, nouvelleParametrisation2);
		}
       //log.info("fin de create exp");
	}
   
	
	/**
	* 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 void crossover(int i, int generation, Population pop){
        boolean doCrossover = isCrossover(generation);
		List<Experience> genPrec = new ArrayList<Experience>(historique.getGeneration(generation-1).get(pop));
		while (i < param_acceptationRank){
            i += 2;
            Experience p1 = peekOne(genPrec, "Non elite");
            Experience p2 = peekOne(genPrec, "Non elite");
            p1.crossed = true;//Marquer les parents comme ayant ete croises
            p2.crossed = true;//Marquer les parents comme ayant ete croises
			croiser(pop, i, generation, p1, p2);
		}
	}

    /**
     * Pioche sans remise et enlevant aussi ceux qui ont ete essayé et qui ne convenaient pas
     */
    private Experience peekOne(List<Experience> exps, String status) {
        Experience result = null;
        while(exps.size() > 0 && result == null) {
            int choice = hasard1(exps.size());
            Experience e = exps.remove(choice);
            if (e.status.equals(status)) {
                result = e;
            }
        }
        if (result == null) {
            throw new RuntimeException("Impossible de trouve une experience pour le croisement :(");
        }
        return result;
    }

    /** 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. Creates a random parametrisation, using an uniform law and the parameters bounds.
	* @return the parametrisation
	*/
	public Parameter [] initParam(int id, Population pop){
		Parameter M1[] = new Parameter[pop.sizePopulationGroup()];
			for (int i=0; i<pop.sizePopulationGroup(); i++){
                //System.out.println("map size: "+mapPopBoundsInf.get(pop).size());
                //System.out.println("map val i: "+mapPopBoundsInf.get(pop).get(i));
				double inf = Double.parseDouble(mapPopBoundsInf.get(pop).get(i));
				double sup = Double.parseDouble(mapPopBoundsSup.get(pop).get(i));
				double val = 0;
				double uni = 0;
				if(StringUtils.isNoneBlank(param_initialPopulation)){
					MatrixND mat = mapMatrixInit.get(pop);
					uni = mat.getValue(id,i);
				}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)param_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
		    log.error("Generation " + generation + " et critere d'Aytug " + critAytug);
		}
		//log.info("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(param_crossoverVariationsMethod.equals("Geometric")){
		//Methode 1 : on fait des crossovers ou non avec un seuil
			seuilCrossover = Math.pow(0.9975,generation)*param_crossoverThreshold;//Cas de diminution geometrique du seuil de refus (voir les autres methodes aussi)
		}else if (param_crossoverVariationsMethod.equals("Fixe")){
		//Methode 2 : fixe
			seuilCrossover = param_crossoverThreshold;
		}	//Autres methodes
		
		double rand=hasardUniforme();
		if (rand > seuilCrossover){
			bool = false;
		}
		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 = param_mutationThreshold*((genMemeFO-nbGenLimite)/nbGenLimite)*(beta-param_mutationThreshold);//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 = param_mutationThreshold;}*/
        if(param_crossoverVariationsMethod.equals("Geometric")){
        //Methode 1 : on fait des crossovers ou non avec un seuil
            seuilMutation = Math.pow(1.002506,generation)*param_mutationThreshold;//Cas de diminution geometrique (voir les autres methodes aussi)
        }else if(param_mutationVariationsMethod.equals("Fixe")){
        //Methode 1 : fixe
            seuilMutation = param_mutationThreshold;
        }//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 (param_mutationMethod.equals("Classic")){
    			amplitude = hasard4();
    		}
    		else if (param_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 new 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.
		}
	}

    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 void remplacement(int i, int generation, Population pop){
		List<Experience> genPrecInit = historique.getGeneration(generation -1).get(pop);//historique.subList(taillePop*(generation-1),taillePop*(generation-1)+(taillePop-1));
		while ((i < param_taillePop)){
            List<Experience> genPrec = new ArrayList<Experience>(genPrecInit);
            i += 2;
            Experience p1 = peekOne(genPrec, "Elite");
            Experience p2 = peekOne(genPrec, "Elite");
            p1.crossed = true;//Marquer les parents comme ayant ete croises
            p2.crossed = true;//Marquer les parents comme ayant ete croises
		    croiser(pop, i, generation, p1, p2);
		}
	}
	   
}