package org.treesched.schedules;

import java.io.Serializable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import org.treesched.ConstrainedSchedule;
import org.treesched.DateUtils;

/**
 * A shcedule that returns an interval which is between times of the
 * day, or between times of the day at given intervals.
 * <p>
 * Typical configuration is:
 * <code><pre>
 * &lt;time from="10:00" to="14:00"/&gt;
 * </pre></code>
 * 
 * @author Rob Gordon
 */

final public class TimeSchedule extends ConstrainedSchedule implements Serializable {

    private static final long serialVersionUID = 20050226;
    
	public static final String DEFAULT_DATE_FORMAT = "HH:mm";
	private String format = DEFAULT_DATE_FORMAT;

	/** Milliseconds at midnight, the previous night. */
	private final long zeroHour;

	/** Milliseconds since midnight for the from time. */
	private Long startMillis;
	
	/** Milliseconds since midnight for the to time. */
	private Long endMillis;
	
	private String stringFrom;
	private String stringTo;
	
	/**
	 * Constructor.
	 */
	public TimeSchedule() {
		// why isn't 00:00 0? don't know but define our own zero
		SimpleDateFormat format = new SimpleDateFormat("HH:mm");
		long maybe = 0; 
		try {
			maybe = format.parse("00:00").getTime();
		}
		catch (ParseException e) {
			// This should never happen.
		}
	    zeroHour = maybe;
	}		
	
	/**
	 * Set the date format to use for translating string attributes.
	 * 
	 * @param dateFormat The date format.
	 */		
	public void setFormat(String dateFormat) {	
		this.format = dateFormat;
	}

	/**
	 * Get the date format used for translating string attributes.
	 * 
	 * @return The date format being used.
	 */
	public String getFormat() {		
		return this.format;	
	}

	/*
	 *  (non-Javadoc)
	 * @see org.treesched.ConstrainedSchedule#setFrom(java.lang.String)
	 */
	public void setFrom(String from) throws ParseException {
		stringFrom = from;
		if (from == null) {
		    startMillis = null;
		}
		else {
			SimpleDateFormat format = new SimpleDateFormat(getFormat());
			Date fromDate = format.parse(from);
			startMillis = new Long(fromDate.getTime() 
			        - DateUtils.startOfDay(fromDate).getTime());
		}
	}

	/*
	 *  (non-Javadoc)
	 * @see org.treesched.ConstrainedSchedule#getFrom()
	 */
	public String getFrom() {
	    return stringFrom;
	}
	
	/*
	 *  (non-Javadoc)
	 * @see org.treesched.ConstrainedSchedule#setTo(java.lang.String)
	 */
	public void setTo(String to) throws ParseException {
		stringTo = to;
	    if (to == null) {
	        endMillis = null;
	    }
	    else {
			SimpleDateFormat format = new SimpleDateFormat(getFormat());
			Date toDate = format.parse(to);
			endMillis = new Long(toDate.getTime() 
			        - DateUtils.startOfDay(toDate).getTime());
	    }
	}

	/*
	 *  (non-Javadoc)
	 * @see org.treesched.ConstrainedSchedule#getTo()
	 */
	public String getTo() {
	    return stringTo;
	}

	/**
	 * Get the milliseconds in since the start of the
	 * given day as a calendar.
	 * 
	 * @param date The date to take the day from.
	 * @param time The time in milliseoncs since the
	 * start of day.
	 * @return A Calander.
	 */
	public Calendar calendarFor(Date date, long time) {
	    Calendar dateCal = Calendar.getInstance();
	    dateCal.setTime(date);
	    Calendar timeCal = new GregorianCalendar(dateCal.get(Calendar.YEAR),
	            dateCal.get(Calendar.MONTH),
	            dateCal.get(Calendar.DATE));
	    timeCal.set(Calendar.MILLISECOND, (int)time);
	    return timeCal;
	}
	
	/*
	 *  (non-Javadoc)
	 * @see org.treesched.ConstrainedSchedule#getStartDate(java.util.Date)
	 */
	public Date getStartDate(Date now) {
	    if (startMillis == null) {
	        return new Date(0);
	    }
	    long lStartMillis = startMillis.longValue();
	    Calendar fromCal = calendarFor(now, lStartMillis);
	    if (endMillis == null) {
	        return fromCal.getTime();
	    }
	    long lEndMillis = endMillis.longValue();
	    if (lEndMillis != lStartMillis) {
	        lEndMillis -= 1;
	    }
	    Calendar toCal = calendarFor(now, lEndMillis);
	    long nowMillis = now.getTime() - DateUtils.startOfDay(now).getTime();
	    if (lStartMillis > lEndMillis) {
	        if (nowMillis > lEndMillis) {
	            return fromCal.getTime();
	        }
	        else {
	            fromCal.add(Calendar.DATE, -1);
	            return fromCal.getTime();	            	            
	        }
	    } 
	    else {
	        if (nowMillis > lEndMillis) {
	            fromCal.add(Calendar.DATE, 1);
	            return fromCal.getTime();
	        }
	        else {
	            return fromCal.getTime();
	        }
	    }
	}
	
	/*
	 *  (non-Javadoc)
	 * @see org.treesched.ConstrainedSchedule#getEndDate(java.util.Date)
	 */
	public Date getEndDate(Date now) {
	    if (endMillis == null) {
	        return new Date(Long.MAX_VALUE);
	    }
	    long lEndMillis = endMillis.longValue();
	    Calendar toCal = calendarFor(now, lEndMillis);
	    if (startMillis == null) {
	        return DateUtils.oneMillisBefore(toCal.getTime());
	    }
	    long lStartMillis = startMillis.longValue();
	    if (lEndMillis != lStartMillis) {
	        lEndMillis -= 1;
		    toCal = calendarFor(now, lEndMillis);
	    }
	    Calendar fromCal = calendarFor(now, lStartMillis);
	    long nowMillis = now.getTime() - DateUtils.startOfDay(now).getTime();
	    Date startOfEndDay;
	    if (nowMillis > lEndMillis) {
            toCal.add(Calendar.DATE, 1);
            return toCal.getTime();
        }
        else {
            return toCal.getTime();
        }
	}

}
