/*
 * TreeUtils.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.beauti.util;

import dr.evolution.tree.MutableTree;
import dr.evolution.tree.NodeRef;
import dr.evolution.tree.Tree;
import dr.evolution.util.Date;
import dr.evolution.util.Taxon;
import dr.evolution.util.TimeScale;
import dr.stats.Variate;

/**
 * Class for getting information from trees.
 *
 * @author Andrew Rambaut
 * @author Alexei Drummond
 * @version $Id: TreeUtils.java,v 1.7 2005/07/11 14:07:25 rambaut Exp $
 */
public class TreeUtils {

    /**
     * Gets the root to tip distances from a tree.
     */
    public static void getRootToTipDistances(Tree tree, Variate distances) {

        double rootHeight = tree.getNodeHeight(tree.getRoot());
        double height;

        for (int i = 0; i < tree.getExternalNodeCount(); i++) {

            height = tree.getNodeHeight(tree.getExternalNode(i));
            distances.add(rootHeight - height);
        }
    }

    /**
     * Gets the tip dates from a tree.
     */
    public static void getTipDates(Tree tree, Variate dates) {

        for (int i = 0; i < tree.getExternalNodeCount(); i++) {

            Taxon taxon = tree.getNodeTaxon(tree.getExternalNode(i));
            Object date = taxon.getAttribute("date");
            if (date != null) {
                if (date instanceof Date) {
                    dates.add(((Date) date).getTimeValue());
                } else {
                    try {
                        dates.add(Double.parseDouble(date.toString()));
                    } catch (NumberFormatException nfe) {
                        dates.add(0.0);
                    }
                }
            } else {
                dates.add(0.0);
            }
        }
    }

    /**
     * Gets the root to tip distances from a node.
     */
    public static void getRootToTipDistances(Tree tree, NodeRef node, double rootHeight,
                                             Variate distances) {

        if (tree.isExternal(node)) {
            double height = tree.getNodeHeight(node);
            distances.add(rootHeight - height);
        } else {

            getRootToTipDistances(tree, tree.getChild(node, 0), rootHeight, distances);
            getRootToTipDistances(tree, tree.getChild(node, 1), rootHeight, distances);
        }
    }

    /**
     * Gets the tip dates from a node.
     */
    public static void getTipDates(Tree tree, NodeRef node, Variate dates) {

        if (tree.isExternal(node)) {

            String date = (String) tree.getNodeTaxon(node).getAttribute("date");
            if (date != null) {
                dates.add(Double.parseDouble(date));
            } else {
                dates.add(0.0);
            }
        } else {

            getTipDates(tree, tree.getChild(node, 0), dates);
            getTipDates(tree, tree.getChild(node, 1), dates);
        }
    }

    /**
     * Returns -1 if there is no number suffix
     */
    public static double guessDate(String s) {

        int i = s.length();
        char c;
        do {
            i--;
            c = s.charAt(i);
        } while (i >= 0 && (Character.isDigit(c) || c == '.'));

        if (i == s.length()) {
            return 0.0;
        }

        return Double.parseDouble(s.substring(i + 1));
    }

    /**
     * Sets the tip heights from the tip dates
     */
    public static void setHeightsFromDates(MutableTree tree) {

        dr.evolution.util.Date mostRecent = null;

        for (int i = 0; i < tree.getExternalNodeCount(); i++) {

            Taxon taxon = tree.getNodeTaxon(tree.getExternalNode(i));

            dr.evolution.util.Date date = (dr.evolution.util.Date) taxon.getAttribute("date");

            if (date != null) {

                if ((mostRecent == null) || date.after(mostRecent)) {
                    mostRecent = date;
                }
            }
        }

        TimeScale timeScale = new TimeScale(mostRecent.getUnits(), true, mostRecent.getAbsoluteTimeValue());

        for (int i = 0; i < tree.getExternalNodeCount(); i++) {
            NodeRef node = tree.getExternalNode(i);
            Taxon taxon = tree.getNodeTaxon(node);
            dr.evolution.util.Date date = (dr.evolution.util.Date) taxon.getAttribute("date");

            if (date != null) {
                double height = timeScale.convertTime(date.getTimeValue(), date);
                tree.setNodeHeight(node, height);
            } else {
                tree.setNodeHeight(node, 0.0);
            }
        }

        adjustInternalHeights(tree, tree.getRoot());

        if (mostRecent != null) {
            tree.setUnits(mostRecent.getUnits());
        }
    }

    // **************************************************************
    // Private static methods
    // **************************************************************

    private static void adjustInternalHeights(MutableTree tree, NodeRef node) {

        if (!tree.isExternal(node)) {
            // pre-order recursion
            for (int i = 0; i < tree.getChildCount(node); i++) {
                adjustInternalHeights(tree, tree.getChild(node, i));
            }
        }

        NodeRef parent = tree.getParent(node);

        if (parent != null) {

            if (tree.getNodeHeight(parent) < tree.getNodeHeight(node)) {
                tree.setNodeHeight(parent, tree.getNodeHeight(node));
            }
        }
    }


}
