/*
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.fileio;

import converter.Converter;
import converter.DefaultFactory;
import converter.Factory;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.PushbackReader;

import es.uvigo.darwin.prottest.util.exception.AlignmentParseException;
import es.uvigo.darwin.prottest.util.exception.TreeFormatException;

import es.uvigo.darwin.prottest.util.logging.ProtTestLogger;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import pal.alignment.Alignment;
import pal.alignment.ReadAlignment;
import pal.tree.ReadTree;
import pal.tree.Tree;
import pal.tree.TreeParseException;

// TODO: Auto-generated Javadoc
import parser.ParseException;
/**
 * The Class AlignmentReader.
 */
public class AlignmentReader {

    /**
     * Instantiates a new alignment reader.
     */
    public AlignmentReader() {

    }

    /**
     * Read alignment.
     * 
     * @param output the output writer
     * @param filename the filename
     * @param debug the debug
     * 
     * @return the alignment
     * 
     * @throws AlignmentParseException the alignment parse exception.
     * @throws FileNotFoundException Signals that the input filename does not exist.
     * @throws IOException Signals that an I/O exception has occured.
     */
    public static Alignment readAlignment(PrintWriter output, String filename, boolean debug)
            throws AlignmentParseException, FileNotFoundException, IOException {

        Alignment alignment;
        StringBuilder text = new StringBuilder();

        BufferedReader br = new BufferedReader(new FileReader(filename));
        String s;
        while ((s = br.readLine()) != null) {
            text.append(s).append("\r\n");
        }
        br.close();


        //Get options
        String in = text.toString();
        String inO = "";
        String inP = "";
        String inF = "";
        boolean autodetect = true;
        boolean collapse = false;
        boolean gaps = false;
        boolean missing = false;
        int limit = 0;
        String out = "";
        String outO = "Linux";
        String os = System.getProperty("os.name");
        if (os.startsWith("Mac")) {
            outO = "MacOS";
        } else if (os.startsWith("Linux")) {
            outO = "Linux";
        } else if (os.startsWith("Win")) {
            outO = "Windows";
        }
        String outP = "ProtTest";
        String outF = "PHYLIP";
        boolean lower = false;
        boolean numbers = false;
        boolean sequential = true;
        boolean match = false;

        //Get converter and convert MSA
        Factory factory = new DefaultFactory();
        Converter converter;

        //Inicializar logger
        Logger logger = Logger.getLogger("alter" + System.currentTimeMillis());
        logger.setUseParentHandlers(false);
        logger.setLevel(Level.ALL);
        for (Handler handler : ProtTestLogger.getDefaultLogger().getHandlers()) {
            logger.addHandler(handler);
        }

        try {
            converter = factory.getConverter(inO, inP, inF, autodetect,
                    collapse, gaps, missing, limit,
                    outO, outP, outF, lower, numbers, sequential, match, logger.getName());
            out = converter.convert(in);
        } catch (UnsupportedOperationException ex) {
            throw new AlignmentParseException("There's some error in your data: " + ex.getMessage());
        } catch (ParseException ex) {
            throw new AlignmentParseException("There's some error in your data: " + ex.getMessage());
        }

        try {
        	PushbackReader pr = new PushbackReader(new StringReader(out));
        	alignment = readAlignment(output, pr, debug);
        } catch (AlignmentParseException ex) {
        	sequential = false;
        	try {
                converter = factory.getConverter(inO, inP, inF, autodetect,
                        collapse, gaps, missing, limit,
                        outO, outP, outF, lower, numbers, sequential, match, logger.getName());
                out = converter.convert(in);
            } catch (UnsupportedOperationException exUO) {
                throw new AlignmentParseException("There's some error in your data: " + ex.getMessage());
            } catch (ParseException exP) {
                throw new AlignmentParseException("There's some error in your data: " + ex.getMessage());
            }
        	PushbackReader pr = new PushbackReader(new StringReader(out));
        	alignment = readAlignment(output, pr, debug);
        }
        
        if (alignment == null) {
            throw new AlignmentParseException("There's some error in your data, exiting...");
        }

        return alignment;
    }

