/*
 * AlloppSpeciesBindings.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.Arrays;
import java.util.Formatter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import jebl.util.FixedBitSet;

import dr.evolution.tree.NodeRef;
import dr.evolution.util.Taxon;
import dr.evomodel.tree.TreeModel;
import dr.evomodelxml.speciation.AlloppSpeciesBindingsParser;
import dr.inference.loggers.LogColumn;
import dr.inference.loggers.Loggable;
import dr.inference.model.AbstractModel;
import dr.inference.model.Model;
import dr.inference.model.ModelListener;
import dr.inference.model.Variable;
import dr.inference.model.Variable.ChangeType;
import dr.math.MathUtils;
import dr.util.AlloppMisc;

/**
 *
 * Knows how species (diploid and allopolyploid species) are made of individuals
 * and individuals are made of taxa (=sequences).
 *
 * @author Graham Jones
 *         Date: 30/04/2011
 */



/*
 *
 * AlloppSpeciesBindings knows how species are made of individuals
 * and how individuals are made of taxa (= diploid genomes within individuals).
 *
 * It also contains the list of gene trees - tree topologies and node
 * times, plus popfactors. Given a AlloppSpeciesNetworkModel
 * it can say if a gene tree is compatible.
 *
 * It is here that assignments of sequence copies within individuals
 * get permuted.
 *
 * The class GeneUnionTree, defined here, is used during calculations.
 *
 * ******************************
 *
 * geneTreeInfos. The array of gene trees. Each one has an array of
 * sequence assignments.
 *
 * apspecies. List of species containing Individuals
 *
 * indivs. A 'flattened' array of all Individuals from all species
 *
 * taxa. A 'flattened' array of all Taxons from all Individuals
 *
 * taxon2index, apspecies2index: maps to indices
 *
 * spsq used to map (species index, sequence index) to a single index.
 *
 * Eg of two individuals from a diploid "a" and two individuals from a tetraploid "b"
 * apspecies: a(01,02), b(03,04)
 * indivs:    01_a(0), 02_a(0), 03_b(0,1), 04_b(0,1)
 * taxa:      01_a0, 02_a0, 03_b0, 03_b1, 04_b0, 04_b1
 *
 */
public class AlloppSpeciesBindings extends AbstractModel implements Loggable {

    private final GeneTreeInfo[] geneTreeInfos;

    private final ApSpInfo[] apspecies;
    private final Taxon[] taxa;
    private final Map<Taxon, Integer> taxon2index = new HashMap<Taxon, Integer>();
    private final int spsq[][];
    private final int numberOfSpSeqs;
    private final double initialmingenenodeheight; // for starting network



    /* ************  subclasses ********************/

    /* class AlloppSpeciesBindings.Individual
     *
     * Individual is a list of Taxons. One for diploid individual, two  for
     * tetraploid, etc.
     */
    public static class Individual extends Taxon {
        final public String id; // individual ID, such as "02_Alpha" in AlloppSpeciesInfoParser XML example
        public final Taxon[] taxa;

        public Individual(String id, Taxon[] taxa) {
            super(id);
            this.id = id;
            this.taxa = taxa;
        }
    }


    private class SpeciesIndivPair {
        public int spIndex;
        public int ivIndex;

        public SpeciesIndivPair(int spIndex, int ivIndex) {
            this.spIndex = spIndex;
            this.ivIndex = ivIndex;
        }
    }


    /* class AlloppSpeciesBindings.ApSpInfo
     *
     * Information on one allopolyploid species
     *
     * name is species name, such as "Alpha" in AlloppSpeciesInfoParser XML example
     *
     * ploidylevel; 2 means diploid, 4 means allotetraploid, etc
     *
     * individuals[]
     */
    public static class ApSpInfo extends Taxon {

        final public String name;
        final public int ploidylevel; // 2 means diploid, 4 means allotetraploid, etc
        final Individual[] individuals;

