/*
 * AlloppDiploidHistory.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.evomodel.speciation;

import java.util.ArrayList;
import java.util.Formatter;
import java.util.Locale;
import java.util.Stack;

import dr.evolution.tree.NodeRef;
import dr.evolution.tree.SimpleNode;
import dr.evolution.tree.SimpleTree;
import dr.evolution.tree.SlidableTree;
import dr.evolution.util.Taxon;
import dr.math.MathUtils;
import dr.util.AlloppMisc;
import jebl.util.FixedBitSet;


/**
 * AlloppDiploidHistory represents part of the network before hybridizations.
 * It is basically a tree with some tips representing diploid species at present time
 * and others representing the points at which hybridization occurs.
 *
 * @author Graham Jones
 *         Date: 13/03/2012
 */


/*
 * This a tree with some tips representing diploid species at present time
 * and others representing the points at which hybridization (to form a
 * tetraploid) occurs. The latter are in pairs and have times before present,
 * and I call them `hydridization tips' or `hyb-tips'. From the point of
 * view of a tetraploid tree these hyb-tips are feet at the ends of legs.
 *
 * The purpose of this class is to represent the part of the network before
 * hybridizations (=`diploid history') in a form (the array of DipHistNode's)
 * that can be subjected to Mau-type moves.
 *
 * The diploid history is constructed from a diploid tree and a single
 * tetraploid tree initially. MCMC moves can change the number of tetraploid
 * trees.
 *
 */



public class AlloppDiploidHistory implements SlidableTree {
    private DipHistNode[] dhnodes;
    private int rootn;
    private int nextn;
    private AlloppSpeciesBindings apsp;


    /******************************** Inner classes ****************************************/

    public enum LegLorR {left, right, dud};


    // Small class for returning two heights. For ChangeNumHybs move
    public class FootAncHeights {
        public double anchgt;
        public double ancanchgt;
        FootAncHeights(double anchgt, double ancanchgt) {
            this.anchgt = anchgt;
            this.ancanchgt = ancanchgt;
        }
    }

    /*
    * parent, child[] implement the tree topology.
    *
    * height is the node height; can be > 0 for hyb-tips.
    *
    * union is a spseq-union. At diploid tips they are used normally.
    * Hyb-tips have unions derived from the tetratree and leg index (0 or 1).
    * Unions are taken towards the root.
    *
    * For a hyb-tip, tettree specifies the index of the tetratree whose
    * root comes from it. It is not used for other nodes.
    */
    private class DipHistNode extends AlloppNode.Abstract implements AlloppNode, NodeRef {
        private int anc;
        private int lft;
        private int rgt;
        private double height;
        private Taxon taxon;
        private FixedBitSet union;
        private int tettree;
        private LegLorR leg;
        private int nodeNumber;

        // dud constuctor
        DipHistNode(int nn) {
            anc = -1;
            lft = -1;
            rgt = -1;
            height = -1.0;
            taxon = new Taxon("");
            union = null;
            tettree = -1;
            leg = LegLorR.dud;
            nodeNumber = nn;
        }

        // copy constructor
        public DipHistNode(DipHistNode node) {
            anc = node.anc;
            lft = node.lft;
            rgt = node.rgt;
            nodeNumber = node.nodeNumber;
            copyNonTopologyFields(node);
        }


        // no-topology constructor. Copies all fields of argument node,
        // except the anc, lft, rgt fields which are set to `unknown',
        // and the index field, which is set to nn
        public DipHistNode(int nn, DipHistNode node) {
            anc = -1;
            lft = -1;
            rgt = -1;
            nodeNumber = nn;
            copyNonTopologyFields(node);
        }

        private void copyNonTopologyFields(DipHistNode node) {
            height = node.height;
            taxon = new Taxon(node.taxon.getId());
            if (node.union == null) {
                union = null;
            } else {
                union = new FixedBitSet(node.union);
            }
            tettree = node.tettree;
            leg = node.leg;
        }


        @Override
        public AlloppNode getChild(int ch) {
            return ch==0 ? dhnodes[lft] : dhnodes[rgt];
        }

