package beast.base.evolution.tree;

/*
 * IntervalList.java
 *
 * Copyright (C) 2002-2006 Alexei Drummond and Andrew Rambaut
 *
 * 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
 */


/**
 * An interface for a set of coalescent intervals.
 *
 * @author Andrew Rambaut
 * @author Alexei Drummond
 * @version $Id: IntervalList.java,v 1.7 2005/05/24 20:25:56 rambaut Exp $
 */
public interface IntervalList {

    /**
     * @return the number of intervals
     */
    int getIntervalCount();

    /**
     * @return the total number of sampling events.
     */
    int getSampleCount();

    /**
     * @param i the index of the interval
     * @return the size of the i'th interval.
     */
    double getInterval(int i);

    /**
     * Required for s-coalescents, where new lineages are added as
     * earlier samples are come across.
     *
     * @param i the index of the interval
     * @return the number of uncoalesced lineages within this interval.
     */
    int getLineageCount(int i);

    /**
     * @param i the index of the interval
     * @return the number coalescent events in an interval
     */
    int getCoalescentEvents(int i);

    /**
     * @param i the index of the interval
     * @return the type of interval observed.
     */
    IntervalType getIntervalType(int i);

    /**
     * @return the total duration of these intervals.
     */
    double getTotalDuration();

    /**
     * @return true if this set of coalescent intervals is fully resolved
     *         (i.e. whether is has exactly one coalescent event in each
     *         subsequent interval), false otherwise.
     */
    boolean isBinaryCoalescent();

    /**
     * @return true if this set of coalescent intervals coalescent only
     *         (i.e. whether is has exactly one or more coalescent event in each
     *         subsequent interval), false otherwise
     */
    boolean isCoalescentOnly();


    public class Utils {

        /**
         * @param intervals the intervals
         * @param t         the time that you are counting the number of lineages
         * @return the number of lineages at time t.
         */
        public static int getLineageCount(IntervalList intervals, double t) {

            int i = 0;
            while (i < intervals.getIntervalCount() && t > intervals.getInterval(i)) {
                t -= intervals.getInterval(i);
                i += 1;
            }
            if (i == intervals.getIntervalCount()) return 1;
            return intervals.getLineageCount(i);
        }

        /**
         * @param intervals the intervals for which the delta parameter is calculated.
         * @return the delta parameter of Pybus et al (Node spread statistic)
         */
        public static double getDelta(IntervalList intervals) {

            // Assumes ultrametric beast.tree!
            if (!intervals.isCoalescentOnly()) {
                throw new IllegalArgumentException("Assumes ultrametric beast.tree!");
            }

            int n = intervals.getIntervalCount();

            int numTips = n + 1;

            double transTreeDepth = 0.0;
            double cumInts = 0.0;
            double sum = 0.0;

            // transform intervals
            for (int j = 0; j < n; j++) { // move from tips to root

                double transInt = intervals.getInterval(j) *
                        beast.base.util.Binomial.choose2(intervals.getLineageCount(j)); // coalescent version
                //intLenCopy[j] = getInterval(j)*getLineageCount(j); // birth-death version

                // don't include the last interval so put this before...
                sum += cumInts;

                // ...incrementing the cumInts
                cumInts += transInt;

                transTreeDepth += transInt;
            }

            double halfTreeDepth = transTreeDepth / 2.0;

            sum *= (1.0 / (numTips - 2.0));
            double top = halfTreeDepth - sum;
            double bottom = transTreeDepth * Math.sqrt((1.0 / (12.0 * (numTips - 2.0))));

            return (top / bottom);
        }
    }
}