
/*
 * SlidableTree.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.evolution.tree;

import dr.evolution.util.Taxon;
import dr.math.MathUtils;

/**
 * Interface for a binary tree which can support a Mau-et-al MCMC move 
 * in which node heights `slide' and can change topology.
 *
 * @author Graham Jones
 * Date 2012-03-30
 *
 */

/*
The aim is to simplify code by not having to use a MutableTree with its long list of
methods. The users of SlidableTree are

1. AlloppLeggedTree, for moves within tetraploid subtrees.

2. AlloppDiploidHistory. A SimpleTree is made for testing purposes.

3. PopsIOSpeciesTreeModel. Needs to be a Tree for logging. So I
make and update a SimpleTree and delegate to that.

4. MulSpeciesTreeModel does not use it yet, but could do. Currently a MutableTree and
uses near-copy of JH's code for MCMC moves. Same issue as PopsIOSpeciesTreeModel if use
SlidableTree.

It uses NodeRefs cause that's what SimpleTree uses. This means that clients have
to implement get/setNumber when they have no use for node numbers.

 */


public interface SlidableTree {

    NodeRef getSlidableRoot();

    int getSlidableNodeCount();

    Taxon getSlidableNodeTaxon(NodeRef node);

    double getSlidableNodeHeight(NodeRef node);

    void setSlidableNodeHeight(NodeRef node, double height);

    boolean isExternalSlidable(NodeRef node);

    NodeRef getSlidableChild(NodeRef node, int i);

    void replaceSlidableChildren(NodeRef node, NodeRef lft, NodeRef rgt);

    void replaceSlidableRoot(NodeRef root);


    public class Utils {

        /*
           * This is an implementation of the ideas of
           *
           * Bayesian Phylogenetic Inference via Markov Chain Monte Carlo Methods
           * Bob Mau, Michael A. Newton, Bret Larget
           * Biometrics, Vol. 55, No. 1 (Mar., 1999), pp. 1-12
           *
           * similar to implementation by Joseph Heled in TreeNodeSlide.
           * This version works on a SlidableTree rather than a SimpleTree and is
           * simpler than JH's version. It does not keep track of what was swapped.
           * Instead I assign popvalues to nodes via an ordering of species (see
           * fillinpopvals() in network).
           */


        /*
           * For Mau-Newton-Larget MCMC moves on tree.
           * Do this, then change a node height, then call mnlReconstruct()
           *
           */
        static public NodeRef[] mnlCanonical(SlidableTree tree) {
            final int count = tree.getSlidableNodeCount();
            NodeRef[] order = new NodeRef[count];
            mnlCanonicalSub(tree, tree.getSlidableRoot(), 0, order);
            return order;
        }


        /*
           * For Mau-etal MCMC moves on tree
           */
        static public void mnlReconstruct(SlidableTree tree, NodeRef[] order) {
            final NodeRef root = mnlReconstructSub(tree, 0, (order.length - 1) / 2, order);
            tree.replaceSlidableRoot(root);
        }


        /*
       * ****************** private methods **********************
       */


        static private int mnlCanonicalSub(SlidableTree tree, NodeRef node,
                                           int nextloc, NodeRef[] order) {
            if( tree.isExternalSlidable(node) ) {
                order[nextloc] = node;     assert (nextloc & 0x1) == 0;
                nextloc++;
                return nextloc;
            }
            final boolean swap = MathUtils.nextBoolean();
            nextloc = mnlCanonicalSub(tree, tree.getSlidableChild(node, swap ? 1 : 0), nextloc, order);
            order[nextloc] = node;   assert (nextloc & 0x1) == 1;
            nextloc++;
            nextloc = mnlCanonicalSub(tree, tree.getSlidableChild(node, swap ? 0 : 1), nextloc, order);
            return nextloc;
        }



        static private NodeRef mnlReconstructSub(SlidableTree tree, int from, int to,
                                                 NodeRef[] order) {
            if (from == to) {
                return order[2*from];
            }
            int rootIndex = highestNode(tree, order, from, to);
            NodeRef root = order[2 * rootIndex + 1];
            NodeRef lft = mnlReconstructSub(tree, from, rootIndex, order);
            NodeRef rgt = mnlReconstructSub(tree, rootIndex+1, to, order);
            tree.replaceSlidableChildren(root, lft, rgt);
            return root;
        }






        static private int highestNode(SlidableTree tree, NodeRef[] order, int from, int to) {
            int rootIndex = -1;
            double maxh = -1.0;

            for (int i = from; i < to; ++i) {
                final double h = tree.getSlidableNodeHeight(order[2 * i + 1]);
                if (h > maxh) {
                    maxh = h;
                    rootIndex = i;
                }
            }
            return rootIndex;
        }



    }
}