        @Override
        public AlloppNode getAnc() {
            return dhnodes[anc];
        }


        @Override
        public double getHeight() {
            return height;
        }

        @Override
        public Taxon getTaxon() {
            return taxon;
        }

        @Override
        public FixedBitSet getUnion() {
            return union;
        }

        @Override
        public void setChild(int ch, AlloppNode newchild) {
            int newch = ((DipHistNode)newchild).nodeNumber;
            if (ch == 0) {
                lft = newch;
            } else {
                rgt = newch;
            }
        }

        @Override
        public void setAnc(AlloppNode anc) {
            this.anc = ((DipHistNode)anc).nodeNumber;
        }

        @Override
        public void setTaxon(String name) {
            this.taxon = new Taxon(name);

        }


        @Override
        public void setHeight(double height) {
            this.height = height;

        }

        @Override
        public void setUnion(FixedBitSet union) {
            this.union = union;
        }

        @Override
        public void addChildren(AlloppNode c0, AlloppNode c1) {
            lft = ((DipHistNode)c0).nodeNumber;
            dhnodes[lft].anc = nodeNumber;
            rgt = ((DipHistNode)c1).nodeNumber;
            dhnodes[rgt].anc = nodeNumber;
        }

        @Override
        public String asText(int indentlen) {
            StringBuilder s = new StringBuilder();
            Formatter formatter = new Formatter(s, Locale.US);
            if (lft < 0) {
                String nodename;
                if (tettree >= 0) {
                    nodename = String.valueOf(tettree);
                    String legtext = "*";
                    if (leg == LegLorR.left) {
                        legtext = "L";
                    }
                    if (leg == LegLorR.right) {
                        legtext = "R";
                    }
                    nodename += legtext;
                } else {
                    nodename = taxon.getId();
                }
                formatter.format("%s ", nodename);
            } else {
                formatter.format("%s ", "+");
            }
            while (s.length() < 25-indentlen) {
                formatter.format("%s", " ");
            }
            formatter.format("%s ", AlloppMisc.nonnegIn8Chars(height));
            return s.toString();
        }


        @Override
        public int nofChildren() {
            return (lft < 0) ? 0 : 2;
        }

        @Override
        public int getNumber() {
            return nodeNumber;
        }

        @Override
        public void setNumber(int n) {
            nodeNumber = n;

        }



    }


    /******************************** Constructors ****************************************/



    private class JoiningNode {
        int nn;
        boolean hasdip;
        JoiningNode(int nn, boolean hasdip) {
            this.nn = nn;
            this.hasdip = hasdip;
        }
    }


