package org.treesched.schedules;

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

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

/**
 * Implement a schedule based on days of the month. The day of the month is given
 * as an integer, but unlike the java GregorianCalander, 0 and negative numbers are taken to be
 * this month, not the previous month. i.e. on="0" is the last day of the month.
 * <p>
 * Typical configuration is
 * <code><pre>
 * &lt;dayofmonth from="17" to="22"/&gt;
 * &lt;dayofmonth on="15"/&gt;
 * </pre></code>
 * 
 * @author Rob Gordon
 */


final public class DayOfMonthSchedule extends ConstrainedSchedule implements Serializable {

    private static final long serialVersionUID = 20050226;
    
    /** from day of month. */
    private Integer fromDay;
    
    /** to day of month. */
    private Integer toDay;
    
    /*
     *  (non-Javadoc)
     * @see org.treesched.ConstrainedSchedule#setFrom(java.lang.String)
     */
    public void setFrom(String from) {
        if (from == null) {
            fromDay = null;
        }
        else {
            fromDay = new Integer(from);            
        }
    }
    
    /*
     *  (non-Javadoc)
     * @see org.treesched.ConstrainedSchedule#getFrom()
     */
    public String getFrom() {
        if (fromDay == null) {
            return null;
        }
        return fromDay.toString();
    }
    
    /*
     *  (non-Javadoc)
     * @see org.treesched.ConstrainedSchedule#setTo(java.lang.String)
     */
    public void setTo(String to) {
        if (to == null) {
            toDay = null;
        }
        else {
            toDay = new Integer(to);
        }
    }
    
    /*
     *  (non-Javadoc)
     * @see org.treesched.ConstrainedSchedule#getTo()
     */
    public String getTo() {
        if (toDay == null) {
            return null;
        }
        return toDay.toString();
    }

    /**
     * Utility function to get a calendar which 
     * represents the day of the month in which now is.
     * @param now The date to take month from.
     * @param day The day.
     * @return The Calendar.
     */
	private Calendar calendarFor(Date now, int day) {
		GregorianCalendar c1 = new GregorianCalendar();
		c1.setTime(now);
		
		int month = day > 0 ? c1.get(Calendar.MONTH) : c1.get(Calendar.MONTH) + 1;
		
		GregorianCalendar c2 = new GregorianCalendar(
				c1.get(Calendar.YEAR), month, day);
		return c2;
	}
			
	/*
	 *  (non-Javadoc)
	 * @see org.treesched.ConstrainedSchedule#getStartDate(java.util.Date)
	 */
	public Date getStartDate(Date now) {
	    if (fromDay == null) {
	        return new Date(0);
	    }
	    Calendar fromCal = calendarFor(now, fromDay.intValue());
	    if (toDay == null) {
	        return fromCal.getTime();
	    }
	    Calendar toCal = calendarFor(now, toDay.intValue());
	    int nowDay = DateUtils.dayOfMonth(now);
	    int fromDay = fromCal.get(Calendar.DAY_OF_MONTH);
	    int toDay = toCal.get(Calendar.DAY_OF_MONTH);
	    if (fromDay > toDay) {
	        if (nowDay > toDay) {
	            return new GregorianCalendar(
	                    fromCal.get(Calendar.YEAR),
	                    fromCal.get(Calendar.MONTH),
	                    fromDay).getTime();
	        }
	        else {
	            return new GregorianCalendar(
	                    fromCal.get(Calendar.YEAR),
	                    fromCal.get(Calendar.MONTH) - 1,
	                    fromDay).getTime();	            
	        }
	    } 
	    else {
	        if (nowDay > toDay) {
	            return new GregorianCalendar(
	                    fromCal.get(Calendar.YEAR),
	                    fromCal.get(Calendar.MONTH) + 1,
	                    fromDay).getTime();
	        }
	        else {
	            return new GregorianCalendar(
	                    fromCal.get(Calendar.YEAR),
	                    fromCal.get(Calendar.MONTH),
	                    fromDay).getTime();	            
	        }
	    }
	}
	
	/*
	 *  (non-Javadoc)
	 * @see org.treesched.ConstrainedSchedule#getEndDate(java.util.Date)
	 */
	public Date getEndDate(Date now) {
	    if (toDay == null) {
	        return new Date(Long.MAX_VALUE);
	    }
	    Calendar toCal = calendarFor(now, toDay.intValue());
	    if (fromDay == null) {
	        return toCal.getTime();
	    }
	    Calendar fromCal = calendarFor(now, fromDay.intValue());
	    int nowDay = DateUtils.dayOfMonth(now);
	    int fromDay = fromCal.get(Calendar.DAY_OF_MONTH);
	    int toDay = toCal.get(Calendar.DAY_OF_MONTH);
	    Date startOfEndDay;
	    if (fromDay > toDay) {
	        if (nowDay > toDay) {
	            startOfEndDay = new GregorianCalendar(
	                    toCal.get(Calendar.YEAR),
	                    toCal.get(Calendar.MONTH) + 1,
	                    toDay).getTime();
	        }
	        else {
	            startOfEndDay = new GregorianCalendar(
	                    toCal.get(Calendar.YEAR),
	                    toCal.get(Calendar.MONTH),
	                    toDay).getTime();	            
	        }
	    } 
	    else {
	        if (nowDay > toDay) {
	            startOfEndDay = new GregorianCalendar(
	                    toCal.get(Calendar.YEAR),
	                    toCal.get(Calendar.MONTH) + 1,
	                    toDay).getTime();
	        }
	        else {
	            startOfEndDay = new GregorianCalendar(
	                    toCal.get(Calendar.YEAR),
	                    toCal.get(Calendar.MONTH),
	                    toDay).getTime();	            
	        }
	    }
	    return DateUtils.endOfDay(startOfEndDay);
	}
}
