package analyseplans;
import static org.nuiton.i18n.I18n._;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import scripts.ResultName;
import java.io.*;
import java.util.*;
import org.nuiton.math.matrix.*;
import org.nuiton.topia.*;// pour pouvoir utiliser la methode StringUtil.toDouble()
import org.nuiton.util.*;// pour pouvoir utiliser la methode StringUtil.toDouble()
import fr.ifremer.isisfish.*;
import fr.ifremer.isisfish.types.*;
import fr.ifremer.isisfish.simulator.SimulationContext;
import fr.ifremer.isisfish.types.Date;
import fr.ifremer.isisfish.entities.*;
import fr.ifremer.isisfish.simulator.AnalysePlan;
import fr.ifremer.isisfish.simulator.AnalysePlanContext;
import fr.ifremer.isisfish.simulator.SimulationParameter;
import fr.ifremer.isisfish.datastore.SimulationStorage;
import fr.ifremer.isisfish.datastore.ResultStorage;

/**
 * /////***File must be copied in isis-database-3/ analyseplans/
 * File name : CalibrationEspeceq1q2Export.java
 * /////*** File name could be modified if needed
 * /////*** BUT class name must be identical to file name (without the extention ".java") see below
 *
 * Created: 17 septembre 2007
 *
 * @author   <>
 * @version $Revision: 1.27 $
 *
 * Last update: $Date: 2007/05/24 09:29:18 $
 * by : $Author: bpoussin $
 */


/////***You can modify class name if you want
/////***BUT attention : file name and class name must be the same (without the extention ".java"), ie here :  "calibrationCamille"
public class calibrationCamille3Parametres implements AnalysePlan {

    /** to use log facility, just put in your code: log.info("..."); */

/////***class name to check here
    static private Log log = LogFactory.getLog(calibrationCamille3Parametres.class);

    enum State {STATE_INIT, STATE_0, STATE_1, STATE_2, STATE_3, STATE_4};

/////***here must appear the path to export the historic file ("Historic.csv") where q1, q2 and criteria computed at each simulation are written
/////***Attention : before beginning a new calibration rename your eventual old file Historic.csv otherwise it will be lost
    File exportHistoric = new File ("C:/Users/Camille/Desktop/STAGE IFREMER/SIMPLEX CAPTURABILITE/ajustees.csv");
    protected String exportHisto = "";


//initial points of the simplex : dÃƒÆ’Ã†â€™Ãƒâ€ Ã¢â‚¬â„¢ÃƒÆ’Ã¢â‚¬Å¡Ãƒâ€šÃ‚Â©finition des trois sommets
    public Population param_Population = null;
    public String param_M1 = "2.42e-5;2.11e-6;2.14e-7";// devient un parametre du plan d analyse
    public String param_M2 = "2.34e-5;2.59e-6;2.78e-7";// devient un parametre du plan d analyse
    public String param_M3 = "2.59e-5;2.41e-6;2.79e-7";// devient un parametre du plan d analyse
    public String param_M4 = "3.45e-6;2.87e-7;5.42e-7";// devient un parametre du plan d analyse

///// ***put here the path and name of the file containing the data on which you calibrate your fichery ( here observed catches)
    public String param_nomfichier_debarquements = "C:/Users/Camille/Desktop/STAGE IFREMER/SIMPLEX CAPTURABILITE/moyenne landings.csv";//in row : time ; in columns : age or length group
    protected File  debarquementsObserves;
    protected MatrixND matrixDebarquement;