    /*
      * Constructor makes initial random AlloppDiploidHistory from a diploid tree
      * and previously constructed tetratrees.
      * apsp needed for speciesseqEmptyUnion(). grjtodo-oneday really needed?
      *
      */
    AlloppDiploidHistory(Taxon [] dipspp, ArrayList<AlloppLeggedTree> tettrees,
                         boolean diploidrootisroot, double rate, AlloppSpeciesBindings apsp) {
        this.apsp = apsp;
        int nofdips = dipspp.length;
        int ntips = nofdips + 2 * tettrees.size();
        // Make array of dud nodes
        dhnodes = new DipHistNode[2 * ntips - 1];
        for (int i = 0; i < dhnodes.length; i++) {
            dhnodes[i] = new DipHistNode(i);
        }
        // Make tips for diploids and add them to tojoin[]
        ArrayList<JoiningNode> tojoin = new ArrayList<JoiningNode>();
        for (nextn = 0; nextn < nofdips; nextn++) {
            dhnodes[nextn].taxon = new Taxon(dipspp[nextn].getId());
            dhnodes[nextn].height = 0.0;
            tojoin.add(new JoiningNode(nextn, true));
        }
        // Make two tips for each tet tree, and add to tojoin[]
        for (int tt = 0; tt < tettrees.size(); tt++) {
            AlloppLeggedTree ttree = tettrees.get(tt);
            double tettipsheight = ttree.getRootHeight() + MathUtils.nextExponential(rate);
            dhnodes[nextn].tettree = tt;
            dhnodes[nextn].leg = LegLorR.left;
            ttree.setDiphistLftLeg(nextn);
            dhnodes[nextn].height = tettipsheight;
            tojoin.add(new JoiningNode(nextn, false));
            nextn++;
            dhnodes[nextn].tettree = tt;
            dhnodes[nextn].leg = LegLorR.right;
            ttree.setDiphistRgtLeg(nextn);
            dhnodes[nextn].height = tettipsheight;
            tojoin.add(new JoiningNode(nextn, false));
            nextn ++;
        }

        // build the tree from tips, taking care not to join the last diploids before all tets used up
        for (int i = 0; i < ntips-1; i++) {
            int numtojoin = tojoin.size();
            int numwithdips = 0;
            for (JoiningNode jn : tojoin) {
                numwithdips += jn.hasdip ? 1 : 0;
            }
            int j = MathUtils.nextInt(numtojoin);
            JoiningNode child0 = tojoin.get(j);
            // if diploidrootisroot, and down to 2 diploids, and still a tet, ensure ch0 has no dips
            if (diploidrootisroot  &&  numtojoin > 2  &&  numwithdips == 2) {
                while (child0.hasdip) {
                    j = MathUtils.nextInt(numtojoin);
                    child0 = tojoin.get(j);
                }
            }
            tojoin.remove(j);
            int k = MathUtils.nextInt(numtojoin-1);
            JoiningNode child1 = tojoin.get(k);
            tojoin.remove(k);
            dhnodes[nextn].lft = child0.nn;   dhnodes[child0.nn].anc = nextn;
            dhnodes[nextn].rgt = child1.nn;   dhnodes[child1.nn].anc = nextn;
            double maxchhgt = Math.max(dhnodes[child0.nn].height, dhnodes[child1.nn].height);
            dhnodes[nextn].height = maxchhgt + MathUtils.nextExponential(numtojoin*rate);
            tojoin.add(new JoiningNode(nextn, child0.hasdip || child1.hasdip));
            nextn++;
        }
        rootn = nextn - 1;
        assert diphistOK(diploidrootisroot);
        makesimpletree();
    }




    // copy constructor.
    public AlloppDiploidHistory(AlloppDiploidHistory x) {
        dhnodes = new DipHistNode[x.dhnodes.length];
        for (int n = 0; n < dhnodes.length; n++) {
            dhnodes[n] = new DipHistNode(x.dhnodes[n]);
        }
        rootn = x.rootn;
        nextn = x.nextn;
        apsp = x.apsp;
    }


    //  constructor for testing.
    public AlloppDiploidHistory(SimpleNode[] snodes, int sroot, ArrayList<AlloppLeggedTree> tettrees,
                                boolean diprootisroot, AlloppSpeciesBindings apsp) {
        this.apsp = apsp;
        // Make array of dud nodes
        dhnodes = new DipHistNode[snodes.length];
        for (int i = 0; i < dhnodes.length; i++) {
            dhnodes[i] = new DipHistNode(i);
        }
        // Convert snodes tree into dhnodes tree
        nextn = 0;
        simpletree2dhtesttree(snodes[sroot]);
        rootn = nextn - 1;

        // link the hyb-tips to tet roots.
        for (int n = 0; n < dhnodes.length; n++) {
            String tipname = dhnodes[n].taxon.getId();
            LegLorR leg = LegLorR.dud;
            int tt = -1;
            if (tipname.contains("L")) { leg = LegLorR.left; }
            if (tipname.contains("R")) { leg = LegLorR.right; }
            if (tipname.contains("0")) { tt = 0; }
            if (tipname.contains("1")) { tt = 1; }
            if (tipname.contains("2")) { tt = 2; }
            if (leg != LegLorR.dud) {
                assert tt >= 0;
                dhnodes[n].tettree = tt;
                dhnodes[n].leg = leg;
                if (leg == LegLorR.left) { tettrees.get(tt).setDiphistLftLeg(n); }
                if (leg == LegLorR.right) { tettrees.get(tt).setDiphistRgtLeg(n); }
            }
        }
        dhnodes[rootn].fillinUnionsInSubtree(apsp.numberOfSpSeqs());
        assert diphistOK(diprootisroot);
        makesimpletree();
    }