        public ApSpInfo(String name, int ploidylevel, Individual[] individuals) {
            super(name);
            this.name = name;
            this.individuals = individuals;
            this.ploidylevel = ploidylevel;

            // check
            if (individuals != null) {
                int ntaxaperindiv = ploidylevel / 2;
                for (int ii = 0; ii < individuals.length; ++ii) {
                    assert(individuals[ii].taxa.length == ntaxaperindiv);
                    // may want to allow 3 as well as 2 for tetraploid for organelle DNA
                }
            }
        }


        public Taxon taxonFromIndSeq(int i, int sq) {
            return individuals[i].taxa[sq];
        }
    }


    /* class AlloppSpeciesBindings.GeneTreeInfo
     *
     * Adapted from SpeciesBindings
     *
     * tree is the gene tree
     *
     * seqassigns[]  is species index and sequence index. The latter
     * is what MCMC acts on when permute sequence assignments
     *
     * lineagesCount[] is count for each species, and applies to one or
     * more tips in MulLabTree. I assume this is the same for all genes
     * though the info is stored per gene tree
     *
     * popFactor (normal diploid=2, X chromosome=1.5, chloroplast=.5, etc)
     *
     * I don't like the CoalInfo method of determining compatibility.
     * Instead I copy the gene tree topology and heights into a new
     *  tree with unions in nodes. Maybe I'll find out why JH used CoalInfos...
     */
    private class GeneTreeInfo {
        private final TreeModel tree;
        private SequenceAssignment seqassigns[];
        private SequenceAssignment oldseqassigns[];
        private final int[] lineagesCount;
        private final double popFactor; // grjtodo-oneday will mul pops by this, eg for chloroplast data.


        /* class GeneTreeInfo.SequenceAssignments
         *
         * spIndex is an index for an allopolyploid species. For example, it identifies
         * a bit in a FixedBitSet (union) in a MulLabTree
         *
         * seqIndex identifies a sequence copy for this gene and for each individual.
         * seqIndex is 0 or 1 for tetraploids and it is these that get flipped to change
         * assignments of sequence copies to legs in AlloppSpeciesNetworkModel (or
         * equivalently to tips in a MulLabTree).
         *
         * 2011-06-23 spIndex is the same for all gene trees. Maybe
         * allow non-rectangular data later.
         */
        private class SequenceAssignment {
            public int spIndex;
            public int seqIndex;

            public SequenceAssignment(int spIndex, int seqIndex) {
                this.spIndex = spIndex;
                this.seqIndex = seqIndex;
            }

            public String toString() {
                String s = "" + seqIndex;
                return s;
            }
        }




        /* class GeneTreeInfo.GeneUnionNode
         *
         * Node for GeneTreeInfo.GeneUnionTree.
         */
        private class GeneUnionNode {
            private GeneUnionNode child[];
            private double height;
            private FixedBitSet union;
            private String name; // for debugging

            // Constructor makes a half-formed tip node. Tips need unions
            // and internal nodes need all fields filling in.
            public GeneUnionNode() {
                child = new GeneUnionNode[0];
                height = 0.0;
                union = new FixedBitSet(numberOfSpSeqs());
                name = "";
            }



            public String asText(int indentlen) {
                StringBuilder s = new StringBuilder();
                Formatter formatter = new Formatter(s, Locale.US);
                if (child.length == 0) {
                    formatter.format("%s ", name);
                } else {
                    formatter.format("%s ", "+");
                }
                while (s.length() < 30-indentlen) {
                    formatter.format("%s", " ");
                }
                formatter.format("%s ", AlloppMisc.nonnegIn8Chars(height));
                formatter.format("%20s ", AlloppMisc.FixedBitSetasText(union));

                return s.toString();
            }

        }

        /* class GeneTreeInfo.GeneUnionTree
         *
         * Copy of gene tree topology and heights with unions,
         * implemented as an array of GeneUnionNodes. This is
         * used during calculations fitsInNetwork(), treeLogLikelihood()
         * for one gene tree at a time, then discarded.
         */
        private class GeneUnionTree {
            private GeneUnionNode[] nodes;
            private int nextn;


