/*
 * RootToTip.java
 *
 * Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
 *
 * This file is part of BEAST.
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership and licensing.
 *
 * BEAST is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 *  BEAST 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with BEAST; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

package dr.app.pathogen;

import dr.app.beast.BeastVersion;
import dr.app.beauti.options.DateGuesser;
import dr.app.util.Arguments;
import dr.app.tools.NexusExporter;
import dr.evolution.io.Importer;
import dr.evolution.io.NexusImporter;
import dr.evolution.io.TreeImporter;
import dr.evolution.tree.Tree;
import dr.evolution.util.TaxonList;
import dr.stats.Regression;
import dr.stats.Variate;
import dr.util.Version;

import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

/*
 * Essentially a command line version of PathOgen. Written to
 * perform the analysis on sets of trees.
 * 
 * @author Andrew Rambaut
 */

public class RootToTip {

    private final static Version version = new Version() {
        @Override
        public String getVersion() {
            return "1.5";
        }

        @Override
        public String getVersionString() {
            return "v1.5";
        }

        @Override
        public String getBuildString() {
            return "";
        }

        @Override
        public String getDateString() {
            return "2003-2015";
        }

        @Override
        public String[] getCredits() {
            return new String[0];
        }

        @Override
        public String getHTMLCredits() {
            return "";
        }
    };

    public RootToTip(int burnin, String dateOrder, final boolean keepRoot, String outgroup,
                     boolean writeTree, String inputFileName, String outputFileName) throws IOException {

        System.out.println("Reading tree(s)...");

        boolean firstTree = true;
        FileReader fileReader = new FileReader(inputFileName);
        TreeImporter importer = new NexusImporter(fileReader);

        List<Regression> regressions = new ArrayList<Regression>();
        List<Tree> trees = new ArrayList<Tree>();

        DateGuesser dg = new DateGuesser();
        dg.fromLast = false;
        if (dateOrder.equals("FIRST")) {
            dg.order = 0;
        } else if (dateOrder.equals("LAST")) {
            dg.order = 0;
            dg.fromLast = true;
        } else {
            dg.order = Integer.parseInt(dateOrder) - 1;
            if (dg.order < 0 || dg.order > 100) {
                System.err.println("Error Parsing order of date field: " + dateOrder);
            }
        }

        TaxonList taxa = null;
        TemporalRooting temporalRooting = null;

        try {
            while (importer.hasTree()) {
                Tree tree = importer.importNextTree();

                if (firstTree) {
                    taxa = tree;

                    dg.guessDates(taxa);

                    temporalRooting = new TemporalRooting(taxa);

                    firstTree = false;
                }

                if (totalTrees >= burnin) {
                    Tree rootedTree = tree;

                    if (!keepRoot) {
                        rootedTree = temporalRooting.findRoot(tree, TemporalRooting.RootingFunction.CORRELATION);
                    }

                    regressions.add(temporalRooting.getRootToTipRegression(rootedTree));

                    if (writeTree) {
                        trees.add(rootedTree);
                    }
                    totalTreesUsed += 1;
                }
                totalTrees += 1;

            }
        } catch (Importer.ImportException e) {
            System.err.println("Error Parsing Input Tree: " + e.getMessage());
            return;
        }
        fileReader.close();

        PrintWriter printWriter;

        if (!writeTree && outputFileName != null) {
            printWriter = new PrintWriter(outputFileName);
        } else {
            printWriter = new PrintWriter(System.out);
        }

        if (regressions.size() == 1) {
            Regression r = regressions.get(0);

            Variate dates = r.getXData();
            Variate distances = r.getYData();

            printWriter.println("date\tdistance");
            for (int i = 0; i < dates.getCount(); i++) {
                printWriter.println(dates.get(i) + "\t" + distances.get(i));
            }
            printWriter.println();
            printWriter.println("Regression slope = " + r.getGradient());
            printWriter.println("X-Intercept = " + r.getXIntercept());
            printWriter.println("Y-Intercept = " + r.getYIntercept());
            printWriter.println("Residual mean squared = " + r.getResidualMeanSquared());
            printWriter.println("R^2 = " + r.getRSquared());
            printWriter.println("Correlation coefficient = " + r.getCorrelationCoefficient());

        } else {
            printWriter.println("tree\tslope\tx-intercept\ty-intercept\tcorrelation");
            int i = 1;
            for (Regression r : regressions) {
                printWriter.print("\t" + i);
                printWriter.print("\t" + r.getGradient());
                printWriter.print("\t" + r.getXIntercept());
                printWriter.println("\t" + r.getYIntercept());
                printWriter.println("\t" + r.getCorrelationCoefficient());
            }

        }

        printWriter.close();

        if (writeTree) {
            PrintStream printStream;

            if (outputFileName != null) {
                printStream = new PrintStream(outputFileName);
            } else {
                printStream = new PrintStream(System.out);
            }

            NexusExporter exporter = new NexusExporter(printStream);
            Tree[] treeArray = new Tree[trees.size()];
            trees.toArray(treeArray);

            exporter.exportTrees(treeArray);

            printStream.close();
        }
    }