    /******************************** SlidableTree implementation ****************************************/


    @Override
    public NodeRef getSlidableRoot() {
        assert dhnodes[rootn].anc < 0;
        return dhnodes[rootn];
    }


    @Override
    public void replaceSlidableRoot(NodeRef root) {
        rootn = root.getNumber();
        dhnodes[rootn].anc = -1;
    }


    @Override
    public int getSlidableNodeCount() {
        return dhnodes.length;
    }


    @Override
    public double getSlidableNodeHeight(NodeRef node) {
        return dhnodes[node.getNumber()].getHeight();
    }

    @Override
    public Taxon getSlidableNodeTaxon(NodeRef node) {
        return dhnodes[node.getNumber()].getTaxon();
    }

    @Override
    public void setSlidableNodeHeight(NodeRef node, double height) {
        dhnodes[node.getNumber()].height = height;
    }


    @Override
    public boolean isExternalSlidable(NodeRef node) {
        return (dhnodes[node.getNumber()].lft < 0);
    }



    @Override
    public NodeRef getSlidableChild(NodeRef node, int j) {
        int n = node.getNumber();
        return j == 0 ? dhnodes[ dhnodes[n].lft ] : dhnodes[ dhnodes[n].rgt ];
    }



    @Override
    public void replaceSlidableChildren(NodeRef node, NodeRef lft, NodeRef rgt) {
        int nn = node.getNumber();
        int lftn = lft.getNumber();
        int rgtn = rgt.getNumber();
        assert dhnodes[nn].lft >= 0;
        dhnodes[nn].lft = lftn;
        dhnodes[nn].rgt = rgtn;
        dhnodes[lftn].anc = dhnodes[nn].nodeNumber;
        dhnodes[rgtn].anc = dhnodes[nn].nodeNumber;
    }



    String asText() {
        String header = "Diploid history            height" + System.getProperty("line.separator");
        String s = "";
        Stack<Integer> x = new Stack<Integer>();
        return header + AlloppNode.Abstract.subtreeAsText(dhnodes[rootn], s, x, 0, "");
    }


    int getInternalNodeCount() {
        return (dhnodes.length - 1) / 2;
    }


    int getDiploidTipCount() {
        int ndiptips = 0;
        for (int i = 0; i < dhnodes.length; i++) {
            if (dhnodes[i].lft < 0  &&  dhnodes[i].tettree < 0) {
                ndiptips++;
            }
        }
        return ndiptips;
    }


    public ArrayList<Integer> collectFeet() {
        ArrayList<Integer> feet = new ArrayList<Integer>();
        for (int i = 0; i < dhnodes.length; i++) {
            if (dhnodes[i].tettree >= 0) {
                feet.add(i);
            }
        }
        return feet;
    }


    public boolean tipIsDiploidTip(NodeRef node) {
        assert dhnodes[node.getNumber()].lft < 0;
        return (dhnodes[node.getNumber()].tettree < 0);
    }

    // For move that merges two tettrees. This is part of
    // test that they can be merged.
    public boolean tettreesShareLegs(AlloppLeggedTree ttree1, AlloppLeggedTree ttree2) {
        int lftleg1 = ttree1.getDiphistLftLeg();
        int lftleg1anc = dhnodes[lftleg1].anc;
        int lftleg2 = ttree2.getDiphistLftLeg();
        int lftleg2anc = dhnodes[lftleg2].anc;
        int rgtleg1 = ttree1.getDiphistRgtLeg();
        int rgtleg1anc = dhnodes[rgtleg1].anc;
        int rgtleg2 = ttree2.getDiphistRgtLeg();
        int rgtleg2anc = dhnodes[rgtleg2].anc;
        boolean llrr = ((lftleg1anc == lftleg2anc)  &&  (rgtleg1anc == rgtleg2anc));
        return llrr;
        /*
        boolean lrrl = ((lftleg1anc == rgtleg2anc)  &&  (rgtleg1anc == lftleg2anc));
        return  (llrr || lrrl);  */
    }