            public GeneUnionTree() {
                nodes = new GeneUnionNode[tree.getNodeCount()];
                for (int i = 0; i < nodes.length; i++) {
                    nodes[i] = new GeneUnionNode();
                }
                genetree2geneuniontree(tree.getRoot());
            }


            public GeneUnionNode getRoot() {
                return nodes[nodes.length-1];
            }



            private boolean subtreeFitsInNetwork(GeneUnionNode node,
                                                 final AlloppSpeciesNetworkModel asnm) {
                for (int i = 0; i < node.child.length; i++) {
                    if (!subtreeFitsInNetwork(node.child[i], asnm)) {
                        return false;
                    }
                }
                return asnm.coalescenceIsCompatible(node.height, node.union);
            }


            private void subtreeRecordCoalescences(GeneUnionNode node,
                                                   final AlloppSpeciesNetworkModel asnm) {
                for (int i = 0; i < node.child.length; i++) {
                    subtreeRecordCoalescences(node.child[i], asnm);
                }
                if (node.child.length > 0) {
                    asnm.recordCoalescence(node.height, node.union);
                }
            }


            /*
            * Recursively copies the topology from subtree rooted at node into
            * GeneUnionTree implemented as array nodes[].
            * Fills in union fields.
            *
            */
            private void genetree2geneuniontree(NodeRef gnode) {
                if (tree.isExternal(gnode)) {
                    nodes[nextn].child = new GeneUnionNode[0];
                    int ti = taxon2index.get(tree.getNodeTaxon(gnode));
                    int spseq = spsq[seqassigns[ti].spIndex][seqassigns[ti].seqIndex];
                    nodes[nextn].union.set(spseq);
                    nodes[nextn].name = tree.getNodeTaxon(gnode).getId();
                } else {
                    genetree2geneuniontree(tree.getChild(gnode,0));
                    int c0 = nextn - 1;
                    genetree2geneuniontree(tree.getChild(gnode,1));
                    int c1 = nextn - 1;
                    nodes[nextn].child = new GeneUnionNode[2];
                    nodes[nextn].child[0] = nodes[c0];
                    nodes[nextn].child[1] = nodes[c1];
                    nodes[nextn].union.union(nodes[c0].union);
                    nodes[nextn].union.union(nodes[c1].union);
                }
                nodes[nextn].height = tree.getNodeHeight(gnode);
                nextn++;
            }





            public String asText() {
                String s = "";
                Stack<Integer> x = new Stack<Integer>();
                return subtreeAsText(getRoot(), s, x, 0, "");
            }


            private String subtreeAsText(GeneUnionNode node, String s, Stack<Integer> x, int depth, String b) {
                Integer[] y = x.toArray(new Integer[x.size()]);
                StringBuffer indent = new StringBuffer();
                for (int i = 0; i < depth; i++) {
                    indent.append("  ");
                }
                for (int i = 0; i < y.length; i++) {
                    indent.replace(2*y[i], 2*y[i]+1, "|");
                }
                if (b.length() > 0) {
                    indent.replace(indent.length()-b.length(), indent.length(), b);
                }
                s += indent;
                s += node.asText(indent.length());
                s += System.getProperty("line.separator");
                String subs = "";
                if (node.child.length > 0) {
                    x.push(depth);
                    subs += subtreeAsText(node.child[0], "", x, depth+1, "-");
                    x.pop();
                    subs += subtreeAsText(node.child[1], "", x, depth+1, "`-");
                }
                return s + subs;
            }

        } // end GeneTreeInfo.GeneUnionTree