    int totalTrees = 0;
    int totalTreesUsed = 0;

    public static void printTitle() {
        System.out.println();
        centreLine("RootToTip " + version.getVersionString() + ", " + version.getDateString(), 60);
        centreLine("Root to tip distance vs. time of sampling", 60);
        centreLine("by", 60);
        centreLine("Andrew Rambaut", 60);
        System.out.println();
        System.out.println();
    }

    public static void centreLine(String line, int pageWidth) {
        int n = pageWidth - line.length();
        int n1 = n / 2;
        for (int i = 0; i < n1; i++) {
            System.out.print(" ");
        }
        System.out.println(line);
    }


    public static void printUsage(Arguments arguments) {

        arguments.printUsage("roottotip", "<input-file-name> [<output-file-name>]");
        System.out.println();
        System.out.println("  Example: roottotip -burnin 100 test.trees rootToTip.txt");
        System.out.println();
    }

    //Main method
    public static void main(String[] args) throws IOException {

        String inputFileName = null;
        String outputFileName = null;

        printTitle();

        Arguments arguments = new Arguments(
                new Arguments.Option[]{
                        new Arguments.IntegerOption("burnin", "the number of trees to be ignored as 'burn-in' [default = 0]"),
                        new Arguments.StringOption("dateorder", "date_order", "order of date field in taxon name: first, last, 1, 2 etc. [default = last]"),
//                        new Arguments.StringOption("outgroup", "{taxon list}", "one or more taxa that will be used to root the tree(s) [default = find root]"),
                        new Arguments.Option("keeproot", "keep the existing root of the input trees [default = estimate root]"),
                        new Arguments.Option("writetree", "Write the optimally rooted tree to the output file"),
                        new Arguments.Option("help", "option to print this message"),
                });

        try {
            arguments.parseArguments(args);
        } catch (Arguments.ArgumentException ae) {
            System.out.println(ae);
            printUsage(arguments);
            System.exit(1);
        }

        if (arguments.hasOption("help")) {
            printUsage(arguments);
            System.exit(0);
        }

        int burnin = 0;
        if (arguments.hasOption("burnin")) {
            burnin = arguments.getIntegerOption("burnin");
        }

        String dateOrder = "LAST";
        if (arguments.hasOption("dateorder")) {
            dateOrder = arguments.getStringOption("dateorder").toUpperCase();
        }

        String outgroup = null;
        if (arguments.hasOption("outgroup")) {
            outgroup = arguments.getStringOption("dateorder").toUpperCase();
        }

        boolean keepRoot = arguments.hasOption("keeproot");

        boolean writeTree = arguments.hasOption("writetree");

        String[] args2 = arguments.getLeftoverArguments();

        if (args2.length > 2) {
            System.err.println("Unknown option: " + args2[2]);
            System.err.println();
            printUsage(arguments);
            System.exit(1);
        }

        if (args2.length == 0) {
            System.err.println("Missing input file name");
            printUsage(arguments);
            System.exit(1);
        }


        inputFileName = args2[0];
        if (args2.length == 2) {
            outputFileName = args2[1];
        }

        new RootToTip(burnin,
                dateOrder,
                keepRoot,
                outgroup,
                writeTree,
                inputFileName,
                outputFileName
        );

        System.exit(0);
    }

}