/*
 * #%L
 * IsisFish data
 * %%
 * Copyright (C) 2009 - 2014 Ifremer, Code Lutin, Jean Couteau
 * %%
 * 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>.
 * #L%
 */

package sensitivityanalysis;

import fr.ifremer.isisfish.datastore.SimulationStorage;
import fr.ifremer.isisfish.export.SensitivityExport;
import fr.ifremer.isisfish.simulator.SimulationParameter;
import fr.ifremer.isisfish.simulator.sensitivity.*;
import fr.ifremer.isisfish.simulator.sensitivity.domain.ContinuousDomain;
import fr.ifremer.isisfish.simulator.sensitivity.domain.DiscreteDomain;
import fr.ifremer.isisfish.annotations.Doc;
import org.apache.commons.lang3.StringUtils;
import org.nuiton.j2r.REngine;
import org.nuiton.j2r.RException;
import org.nuiton.j2r.types.RDataFrame;

import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * User: couteau
 * Date: 14 janv. 2010
 * Time: 11:58:09
 */
public class DOptimal extends AbstractSensitivityAnalysis {

    @Doc("True to be able to modify the code sent to R")
    public boolean param_modifR = false;

    @Doc("Order")
    public int param_order=1;

    /**
     * Retourne vrai si le calculateur sait gerer la cardinalité des facteurs
     * continue.
     *
     * @return {@code true} s'il sait la gerer
     */
    @Override
    public boolean canManageCardinality() {
        return false;
    }