        /*
         * GeneTreeInfo constructor
         *
         * JH's SpeciesBindings code has test
         *   if (tree.getTaxonIndex(t) >= 0) { add taxon to count }
         *
         * I am not clear about what happens if some gene trees don't have all taxa.
         * I insist all gene trees have all taxa, and use missing data
         * grjtodo-oneday make more efficient
         */
        GeneTreeInfo(TreeModel tree, double popFactor, boolean permuteSequenceAssignments) {
            this.tree = tree;
            this.popFactor = popFactor;
            seqassigns = new SequenceAssignment[taxa.length];
            oldseqassigns = new SequenceAssignment[taxa.length];

            // This uses taxa list for *all* gene trees, not this gene tree.
            for (int s = 0; s < apspecies.length; s++) {
                for (int i = 0; i < apspecies[s].individuals.length; i++) {
                    int nseqs = apspecies[s].individuals[i].taxa.length;
                    int asgns[] = new int [nseqs];
                    for (int x = 0; x < nseqs; x++) {
                        asgns[x] = x;
                    }
                    if (permuteSequenceAssignments) { MathUtils.permute(asgns); }
                    for (int x = 0; x < nseqs; x++) {
                        int t = taxon2index.get(apspecies[s].individuals[i].taxa[x]);
                        seqassigns[t] = new SequenceAssignment(s, asgns[x]);
                        oldseqassigns[t] = new SequenceAssignment(s, asgns[x]);
                    }
                }
            }

            lineagesCount = new int[apspecies.length];
            Arrays.fill(lineagesCount, 0);

            for (int nl = 0; nl < lineagesCount.length; ++nl) {
                for (Individual indiv : apspecies[nl].individuals) {
                    boolean got = false;
                    for (Taxon t : indiv.taxa) {
                        if (tree.getTaxonIndex(t) >= 0) {
                            got = true;
                        }
                    }
                    for (Taxon t : indiv.taxa) {
                        assert (tree.getTaxonIndex(t) >= 0) == got;
                    }
                    assert got;
                    if (got) {
                        ++lineagesCount[nl];
                    }
                }
            }
        }


        public String seqassignsAsText() {
            String s = "Sequence assignments" + System.getProperty("line.separator");
            for (int tx = 0; tx < seqassigns.length; tx++) {
                s += taxa[tx];
                s += ":";
                s += seqassigns[tx].seqIndex;
                if (tx+1 < seqassigns.length  &&  seqassigns[tx].spIndex != seqassigns[tx+1].spIndex) {
                    s += System.getProperty("line.separator");
                } else {
                    s += "  ";
                }
            }
            return s;
        }



        public String genetreeAsText() {
            GeneUnionTree gutree = new GeneUnionTree();
            return gutree.asText();
        }


        public boolean fitsInNetwork(final AlloppSpeciesNetworkModel asnm) {
            GeneUnionTree gutree = new GeneUnionTree();
            boolean fits = gutree.subtreeFitsInNetwork(gutree.getRoot(), asnm);
            if (AlloppSpeciesNetworkModel.DBUGTUNE) {
                if (!fits) {
                    System.err.println("INCOMPATIBLE");
                    System.err.println(seqassignsAsText());
                    System.err.println(gutree.asText());
                    System.err.println(asnm.mullabTreeAsText());
                }
            }
            return fits;
        }


        // returns log(P(g_i|S)) = probability that gene tree fits into species network
        public double treeLogLikelihood(final AlloppSpeciesNetworkModel asnm) {
            GeneUnionTree gutree = new GeneUnionTree();
            asnm.clearCoalescences();
            gutree.subtreeRecordCoalescences(gutree.getRoot(), asnm);
            asnm.sortCoalescences();
            asnm.recordLineageCounts();
            double llhood = asnm.geneTreeInNetworkLogLikelihood();
            if (AlloppSpeciesNetworkModel.DBUGTUNE) {
                System.err.println("COMPATIBLE: log-likelihood = " + llhood);
                System.err.println(seqassignsAsText());
                System.err.println(gutree.asText());
                System.err.println(asnm.mullabTreeAsText());
            }
            return llhood;
        }


        public void storeSequenceAssignments() {
            for (int i = 0; i < seqassigns.length; i++) {
                oldseqassigns[i].seqIndex = seqassigns[i].seqIndex;
            }
        }

        public void restoreSequenceAssignments() {
            for (int i = 0; i < seqassigns.length; i++) {
                seqassigns[i].seqIndex = oldseqassigns[i].seqIndex;
            }
        }