    // For (old?) move that merges two tettrees. This is for Hastings ratio calculation.
    public double intervalOfFoot(AlloppLeggedTree ttree, boolean left) {
        double hybh = getHybHeight(ttree);
        int foot = left ? ttree.getDiphistLftLeg() : ttree.getDiphistRgtLeg();
        int footanc = dhnodes[foot].anc;
        assert footanc >= 0;
        int footancanc = dhnodes[footanc].anc;
        assert footancanc >= 0;
        return (dhnodes[footancanc].height  -  hybh);
    }


    // For move that merges two tettrees. This is for Hastings ratio calculation.
    public FootAncHeights intervalOfFootAncestor(AlloppLeggedTree ttree, LegLorR leg) {
        double hybh = getHybHeight(ttree);
        int foot = (leg==LegLorR.left) ? ttree.getDiphistLftLeg() : ttree.getDiphistRgtLeg();
        assert hybh == dhnodes[foot].height;
        int footanc = dhnodes[foot].anc;
        int footancanc = dhnodes[footanc].anc;
        assert footancanc >= 0;
        return new FootAncHeights(dhnodes[footanc].height, dhnodes[footancanc].height);
    }



    public void setHybridHeight(AlloppLeggedTree ttree, double newh) {
        int foot1 = ttree.getDiphistLftLeg();
        int foot2 = ttree.getDiphistRgtLeg();
        dhnodes[foot1].height = dhnodes[foot2].height = newh;
    }



    // For move that merges two tettrees. This is for after merge.
    // It removes two tips that were joined to ttree
    public void removeFeet(AlloppSpeciesNetworkModel apspnet, AlloppLeggedTree ttree) {
        DipHistNode [] tmpnodes  = new DipHistNode[dhnodes.length];
        for (int i = 0;  i < tmpnodes.length; i++) {
            tmpnodes[i] = new DipHistNode(dhnodes[i]);
        }
        removeTip(ttree.getDiphistLftLeg(), tmpnodes);
        removeTip(ttree.getDiphistRgtLeg(), tmpnodes);
        dhnodes = new DipHistNode[tmpnodes.length - 4];
        nextn = 0;
        buildSubtreeFromNodes(apspnet, tmpnodes, rootn);
        rootn = nextn - 1;
    }


    // Adds two hyb-tips for tettree tt1. these are joined to legs of tettree tt2
    public void addTwoDipTips(AlloppSpeciesNetworkModel apspnet, int tt1, int tt2, double lfthgt, double rgthgt, double tiphgt) {
        AlloppLeggedTree tettree1 = apspnet.getTetraploidTree(tt1);
        AlloppLeggedTree tettree2 = apspnet.getTetraploidTree(tt2);
        int lftleg = tettree2.getDiphistLftLeg();
        int rgtleg = tettree2.getDiphistRgtLeg();
        int oldn = dhnodes.length;
        DipHistNode [] tmpnodes = new DipHistNode[oldn + 4];
        for (int n = 0; n < oldn; n++) {
            tmpnodes[n] = new DipHistNode(dhnodes[n]);
        }

        // two new diploid tips for tettree tt1
        tmpnodes[oldn] = new DipHistNode(oldn);
        tmpnodes[oldn].height = tiphgt;
        tmpnodes[oldn].tettree = tt1;
        tmpnodes[oldn].leg = LegLorR.left;
        tettree1.setDiphistLftLeg(oldn);

        tmpnodes[oldn+1] = new DipHistNode(oldn+1);
        tmpnodes[oldn+1].height = tiphgt;
        tmpnodes[oldn+1].tettree = tt1;
        tmpnodes[oldn+1].leg = LegLorR.right;
        tettree1.setDiphistRgtLeg(oldn+1);

        // two new nodes to go into existing branches
        tmpnodes[oldn+2] = new DipHistNode(oldn+2);
        tmpnodes[oldn+2].height = lfthgt;
        tmpnodes[oldn+2].anc = tmpnodes[lftleg].anc;
        tmpnodes[oldn+2].lft = oldn;
        tmpnodes[oldn+2].rgt = lftleg;

        tmpnodes[oldn+3] = new DipHistNode(oldn+3);
        tmpnodes[oldn+3].height = rgthgt;
        tmpnodes[oldn+3].anc = tmpnodes[rgtleg].anc;
        tmpnodes[oldn+3].lft = oldn+1;
        tmpnodes[oldn+3].rgt = rgtleg;

        // divide branch by pointing to new nodes oldn+2,+3
        int lftfootanc = tmpnodes[lftleg].anc;
        if (tmpnodes[lftfootanc].lft == lftleg) {
            tmpnodes[lftfootanc].lft = oldn+2;
        } else {
            assert tmpnodes[lftfootanc].rgt == lftleg;
            tmpnodes[lftfootanc].rgt = oldn+2;
        }

        int rgtfootanc = tmpnodes[rgtleg].anc;
        if (tmpnodes[rgtfootanc].lft == rgtleg) {
            tmpnodes[rgtfootanc].lft = oldn+3;
        } else {
            assert tmpnodes[rgtfootanc].rgt == rgtleg;
            tmpnodes[rgtfootanc].rgt = oldn+3;
        }

        dhnodes = new DipHistNode[tmpnodes.length];
        nextn = 0;
        buildSubtreeFromNodes(apspnet, tmpnodes, rootn);
        rootn = nextn - 1;
    }


