/*
Copyright (C) 2009  Diego Darriba

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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package es.uvigo.darwin.prottest.util.factory;

import static es.uvigo.darwin.prottest.global.ApplicationGlobals.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.Calendar;
import java.text.SimpleDateFormat;

import pal.alignment.Alignment;
import pal.tree.Tree;
import es.uvigo.darwin.prottest.exe.PhyMLv3AminoAcidRunEstimator;
import es.uvigo.darwin.prottest.exe.RaxMLAminoAcidRunEstimator;
import es.uvigo.darwin.prottest.exe.RunEstimator;
import es.uvigo.darwin.prottest.global.AminoAcidApplicationGlobals;
import es.uvigo.darwin.prottest.global.ApplicationGlobals;
import es.uvigo.darwin.prottest.global.RaxmlAminoAcidApplicationGlobals;
import es.uvigo.darwin.prottest.global.options.ApplicationOptions;
import es.uvigo.darwin.prottest.model.AminoAcidModel;
import es.uvigo.darwin.prottest.model.Model;
import es.uvigo.darwin.prottest.selection.printer.AminoAcidPrintFramework;
import es.uvigo.darwin.prottest.selection.printer.PrintFramework;
import es.uvigo.darwin.prottest.util.argumentparser.AminoAcidArgumentParser;
import es.uvigo.darwin.prottest.util.argumentparser.ProtTestArgumentParser;
import es.uvigo.darwin.prottest.util.exception.ProtTestInternalException;
import es.uvigo.darwin.prottest.util.logging.ProtTestLogFormatter;
import java.io.File;
import java.io.FileOutputStream;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.StreamHandler;

/**
 * A factory for creating ProtTest objects.
 */
public class ProtTestFactory {

    /** The Constant PROTEIC. */
    public static final int PROTEIC = 1;
    /** The Constant NUCLEIC. */
    public static final int NUCLEIC = 2;
    /** The Constant MAX_SORT. */
    private static final int MAX_SORT = 2;
    /** The instance. */
    private static ProtTestFactory instance;
    /** The sort. */
    private int sort;
    /** Unique log handler. */
    private Handler logHandler;

    /**
     * Instantiates a new prot test factory.
     * 
     * @param sort the sort
     * 
     * @throws IllegalArgumentException the illegal argument exception
     */
    private ProtTestFactory(int sort)
            throws IllegalArgumentException {
        if (sort <= 0 || sort > MAX_SORT) {
            throw new IllegalArgumentException(
                    "Cannot create factory (unexistent sort : " + sort + ")");
        }
        this.sort = sort;
    }

    /**
     * Initialize.
     * 
     * @param args the args
     * 
     * @return the string[]
     */
    public static String[] initialize(String[] args) {
        if (instance == null) {
            int sort;
            List<String> argList = new ArrayList<String>(Arrays.asList(args));
            int index = argList.indexOf(ProtTestArgumentParser.ARG_TOKEN + ProtTestArgumentParser.PARAM_DATA_TYPE);
            if (index < 0) //set default
            {
                sort = PROTEIC;
            } else {
                String value = argList.get(index + 1);
                argList.remove(index + 1);
                argList.remove(index);
                args = argList.toArray(new String[0]);
                if (value.equals(ProtTestArgumentParser.DATA_TYPE_AMINOACID)) {
                    sort = PROTEIC;
                } else if (value.equals(ProtTestArgumentParser.DATA_TYPE_NUCLEOTIDE)) {
                    sort = NUCLEIC;
                } else {
                    throw new IllegalArgumentException("Invalid data type " + value);
                }
            }
            instance = new ProtTestFactory(sort);
            return args;
        } else {
            throw new ProtTestInternalException("ProtTestFactory was already initialized");
        }
    }

    /**
     * Gets the single instance of ProtTestFactory.
     * 
     * @return single instance of ProtTestFactory
     */
    public static ProtTestFactory getInstance() {
        if (instance == null) {
            initialize(new String[0]);
        }
//			throw new ProtTestInternalException("ProtTestFactory should be initialized");

        return instance;
    }

    /**
     * Creates a new ProtTest object.
     * 
     * @param args the args
     * @param options the options
     * 
     * @return the prot test argument parser
     */
    public ProtTestArgumentParser createProtTestArgumentParser(String[] args, ApplicationOptions options) 
        throws IllegalArgumentException{
        ProtTestArgumentParser mtap = null;
        switch (sort) {
            case PROTEIC:
                mtap = new AminoAcidArgumentParser(args, options);
                break;
            case NUCLEIC:
                throw new ProtTestInternalException("Unsupported operation: nucleic data");
        }
        return mtap;
    }