        public double spseqUpperBound(FixedBitSet spsq0, FixedBitSet spsq1) {
            GeneUnionTree gutree = new GeneUnionTree();
            return subtreeSpseqUpperBound(gutree.getRoot(), spsq0, spsq1, Double.MAX_VALUE);
        }



        public void permuteOneSpeciesOneIndiv() {
            int sp = MathUtils.nextInt(apspecies.length);
            int iv = MathUtils.nextInt(apspecies[sp].individuals.length);
            flipOneAssignment(sp, iv);
        }


        /* grjtodo-oneday
         * This is a bit odd. It collects individuals as (sp, iv) indices
         * that `belong' to a node in the sense that any taxon (sequence)
         * of an individual belongs to the clade of the node.
         * I've used a set but not made SpeciesIndivPair's comparable
         * so that if both sequences of an individual occurs in clade it appears
         * twice. Then flipOneAssignment() flips everything so that those
         * occurring twice get flipped twice and so not changed.
         *
         * Result is that individuals with one but not two sequences in
         * the clade of the node get flipped. Sometimes all individuals
         * are flipped, sometimes none, sometimes just one, the last is the
         * same as permuteOneSpeciesOneIndiv().
         *
         * 2011-07-29 it appears to work OK on minimal testing and I
         * don't have a good idea for a more rational or efficient version.
         *
         */
        public void permuteSetOfIndivs() {
            int num = tree.getInternalNodeCount();
            int i = MathUtils.nextInt(num);
            NodeRef node = tree.getInternalNode(i);
            Set<SpeciesIndivPair> spivs = new HashSet<SpeciesIndivPair>();
            collectIndivsOfNode(node, spivs);
            for (SpeciesIndivPair spiv : spivs) {
                flipOneAssignment(spiv.spIndex, spiv.ivIndex);
            }
        }




        public SequenceAssignment getSeqassigns(int tx) {
            return seqassigns[tx];
        }


        // called when a gene tree has changed, which affects likelihood.
        // 2011-08-12 I am not using dirty flags (yet). I return
        // false from getLikelihoodKnown() in AlloppMSCoalescent
        // and that seems to be sufficient.
        public void wasChanged() {
        }






        private void collectIndivsOfNode(NodeRef node, Set<SpeciesIndivPair> spivs) {
            if (tree.isExternal(node)) {
                SpeciesIndivPair x = apspeciesId2speciesindiv(tree.getNodeTaxon(node).getId());
                spivs.add(x);
            } else {
                collectIndivsOfNode(tree.getChild(node, 0), spivs);
                collectIndivsOfNode(tree.getChild(node, 1), spivs);
            }
        }





        // start at root of gutree and recurse.
        // A node which has one child which contains some of species spp0
        // and where the other contains some of species spp1, imposes a limit
        // on how early a speciation can occur.
        private double subtreeSpseqUpperBound(GeneUnionNode node,
                                              FixedBitSet spsq0, FixedBitSet spsq1, double bound) {
            if (node.child.length == 0) {
                return bound;
            }
            for (GeneUnionNode ch : node.child) {
                bound = Math.min(bound, subtreeSpseqUpperBound(ch, spsq0, spsq1, bound));
            }
            FixedBitSet genespp0 = node.child[0].union;
            int int00 = genespp0.intersectCardinality(spsq0);
            int int01 = genespp0.intersectCardinality(spsq1);
            FixedBitSet genespp1 = node.child[1].union;
            int int10 = genespp1.intersectCardinality(spsq0);
            int int11 = genespp1.intersectCardinality(spsq1);
            if ((int00 > 0 && int11 > 0)  ||  (int10 > 0 && int01 > 0)) {
                bound = Math.min(bound, node.height);
            }
            return bound;
        }



        private void flipOneAssignment(int sp, int iv) {
            // grjtodo-tetraonly
            int tx;
            if (apspecies[sp].individuals[iv].taxa.length == 2) {
                tx = taxon2index.get(apspecies[sp].individuals[iv].taxa[0]);
                seqassigns[tx].seqIndex = 1 - seqassigns[tx].seqIndex;
                tx = taxon2index.get(apspecies[sp].individuals[iv].taxa[1]);
                seqassigns[tx].seqIndex = 1 - seqassigns[tx].seqIndex;
            }
        }