    // For move that splits a tetree
    public double getAncHeight(int n) {
        int nanc = dhnodes[n].anc;
        assert nanc >= 0;
        return dhnodes[nanc].height;
    }


    // next bunch for AlloppMulLabTree constructor
    int getRootIndex() {
        return rootn;
    }

    double getHeightFromIndex(int n) {
        return dhnodes[n].height;
    }

    int getLftFromIndex(int n) {
        return dhnodes[n].lft;
    }
    int getRgtFromIndex(int n) {
        return dhnodes[n].rgt;
    }
    Taxon getTaxonFromIndex(int n) {
        return dhnodes[n].taxon;
    }


    double getRootHeight() {
        return dhnodes[rootn].height;
    }


    public double getHybHeight(AlloppLeggedTree tettree) {
        return dhnodes[tettree.getDiphistLftLeg()].height;
    }


    // for prior lhood
    void collectInternalAndHybHeights(ArrayList<Double> heights) {
        for (DipHistNode node : dhnodes) {
            if (node.lft >= 0) {
                heights.add(node.height);
            } else {
                if (node.tettree >= 0  &&  node.leg == LegLorR.left) {
                    heights.add(node.height);
                }
            }
        }
    }


    public void moveHybridHeight(int foot1, int foot2, double minh) {
        int f1anc = dhnodes[foot1].anc;
        int f2anc = dhnodes[foot2].anc;
        double maxh = Math.min(dhnodes[f1anc].height, dhnodes[f2anc].height);
        double oldh = dhnodes[foot1].height;
        double newh = AlloppMisc.uniformInRange(oldh, minh, maxh, 0.3);
        dhnodes[foot1].height = dhnodes[foot2].height = newh;
    }


    int scaleAllHeights(double scale) {
        int nofnodes = dhnodes.length;
        int count = 0;
        for (int n = 0; n < nofnodes; n++) {
            if (dhnodes[n].lft >= 0 || dhnodes[n].tettree >= 0)   {
                dhnodes[n].height *= scale;
                count++;
            }
        }
        return count;
    }


    // for assert tests during merging tetrees in move
    public void clearAllNodeTettree() {
        for (int n = 0; n < dhnodes.length; ++n) {
            dhnodes[n].tettree = -1;
        }
    }

    // for moving hyb time, and for assert tests during merging tetrees in move, and
    public int getNodeTettree(int node) {
        return dhnodes[node].tettree;
    }

    LegLorR getNodeLeg(int node) {
        return dhnodes[node].leg;
    }

    // for move that flips all seqs of tet tree and its legs
    void setNodeLeg(int nn, LegLorR leg) {
        dhnodes[nn].leg = leg;
    }


    // for merging tetrees in move
    public void setNodeTettree(int node, int tt) {
        dhnodes[node].tettree = tt;
    }