    @Override
    public SensitivityScenarios compute(DesignPlan plan, File outputDirectory)
            throws SensitivityException {

        SensitivityScenarios thisExperiment = new SensitivityScenarios();

        setIsisFactorsR(plan, outputDirectory);

        //extract the factors from the design plan
        List<Factor> factors = plan.getFactors();
        int factorsNb = factors.size();

        try {
            REngine engine = openEngine(outputDirectory);

            engine.voidEval("library(AlgDesign)");

            //Create a list of the factors under the form factor1=factor1,...
            String factorsNames = "";

            //Create a list of the factors under the form factor1,factor2,...
            String factorNames = "";

            //Create a list of the factors under the form factor1+factor2+...
            String factorPlus="";

            //create the factors vectors
            for (Factor factor : factors) {
                //The factor values vector
                String factorName = factor.getName().replaceAll(" ", "");
                factorsNames += factorName + "=" + factorName + ",";
                factorNames += factorName + ",";
                factorPlus += factorName + "+";

                String vector = factorName + "<-c(";

                Domain domain = factor.getDomain();

                if (domain instanceof ContinuousDomain) {
                    //ContinuousDomain contDomain = (ContinuousDomain) domain;
                    int card = factor.getCardinality();
                    Double min = 0.0;
                    Double max = 1.0;

                    for (int i = 0; i < card; i++) {
                        //add the ith value
                        vector += (min + (i * (max - min) / (card - 1))) + ",";
                    }
                } else {
                    int nbValues = ((DiscreteDomain) domain).getValuesCount();
                    for (int i = 0; i < nbValues; i++) {
                        vector += "as.integer(" + i + "),";
                    }
                }

                vector = vector.substring(0, vector.length() - 1);

                vector += ")";

                engine.voidEval(vector);


            }

            factorsNames = factorsNames.substring(0,factorsNames.length()-1);
            factorNames = factorNames.substring(0,factorNames.length()-1);
            factorPlus = factorPlus.substring(0,factorPlus.length()-1);


            //X<-expand.grid(vector=???,weight=???)
            String expandGrid = "expandgrid<-expand.grid(%s)";



            String rCall = String.format(expandGrid,factorsNames);

            engine.voidEval(rCall);

            if (param_order ==1 ){
                rCall = "isis.methodAnalyse<-optFederov(~(%s),data=expandgrid, " +
                        "approximate =FALSE, center=F, criterion = \"D\"," +
                        "nRepeats=10)";
            } else {
                rCall = "isis.methodAnalyse<-optFederov(~(%s)^"+param_order+",data=expandgrid, " +
                        "approximate =FALSE, center=F, criterion = \"D\"," +
                        "nRepeats=10)";
            }

            rCall = String.format(rCall,factorPlus);

            if (param_modifR) {
                rCall = editRInstruction(rCall);
            }

            engine.voidEval("runif(1)");

            engine.voidEval(rCall);

            engine.eval("expPlan<-as.data.frame(isis.methodAnalyse$design)");
            RDataFrame dataFrame = (RDataFrame)engine.eval("expPlan");
            dataFrame.setVariable("expPlan");
            int nbExperiments = (Integer)engine.eval("dim(isis.methodAnalyse$design)[1]");

            List<Scenario> thisExperimentScenarios = thisExperiment.getScenarios();
            for (int j = 0; j < nbExperiments; j++) {
                Scenario experimentScenario = new Scenario();
                for (int i = 0; i < factorsNb; i++) {
                    Factor factor = factors.get(i);
                    factor.setValueForIdentifier(dataFrame.get(i, j));
                    experimentScenario.addFactor(factor);
                }
                thisExperimentScenarios.add(experimentScenario);
                thisExperiment.setScenarios(thisExperimentScenarios);
            }


            String factorDistribution = "isis.factor.distribution<-data.frame(" +
                "NomFacteur=c(%s)," +
                "NomDistribution=c(%s)," +
                "ParametreDistribution=c(%s))";

            String distribution = "";
            String parameters = "";

            for (int i = 0; i < factorsNb; i++) {
                Domain domain = factors.get(i).getDomain();
                if (i != 0) {
                    distribution += ",";
                    parameters += ",";
                }
                if (domain instanceof ContinuousDomain){
                    //ContinuousDomain contDomain = (ContinuousDomain)domain;
                    distribution += "\"qunif\"";
                    parameters += "\"[0.0;1.0]\"";
                } else {
                    DiscreteDomain discDomain = (DiscreteDomain)domain;
                    distribution += "\"discrete\"";
                    parameters+="\"[";
                    Map<Object, Object> values = discDomain.getValues();
                    int count=0;
                    Collection<Object> collecValues = values.values();
                    for (Object value:collecValues) {
                        if (count!=0){
                            parameters += ",";
                        }
                        parameters += StringUtils.replace(value.toString(), "\"", "\\\"") ;
                        count++;
                    }
                    parameters+="]\"";
                }
            }
            engine.voidEval(String.format(factorDistribution, factorNames,
                    distribution, parameters));

            engine.voidEval("call<-\"" + rCall + "\"");
            engine.voidEval("isis.methodExp<-list(" +
                    "\"isis.factors\"=isis.factors," +
                    "\"isis.factor.distribution\"=isis.factor.distribution," +
                    "\"call\"=call)");
            engine.voidEval("attr(isis.methodExp," +
                    "\"nomModel\")<-\"isis-fish-externe-R\"");
            engine.voidEval("isis.simule<-isis.methodAnalyse$design");
            engine.voidEval("attr(isis.simule," +
                    "\"nomModel\")<-\"isis-fish-externe-R\"");
            engine.voidEval("names(isis.simule)<-isis.factors[[1]]");

            closeEngine(engine, outputDirectory);

        } catch (RException eee) {
            throw new SensitivityException("Can't generate scenarios", eee);
        }

        return thisExperiment;
    }

