/*
 * TimeScale.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.util;

/**
 * Class for defining and converting between time scales
 *
 * @version $Id: TimeScale.java,v 1.11 2005/05/24 20:25:57 rambaut Exp $
 *
 * @author Alexei Drummond
 * @author Andrew Rambaut
 */
public class TimeScale implements Units { 

	/**
	 * Constructor for a timescale that starts at Jan 1st 1970. This constructor
	 * can be used if we are not interested in absolute time.
	 * param units The units
	 * param backwards True if the timescale goes backwards in time
	 */
	public TimeScale(Type units, boolean backwards) {
		this(units, backwards, 0.0);
	}
	
	/**
	 * Constructor for a timescale that starts at origin
	 * param units The units
	 * param backwards True if the timescale goes backwards in time
	 * param origin The origin specified relative to 1970 in same units
	 */
	public TimeScale(Type units, boolean backwards, double origin) {
		this.units = units;
		this.backwards = backwards;
		this.origin = origin;
	}
	
	/**
	 * Constructor for a timescale that starts at origin
	 * param units The units
	 * param backwards True if the timescale goes backwards in time
	 * param origin The origin specified as a date
	 */
	public TimeScale(Type units, boolean backwards, java.util.Date origin) {
		this.units = units;
		this.backwards = backwards;
		
		long millisAhead = origin.getTime();
		
		double daysAhead = ((double)millisAhead)/MILLIS_PER_DAY;
		
		switch (units) {	
			case DAYS: this.origin = daysAhead; break;
			case MONTHS: this.origin = daysAhead / DAYS_PER_MONTH; break;
			case YEARS: this.origin = daysAhead / DAYS_PER_YEAR; break;
			default: throw new IllegalArgumentException();
		}
		
	}
	
	/**
	 * @return the units of this timescale
	 */
	public Type getUnits() { return units; }
	
	/**
	 * Sets the units for this timescale.
	 */
	public void setUnits(Type units) { this.units = units; }
	
	/**
	 * @return true if larger numbers represent older dates in this time scale.
	 */
	public boolean isBackwards() { return backwards; }
	
	/**
	 * @return the date corresponding to the value zero in this time scale.
	 */
	public double getOrigin() { return origin; }
	
	/** 
	 * @return a time given in timescale scale as a time in this timescale
	 */
	public double convertTime(double time, TimeScale timeScale) {
		
		// make it forwards
		if (timeScale.isBackwards()) time = -time;
		
		// make it absolute
		time += timeScale.getOrigin();
		
		// convert to the new timescale units
		double newTime = convertTimeUnits(time, getUnits(), timeScale.getUnits());
		
		// make it relative
		newTime -= origin;
		
		// make it backwards if required
		if (backwards) newTime = -newTime;

		return newTime;
	}
	
	public String toString() {
	
		StringBuffer buffer = new StringBuffer("timescale(");
		buffer.append(unitString(0.0));
		if (backwards) {
			buffer.append(", backwards");
		} else {
			buffer.append(", forewards");
		}
		buffer.append(" from " + origin + ")");
		
		return buffer.toString();
	}
	
	public String unitString(double time) {
		String unitString = null;
		switch (units) {	
			case DAYS: unitString = "day"; break;
			case MONTHS: unitString = "month"; break;
			case YEARS: unitString = "year"; break;
			default: throw new IllegalArgumentException();
		}
		if (time == 1.0) {
			return unitString;
		} else return unitString + "s";
	}
	
	public static void main(String[] args) {
	
		TimeScale timeScale1 = new TimeScale(Units.Type.DAYS, true);
		TimeScale timeScale2 = new TimeScale(Units.Type.YEARS, true);
		
		System.out.println(timeScale1);
		System.out.println(timeScale2);
		
		double testTime = 100.0;
		System.out.println("Test time = " + testTime);
		
		System.out.println("timeScale1.convertTime(" + testTime + ", timeScale2)=" + timeScale1.convertTime(testTime, timeScale2));
		System.out.println("timeScale2.convertTime(" + testTime + ", timeScale1)=" + timeScale2.convertTime(testTime, timeScale1));
	}
	
	
	//*************************************************************************
	// STATIC STUFF
	//*************************************************************************
	
	/** 
	 * @return time in currentUnits as newUnits.
	 */
	public static double convertTimeUnits(double time, Type currentUnits, Type newUnits) {
		
		return time * getScale(currentUnits, newUnits);
	}
	
	/** 
	 * @return the scaling factor for converting currentUnits into newUnits.
	 */
	public static double getScale(Type currentUnits, Type newUnits) {
		if (currentUnits == newUnits) return 1.0;
		
		switch (currentUnits) {
			case DAYS:
				switch (newUnits) {
					case MONTHS: return 1.0/DAYS_PER_MONTH;
					case YEARS: return 1.0/DAYS_PER_YEAR;
					default: throw new IllegalArgumentException();
				}
			case MONTHS:
				switch (newUnits) {
					case DAYS: return DAYS_PER_MONTH;
					case YEARS: return 1.0/MONTHS_PER_YEAR;
					default: throw new IllegalArgumentException();
				}
			case YEARS:
				switch (newUnits) {
					case DAYS: return DAYS_PER_YEAR;
					case MONTHS: return MONTHS_PER_YEAR;
					default: throw new IllegalArgumentException();
				}
			default: throw new IllegalArgumentException();
		}
	}
	
	//*************************************************************************
	// PRIVATE STUFF
	//*************************************************************************

	// The origin is specified in days relative to 1st January 1970
	protected double origin;
	protected Type units;
	protected boolean backwards;
	
	protected static double MILLIS_PER_DAY = 86400000.0;
	protected static double DAYS_PER_YEAR = 365.25;
	protected static double MONTHS_PER_YEAR = 12.0;
	protected static double DAYS_PER_MONTH = DAYS_PER_YEAR / MONTHS_PER_YEAR;
}