    protected State state = State.STATE_INIT;
    public Experiences experiences = new Experiences();
    public String [] necessaryResult = {
    	ResultName.MATRIX_CATCH_PER_STRATEGY_MET_PER_ZONE_POP
    };

    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 variable step Simplex method (Walters): user gives a file of observations (here catches) by time step and group (.csv), output will try to approach oservations by changing the values of catchability");
   }

    /**
     * Appele au demarrage de la simulation, cette methode permet d'initialiser
     * des valeurs
     * @param simulation La simulation pour lequel on utilise cette regle
     */
    public void init(AnalysePlanContext context) throws Exception {
    	if (param_nomfichier_debarquements==null || "".equals(param_nomfichier_debarquements)){
    		debarquementsObserves = FileUtil.getFile(".*.csv", "fichier csv separateur ';'");
    	} else {
    		debarquementsObserves = new File(param_nomfichier_debarquements);
    	}
    	int nbYear = context.getParam().getNumberOfYear();

    	TopiaContext db = context.getParam().getRegion().getStorage().beginTransaction();
    	Population pop = (Population)db.findByTopiaId(param_Population.getTopiaId());

 /////*** specify dimention of the matrix containning observations (observed landings for instance)
 /////*** numbers of group/columns : could be equal to your number of classes in ISIS but may also be different if your had only aggregated data
	int[] nbGroup = {55} ;
/////*** enter number of observation per year (if you have observation by quarter put 4) / lines of the observations file
	//int nbTrim = nbYear * 1;
	//int [] dimMatrix = {nbTrim,nbGroup};
	matrixDebarquement = MatrixFactory.getInstance().create(nbGroup);
//matrixDebarquement = MatrixFactory.getInstance().create(new int[]{nbGroup});
// List<PopulationGroup> groups = pop.getPopulationGroup();
// matrixDebarquement = MatrixFactory.getInstance().create(new List[]{groups});
    	matrixDebarquement.importCSV(new FileReader(debarquementsObserves),new int []{0});
		log.info("MatrixDebarquement : " + matrixDebarquement);
    	db.closeContext();
    }

    /**
     * Call before each simulation
     * @param context plan context
     * @param nextSimulation storage used for next simulation
     * @return true if we must do next simulation, false to stop plan
     * @throws Exception
     */
    double g1;
    double g2;
    double g3;
    double worst1;
    double worst2;
    double worst3;

    public boolean beforeSimulation(AnalysePlanContext context, SimulationStorage nextSimulation) throws Exception {
    boolean doNext = true;
    boolean doBoucle = true;
log.info("before simulation");

    	int number = nextSimulation.getParameter().getAnalysePlanNumber();

if (number <4) {
log.info("number<4");

        	String [] M1 = param_M1.split(";");
        	String [] M2 = param_M2.split(";");
        	String [] M3 = param_M3.split(";");
		String [] M4 = param_M4.split(";");
        	double [] q1 = StringUtil.toArrayDouble(M1[0], M2[0], M3[0],M4[0]);
          double [] q2 = StringUtil.toArrayDouble(M1[1], M2[1], M3[1],M4[1]);
          double [] q3 = StringUtil.toArrayDouble(M1[2], M2[2], M3[2],M4[2]);
    		experiences.getExperience(number).q1 = q1[number];
    		experiences.getExperience(number).q2 = q2[number];
		experiences.getExperience(number).q3 = q3[number];

    		changeDB(experiences.getExperience(number), nextSimulation);

} else {

			double q1 = 1000;
			double q2 = 1000;
			double q3 = 1000;
    		double lastCritere = experiences.getExperience(number-1).criteria;
			while (doBoucle){
			doBoucle = false;
    				if (state == State.STATE_INIT) {
doBoucle = false ;
log.info("state init");

//ordonne les 4 premieres experiences selon leur critere : pour savoir de quel cotele simplex va deplacer les sommets
Collections.sort(experiences.current);
//log.info("SIMPLEXE : current 0 = " + experiences.current.get(0).criteria + "current 1 = " + experiences.current.get(1).criteria  + "current 2 = " +  experiences.current.get(2).criteria  );
log.info("SIMPLEXE : current 0 = " + experiences.current.get(0).criteria + "current 1 = " + experiences.current.get(1).criteria  + "current 2 = " +  experiences.current.get(2).criteria );
log.info("SIMPLEXE : Best q1 = " + experiences.current.get(0).q1 + " q2 = " + experiences.current.get(0).q2);
log.info("SIMPLEXE : NextBest q1 = " + experiences.current.get(1).q1 + " q2 = " + experiences.current.get(1).q2);
log.info("SIMPLEXE : Worst q1 = " + experiences.current.get(2).q1 + " q2 = " + experiences.current.get(2).q2);


//Calcul et evaluation de R
double g1 = (experiences.current.get(0).q1 + experiences.current.get(1).q1) / 2.0;
double g2 = (experiences.current.get(0).q2 + experiences.current.get(1).q2) / 2.0;
double g3 = (experiences.current.get(0).q3 + experiences.current.get(1).q3) / 2.0;
double worst1 = experiences.current.get(2).q1;
double worst2 = experiences.current.get(2).q2;
double worst3 = experiences.current.get(2).q3;

state = State.STATE_0;

    			q1 = 2 * g1 - worst1;
    			q2 = 2 * g2 - worst2;
			q3 = 2 * g3 - worst3;

log.info ("R : q1 = " + q1 + " q2 = " + q2 );

    				} else if (state == State.STATE_0) {
doBoucle = false;
log.info("state 0");

    			// on fait la 5eme avec des q qui dependent de la 4eme dans le dernier cas
//log.info("g1 = " + g1 + " " + "g2 = " + g2);
//log.info("worst1 = " + worst1 + " " + "worst2 = " + worst2);

    						if (lastCritere > experiences.current.get(3).criteria) {
log.info("State 0 : R : lastCtritere > current2 : R pire de W");
					state = State.STATE_1;
					//calcul de Cw
					q1 = ((experiences.current.get(0).q1 + experiences.current.get(1).q1) / 2.0) - ( ((experiences.current.get(0).q1 + experiences.current.get(1).q1) / 2.0) - experiences.current.get(2).q1 ) / 2.0;
					q2 = ((experiences.current.get(0).q2 + experiences.current.get(1).q2) / 2.0) - ( ((experiences.current.get(0).q2 + experiences.current.get(1).q2) / 2.0) - experiences.current.get(2).q2 ) / 2.0;
					q3 = ((experiences.current.get(0).q3 + experiences.current.get(1).q3) / 2.0) - ( ((experiences.current.get(0).q3 + experiences.current.get(1).q3) / 2.0) - experiences.current.get(2).q3 ) / 2.0;
log.info("Cw : q1 = " + q1 + " q2 = " + q2);


    						} else if (lastCritere > experiences.current.get(2).criteria) {
log.info("State 0 :R : lastCritere > current 1 : R meilleur que W et moins bon que N");
					state = State.STATE_2;
					// calcul de Cr
					q1 = ((experiences.current.get(0).q1 + experiences.current.get(1).q1) / 2.0) + ( ((experiences.current.get(0).q1 + experiences.current.get(1).q1) / 2.0) - experiences.current.get(2).q1 ) / 2.0;
					q2 = ((experiences.current.get(0).q2 + experiences.current.get(1).q2) / 2.0) + ( ((experiences.current.get(0).q2 + experiences.current.get(1).q2) / 2.0) - experiences.current.get(2).q2 ) / 2.0;
					q3 = ((experiences.current.get(0).q3 + experiences.current.get(1).q3) / 2.0) - ( ((experiences.current.get(0).q3 + experiences.current.get(1).q3) / 2.0) - experiences.current.get(2).q3 ) / 2.0;
log.info("Cr : q1 = " + q1 + " q2 = " + q2);

    						} else if (lastCritere > experiences.current.get(0).criteria) {
log.info("State 0 :R : lastCritere > current0 : R meilleur que N et moins bon que B");
    				state = State.STATE_INIT;
					experiences.current.remove(3);
					doBoucle = true;
log.info("remove W, simplex BXNR");

    						} else { // dernier cas possible: if (lastCritere < experiences.current.get(0).critere) {
log.info("State 0 :R : lastCritere < current 0 : R meilleur que B, calcul de E");
    				state = State.STATE_4;

    				q1 = experiences.getExperience(number-1).q1 + (experiences.current.get(1).q1 + experiences.current.get(2).q1) / 2.0 - experiences.current.get(3).q1;
				q2 = experiences.getExperience(number-1).q2 + (experiences.current.get(1).q2 + experiences.current.get(2).q2) / 2.0 - experiences.current.get(3).q2;
				q3 = experiences.getExperience(number-1).q3 + (experiences.current.get(1).q3 + experiences.current.get(2).q3) / 2.0 - experiences.current.get(3).q3;
					//q1 = experiences.current.get(3).q1 + (experiences.current.get(0).q1 + experiences.current.get(1).q1) / 2.0 - experiences.current.get(2).q1;
    				//q2 = experiences.current.get(3).q2 + (experiences.current.get(0).q2 + experiences.current.get(1).q2) / 2.0 - experiences.current.get(2).q2;
log.info("E : q1 = " + q1 + " q2 = " + q2);
    			}

    				} else if (state == State.STATE_1) {
log.info("state 1, simplex BXNCw");
					experiences.current.remove(4);
					experiences.current.remove(3);
					state = State.STATE_INIT;
					doBoucle = true;


    				} else if (state == State.STATE_2) {
log.info("state 2, simplex BXNCr");
					experiences.current.remove(4);
					experiences.current.remove(3);
					state = State.STATE_INIT;
					doBoucle = true;


    				} else if (state == State.STATE_4) {
log.info("state 4 :comparaison de E a B");
					doBoucle = true;
				if (lastCritere < experiences.current.get(0).criteria) {
log.info("E meilleur que B, remove 3 et 4 : simplex BXNE");
				    experiences.current.remove(4);
					experiences.current.remove(3);
				} else {
log.info("E moins bon que B, remove 3 et 5, simplex BXNR");
					experiences.current.remove(5);
					experiences.current.remove(3);
				}

				state = State.STATE_INIT;
   				}


}//fin du while
//on remplit la table experiences
			experiences.getExperience(number).q1 = q1;
      		experiences.getExperience(number).q2 = q2;
      		experiences.getExperience(number).q3 = q3;

log.info("on change Q dans la DB avec : q1 = " + q1 + " " + "q2 = " + q2);

// on change la valeur de q dans la DB
        	changeDB(experiences.getExperience(number), nextSimulation);
    	}// fin du else (number > 3)
return doNext;
    }// fin du before simulation



    /**

     * Call after each simulation, compute criteria for last simulation
     * @param context plan context
     * @param nextSimulation storage used for next simulation
     * @return true if we must do next simulation, false to stop plan
     * @throws Exception
    */

    public boolean afterSimulation(AnalysePlanContext context, SimulationStorage lastSimulation) throws Exception {
        boolean doNext = true;
log.info("after simulation");
        int number = lastSimulation.getParameter().getAnalysePlanNumber();
        ResultStorage result = lastSimulation.getResultStorage();

/////*** Simulated catches are cumulated over strategies, areas and metiers

          MatrixND L = result.getMatrix(param_Population, ResultName.MATRIX_CATCH_WEIGHT_PER_STRATEGY_MET_PER_ZONE_POP);
// log.info("dim de L2" + " " + Arrays.toString(L2.getDim()));
/////*** If some strategies, metiers or areas must not be included in the cumulated catches see below else put "//" at the beginning of line 297 and replace "L2" by "L" at line 289
/////*** the strategies or so to exclude must be at the begining or end of the list you enter in the parameters interface
/////*** fill the line  MatrixND L = L2.getSubMatrix(a,b,c).copy(); as follow :
/////*** a = 1 if you want to exclude strategies, 2 if metiers and 4 if areas
/////*** b = indice of the first object considered
/////*** c = number of object to keep after b, b included
	//	 MatrixND L = L2.getSubMatrix(1,2,4).copy(); // for instance : keep strategies 2,3,4,5
		 // si tu veux toutes les strategies:
		 // MatrixND  L = L2;
//log.info("sous matrice extraite");
		L = L.sumOverDim(0);//sum over dates
		L = L.sumOverDim(1);// sum over strategies
        L = L.sumOverDim(2);// sum over metiers
        L = L.sumOverDim(4);// sum over zones
log.info("sommes sur les strategies, metiers et zones faites");

/////*** if observations are not cumulated by groups else put "//" at the beginning of the following line
//L = L.sumOverDim(3);
/////*** else if number of groups in your database differs from number of groups in observations ... write me an e-mail

////*** if observations are cumulated over quarter a = 3, over year a = 12, else put "//" at the beginning of the following line
      // L = L.sumOverDim(0,a);

	   L = L.reduce();


///////////////////Calcul du critere//////////////////
log.info("calcul du critere");
log.info("dim de L" + " " + Arrays.toString(L.getDim()));
log.info("dim de obs" + " " + Arrays.toString(matrixDebarquement.getDim()));
 double crit = 0;
        for ( MatrixIterator g = L.iterator(); g.hasNext();){
            g.next();
//boucle sur les trimestres et les classes d age

            int [] dim = g.getCoordinates();

            double obs = matrixDebarquement.getValue(dim);
            double simules = g.getValue();

            crit += Math.pow(obs-simules, 2); // crit = crit + (obs-simules)^2
        }// fin du for
log.info("critere " + number + " = " + crit );

//ajoute le critere dans la table experiences
        experiences.getExperience(number).criteria = crit;

//ecriture de la table historic
exportHisto += experiences.getExperience(number).q1 +";"+ experiences.getExperience(number).q2 +";"+ experiences.getExperience(number).criteria + "\n";
org.nuiton.util.FileUtil.writeString(exportHistoric, exportHisto);


return doNext;

}// fin du after simulation

    /**
     * Modify nextSimulation database with q1 and q2 in exp.
     * @param exp
     * @param nextSimulation
     * @throws Exception
     */


    protected void changeDB(Experience exp, SimulationStorage nextSimulation) throws Exception {
//methode appelee dans before simualtion
		TopiaContext db = nextSimulation.getStorage().beginTransaction();//ouvrir un context pour modifier les donnees
		Population pop = (Population)db.findByTopiaId(param_Population.getTopiaId()); //reccupere la pop ciblee
		MatrixND c = pop.getCapturability(); // reccupere la matrice de capturabilite

//log.info("Pour cette simulation : q1 = " + exp.q1 + ";" + "q2 = " + exp.q2 );

/////*** 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];
			PopulationSeasonInfo season = (PopulationSeasonInfo)sem[1];

////*** exemple when q2 corresponds to the 21 first groups (+ de 50% d'immatures)
			if (group.getId() < 9){
				i.setValue(exp.q1);
			}else if (group.getId()>=9 && group.getId()<=21){
				i.setValue(exp.q2);
			}
			else{
				i.setValue(exp.q3);
		     }
/////*** exemple when it depends on seasons and groups
/*			if (season.getFirstMonth().after(Month.JULY) && group.getId() >=18){ //month >= aout && groupID >= 18
                    i.setValue(exp.q2);
      		}else {
		  	    	i.setValue(exp.q1);
		    }
*/

		}//fin du for

		db.commitTransaction(); // effectue la modification
		db.closeContext(); // ferme le context
}




    static public class Experiences {
// cree la liste experiences ou sont stoque q1,q2 et critere pour chaque simulation

	/** contains last simplex and potentialy 2 more simulation */
    	public List<Experience> current = new ArrayList<Experience>();

	/** contains all experience done */
    	public List<Experience> history = new ArrayList<Experience>();
        /**
         * return experience requested, if this experience doesn't exist
         * create it.
         *
         * @param i simulation number
         * @return experience with simulation number fixed if new experience
         * is returned
         */
    	public Experience getExperience(int i) {
    		Experience result;
    		if (i<history.size()) {
    			result = history.get(i);
    		} else {
    			result = new Experience();
    			result.simNumber = i;
    			history.add(i, result);
	   			current.add(result);
    		}
    		return result;
    	}//fin de la definition de getExperience


/**
         * @return the history
         */
        public List<Experience> getHistory() {
            return this.history;
		}
    }// fin de la creation des listes experiences



    static public class Experience implements Comparable {
    	public int simNumber;
    	public double criteria;
    	public double q1;
    	public double q2;
   		/**
         * Permit to order experience, first is experience with smallest criteria
         */
    	public int compareTo(Object arg0) {
    		Experience other = (Experience)arg0;
    		int result = Double.compare(this.criteria, other.criteria);
    		return result;
    	}
   }


}