        private void flipAssignmentsForSpecies(int sp) {
            for (int iv = 0; iv < apspecies[sp].individuals.length; iv++) {
                flipOneAssignment(sp, iv);
            }
        }



    }
    // end of GeneTreeInfo




    /* ******************** Constructor *****************************/

    /*
     * The standard constructor calls this with permuteSequenceAssignments==true.
     * permuteSequenceAssignments==false is for testing.
     */
    public AlloppSpeciesBindings(ApSpInfo[] apspecies, TreeModel[] geneTrees,
                                 double minheight, double[] popFactors, boolean permuteSequenceAssignments) {
        super(AlloppSpeciesBindingsParser.ALLOPPSPECIES);

        this.apspecies = apspecies;
        initialmingenenodeheight = minheight;
        // make the flattened arrays
        int n = 0;
        for (int s = 0; s < apspecies.length; s++) {
            n += apspecies[s].individuals.length;
        }
        Individual [] indivs = new Individual[n];
        n = 0;
        for (int s = 0; s < apspecies.length; s++) {
            for (int i = 0; i < apspecies[s].individuals.length; i++, n++) {
                indivs[n] =  apspecies[s].individuals[i];
            }
        }
        int t = 0;
        for (int i = 0; i < indivs.length; i++) {
            t += indivs[i].taxa.length;
        }
        taxa = new Taxon[t];
        t = 0;
        for (int i = 0; i < indivs.length; i++) {
            for (int j = 0; j < indivs[i].taxa.length; j++, t++) {
                taxa[t] =  indivs[i].taxa[j];
            }
        }
        // set up maps to indices
        for (int i = 0; i < taxa.length; i++) {
            taxon2index.put(taxa[i], i);
        }
        spsq = new int[apspecies.length][];
        int spsqindex = 0;
        for (int sp = 0; sp < apspecies.length; sp++) {
            spsq[sp] = new int[apspecies[sp].ploidylevel/2];
            for (int seq = 0; seq < spsq[sp].length; seq++, spsqindex++) {
                spsq[sp][seq] = spsqindex;
            }
        }
        numberOfSpSeqs = spsqindex;

        geneTreeInfos = new GeneTreeInfo[geneTrees.length];
        for (int i = 0; i < geneTrees.length; i++) {
            geneTreeInfos[i] = new GeneTreeInfo(geneTrees[i], popFactors[i], permuteSequenceAssignments);
        }

        for (GeneTreeInfo gti : geneTreeInfos) {
            NodeRef[] nodes = gti.tree.getNodes();
            for (NodeRef node : nodes) {
                if (!gti.tree.isExternal(node)) {
                    double height = gti.tree.getNodeHeight(node);
                    gti.tree.setNodeHeight(node, minheight+height);
                }
            }
        }
    }


    /*
     * The normal constructor
     */
    public AlloppSpeciesBindings(ApSpInfo[] apspecies, TreeModel[] geneTrees,
                                 double minheight, double[] popFactors) {
        this(apspecies, geneTrees,  minheight, popFactors, true);
    }

    /*
     * Minimal constructor for testing conversions network -> multree, diphist
     * and for testing likelihood in MUL tree.
     */
    public AlloppSpeciesBindings(ApSpInfo[] apspecies) {
        this(apspecies, new TreeModel[0], 0.0, new double[0]);
    }


    public double initialMinGeneNodeHeight() {
        return initialmingenenodeheight;
    }



    public FixedBitSet speciesseqEmptyUnion() {
        FixedBitSet union = new FixedBitSet(numberOfSpSeqs());
        return union;
    }