    @Override
    public void analyzeResult(List<SimulationStorage> simulationStorages,
            File outputDirectory) throws SensitivityException {

        try {

            REngine engine = openEngine(outputDirectory);

            //Get back the factors number
            int factorNumber = (Integer)engine.eval("dim(isis.methodAnalyse$design)[2]");

            SimulationParameter param = simulationStorages.get(0).getParameter();
            int sensitivityNumber = param.getSensitivityExport().size();

            String firstStorageName = outputDirectory.getName().replaceAll("-", "");

            for (int k = 0; k < sensitivityNumber; k++) {

                SensitivityExport sensitivityExport =
                        param.getSensitivityExport().get(k);

                String name = sensitivityExport.getExportFilename();

                String rInstruction = createImportInstruction(sensitivityExport,
                        simulationStorages);

                // Send the simulation results
                engine.voidEval(rInstruction);

                //Put results in isis.simule
                engine.voidEval("isis.simule<-data.frame(isis.simule,"
                        + name + ")");

                //Create the dataforaov data.frame
                String dataFrame = "dataforaov<-data.frame(isis.methodAnalyse$design,"
                        + name + "=" + name + ")";
                engine.voidEval(dataFrame);

                //Call aov()
                String aovCall = "aovresult<-aov(" + name + "~(";
                for (int j = 0; j < factorNumber; j++) {
                    if (j < (factorNumber - 1)) {
                        aovCall = aovCall
                                + engine.eval("names(isis.methodAnalyse$design)[" + (j + 1) + "]")
                                + "+";
                    } else {
                        aovCall = aovCall
                                + engine.eval("names(isis.methodAnalyse$design)[" + (j + 1) + "]")
                                + ")";
                        if(param_order>1){
                            aovCall+="^" + param_order + ",data=dataforaov)";
                        }else{
                            aovCall+=",data=dataforaov)";
                        }
                    }
                }
                engine.voidEval(aovCall);

                /*Export the results
                 *Export format is csv, data separated by ','
                 *Results Export name is sensitivityExportName_Results.csv
                 *Sensitivity Indices export name is sensitivityExportName_SensitivityIndices.csv
                 */

                //Compute Sum of Squares and Sensitivity indices
                engine.voidEval("SoS<-summary(aovresult)[[1]][1:dim(summary(aovresult)[[1]])[1],2]");
                engine.voidEval("names(SoS)<-dimnames(summary(aovresult)[[1]])[[1]][1:dim(summary(aovresult)[[1]])[1]]");
                engine.voidEval("IndSensibilite<-SoS/sum(SoS)");

                //Create a data.frame to export sensitivity important results in one file.
                engine.voidEval("exportsensitivity<-data.frame(" +
                        "SoS[1:dim(summary(aovresult)[[1]])[1]]," +
                        "IndSensibilite[1:dim(summary(aovresult)[[1]])[1]])");
                engine.voidEval("names(exportsensitivity)<-c(" +
                        "\"Sum Of Squares\"," +
                        "\"Sensitivity indices\")");
                engine.voidEval("row.names(exportsensitivity)<-dimnames(summary(aovresult)[[1]])[1][[1]][1:dim(summary(aovresult)[[1]])[1]]");


                //Set dataforaov names
                engine.voidEval("resultsnames<-c(names(x$design),\"Result\")");
                engine.voidEval("names(dataforaov)<-resultsnames");


                /*Set the export directory
                 *Export directory is the first simulation export directory.
                 */
                engine.setwd(outputDirectory);

                //Save the results with the scenarios.
                engine.voidEval("write.csv(dataforaov,\""
                        + param.getSensitivityExport().get(k)

                                .getExportFilename() + "_Results.csv\")");

                //Save the sensitivity indices
                engine.voidEval("write.csv(exportsensitivity,\""
                        + param.getSensitivityExport().get(k)
                                .getExportFilename()
                        + "_SensitivityIndices.csv\")");
                //FIXME export through java to enable export when using Rserve (when distant Rserve).

                //creating isis.methodAnalyse
                String exportMethodAnalyse = String.format("%s.isis.methodAnalyse", firstStorageName + "." + name);
                engine.voidEval(exportMethodAnalyse + "<-list(" +
                        "\"isis.factors\"=isis.factors," +
                        "\"isis.factor.distribution\"=isis.factor.distribution," +
                        "\"isis.simule\"=isis.simule," +
                        "\"call_method\"=\"" + aovCall + "\"," +
                        "\"analysis_result\"=list(aovresult,IndSensibilite))");

                engine.voidEval("attr(" + exportMethodAnalyse + "," +
                        "\"nomModel\")<-\"isis-fish-externe-R\"");
            }

            closeEngine(engine, outputDirectory);

        } catch (Exception e) {
            throw new SensitivityException("Can't evaluate results", e);
        }

    }

    @Override
    public String getDescription() {
        return "Implementation of D optimal method method using R, needs" +
                "AlgDesign package to work";
    }
}