    /*
      * For testing
      */
    public boolean diphistOK(boolean diprootisroot) {
        int nroots = 0;
        for (int i = 0; i < dhnodes.length; i++) {
            if (dhnodes[i].anc < 0) {
                nroots++;
            }
        }
        if (nroots != 1) {
            return false;
        }
        for (int i = 0; i < dhnodes.length; i++) {
            int nparents = 0;
            for (int j = 0; j < dhnodes.length; j++) {
                if (dhnodes[j].lft == i) { nparents++; }
                if (dhnodes[j].rgt == i) { nparents++; }
            }
            if (dhnodes[i].anc < 0  &&  nparents != 0) {
                return false;
            }
            if (dhnodes[i].anc >= 0  &&  nparents != 1) {
                return false;
            }
        }

        for (int i = 0; i < dhnodes.length; i++) {
            if (dhnodes[i].getNumber() != i) {
                return false;
            }
        }

        for (int i = 0; i < dhnodes.length; i++) {
            if (dhnodes[i].lft >= 0) {
                if (dhnodes[i].rgt < 0) {
                    return false;
                }
                int lft = dhnodes[i].lft;
                int rgt = dhnodes[i].rgt;
                if (dhnodes[lft].anc != i) {
                    return false;
                }
                if (dhnodes[rgt].anc != i) {
                    return false;
                }
                if (dhnodes[i].height <= dhnodes[lft].height) {
                    return false;
                }
                if (dhnodes[i].height <= dhnodes[rgt].height) {
                    return false;
                }
                if (dhnodes[i].tettree >= 0) {
                    return false;
                }
            } else {
                if (dhnodes[i].tettree >= 0) {
                    if (dhnodes[i].height <= 0) {
                        return false;
                    }
                    if (dhnodes[i].leg != LegLorR.left  &&  dhnodes[i].leg != LegLorR.right) {
                        return false;
                    }
                } else {
                    if (dhnodes[i].height != 0) {
                        return false;
                    }
                }
            }
        }
        if (dhnodes[rootn].anc >= 0) {
            return false;
        }
        ArrayList<Integer> feet = collectFeet();
        for (Integer f1 : feet) {
            for (Integer f2 : feet) {
                if (dhnodes[f1].tettree == dhnodes[f2].tettree) {
                    if (dhnodes[f1].height != dhnodes[f2].height) {
                        return false;
                    }
                }
            }
        }
        if (diprootisroot) {
            if (!gotDipTipInSubtree(dhnodes[rootn].lft)  ||  !gotDipTipInSubtree(dhnodes[rootn].rgt)) {
                return false;
            }
        }


        return true;
    }


    private boolean gotDipTipInSubtree(int nn) {
        if (dhnodes[nn].lft < 0) {
            return (dhnodes[nn].tettree < 0);
        } else {
            return gotDipTipInSubtree(dhnodes[nn].lft)  ||  gotDipTipInSubtree(dhnodes[nn].rgt);
        }
    }



    /*
      * **************************************************************************
      *                      PRIVATE methods
      * **************************************************************************
      */