    // Taxons vs species.
    // Taxons may have a final "0", "1",... to distinguish sequences, while
    // species do not. AlloppLeggedTree uses a SimpleTree, which only has
    // Taxons, so same thing there. Multree needs distinguishable Taxons
    // so has suffices.
    public FixedBitSet taxonseqToTipUnion(Taxon tx, int seq) {
        FixedBitSet union = speciesseqEmptyUnion();
        int sp = apspeciesId2index(tx.getId());
        int spseq = spandseq2spseqindex(sp, seq);
        union.set(spseq);
        return union;
    }


    public FixedBitSet spsqunion2spunion(FixedBitSet spsqunion) {
        FixedBitSet spunion = new FixedBitSet(apspecies.length);
        for (int sp = 0; sp < apspecies.length; sp++) {
            boolean got = false;
            for (int seq = 0; seq < spsq[sp].length; seq++) {
                if (spsqunion.contains(spsq[sp][seq])) {
                    got = true;
                }
            }
            if (got) {
                spunion.set(sp);
            }
        }
        return spunion;
    }


    public int numberOfGeneTrees() {
        return geneTreeInfos.length;
    }


    public double maxGeneTreeHeight() {
        if (geneTreeInfos.length == 0) {
            return 999;   // for test code only
        }
        double maxheight = 0.0;
        for (GeneTreeInfo gti : geneTreeInfos) {
            double height = gti.tree.getNodeHeight(gti.tree.getRoot());
            if (height > maxheight) {
                maxheight = height;
            }
        }
        return maxheight;
    }




    public boolean geneTreeFitsInNetwork(int i, final AlloppSpeciesNetworkModel asnm) {
        return geneTreeInfos[i].fitsInNetwork(asnm);
    }

    public double geneTreeLogLikelihood(int i, final AlloppSpeciesNetworkModel asnm) {
        return geneTreeInfos[i].treeLogLikelihood(asnm);
    }


    public int numberOfSpecies() {
        return apspecies.length;
    }



    public String apspeciesName(int i) {
        return apspecies[i].name;
    }



    public Taxon[] SpeciesWithinPloidyLevel(int pl) {
        ArrayList<Taxon> names = new ArrayList<Taxon>();
        for (int i = 0; i < apspecies.length; i++) {
            if (apspecies[i].ploidylevel == pl) {
                names.add(new Taxon(apspecies[i].name));
            }
        }
        Taxon[] spp = new Taxon[names.size()];
        names.toArray(spp);
        return spp;
    }


    public int spandseq2spseqindex(int sp, int seq) {
        return spsq[sp][seq];
    }



    public int spseqindex2sp(int spsqindex) {
        return spseqindex2spandseq(spsqindex)[0];
    }

    public int spseqindex2seq(int spsqindex) {
        return spseqindex2spandseq(spsqindex)[1];
    }



    public int apspeciesId2index(String apspId) {
        int index = -1;
        for (int i = 0; i < apspecies.length; i++) {
            if (apspecies[i].name.compareTo(apspId) == 0) {
                assert index == -1;
                index = i;
            }
        }
        if (index == -1) {
            System.out.println("BUG in apspeciesId2index");
        }
        assert index != -1;
        return index;
    }


    public SpeciesIndivPair apspeciesId2speciesindiv(String apspId) {
        int sp = -1;
        int iv = -1;
        for (int s = 0; s < apspecies.length; s++) {
            for (int i = 0; i < apspecies[s].individuals.length; i++) {
                for (int t = 0; t < apspecies[s].individuals[i].taxa.length; t++) {
                    Taxon taxon = apspecies[s].individuals[i].taxa[t];
                    if (taxon.getId().compareTo(apspId) == 0) {
                        sp = s;
                        iv = i;
                    }
                }
            }
        }
        assert sp != -1;
        SpeciesIndivPair x = new SpeciesIndivPair(sp, iv);

        return x;
    }


    public int numberOfSpSeqs() {
        return numberOfSpSeqs;
    }


    int nLineages(int speciesIndex) {
        int n = geneTreeInfos[0].lineagesCount[speciesIndex];
        for (GeneTreeInfo gti : geneTreeInfos) {
            assert gti.lineagesCount[speciesIndex] == n;
        }
        return n;
    }



