/*
 * #%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 java.io.File;
import java.util.List;

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

import org.nuiton.j2r.REngine;
import org.nuiton.j2r.RException;

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

import org.nuiton.j2r.types.RDataFrame;

/**
 * Implementation of Fast 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 Fast extends AbstractSensitivityAnalysis {

    @Doc("an integer giving the sample size, i.e. the length of the " +
    "discretization of the s-space (see Cukier et al.). (default=20)")
    public int param_n = 20;

    @Doc("an integer specifying the interference parameter, i.e. the number " +
    "of harmonics to sum in the Fourier series decomposition (see Cukier et " +
    "al.). (default=6)")
    public int param_M = 6;

    @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();

        // only run with continuous
        checkAllFactorContinuous(factors);

        String rInstruction = "isis.methodAnalyse<-fast99(" +
                "model=NULL," +
                "factors=%s, " +
                "n=%s, " +
                "M=%s, " +
                "q = \"qunif\", " +
                "q.arg=list(min=0,max=1))";
        String rCall = String.format(rInstruction, factors.size(), param_n,
                param_M);

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

        try {

            REngine engine = openEngine(outputDirectory);

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

            // Run sensitivity analysis
            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 = (Integer)engine.eval("dim(isis.methodAnalyse$X)[1]");

            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);
                    factor.setValueForIdentifier(dataFrame.get(i,j));
                    experimentScenario.addFactor(factor);
                }
                thisExperimentScenarios.add(experimentScenario);
                thisExperiment.setScenarios(thisExperimentScenarios);
            }

            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 {

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

        try {

            REngine engine = openEngine(outputDirectory);

            // for export
            engine.setwd(outputDirectory);

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

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

            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++) {

                // Creates the R expression to import results in R
                String name = param.getSensitivityExport().get(k)
                        .getExportFilename();

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

                engine.voidEval("names(isis.methodAnalyse$V)<-isis.factor.distribution$NomFacteur");
                engine.voidEval("names(isis.methodAnalyse$X)<-isis.factor.distribution$NomFacteur");
                engine.voidEval("names(isis.methodAnalyse$D1)<-isis.factor.distribution$NomFacteur");
                engine.voidEval("names(isis.methodAnalyse$Dt)<-isis.factor.distribution$NomFacteur");

                //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(isis.methodAnalyse,y=" + name + ")" + "\"," +
                    "\"analysis_result\"=isis.methodAnalyse)");

                //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 + "_V.csv\")");

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

                //Export Dt
                engine.voidEval("write.csv(isis.methodAnalyse$Dt,\"" + name + "_Dt.csv\")");
                //Set dfresults names
                engine.voidEval("resultsnames<-c(isis.factor.distribution$NomFacteur,\"Result\")");
                engine.voidEval("names(dfresults)<-isis.factor.distribution$NomFacteur");

                //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 FAST method using R (needs the sensitivity " +
            "package to work)";
    }

}
