/*
 * EmpiricalPiecewiseConstant.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
 */

package dr.evolution.coalescent;

/**
 *
 * @version $Id: EmpiricalPiecewiseConstant.java,v 1.4 2004/10/01 23:30:16 alexei Exp $
 *
 * @author Alexei Drummond
 * @author Andrew Rambaut
 */
public class EmpiricalPiecewiseConstant extends DemographicFunction.Abstract {

	/**
	 * Creates a piecewise constant model with the given break points.
	 * @param intervals an array of intervals, Each interval represents time
	 * over which a different population size is assumed, the first interval starts from the
	 * present and proceeds back into the past.
	 * @param popSizes the effective population sizes of each interval. The last population size represents the
	 * time from the end of the last interval out to infinity. 
	 * @param lag a lag to align the break points with the most recent sample time (must be positive)
	 */
	public EmpiricalPiecewiseConstant(double[] intervals, double[] popSizes, double lag, Type units) {
		super(units);
		if (popSizes == null || intervals == null) { throw new IllegalArgumentException(); }
		if (popSizes.length != intervals.length + 1) { throw new IllegalArgumentException(); }
		if (lag < 0.0) throw new IllegalArgumentException("Lag must be greater than 1.");

		this.intervals = intervals;
		this.popSizes = popSizes;
		this.lag = lag;
	}
	
	public void setLag(double lag) {
		this.lag = lag;
	}
	
	public void setPopulationSizes(double[] popSizes) {
		this.popSizes = popSizes;
	}
	
	// **************************************************************
	// Implementation of abstract methods
	// **************************************************************
	
	public double getDemographic(double t) {
        int epoch = 0;
		double t1 = t+lag;
		while (t1 > getEpochDuration(epoch)) {
			t1 -= getEpochDuration(epoch);
			epoch += 1;
		}
		return getDemographic(epoch, t1);
	}

	public double getIntensity(double t) { 
	
		// find the first epoch that is involved
		double t2 = lag;
		int epoch = 0;
		while (t2 > getEpochDuration(epoch)) {
			t2 -= getEpochDuration(epoch);		
			epoch += 1;
		}
		
		// add last fraction of first epoch
		double intensity = getIntensity(epoch)-getIntensity(epoch, t2);
				
		double t1 = t-(getEpochDuration(epoch)-t2);
		epoch += 1;
		while (t1 > getEpochDuration(epoch)) {
			t1 -= getEpochDuration(epoch);		
			intensity += getIntensity(epoch);
			epoch += 1;
		}
		// add last fraction of intensity
        // when t1 may be negative (for example when t is in the first epoch) the intensity need
        // to be substracted
        intensity += t1 >= 0 ? getIntensity(epoch, t1) : getIntensity(epoch-1, t1);
	
		return intensity; 
	}
	
	public double getInverseIntensity(double x) { 
		throw new RuntimeException("Not implemented!");	
	}
	
	public double getUpperBound(int i) { return 1e9;}
	public double getLowerBound(int i) { return Double.MIN_VALUE;}
	
	public int getNumArguments() { return 1; }
	
	public String getArgumentName(int i) { 
		return "lag";
	}
	
	public double getArgument(int i) { 
		return lag;
	}
	
	public void setArgument(int i, double value) { 
		lag = value; 
	}
	
	public DemographicFunction getCopy() {
		EmpiricalPiecewiseConstant df = new EmpiricalPiecewiseConstant(new double[intervals.length], new double[popSizes.length], lag, getUnits());
		System.arraycopy(intervals, 0, df.intervals, 0, intervals.length);
		System.arraycopy(popSizes, 0, df.popSizes, 0, popSizes.length);
		
		return df;
	}

	
	/**
	 * @return the value of the demographic function for the given epoch and time relative to start of epoch.
	 */
	protected double getDemographic(int epoch, double t) {
		return getEpochDemographic(epoch);
	}
	
	/**
	 * @return the value of the intensity function for the given epoch.
	 */
	protected double getIntensity(int epoch) {
		return getEpochDuration(epoch) / getEpochDemographic(epoch);
	}
	
	/**
	 * @return the value of the intensity function for the given epoch and time relative to start of epoch.
	 */
	protected double getIntensity(int epoch, double relativeTime) {
		return relativeTime / getEpochDemographic(epoch);
	}
	
	/** 
	 * @return the duration of the specified epoch (in whatever units this demographic model is specified in).
	 */
    public double getEpochDuration(int epoch) {
        if (epoch < intervals.length) {
            return intervals[epoch];
        }
        return Double.POSITIVE_INFINITY;
    }

	/**
	 * @return the pop size of a given epoch.
	 */
	public double getEpochDemographic(int epoch) {
		if (epoch >= popSizes.length) { throw new IllegalArgumentException(); }
		return popSizes[epoch];
	}
	
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		buffer.append(popSizes[0]);
		for (int i =1; i < popSizes.length; i++) {
            buffer.append("\t").append(popSizes[i]);
		}
		return buffer.toString();
	}
	
	double[] intervals;
	double[] popSizes;
	double lag;
}
