// ClassicSkylinePlot.java
//
// (c) 1999-2000 PAL Development Core Team
//
// This package may be distributed under the
// terms of the GNU General Public License (GPL)

 
package pal.coalescent;

import pal.tree.*;
import pal.misc.*;
import pal.math.*;
import pal.io.*;

import java.io.*;

/**
 * Skyline plot derived from a strictly bifurcating tree
 * or a coalescent interval.
 *
 * This class implements the "classic" skyline plot by Pybus et al.
 * 2000. Genetics 155:1429-1437
 *
 * Note that this method has been superseeded by the generalized skyline plot
 * which is implemented in pal.coalescent.SkylinePlot.
 * 
 * @version $Id: ClassicSkylinePlot.java,v 1.2 2000/10/28 18:24:45 korbinian Exp $
 *
 * @author Korbinian Strimmer
 * @author Alexei Drummond
 */
public class ClassicSkylinePlot implements Report, Units, Serializable
{
	//
	// Public stuff
	//
	
	/** estimated population size in a coalescent interval */
	public double[] populationSize; 


	/**
	 * Construct skyline plot from tree
	 */
	public ClassicSkylinePlot(Tree tree)
	{
		this(IntervalsExtractor.extractFromClockTree(tree));
	}


	/**
	 * Construct skyline plot from given coalescent intervals
	 */
	public ClassicSkylinePlot(CoalescentIntervals ci)
	{
		if (!ci.isBinaryCoalescent())
		{
			throw new IllegalArgumentException("Coalescent intervals must purely consist of only single coalescents");
		}
		
		fo = FormattedOutput.getInstance();
		
		size = ci.getIntervalCount();
		
		this.ci = ci;
		
		populationSize = new double[size];
		
		binom = new Binomial();
		int max = 0;
		for (int i = 0; i < size; i++)
		{
			if (ci.getNumLineages(i) > max) max = ci.getNumLineages(i);
		}
		binom.setMax(max);
		
		for (int i = 0; i < size; i++)
		{
			// skyline plot
			
			populationSize[i] =
				binom.getNChoose2(ci.getNumLineages(i))
				*ci.getInterval(i);
		}
		
		/* cummulative interval sizes */
		cis = new double[size];
		
		maxTime = 0.0;
		for (int i = 0; i < size; i++)
		{
			cis[i] = maxTime;
			maxTime += ci.getInterval(i);
		}
	}

	
	public String toString()
	{		
		OutputTarget out = OutputTarget.openString();
		report(out);
		out.close();
		
		return out.getString();
	}
	
	public void report(PrintWriter out)
	{
		out.println("Classic Skyline Plot (Pybus et al. 2000)");
		out.println();
		out.print("Unit of time: ");
		if (ci.getUnits() == GENERATIONS)
		{
			out.print("generations");
		}
		else
		{
			out.print("expected substitutions");
		}
		out.println();
		out.println("Int.\tTime\tEstimated N(t)");
		double total = 0.0;
		for (int i = 0; i < size; i++)
		{
			out.print(i+"\t");
			fo.displayDecimal(out, total , 4);
			out.print("\t");
			fo.displayDecimal(out, populationSize[i], 4);
			out.println();
			
			total += ci.getInterval(i);
			
			out.print(i+"\t");
			fo.displayDecimal(out, total , 4);
			out.print("\t");
			fo.displayDecimal(out, populationSize[i], 4);
			out.println();

		}
	}

	/**
	 * Find interval corresponding to a specific time
	 */
	public double findInterval(double time)
	{
		if (time < 0) throw new IllegalArgumentException("Negative values for time are not allowed");
		
		for (int i = 0; i < size-1; i++)
		{
			if (time >= cis[i] && time < cis[i+1]) return i;
		}
		
		return size-1;
	}
	 
	/**
	 * Returns the largest value of time defined in this plot (smallest is zero)
	 */
	public double getMaxTime()
	{
		return maxTime;
	}


	/**
	 * Returns the largest estimate of population size.
	 */
	public double getMaxPopulationSize() {
		double max = 0.0;
		for (int i = 0; i < size; i++) {
			if (populationSize[i] > max) {
				max = populationSize[i];
			}
		}

		return max;
	}

	/**
	 * Returns the coalescent intervals in this skyline plot.
	 */
	public CoalescentIntervals getIntervals() {
		return ci;
	}

	/**
	 * Returns the number of intervals in this skyline plot.
	 */
	public int getSize() {
		return size;
	}

	/**
	 * Returns unit of time.
	 */
	public int getUnits()
	{
		return ci.getUnits();
	}


	// private
	
	private CoalescentIntervals ci;
	private FormattedOutput fo;
	private int size;
	private Binomial binom;
	private double maxTime;
	
	/** cummulative interval sizes */
	private double[] cis;
}
