package org.treesched.schedules;

import java.io.Serializable;
import java.util.Date;

import org.apache.log4j.Logger;
import org.treesched.CompoundSchedule;
import org.treesched.DateUtils;
import org.treesched.Interval;
import org.treesched.Schedule;



/**
 * This schedule allows a normal schedule to be broken by the results of another
 * schedule. This might be a list of bank holidays, or time of day, or any other
 * schedule.
 * <p>
 * Typical configuration would be:
 * 
 * <code><pre>
 * &lt;broken&gt;
 *   &lt;schedule&gt;
 *     &lt;daily/&gt;
 *   &lt;/schedule&gt;
 *   &lt;breaks&gt;
 *     &lt;date from="25-dec-04" to="26-dec-04"/&gt;
 *   &lt;/breaks&gt;
 * &lt;/broken&gt;
 * 
 * @author Rob Gordon
 */

public class BrokenSchedule implements Serializable, CompoundSchedule {

    private static final long serialVersionUID = 20050226;
    
    private static final Logger logger = Logger.getLogger(BrokenSchedule.class);
    
    /** The schedule. */
	private Schedule schedule;
	
	/** The breaks. */
	private Schedule breaks;

	/** The limits. */
	private Interval limits;

	/*
	 *  (non-Javadoc)
	 * @see org.treesched.Schedule#setLimits(org.treesched.Interval)
	 */
	public void setLimits(Interval limits) {
	    this.limits = limits;
	}
	
	/**
	 * Get any limits set for this schedule.
	 * 
	 * @return The limits, may be null.
	 */
	public Interval getLimits() {
	    return limits;
	}
	
	/**
	 * Set the schedule to break up.
	 * 
	 * @param schedule The schedule to break up.
	 */
	public void setSchedule(Schedule schedule) {
		this.schedule = schedule;
	}

	/**
	 * Get the schedule to break up.
	 * 
	 * @return The schedule to break up.
	 */
	public Schedule getSchedule() {
		return this.schedule;
	}
		
	/**
	 * Set the breaks which will break up the schedule.
	 * 
	 * @param breaks The breaks schedule.
	 */
	public void setBreaks(Schedule breaks) {		
		this.breaks = breaks;
	}
	
	/**
	 * Get the breaks which will break up the schedule.
	 * 
	 * @return The break Schedule.
	 */
	
	public Schedule getBreaks() {
		return this.breaks;
	}
	
	/**
	 * Implement the schedule.
	 */
	synchronized public Interval nextDue(Date now) {		
		if (now == null) {
		    throw new NullPointerException();
		}

		logger.debug(this + ": in interval is " + now);

		// sanity checks
		if (schedule == null) {			
			return null;
		}
	    schedule.setLimits(getLimits());
	    if (breaks == null) {
			return schedule.nextDue(now);
		}

		Date use = now; 

		// loop until we get a valid interval
		while (true) {
			if (use == null) {
				return null;
			}

			Interval next = schedule.nextDue(use);	
			// if the next schedule is never due return.
			if (next == null) {
				return null;
			}

			// find the first exclusion interval
			Interval exclude = breaks.nextDue(next.getFromDate());
				
			if (exclude == null) {
			    return next;
			}
			// if this interval is before the break
			if (next.isBefore(exclude)) {
				return next;				
			}
			
			// if we got here the last interval is blocked by an exclude so move
			// the interval on.			
			use = DateUtils.oneMillisAfter(next.getToDate());
		}
	}

	/**
	 * Reset the schedule.
	 */
	public void reset() {
		getSchedule().reset();
		getBreaks().reset();
	}

	/**
	 * Provide a simple string description.
	 */	
	public String toString() {
		return "Broken Schedule";
	}
}