    /**
     * Gets the application globals.
     * 
     * @return the application globals
     */
    public ApplicationGlobals getApplicationGlobals() {
        ApplicationGlobals ap = null;
        String analyzer = APPLICATION_PROPERTIES.getProperty("analyzer");
        switch (sort) {
            case PROTEIC:
                if (analyzer.equals(ANALYZER_RAXML)) {
                    ap = new RaxmlAminoAcidApplicationGlobals();
                } else if (analyzer.equals(ANALYZER_PHYML)) {
                    ap = new AminoAcidApplicationGlobals();
                } else {
                    throw new ProtTestInternalException("Analyzer " + analyzer + " not supported by RunEstimator. Check your prottest.properties file");
                }
                break;
            case NUCLEIC:
                throw new ProtTestInternalException("Unsupported operation: nucleic data");
        }
        return ap;
    }

    /**
     * Creates a new ProtTest object.
     * 
     * @param matrix the matrix
     * @param distribution the distribution
     * @param modelProperties the model APPLICATION_PROPERTIES
     * 
     * @return the model
     */
    public Model createModel(String matrix, int distribution, Properties modelProperties,
            Alignment alignment, Tree tree, int ncat) {
        Model model = null;
        boolean plusF;
        if (modelProperties != null) {
            plusF = Boolean.parseBoolean(
                    modelProperties.getProperty(
                    AminoAcidModel.PROP_PLUS_F, "false"));
        } else {
            plusF = false;
        }
        switch (sort) {
            case PROTEIC:
                model = new AminoAcidModel(matrix, distribution, plusF,
                        alignment, tree, ncat);
                break;
            case NUCLEIC:
                throw new ProtTestInternalException("Unsupported operation: nucleic data");
        }
        return model;
    }

    /**
     * Creates a new ProtTest object.
     * 
     * @param options the options
     * @param model the model
     * 
     * @return the run estimator
     */
    public RunEstimator createRunEstimator(ApplicationOptions options, Model model) {
        return createRunEstimator(options, model, 1);
    }

    /**
     * Creates a new ProtTest object.
     * 
     * @param options the options
     * @param model the model
     * 
     * @return the run estimator
     */
    public RunEstimator createRunEstimator(ApplicationOptions options, Model model, int numberOfThreads) {
        RunEstimator runEstimator = null;
        String analyzer = APPLICATION_PROPERTIES.getProperty("analyzer");
        if (analyzer.equals(ANALYZER_PHYML)) {
            switch (sort) {
                case PROTEIC:
                    runEstimator = new PhyMLv3AminoAcidRunEstimator(options, model, numberOfThreads);
                    break;
                case NUCLEIC:
                    throw new ProtTestInternalException("Unsupported operation: nucleic data");
            }
        } else if (analyzer.equals(ANALYZER_RAXML)) {
            switch (sort) {
                case PROTEIC:
                    runEstimator = new RaxMLAminoAcidRunEstimator(options, model);
                    break;
                case NUCLEIC:
                    throw new ProtTestInternalException("Unsupported operation: nucleic data");
            }
        } else {
            throw new ProtTestInternalException("Analyzer " + analyzer + " not supported by RunEstimator");
        }
        return runEstimator;
    }

    public Handler createLogHandler() throws IOException {
        //   Log level is configurable:
        //   'info'    Only general information messages are logged (default)
        //   'fine'    General debug information is also logged
        //   'finer'   More complex debug information is logged
        //   'finest'  All activity is tracked

        if (logHandler == null) {

            String[] supportedLevels = {"INFO", "FINE", "FINER", "FINEST"};

            String logDirName = APPLICATION_PROPERTIES.getProperty("log_dir");
            String level = APPLICATION_PROPERTIES.getProperty("log_level", "info").toUpperCase();
            boolean supported = false;
            for (String testLevel : supportedLevels) {
                supported |= testLevel.equals(level);
            }

            if (logDirName != null && supported) {
                File logDir = new File(logDirName);
                if (!logDir.isAbsolute()) {
                	logDir = new File(ApplicationGlobals.PATH + File.separator + logDir);
                }
                if (!logDir.exists()) {
                    logDir.mkdirs();
                }

		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
                //File logFile = new File(logDir.getAbsolutePath() + File.separator + "prottest3_" +
		//	sdf.format(Calendar.getInstance().getTime()));
		File logFile = File.createTempFile("prottest3_"+sdf.format(Calendar.getInstance().getTime())+"_",
			 ".log", logDir);
                FileOutputStream fos = new FileOutputStream(logFile);
                logHandler = new StreamHandler(fos, new ProtTestLogFormatter());
                logHandler.setLevel(Level.parse(level));
            }

        }
        return logHandler;

    }

    /**
     * Creates a new ProtTest object.
     *
     * @return the prints the framework
     */
    public PrintFramework createPrintFramework() {
        PrintFramework pf = null;

        switch (sort) {
            case PROTEIC:
                pf = new AminoAcidPrintFramework();
                break;
            case NUCLEIC:
                throw new ProtTestInternalException("Unsupported operation: nucleic data");
        }

        return pf;
    }
}
