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

/**
 * Implementation of Morris method using R.
 *
 * @author jcouteau
 * @version $Revision$
 *
 * Last update : $Date$ By :
 * $Author$
 */
public class Morris 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 {@code true} 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;
        }

        checkAllUniformDistribution(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);
            if (i != 0) {
                factorNames += ",";
                levels += ",";
                gridJump += ",";
                binf += ",";
                bsup += ",";
            }

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

            levels += factor.getCardinality();

            gridJump += factor.getCardinality() / 2;

            binf += 0;
            bsup += 1;
        }

        String rInstruction = "isis.methodAnalyse<-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 = 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(%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(isis.methodAnalyse$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 r_distribution = domain.getDistribution();

                distribution += "\""+ r_distribution.getInstruction() +"\"";
                parameters += "\"[";

                for (Map.Entry<String,Object> param:domain.getDistributionParameters().entrySet()){
                    parameters += param.getKey() + "=" + param.getValue() + ";";
                }

                parameters = StringUtils.removeEnd(parameters,";");
                parameters += "]\"";
            }
            engine.voidEval(String.format(factorDistribution, factorNames,
                    distribution, parameters));

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

            // 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);
            }
            
            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);

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

                //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\"");

                // Get back the sensitivity results, mu, mu star and sigma.
                engine.voidEval("mu<-apply(isis.methodAnalyse$ee, 2, mean)");
                engine.voidEval("mu.star <- apply(isis.methodAnalyse$ee, 2, function(a) mean(abs(a)))");
                engine.voidEval("sigma <- apply(isis.methodAnalyse$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)<-isis.factor.distribution$NomFacteur");

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

                //Set dfresults names
                engine.voidEval("resultsnames<-c(isis.factor.distribution$NomFacteur,\"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

            }

            closeEngine(engine, outputDirectory);

        } 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)";
    }

}