    /*
      * This builds a new tree in dhnodes[] from the one in tmpnodes.
      * The reason for not just copying the array is that the order of
      * nodes in tmpnodes[] is not postorder. (One could live with the
      * nodes in any order, but it would complicate things elsewhere.)
      */
    private void buildSubtreeFromNodes(AlloppSpeciesNetworkModel apspnet,
                                       DipHistNode[] tmpnodes, int n) {
        if (tmpnodes[n].lft < 0) {
            assert tmpnodes[n].rgt < 0;
            dhnodes[nextn] = new DipHistNode(nextn, tmpnodes[n]);
            int tt = dhnodes[nextn].tettree;
            LegLorR leg = dhnodes[nextn].leg;
            AlloppLeggedTree ttree;
            if (tt >= 0) {
                ttree = apspnet.getTetraploidTree(tt);
                if (ttree.getDiphistLftLeg() == n) {
                    if (leg == LegLorR.left) {
                        ttree.setDiphistLftLeg(nextn);
                    } else {
                        assert leg == LegLorR.right;
                        ttree.setDiphistRgtLeg(nextn);
                    }
                } else {
                    assert apspnet.getTetraploidTree(tt).getDiphistRgtLeg() == n;
                    if (leg == LegLorR.left) {
                        ttree.setDiphistLftLeg(nextn);
                    } else {
                        assert leg == LegLorR.right;
                        ttree.setDiphistRgtLeg(nextn);
                    }
                }
            }
            nextn ++;
        } else {
            assert tmpnodes[n].rgt >= 0;
            buildSubtreeFromNodes(apspnet, tmpnodes, tmpnodes[n].lft);
            int lft = nextn - 1;
            buildSubtreeFromNodes(apspnet, tmpnodes, tmpnodes[n].rgt);
            int rgt = nextn - 1;
            dhnodes[nextn] = new DipHistNode(nextn, tmpnodes[n]);
            dhnodes[nextn].lft = lft;
            dhnodes[lft].anc = nextn;
            dhnodes[nextn].rgt = rgt;
            dhnodes[rgt].anc = nextn;
            nextn ++;
        }
    }



    private void removeTip(int leg, DipHistNode[] tmpnodes) {
        int legsibling;
        int leganc = tmpnodes[leg].anc;
        assert leganc >= 0;
        if (tmpnodes[leganc].lft == leg) {
            legsibling = tmpnodes[leganc].rgt;
        } else {
            assert tmpnodes[leganc].rgt == leg;
            legsibling = tmpnodes[leganc].lft;
        }
        int legancanc = tmpnodes[leganc].anc;
        assert legancanc >= 0;
        if (tmpnodes[legancanc].lft == leganc) {
            tmpnodes[legancanc].lft = legsibling;
        } else {
            assert tmpnodes[legancanc].rgt == leganc;
            tmpnodes[legancanc].rgt = legsibling;
        }
    }

    // for testing
    private SimpleTree makesimpletree() {
        SimpleNode[] snodes = new SimpleNode[dhnodes.length];
        for (int n = 0; n < dhnodes.length; n++) {
            snodes[n] = new SimpleNode();
            snodes[n].setTaxon(null); // I use taxon==null to identify joined leg node when removing hybtips
        }
        makesimplesubtree(snodes, 0, dhnodes[rootn]);
        return new SimpleTree(snodes[dhnodes.length-1]);
    }


    // for testing. for makesimpletree()
    private int makesimplesubtree(SimpleNode[] snodes, int nextsn, DipHistNode dhnode) {
        if (dhnode.lft < 0) {
            Taxon tx = new Taxon(dhnode.taxon.getId());
            snodes[nextsn].setTaxon(tx);
            if (dhnode.tettree >= 0) {
                snodes[nextsn].setAttribute("tettree", dhnode.tettree);
                snodes[nextsn].setAttribute("leg", dhnode.leg);
            }
        } else {
            nextsn = makesimplesubtree(snodes, nextsn, dhnodes[dhnode.lft]);
            int subtree0 = nextsn-1;
            nextsn = makesimplesubtree(snodes, nextsn, dhnodes[dhnode.rgt]);
            int subtree1 = nextsn-1;
            snodes[nextsn].addChild(snodes[subtree0]);
            snodes[nextsn].addChild(snodes[subtree1]);
        }
        snodes[nextsn].setHeight(dhnode.height);
        return nextsn+1;
    }


    // for testing
    private void simpletree2dhtesttree(SimpleNode snode) {
        if (snode.getChildCount() == 2) {
            simpletree2dhtesttree(snode.getChild(0));
            int lft = nextn - 1;
            simpletree2dhtesttree(snode.getChild(1));
            int rgt = nextn - 1;
            dhnodes[nextn].lft = lft; dhnodes[lft].anc = nextn;
            dhnodes[nextn].rgt = rgt; dhnodes[rgt].anc = nextn;
        }
        dhnodes[nextn].height = snode.getHeight();
        dhnodes[nextn].taxon = new Taxon(snode.getTaxon().getId());
        dhnodes[nextn].union = apsp.speciesseqEmptyUnion();
        nextn++;
    }









}
