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

/**
 * TimePeriod
 *
 * A utility class that holds a pair of begin/end times
 * These are assumed to be with respect to the same timezone (GMT)
 *
 * Constructor ensures begin & end are not null
 * and that end does not preceede begin.
 *
 * TimePeriod is immutable.
 *
 * Simplifies testing for overlapping TimePeriods
 *
 * @author Jim Pinkham
 */


public class TimePeriod implements Serializable, Cloneable, Comparable
{
    protected Date begin;
    protected Date end;

    static final DateFormat df = DateFormat.getDateTimeInstance();
    static final String errorMsg = "TimePeriod end should not precede begin";

    /**
     *  Default Constructor makes 0 length period of time(now-now)
     */

    public TimePeriod()
    {
        this(null, null);
    }

    /**
     * @param begin Time Period start  (default now if null)
     * @param end Time Period end
     * @exception IllegalArgumentException if end < begin
     */

    public TimePeriod(Date begin, Date end)
    {
        // note we make COPIES of input objects
        if (end != null)
            end = (Date)end.clone();
        if (begin != null)
            begin = (Date)begin.clone();

        if (end == null || begin == null)
        {
            Date now = new Date();
            if (end == null)
                end = now;
            if (begin == null)
                begin = now;
        }

        if (end.before(begin))
            throw new IllegalArgumentException(errorMsg);
        this.begin = begin;
        this.end = end;
    }

    /**
     * This TimePeriod is the begin/end interval and the other is an event timePeriod .
     * This method returns true if the event occurs within the interval.
     *
     * This does NOT includes events which end exactly at the beginning of the interval,
     * or events which begin exactly at the end of the interval.
     *
     * Instantaneous events with begin == end are a special case.  If they occur within
     * or at the LEFT (begin) edge, we say they intersect.  If they occur at the right edge, they do not.
     *
     * @param eventPeriod
     * @return true if the periods have any overlap
     *
     */

    public boolean intersects(TimePeriod eventPeriod)
    {
        if (eventPeriod.begin.equals(eventPeriod.end))  // special case - point events may end AT left edge (begin)
            return eventPeriod.begin.before(end) && !eventPeriod.end.before(begin);

        if (begin.equals(end))  // special case - interval is a point - event may begin AT point
            return !eventPeriod.begin.after(end) && eventPeriod.end.after(begin);

        //  Event must start before right edge and end after left edge
        return eventPeriod.begin.before(end) && eventPeriod.end.after(begin);
    }

    /**
     *
     * This does DOES includes events which end exactly at the beginning of the interval,
     * or events which begin exactly at the end of the interval.
     *
     * @param eventPeriod
     * @return true if the periods have any overlap inclusively
     *
     */

    public boolean intersectsInclusive(TimePeriod eventPeriod)
    {

        if (this.begin.equals(eventPeriod.end))  // events which end exactly at the beginning of the interval
            return true;

        if (this.end.equals(eventPeriod.begin))  // events which begin exactly at the end of the interval
            return true;

        //  call intersects
        return this.intersects(eventPeriod);
    }


    /**
     * @returns true if the given date/time occurs within the interval (endpoints inclusive)
     */

    public boolean contains(Date date)
    {
        return(date.compareTo(begin) >= 0 && date.compareTo(end) <= 0);
    }

    /**
     * @returns the (always positive) number of ms between begin/end
     */

    public long getPeriod()
    {
        return end.getTime() - begin.getTime();
    }

    /**
     * @return the Begin date of the TimePeriod
     */

    public Date getBegin()
    {
        return this.begin;
    }

    /**
     * @return the End date of the TimePeriod
     */

    public Date getEnd()
    {
        return this.end;
    }

    /**
     * @return a printable string
     */

    public String toString()
    {
        return "["+df.format(begin)+","+df.format(end)+"]";
    }

    /**
     * @return 0 if equal, else compares begin dates.
     * If begin dates are equal, compares end dates.
     */

    public int compareTo(Object obj) //throws ClassCastException
    {
        TimePeriod otherPeriod = (TimePeriod)obj;
        int compareBegin = begin.compareTo(otherPeriod.begin);
        if (compareBegin == 0)
            return end.compareTo(otherPeriod.end);
        return compareBegin;
    }

    /**
     * @return a hashCode
     */

    public int hashCode()
    {
        return (begin.hashCode() ^ (end.hashCode() >>>32));
    }

    /**
     * @return whether the TimePeriods are equal
     */

    public boolean equals(Object obj)
    {
        if (obj instanceof TimePeriod)
        {
            TimePeriod otherPeriod = (TimePeriod)obj;
            return begin.equals(otherPeriod.begin) && end.equals(otherPeriod.end);
        }
        return false;
    }

    public Object clone()
        throws CloneNotSupportedException
    {
        return new TimePeriod(begin, end);
    }

    /**
    * @return whether the current time period completely contains the passed in time period
    */

    public boolean contains(TimePeriod timePeriod)
    {
        return begin.before(timePeriod.begin) && end.after(timePeriod.end);
    }

    /**
     * Does this timeperiod "contain" the other (if the endpoints are equal, then include them)
     */

    public boolean containsInclusive(TimePeriod timePeriod)
    {
        boolean b1 = begin.before(timePeriod.begin) || begin.equals(timePeriod.begin);
        boolean b2 = end.after(timePeriod.end) || end.equals(timePeriod.end);

        return b1 && b2;
    }
}