/*
 * #%L
 * IsisFish data
 * %%
 * Copyright (C) 2009 - 2011 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 2 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-2.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.AbstractSensitivityAnalysis;
import fr.ifremer.isisfish.simulator.sensitivity.DesignPlan;
import fr.ifremer.isisfish.simulator.sensitivity.Factor;
import fr.ifremer.isisfish.simulator.sensitivity.Scenario;
import fr.ifremer.isisfish.simulator.sensitivity.SensitivityException;
import fr.ifremer.isisfish.simulator.sensitivity.SensitivityScenarios;
import fr.ifremer.isisfish.simulator.sensitivity.domain.ContinuousDomain;
import fr.ifremer.isisfish.util.Doc;
import org.nuiton.j2r.REngine;
import org.nuiton.j2r.RException;
import org.nuiton.j2r.RProxy;
import org.nuiton.j2r.types.RDataFrame;

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

/**
 * Implementation of Morris method using R.
 *
 * @author jcouteau
 * @version $Revision: 259 $
 *
 * Last update : $Date: 2011-10-13 16:23:27 +0200 (jeu. 13 oct. 2011) $ By :
 * $Author: jcouteau $
 */
public class SensitivityCalculatorRMorrisClean
        extends AbstractSensitivityAnalysis {

    @Doc("Integer giving the number of repetitions of the design, i.e. the number of elementary effect computed per factor. (Default value : 4)")
    public int param_r = 4;

    @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
     * continus.
     *
     * @return <tt>true</tt> s'il sait la gerer
     */
    @Override
    public boolean canManageCardinality() {
        return false;
    }

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

        setIsisFactorsR(plan, outputDirectory);

        RDataFrame dataFrame;
        int nbExperiments;
        int factorNumber = plan.getFactors().size();

        SensitivityScenarios thisExperiment = new SensitivityScenarios();

        List<Factor> factors = plan.getFactors();

        if (param_r == 0) {
            param_r = 4;
        }

        testNoDiscrete(factors);

        // Creating the vectors.
        String factorNames = "";
        String levels = "";
        String gridJump = "";
        String binf = "";
        String bsup = "";
        for (int i = 0; i < factorNumber; i++) {
            Factor factor = factors.get(i);
            ContinuousDomain domain = (ContinuousDomain) factor.getDomain();
            if (i != 0) {
                factorNames += ",";
                levels += ",";
                gridJump += ",";
                binf += ",";
                bsup += ",";
            }

            factorNames += "\"" + factor.getName() + "\"";

            levels += factor.getCardinality();

            gridJump += factor.getCardinality() / 2;

            binf += domain.getCalculatorMinBound();

            bsup += domain.getCalculatorMaxBound();
        }

        String rInstruction = "a<-morris(model=NULL,factors=c(%s),r=%s," +
                "design=list(type=\"oat\",levels=c(%s),grid.jump=c(%s))," +
                "binf=c(%s),bsup=c(%s))";

        String rCall = String.format(rInstruction, factorNames, param_r,
                levels, gridJump, binf, bsup);

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

        try {
            REngine engine = new RProxy();

            engine.clearSession();

            //Get Isis R session
            engine.loadRData(outputDirectory.getParentFile(),
                    outputDirectory.getName());

            // 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(%s)";
            rCall = String.format(rInstruction, factorNames);

            engine.voidEval(rCall);

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

            // Get back experiment plan
            engine.eval("expPlan<-as.data.frame(a$X)");
            dataFrame = (RDataFrame) engine.eval("expPlan");
            dataFrame.setVariable("expPlan");

            nbExperiments = (Integer)engine.eval("dim(expPlan)[1]");

            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 < factorNumber; i++) {
                Factor factor = factors.get(i);
                ContinuousDomain domain = (ContinuousDomain) factor.getDomain();
                if (i != 0) {
                    distribution += ",";
                    parameters += ",";
                }

                distribution += "\"qunif\"";
                parameters += "\"[" + domain.getMinBound() + ";" +
                        domain.getMaxBound() + "]\"";
            }
            engine.voidEval(String.format(factorDistribution, factorNames,
                    distribution, parameters));

            engine.voidEval("call<-a$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(expPlan)");
            engine.voidEval("attr(isis.simule,\"nomModel\")<-" +
                    "\"isis-fish-externe-R\"");
            engine.voidEval("names(isis.simule)<-isis.factors[[1]]");

            // Save Isis R session
            engine.saveRData(outputDirectory.getParentFile(),
                    outputDirectory.getName());

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

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

        return thisExperiment;

    }

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

        SimulationStorage storage = simulationStorages.get(0);
        String simulationName = storage.getName().replaceAll("-", "");

        try {
            REngine engine = new RProxy();

            engine.clearSession();

            //Get Isis R session
            engine.loadRData(outputDirectory.getParentFile(),
                    outputDirectory.getName());

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

            //Set the working directory (for exports)
            engine.setwd(outputDirectory);

            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(a,y=" + name + ")");

                //creating isis.methodAnalyse
                engine.voidEval("isis.methodAnalyse<-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(isis.methodAnalyse,\"nomModel\")<-" +
                        "\"isis-fish-externe-R\"");

                // Get back the sensitivity results, mu, mu star and sigma.
                engine.voidEval("mu<-apply(a$ee, 2, mean)");
                engine.voidEval("mu.star <- apply(a$ee, 2, function(a) mean(abs(a)))");
                engine.voidEval("sigma <- apply(a$ee, 2, sd)");

                //Create the data.frame of sensitivity indices for export purpose
                engine.voidEval("df<-data.frame(mu,mu.star,sigma)");
                engine.voidEval("row.names(df)<-factornames");

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

                //Set dfresults names
                engine.voidEval("resultsnames<-c(factornames,\"Result\")");
                engine.voidEval("names(dfresults)<-resultsnames");

                //Export sensitivity indices
                engine.voidEval("write.csv(df," +
                        "\"" + name + "_SensitivityIndices.csv\")");

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

                String renameIsisMethodAnalyse = "%s.isis.methodAnalyse<-" +
                        "isis.methodAnalyse";

                engine.voidEval(String.format(renameIsisMethodAnalyse,
                        simulationName + "." + name));

            }

            //Rename R objects for saving purpose
            renameObjects(engine,simulationName);
            
            //Clean temporary R objects
            engine.remove("a");
            engine.remove("isis.factors");
            engine.remove("factornames");
            engine.remove("expPlan");
            engine.remove("call");
            engine.remove("isis.MethodExp");
            engine.remove("isis.simule");
            engine.remove("isis.methodAnalyse");
            engine.remove("mu");
            engine.remove("mu.star");
            engine.remove("sigma");
            engine.remove("df");
            engine.remove("dfresults");
            engine.remove("resultsnames");
            engine.remove("Binf");
            engine.remove("Bsup");
            engine.remove("isis.factor.distribution");
            engine.remove("nomFacteur");
            engine.remove("Nominal");
            for (int k = 0; k < sensitivityNumber; k++) {
                SensitivityExport sensitivityExport =
                        param.getSensitivityExport().get(k);
                engine.remove(sensitivityExport.getExportFilename());
            }

            // Save Isis R session
            engine.saveRData(outputDirectory.getParentFile(),
                    outputDirectory.getName());

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

    }

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

}
