/*
 * #%L
 * IsisFish data
 * %%
 * Copyright (C) 2009 - 2012 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 java.io.File;
import java.util.List;

import fr.ifremer.isisfish.export.SensitivityExport;
import fr.ifremer.isisfish.simulator.sensitivity.*;

import org.apache.commons.lang3.StringUtils;
import org.nuiton.j2r.REngine;

import fr.ifremer.isisfish.datastore.SimulationStorage;
import fr.ifremer.isisfish.simulator.SimulationParameter;
import fr.ifremer.isisfish.annotations.Doc;

import org.nuiton.j2r.types.RDataFrame;

/**
 * Implementation of Sobol method using R.
 * 
 * @author jcouteau
 * @version $Revision: 3842 $
 * 
 * Last update : $Date: 2013-11-22 18:52:48 +0100 (ven., 22 nov. 2013) $ By :
 * $Author: jcouteau $
 */
public class Sobol extends AbstractSensitivityAnalysis {

    @Doc("the size of the 2 random samples")
    public int param_n = 20;

    @Doc("the number of bootstrap replicates.")
    public int param_nboot = 20;

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

    /**
     * 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 true;
    }

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

        setIsisFactorsR(plan, outputDirectory);

        RDataFrame dataFrame;
        int nbExperiments;
        int factorNumber = plan.getFactors().size();
        List<Factor> factors = plan.getFactors();
        SensitivityScenarios thisExperiment = new SensitivityScenarios();
        List<Scenario> thisExperimentScenarios = thisExperiment.getScenarios();

        checkAllFactorContinuous(factors);

        String rInstruction = "isis.methodAnalyse<-sobol2002(model=NULL,X1=X1,X2=X2,nboot=%s)";
        String rCall = String.format(rInstruction, param_nboot);

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

        try {
            REngine engine = openEngine(outputDirectory);

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

            String x = "<-data.frame(matrix(c(";

            for (int i = 0; i < factorNumber; i++) {
                x += "runif("+param_n+"),";
            }

            x = StringUtils.removeEnd(x, ",");

            x+="),nrow="+param_n+"))";

            engine.voidEval("X1"+x);
            engine.voidEval("X2"+x);

            engine.voidEval(rCall);

            // Creating the factors vector.
            rInstruction = "factornames<-c(";
            for (int i = 0; i < factorNumber; i++) {
                String factorName = factors.get(i).getName();
                if (i != (factorNumber - 1)) {
                    rInstruction += "\"" + factorName + "\",";
                } else {
                    rInstruction += "\"" + factorName + "\"";
                }
            }

            rInstruction += ")";

            engine.voidEval(rInstruction);

            // Get back experiment plan
            dataFrame = (RDataFrame) engine.eval("isis.methodAnalyse$X");
            dataFrame.setVariable("isis.methodAnalyse$X");

            nbExperiments = dataFrame.dim()[0];

            engine.voidEval(getIsisFactorDistribution(factors));

            engine.voidEval("call<-isis.methodAnalyse$call");

            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<-data.frame(isis.methodAnalyse$X)");

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

            engine.voidEval("names(isis.simule)<-isis.factors[[1]]");

            // Setting up the scenarios.
            for (int j = 0; j < nbExperiments; j++) {
                Scenario experimentScenario = new Scenario();
                for (int i = 0; i < factorNumber; i++) {
                    Factor factor = plan.getFactors().get(i);
                    Double dFValue = (Double) dataFrame.get(i, j);
                    factor.setValueForIdentifier(dFValue);
                    experimentScenario.addFactor(factor);
                }
                thisExperimentScenarios.add(experimentScenario);
            }
            
            closeEngine(engine, outputDirectory);

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

        thisExperiment.setScenarios(thisExperimentScenarios);
        return thisExperiment;

    }

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

        SimulationStorage firstStorage = simulationStorages.get(0);
        SimulationParameter param = firstStorage.getParameter();
        int sensitivityNumber = param.getSensitivityExport().size();
        String simulationName = outputDirectory.getName().replaceAll("-", "");

        try {

            REngine engine = openEngine(outputDirectory);

            // Call R
            // Load sensitivity package into R (if package already loaded,
            // nothing happens.
            engine.voidEval("library(sensitivity)");

            //Set X1 names
            engine.voidEval("names(X1)<-factornames");
            //Set X2 names
            engine.voidEval("names(X2)<-factornames");

            //Set a$X names
            engine.voidEval("names(a$X)<-factornames");

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

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

                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," +
                        sensitivityExport.getExportFilename() + ")");
            }

            //adding attribute to isis.Simule
            engine.voidEval("attr(isis.simule," +
                "\"nomModel\")<-\"isis-fish-externe-R\"");

            engine.voidEval("attr(isis.simule,\"call\")<-isis.methodExp$call");

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

                SensitivityExport sensitivityExport = param.getSensitivityExport().get(k);
                String name = sensitivityExport.getExportFilename();

                //Compute results
                engine.voidEval("tell(a,y=" + name + ")");

                engine.voidEval("row.names(isis.methodAnalyse$S)<-names(isis.methodAnalyse$X)");
                engine.voidEval("row.names(isis.methodAnalyse$T)<-names(isis.methodAnalyse$X)");
                engine.voidEval("row.names(isis.methodAnalyse$V)<-c(\"global\"," +
                    "names(isis.methodAnalyse$X),paste(\"-\",names(isis.methodAnalyse$X),sep=\"\"))");

                //creating isis.methodAnalyse
                String exportMethodAnalyse = String.format("%s.isis.methodAnalyse", simulationName + "." + name);
                engine.voidEval(exportMethodAnalyse + "<-list(" +
                    "\"isis.factors\"=isis.factors," +
                    "\"isis.factor.distribution\"=isis.factor.distribution," +
                    "\"isis.simule\"=isis.simule," +
                    "call_method=\"tell(a,y=" + name + ")" + "\"," +
                    "\"analysis_result\"=a)");

                //setting isis.methodAnalyse attributes
                engine.voidEval("attr(" + exportMethodAnalyse + "," +
                    "\"nomModel\")<-\"isis-fish-externe-R\")");

                //Create the data.frame of scenarios and results for export purpose
                engine.voidEval("dfresults<-data.frame(isis.methodAnalyse$X,isis.methodAnalyse$y)");

                //Set working directory
                engine.setwd(outputDirectory);

                //Export V
                engine.voidEval("write.csv(isis.methodAnalyse$V,\""
                        + name
                        + "_SensitivityIndices.csv\")");
                //Export DD
                engine.voidEval("write.csv(isis.methodAnalyse$D,\""
                        + name + "_D.csv\")");

                //Export S
                engine.voidEval("write.csv(isis.methodAnalyse$S,\""
                        + name + "_S.csv\")");

                //Export results
                engine.voidEval("write.csv(dfresults,\""
                        + name + "_Results.csv\")");
                //FIXME export through java to enable export when using Rserve

            }

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

    @Override
    public String getDescription() {
        return "Implementation of Sobol method using R (use of the R " +
            "sobol2002 method, needs the 'sensitivity' package to work)";
    }
}
