/*
 * #%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.export.SensitivityExport;

import org.nuiton.j2r.REngine;
import org.nuiton.j2r.RException;
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;

public class RegularFractions extends AbstractSensitivityAnalysis {

    @Doc("the path of the directory where the R function is stored (do not indicate the RegularFractions.R file name)")
    public String param_pathToFunction = "";

    @Doc("unique prime number of levels of all input and unit factors")
    public int param_p = 2;

    @Doc("number of unit factors (so that there are N=p^r units)")
    public int param_r = 2;

    @Doc("resolution of the fraction")
    public int param_resolution = 2;

    @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 factorNumber = plan.getFactors().size();
        List<Factor> factors = plan.getFactors();
        SensitivityScenarios thisExperiment = new SensitivityScenarios();
        List<Scenario> thisExperimentScenarios = thisExperiment.getScenarios();

        String factorNames = "";

        //Test all factors, if one is discrete, return null
        checkAllFactorContinuous(factors);

        //Create a string with all factors names
        for (int i = 0; i < factorNumber; i++) {
            if (i != 0) {
                factorNames += ",";
            }

            factorNames += "\"" + factors.get(i).getName() + "\"";
        }

        try {

            REngine engine = openEngine(outputDirectory);

            //Clear session
            engine.clearSession();

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

            //Set the working directory (to import the R function)
            engine.setwd(new File(param_pathToFunction));

            //Import the function
            engine.voidEval("source(\"regularfractions.R\")");

            //Create the instruction
            String rInstruction = "isis.methodAnalyse<-regular.fraction(%s,%s,%s,%s)";
            String rCall = String.format(rInstruction, factors.size(), param_p,
                    param_r, param_resolution);

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

            // Run function
            engine.voidEval(rCall);

            // Run function
            engine.voidEval("call<-\"" + rCall + "\"");

            // Creating the factors vector.
            rInstruction = "factornames<-c(%s)";
            rCall = String.format(rInstruction, factorNames);

            engine.voidEval(rCall);


            // Get back experiment plan
            engine.eval("expPlan<-as.data.frame(isis.methodAnalyse[[1]])");
            dataFrame = (RDataFrame)engine.eval("expPlan");
            dataFrame.setVariable("expPlan");
            
            //Get back the simulation number
            int simulationNumber = (Integer) engine.eval("length(isis.methodAnalyse[[1]][,1])");

            // Setting up the scenarios.
            for (int j = 0; j < simulationNumber; j++) {
                Scenario experimentScenario = new Scenario();
                for (int i = 0; i < factors.size(); i++) {
                    Factor factor = plan.getFactors().get(i);
                    factor.setValueForIdentifier(dataFrame.get(i,j));
                    experimentScenario.addFactor(factor);
                }
                thisExperimentScenarios.add(experimentScenario);
                thisExperiment.setScenarios(thisExperimentScenarios);
            }

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

            //Create the factors vectors and the dataframe instruction
            for (int j = 0; j < factorNumber; j++) {
                Factor factor = factors.get(j);
                String factorName = factor.getName().replaceAll(" ", "");

                String vector = factorName + "<-c(";
                for (int i = 0; i < simulationNumber; i++) {
                    Scenario scenario = thisExperimentScenarios.get(i);
                    List<Factor> newFactors = scenario.getFactors();
                    Factor factor1 = newFactors.get(j);

                    if (i < (simulationNumber - 1)) {
                        vector += factor1.getDisplayedValue() + ",";
                    } else {
                        vector += factor1.getDisplayedValue();
                    }

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

                if (j < factorNumber - 1) {
                    dataframe += factorName + "=factor(" + factorName + "),";
                } else {
                    dataframe += factorName + "=factor(" + factorName + "))";
                }

            }
            engine.voidEval(dataframe);

            engine.voidEval(getIsisFactorDistribution(factors));

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

            //Create isis.Simule
            engine.voidEval("isis.simule<-data.frame(data)");

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

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

            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 {

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

        try {

            REngine engine = openEngine(outputDirectory);

            //Get back the factors number
            int factorNumber = (Integer) engine.eval("dim(isis.simule)[2]");

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

            for (SensitivityExport export : exports) {

                String name = export.getExportFilename();

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

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

                    if (j < (factorNumber - 1)) {
                        aovCall = aovCall
                                + engine.eval("names(isis.simule)[" + (j + 1) + "]")
                                + "+";
                    } else {
                        aovCall = aovCall
                                + engine.eval("names(isis.simule)[" + (j + 1) + "]")
                                + ")";
                        if (param_resolution <= 4) {
                            aovCall += ",data=dataforaov)";
                        } else {
                            aovCall += "^2,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)<-isis.factor.distribution$NomFacteur");

                /*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,\"" + name +
                        "_Results.csv\")");

                //Save the sensitivity indices
                engine.voidEval("write.csv(exportsensitivity,\"" + name +
                        "_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 eee) {
            throw new SensitivityException("Can't evaluate results", eee);
        }

    }

    @Override
    public String getDescription() {
        return "Implementation of Regular fractions method using R";
    }

}