    /**
     * Read alignment.
     * 
     * @param out the out
     * @param pr the pushback reader which contains the alignment
     * @param debug the debug state
     * 
     * @return the alignment
     * 
     * @throws AlignmentParseException the alignment parse exception
     * @throws IOException Signals that an I/O exception has occured.
     */
    public static Alignment readAlignment(PrintWriter out, PushbackReader pr, boolean debug)
            throws AlignmentParseException, IOException {

        if (debug) {
            out.println("");
            out.println("**********************************************************");
            out.println("  Reading alignment...");
        }
        Alignment alignment = null;
        try {
            alignment = new ReadAlignment(pr);
        } catch (pal.alignment.AlignmentParseException e) {
            throw new AlignmentParseException(e.getMessage());
        }

        List<String> seqNames = new ArrayList<String>(alignment.getSequenceCount());
        for (int i = 0; i < alignment.getSequenceCount(); i++) {
            seqNames.add(alignment.getIdentifier(i).getName());
        }

        String currString;
        int size = alignment.getSequenceCount();
        for (int i = 0; i < size; i++) {
            currString = (String) seqNames.get(i);
            for (int j = i + 1; j < size; j++) {
                if (seqNames.get(j).equals(currString)) {
                    throw new AlignmentParseException("ERROR: There are duplicated taxa names in the alignment: " + currString);
                }
            }
        }

        //TODO: print TAXA and check if data is in correct format
        if (debug) {
            for (int i = 0; i < alignment.getSequenceCount(); i++) {
                out.println("    Sequence #" + (i + 1) + ": " + alignment.getIdentifier(i).getName());
            }
            out.println("   Alignment contains " + alignment.getSequenceCount() + " sequences of length " + alignment.getSiteCount());
            out.println("");
            out.println("**********************************************************");
            out.println("");
        }

        return alignment;
    }

    /**
     * Read tree.
     * 
     * @param out the out
     * @param filename the filename
     * @param debug the debug
     * 
     * @return the tree
     * 
     * @throws TreeFormatException When the input filename is not in the right format
     * @throws FileNotFoundException Signals that the input filename does not exist.
     * @throws IOException Signals that an I/O exception has occured.
     */
    public static Tree readTree(PrintWriter out, String filename, boolean debug)
            throws TreeFormatException, FileNotFoundException, IOException {
        //FOR PHYML:
        //if tree is not in newick format, reformat and save to a new file:
        //TreeUtils: public static void printNH(Tree tree, java.io.PrintWriter out)
        if (debug) {
            out.println("Reading tree...");
        }
        Tree tree;
        try {
            tree = new ReadTree(filename);
        } catch (TreeParseException e) {
            throw new TreeFormatException("Error: Wrong tree format : " + e.getMessage());
        }
        if (debug) {
            out.println("Tree contains " + tree.getExternalNodeCount() + " external nodes");
            out.println("");
        }
        return tree;
    }

    /**
     * Read tree.
     * 
     * @param out the out
     * @param in the tree pushback reader
     * @param debug the debug
     * 
     * @return the tree
     * 
     * @throws TreeFormatException the tree format exception
     */
    public static Tree readTree(PrintWriter out, PushbackReader in, boolean debug)
            throws TreeFormatException {
        //FOR PHYML:
        //if tree is not in newick format, reformat and save to a new file:
        //TreeUtils: public static void printNH(Tree tree, java.io.PrintWriter out)
        if (debug) {
            out.println("Reading tree...");
        }
        Tree tree;
        try {
            tree = new ReadTree(in);
        } catch (TreeParseException e) {
            throw new TreeFormatException("Error: Wrong tree format : " + e.getMessage());
        }
        if (debug) {
            out.println("Tree contains " + tree.getExternalNodeCount() + " external nodes");
            out.println("");
        }
        return tree;
    }
}