    public double spseqUpperBound(FixedBitSet left, FixedBitSet right) {
        double bound = Double.MAX_VALUE;
        for (GeneTreeInfo gti : geneTreeInfos) {
            bound = Math.min(bound, gti.spseqUpperBound(left, right));
        }
        return bound;
    }



    public void permuteOneSpeciesOneIndivForOneGene() {
        int i = MathUtils.nextInt(geneTreeInfos.length);
        geneTreeInfos[i].permuteOneSpeciesOneIndiv();
    }


    public void permuteSetOfIndivsForOneGene() {
        int i = MathUtils.nextInt(geneTreeInfos.length);
        geneTreeInfos[i].permuteSetOfIndivs();
    }


    public void flipAssignmentsForAllGenesOneSpecies(int sp) {
        for (GeneTreeInfo gti : geneTreeInfos) {
            gti.flipAssignmentsForSpecies(sp);
        }

    }



    public String seqassignsAsText(int g) {
        return geneTreeInfos[g].seqassignsAsText();
    }


    public String genetreeAsText(int g) {
        String s = "Gene tree " + g + "                     height             union" + System.getProperty("line.separator");
        s += geneTreeInfos[g].genetreeAsText();
        return s;
    }


    @Override
    protected void handleModelChangedEvent(Model model, Object object, int index) {
        for (GeneTreeInfo g : geneTreeInfos) {
            if (g.tree == model) {
                g.wasChanged();
                break;
            }
        }
        fireModelChanged(object, index);
        if (AlloppSpeciesNetworkModel.DBUGTUNE)
            System.err.println("AlloppSpeciesBindings.handleModelChangedEvent() " + model.getId());
    }

    @Override
    protected void handleVariableChangedEvent(Variable variable, int index,
                                              ChangeType type) {
        assert false; // copies SpeciesBindings; not understood
        if (AlloppSpeciesNetworkModel.DBUGTUNE)
            System.err.println("AlloppSpeciesBindings.handleVariableChangedEvent() " + variable.getId());
    }

    @Override
    protected void storeState() {
        for (GeneTreeInfo gti : geneTreeInfos) {
            gti.storeSequenceAssignments();
        }
        if (AlloppSpeciesNetworkModel.DBUGTUNE)
            System.err.println("AlloppSpeciesBindings.storeState()");
    }

    @Override
    protected void restoreState() {
        for (GeneTreeInfo gti : geneTreeInfos) {
            gti.restoreSequenceAssignments();
            if (AlloppSpeciesNetworkModel.DBUGTUNE)
                System.err.println("AlloppSpeciesBindings.restoreState()");
        }

    }

    @Override
    protected void acceptState() {
    }


    public void addModelListeners(ModelListener listener) {
        for (GeneTreeInfo gti : geneTreeInfos) {
            gti.tree.addModelListener(listener);
        }
        addModelListener(listener); // for sequence assignments
    }



    public LogColumn[] getColumns() {
        int ncols = geneTreeInfos.length * taxa.length;
        LogColumn[] columns = new LogColumn[ncols];
        for (int g = 0, i = 0; g < geneTreeInfos.length; g++) {
            for (int tx = 0; tx < taxa.length; tx++, i++) {
                GeneTreeInfo.SequenceAssignment sqa = geneTreeInfos[g].getSeqassigns(tx);
                String header = "Gene" + g + "taxon" + tx;
                columns[i] = new LogColumn.Default(header, sqa);
            }

        }

        return columns;
    }



    private int[] spseqindex2spandseq(int spsqindex) {
        int indexp = -1;
        int indexq = -1;
        for (int p = 0; p < spsq.length; p++) {
            for (int q = 0; q < spsq[p].length; q++) {
                if (spsq[p][q] == spsqindex) {
                    assert indexp == -1;
                    assert indexq == -1;
                    indexp = p;
                    indexq = q;
                }
            }
        }
        assert indexp != -1;
        assert indexq != -1;
        int[] pq = new int[2];
        pq[0] = indexp;
        pq[1] = indexq;
        return pq;
    }

}



