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

import fr.ifremer.isisfish.export.SensitivityExport;
import org.nuiton.j2r.REngine;
import org.nuiton.j2r.types.RDataFrame;

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

/**
 * Implementation of Random Latin Hypercube method using R.
 * 
 * @author jcouteau
 * @version $Revision: 3842 $
 */
public class RandomLHS extends AbstractSensitivityAnalysis {

    @Doc("Simulation number (default=10)")
    public int param_simulationNumber = 10;
    @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);

        int factorNumber = plan.getFactors().size();
        RDataFrame dataFrame; //The dataframe to get back the scenarios from R
        SensitivityScenarios thisExperiment = new SensitivityScenarios(); //The experiment we will build
        List<Scenario> thisExperimentScenarios = thisExperiment.getScenarios(); //The list of scenarios
        List<Factor> factors = plan.getFactors(); //The factors

        checkAllFactorContinuous(factors);

        try {

            REngine engine = openEngine(outputDirectory);

            //Load the lhs library
            engine.voidEval("library(lhs)");

            //Create the scenarios
            String rInstruction = "isis.methodAnalyse<-randomLHS(%s,%s)";
            String rCall = String.format(rInstruction, param_simulationNumber,
                    factorNumber);

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

            engine.voidEval(rCall);

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

            // Setting up the scenarios.
            for (int j = 0; j < param_simulationNumber; j++) {
                Scenario experimentScenario = new Scenario();
                for (int i = 0; i < factorNumber; i++) {
                    Factor factor = plan.getFactors().get(i); //The factor we are setting
                    factor.setValueForIdentifier(dataFrame.get(i,j));
                    experimentScenario.addFactor(factor);
                }
                thisExperimentScenarios.add(experimentScenario);
                thisExperiment.setScenarios(thisExperimentScenarios);
            }

            engine.voidEval(getIsisFactorDistribution(factors));

            engine.voidEval("call<-" + "\"isis.methodAnalyse<-randomLHS("
                    + param_simulationNumber + "," + factorNumber + ")\"");

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

            String data = "data<-data.frame(";

            //Create the factors vectors and the dataFrame instruction
            for (int j = 0; j < factors.size(); j++) {
                Scenario firstScenario = thisExperimentScenarios.get(0);
                Factor factor = firstScenario.getFactors().get(j);
                String factorName = factor.getName().replaceAll(" ", "");

                //the vector of the factor values
                //read all the values one by one from the already created
                //scenarios.
                String vector = factorName + "<-c(";
                for (int i = 0; i < param_simulationNumber; i++) {
                    Scenario scenario = thisExperimentScenarios.get(i);
                    List<Factor> factorList = scenario.getFactors();
                    Factor factor1 = factorList.get(j);
                    if (i < (param_simulationNumber - 1)) {
                        vector += factor1.getDisplayedValue() + ",";
                    } else {
                        vector += factor1.getDisplayedValue();
                    }

                }
                vector = vector + ")";
                engine.voidEval(vector);

                //add factor1=factor(factor1) for each factor...
                if (j < factors.size() - 1) {
                    data += factorName + "=factor(" + factorName + "),";
                } else {
                    data += factorName + "=factor(" + factorName + "))";
                }

            }
            engine.voidEval(data);

            // Creating the factors vector.
            rInstruction = "factornames<-c(";
            for (int i = 0; i < factorNumber; i++) {
                if (i != (factorNumber - 1)) {
                    rInstruction = rInstruction + "\""
                            + factors.get(i).getName() + "\",";
                } else {
                    rInstruction = rInstruction + "\""
                            + factors.get(i).getName() + "\"";
                }
            }
            rInstruction += ")";
            engine.voidEval(rInstruction);
            
            // Clean RData
            for (Factor factor : factors) {
                String factorName = factor.getName().replaceAll(" ", "");
                engine.remove(factorName);
            }

            closeEngine(engine, outputDirectory);

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

        return thisExperiment;
    }

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

        SimulationStorage simulationStorage1 = simulationStorages.get(0);
        String simulationName = outputDirectory.getName().replaceAll("-", "");
        SimulationParameter param = simulationStorage1.getParameter();
        int sensitivityNumber = param.getSensitivityExport().size();
        //List<SensitivityExport> export = param.getSensitivityExport();

        try {
            REngine engine = openEngine(outputDirectory);

            engine.voidEval("factors<-data.frame(isis.methodAnalyse)");

            //Get back the factors number
            int factorNumber = (Integer) engine.eval("length(factors)");

            //Get back the simulation number
            param_simulationNumber = (Integer) (engine.eval("length(factors[,1])"));

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

                //Create the dataforaov data.frame
                String dataframe = "dataforaov<-data.frame(factors," + name
                        + "=" + name + ")";
                engine.voidEval(dataframe);

                //Call aov()
                String aovCall = "aovresult<-aov(" + name + "~";
                for (int j = 0; j < factorNumber; j++) {

                    if (j < (factorNumber - 1)) {
                        aovCall += engine.eval("names(factors)[" + (j + 1) + "]")
                                + "+";
                    } else {
                        aovCall += engine.eval("names(factors)[" + (j + 1) + "]")
                                + ",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(isis.factor.distribution$NomFacteur,\"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", simulationName + "." + 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 Random Latin Hypercube method method using" +
            " R needs the 'lhs' package to work)";
    }

}
