 
/*******************************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ******************************************************************************/
// Package
package gov.va.med.fw.util;

// Java classes
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import org.apache.commons.lang.Validate;

import gov.va.med.fw.model.lookup.AbstractCode;

// ESR classes

/**
 * Project: Framework</br> Created on: 10:10:37 AM </br>
 * 
 * @author DNS   LEV
 */
public class DateUtils extends org.apache.commons.lang.time.DateUtils {

    // Various date formatting constants
    public final static String MM = "MM";
    public final static String MMDD = "MM/dd";
    public final static String YYYY = "yyyy";
    public final static String MMYYYY = "MM/yyyy";
    public final static String MMDDYYYY = "MM/dd/yyyy";
    public final static String MMDDYYYYHHMMSS = "MM/dd/yyyy h:mm:ssa";
    public final static String MMDDYYYYHHMM_EDIT = "MM/dd/yyyy HH:mm";
    
	public static final Code FIRST_QUARTER = new Code("1"); 
	public static final Code SECOND_QUARTER = new Code("2"); 
	public static final Code THIRD_QUARTER = new Code("3"); 
	public static final Code FOURTH_QUARTER = new Code("4"); 
	
	public static final String[] FISCAL_QUARTERS = new String[]{
		FIRST_QUARTER.getCode(),
		SECOND_QUARTER.getCode(),
		THIRD_QUARTER.getCode(),
		FOURTH_QUARTER.getCode()
	};
	
	public static class Code extends AbstractCode {

		/**
		 * An instance of serialVersionUID
		 */
		private static final long serialVersionUID = 1L;

		private Code(String code) {
			super(code);
		}

		public static Code getByCode(String code) {
			return (Code) getCode(Code.class, code);
		}
	}

	/**
	 * A default constructor
	 */
	private DateUtils() {
		super();
	}

    /**
     * Formats a date with the specified format or a default format if null is passed in for the
     * format. The default format does not include a time.
     *
     * @param date The date to format.
     * @param format An optional format.
     *
     * @return The formatted date.
     */
    public static String format(Date date, String format)
    {
        if (date == null)
        {
            return null;
        }
        if (StringUtils.isBlank(format))
        {
            format = MMDDYYYY;
        }
        SimpleDateFormat sdf = new SimpleDateFormat(format);
        sdf.setLenient(false);
        return sdf.format(date);
    }
    
	public static Calendar createCalendar( Date date ) {
		Calendar calendar = Calendar.getInstance();
		calendar.clear();
		calendar.setTime( date );
		return calendar;
	}
	
	public static Calendar createCalendar(int year) {
		Calendar calendar = Calendar.getInstance();
		calendar.clear();
		calendar.set(Calendar.YEAR, year);
		return calendar;
	}

	public static Calendar createCalendar(int year, int month) {
		Calendar calendar = createCalendar(year);
		calendar.set(Calendar.MONTH, month - 1);
		return calendar;
	}

	public static Calendar createCalendar(int year, int month, int day) {
		Calendar calendar = createCalendar(year, month);
		calendar.set(Calendar.DAY_OF_MONTH, day);
		return calendar;
	}

	public static Calendar createCalendar(int year, int month, int day, int hour) {
		Calendar calendar = createCalendar(year, month, day);
		calendar.set(Calendar.HOUR_OF_DAY, hour);
		return calendar;
	}

	public static Calendar createCalendar(int year, int month, int day, int hour,
			int minute) {
		Calendar calendar = createCalendar(year, month, day, hour);
		calendar.set(Calendar.MINUTE, minute);
		return calendar;
	}

	public static Calendar createCalendar(int year, int month, int day, int hour,
			int minute, int second) {
		Calendar calendar = createCalendar(year, month, day, hour, minute);
		calendar.set(Calendar.SECOND, second);
		return calendar;
	}

	public static Calendar createCalendar(int year, int month, int day, int hour,
			int minute, int second, int millisecond) {
		Calendar calendar = createCalendar(year, month, day, hour, minute,
				second);
		calendar.set(Calendar.MILLISECOND, millisecond);
		return calendar;
	}
	
	public static Calendar getFiscalYearFirstQuarter( int year, boolean startDate ) {
		Calendar time = createCalendar( year );
		GregorianCalendar calendar = new GregorianCalendar( time.get( Calendar.YEAR ), 
																			 time.get( Calendar.MONTH ),
																			 time.get( Calendar.DATE ) );
		if( startDate ) {
			calendar.roll( Calendar.MONTH, -3 );
			calendar.roll( Calendar.YEAR, -1 );
			return calendar;
		}
		else {
			calendar.roll( Calendar.DATE, -1 );
			calendar.roll( Calendar.MONTH, -1 );
			calendar.roll( Calendar.YEAR, -1 );
			return calendar;
		}
	}
	
	public static Calendar getFiscalYearSecondQuarter( int year, boolean startDate ) {
		Calendar time = createCalendar( year );
		GregorianCalendar calendar = new GregorianCalendar( time.get( Calendar.YEAR ), 
																			 time.get( Calendar.MONTH ),
																			 time.get( Calendar.DATE ) );
		if( startDate ) {
			return calendar;
		}
		else {
			calendar.roll( Calendar.MONTH, 2 );
			calendar.set( Calendar.DATE, 31 );
			return calendar;
		}
	}
	
	public static Calendar getFiscalYearThirdQuarter( int year, boolean startDate ) {
		Calendar time = createCalendar( year );
		GregorianCalendar calendar = new GregorianCalendar( time.get( Calendar.YEAR ), 
																			 time.get( Calendar.MONTH ),
																			 time.get( Calendar.DATE ) );
		if( startDate ) {
			calendar.roll( Calendar.MONTH, 3 );
			return calendar;
		}
		else {
			calendar.roll( Calendar.MONTH, 5 );
			calendar.set( Calendar.DATE, 30 );
			return calendar;
		}
	}
	
	public static Calendar getFiscalYearFourthQuarter( int year, boolean startDate ) {
		Calendar time = createCalendar( year );
		GregorianCalendar calendar = new GregorianCalendar( time.get( Calendar.YEAR ), 
																			 time.get( Calendar.MONTH ),
																			 time.get( Calendar.DATE ) );
		if( startDate ) {
			calendar.roll( Calendar.MONTH, 6 );
			return calendar;
		}
		else {
			calendar.roll( Calendar.MONTH, 8 );
			calendar.set( Calendar.DATE, 30 );
			return calendar;
		}
	}
	
	public static Calendar getFiscalYearQuarter( int year, boolean startDate, String quarter ) {
		Calendar calendar = null;
		if( FIRST_QUARTER.getCode().equals( quarter ) ) {
			calendar = getFiscalYearFirstQuarter( year, startDate );
		}
		else if( SECOND_QUARTER.getCode().equals( quarter ) ) {
			calendar = getFiscalYearSecondQuarter( year, startDate );
		}
		else if( THIRD_QUARTER.getCode().equals( quarter ) ) {
			calendar = getFiscalYearThirdQuarter( year, startDate );
		}
		else if( FOURTH_QUARTER.getCode().equals( quarter ) ) {
			calendar = getFiscalYearFourthQuarter( year, startDate );
		}
		return calendar;
	}
	
	public static int getFiscalYear( Date date ) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime( date );
	
		return getFiscalYear( calendar ); 
	}
	
	public static int getFiscalYear( Calendar calendar ) {
		Validate.notNull( calendar, "A calendar must not be null" );
		
		int year = calendar.get( Calendar.YEAR );
		Date start = getFiscalYearFirstQuarter( year, true ).getTime();
		Date end = getFiscalYearFourthQuarter( year, false ).getTime();
		Date date = calendar.getTime();
		
		if( !(start.equals( date ) || start.before( date )) ||
			 !(end.equals( date ) || end.after( date )) ) {
			year += 1;
		}
		return year;
	}
	
    public static boolean isDateInYear(Integer year, Date date) {
        Validate.notNull( year, "A year code must not be null" );
        Validate.notNull( date, "A date must not be null" );

        Calendar start = Calendar.getInstance();
        start.set(year.intValue(), Calendar.JANUARY, 1, 0, 0, 0);
        
        Calendar end = Calendar.getInstance();
        end.set(year.intValue(), Calendar.DECEMBER, 31, 23, 59, 59);
       
        return isDateBetween(date, start.getTime(), end.getTime());
       
    }

    public static boolean isDateBetween(Date date, Date start, Date end) {
        boolean inBetween = false;
        if( (start.equals( date ) || start.before( date )) &&
             (end.equals( date ) || end.after( date )) ) {
            inBetween = true;
        }
        return inBetween;
    }

    public static String getQuarterAsString( Date date ) {
		
		return getQuarter(date)+ " Quarter";
	}
	 
	public static String getQuarter( Date date ) {
		String quarter = null;
		for( int i=0; i<FISCAL_QUARTERS.length; i++ ) {
			if( isInQuarter( FISCAL_QUARTERS[i], date ) ) {
				quarter = String.valueOf( (i+1) ); 	
			}
		}
		return quarter;
	}	
	
	public static String getQuarterDateRangeAsString( Date date ) {
	
		DateFormat df = DateFormat.getDateInstance( DateFormat.MEDIUM );
		Calendar[] calendars = null;
		for( int i=0; i<FISCAL_QUARTERS.length; i++ ) {
			if( isInQuarter( FISCAL_QUARTERS[i], date ) ) {
				calendars = getStartEndDateForQuarter( FISCAL_QUARTERS[i], date );
				break;
			}
		}
		return calendars != null ? (df.format( calendars[0].getTime() ) + " - " + df.format( calendars[1].getTime() )) : null;
	}
	
	public static boolean isInQuarter( String quarter, Date date ) {
		Validate.notNull( date, "A date must not be null" );
		Validate.notNull( quarter, "A quarter code must not be null" );
		Calendar[] calendars = getStartEndDateForQuarter( quarter, date );
		
		Date start = calendars[0].getTime();
		Date end = calendars[1].getTime();
		
        return isDateBetween(date, start, end);
	}
	
	public static Calendar[] getStartEndDateForQuarter( String quarter, Date date ) {
		
		Calendar[] calendars = new Calendar[2];
		int year = getFiscalYear( date );
		for( int i=0; i<FISCAL_QUARTERS.length; i++ ) {
			if( FISCAL_QUARTERS[i].equals( quarter ) ) {
				calendars[0] = getFiscalYearQuarter( year, true, FISCAL_QUARTERS[i] );
				calendars[1] = getFiscalYearQuarter( year, false, FISCAL_QUARTERS[i] );
				break;
			}
		}
		return calendars;
	}
	
	public static int getQuarterNumber( Date date ) {
		int quarter = 0;
		if(date!=null)
		{
			for( int i=0; i<FISCAL_QUARTERS.length; i++ ) {
				if( isInQuarter( FISCAL_QUARTERS[i], date ) ) {
					quarter = i+1; 	
				}
			}
		}
		return quarter;
	}	
	
	public static Calendar[] getStartEndDateForPreviousQuarter(Date date ) {
		
		Calendar[] calendars = new Calendar[2];
		if(date!=null)
		{
			int previousQuarter=0;
			int currentQuarter=getQuarterNumber(date);
			int year = getFiscalYear( date );		
			if(String.valueOf(currentQuarter).equals(FIRST_QUARTER.getCode()))
			{
				year=year-1;
				previousQuarter=4;
			}else
			{
				previousQuarter=currentQuarter-1;
			}		
			calendars[0] = getFiscalYearQuarter( year, true, String.valueOf(previousQuarter));
			calendars[1] = getFiscalYearQuarter( year, false, String.valueOf(previousQuarter) );	
		}
		return calendars;
	}
    
	public static Calendar[] getStartEndDateForFiscalYear( Date date ) {
		
		Calendar[] calendars = new Calendar[2];
		if(date!=null)
		{
			int year = getFiscalYear( date );
			calendars[0]=getFiscalYearQuarter( year, true, FIRST_QUARTER.getCode() );
			calendars[1] = getFiscalYearQuarter( year, false, FOURTH_QUARTER.getCode() );
		}
		return calendars;
	}
	
	public static Calendar[] getStartEndDateForPreviousFiscalYear( Date date ) {
		
		Calendar[] calendars = new Calendar[2];
		if(date!=null)
		{
			int year = getFiscalYear( date );
			calendars[0]=getFiscalYearQuarter( year-1, true, FIRST_QUARTER.getCode() );
			calendars[1] = getFiscalYearQuarter( year-1, false, FOURTH_QUARTER.getCode() );		
		}
		return calendars;
	}
	
//	public static Calendar[] getStartEndDateForPreviousMonth( Date date ) {
//		
//		Calendar[] calendars = new Calendar[2];
//		if(date!=null)
//		{
//			Calendar calendar = Calendar.getInstance();
//			calendar.setTime( date );
//			//IF month is Jan roll year also.
//			if(calendar.get(Calendar.MONTH)==0)
//				calendar.roll( Calendar.YEAR, -1 );
//			calendar.roll( Calendar.MONTH, -1 );		
//			calendars[0]=createCalendar(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH)+1, 1);
//			calendars[1]=createCalendar(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH)+1, 
//					calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
//		}
//			
//		return calendars;
//	}
	
 public static Calendar[] getStartEndDateForPreviousMonth( Date date ) {
		
		Calendar[] calendars = new Calendar[2];
		if(date!=null)
		{
			Calendar calendar = Calendar.getInstance();
			calendar.setTime( date );			
			int month=calendar.get(Calendar.MONTH);			
			calendar.set(Calendar.MONTH, month-1);
			calendars[0]=createCalendar(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH)+1, 1);
			calendars[1]=createCalendar(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH)+1, 
					calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
		}
			
		return calendars;
	}
	
	public static Calendar[] getStartEndDateForPreviousWeek( Date date ) {
		Calendar[] calendars = new Calendar[2];
		if(date!=null)
		{
			Calendar calendar = Calendar.getInstance();
			calendar.setTime( date );
			calendar.set( Calendar.DAY_OF_WEEK, Calendar.SUNDAY );	
			int weekOfMonth=calendar.get(Calendar.WEEK_OF_MONTH);			
			calendar.set( Calendar.WEEK_OF_MONTH, weekOfMonth-1 );
			calendars[0]=createCalendar(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH)+1, calendar.get(Calendar.DAY_OF_MONTH));
			calendar.set( Calendar.DAY_OF_WEEK, Calendar.SATURDAY );
			calendars[1]=createCalendar(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH)+1, calendar.get(Calendar.DAY_OF_MONTH));
		}
			
		return calendars;
	}
	
    public static Date getCurrentDate() {
        Calendar calendar = Calendar.getInstance();
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);
        int day = calendar.get(Calendar.DATE);
        calendar.clear();
        calendar.set(year,month,day);
        return calendar.getTime();
    }

    /**
     * Returns date with the time present
     * @return the current date and time
     */
    public static Date getCurrentDateTime()
    {
        Calendar calendar = Calendar.getInstance();
        return calendar.getTime();
    }

    public static Date getYesterdayDate() {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, -1);
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);
        int day = calendar.get(Calendar.DATE);
        calendar.clear();
        calendar.set(year,month,day);
        return calendar.getTime();
    }

    public static Date getTomorrowDate() {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, 1);
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);
        int day = calendar.get(Calendar.DATE);
        calendar.clear();
        calendar.set(year,month,day);
        return calendar.getTime();
    }
    
    
    /**
     * Compare if the first date is before the second date.
     * In case of null value, not-null date value will get a preference.
     * For example, if the first date is null, false is returned.
     * If the second date is null, first is considered after first, so true is returned.
     * 
     * @param date1 the first date
     * @param date2 the second date
     * @return true if the first date is after second date.
     */
    public static boolean isBefore(Date date1, Date date2) {
        boolean compare = false;
        if( date1 != null && date2 != null ) {
           compare = date1.before( date2 );
        }
        else if (date1 != null) {
            compare = true;
        }
        else if (date2 != null) {
            compare = false;
        }
        return compare;
    }

    public static boolean isBeforeIgnoreTime(Date date1, Date date2) {
    	// use consistent logic as in isBefore
        if (date1 == null && date2 == null)
        	return false;    	
        else if (date2 == null)
        	return true;
        else if (date1 == null)
            return false;
    	
    	return isBefore(truncate(date1, Calendar.DAY_OF_MONTH), truncate(date2, Calendar.DAY_OF_MONTH));
    }

    public static boolean isEqualOrBeforeIgnoreTime(Date date1, Date date2) {
    	// use consistent logic as in isBefore
        if (date1 == null && date2 == null)
        	return true; // true since equals    	
        else if (date2 == null)
        	return true;
        else if (date1 == null)
            return false;
    	
    	return isSameDay(date1, date2) || isBefore(truncate(date1, Calendar.DAY_OF_MONTH), truncate(date2, Calendar.DAY_OF_MONTH));
    }

    public static boolean isEqualOrBefore(Date date1, Date date2) {
    	// use consistent logic as in isBefore
        if (date1 == null && date2 == null)
        	return true; // true since equals    	
        else if (date2 == null)
        	return true;
        else if (date1 == null)
            return false;
    	
    	return isSameDay(date1, date2) || isBefore(date1, date2);
    }
    
    /**
     * Compare if the first date is after the second date.
     * In case of null value, not-null date value will get a preference.
     * For example, if the first date is null, false is returned.
     * If the second date is null, first is considered after first, so true is returned.
     * 
     * @param date1 the first date
     * @param date2 the second date
     * @return true if the first date is after second date.
     */
    public static boolean isAfter(Date date1, Date date2) {
        boolean compare = false;
        if( date1 != null && date2 != null ) {
           compare = date1.after( date2 );
        }
        else if (date1 != null) {
            compare = true;
        }
        else if (date2 != null) {
            compare = false;
        }
        return compare;
    }
    
    public static boolean isAfterIgnoreTime(Date date1, Date date2) { 
    	// use consistent logic as in isAfter
        if (date1 == null && date2 == null)
        	return false;    	
        else if (date2 == null)
        	return true;
        else if (date1 == null)
            return false;

    	return isAfter(truncate(date1, Calendar.DAY_OF_MONTH), truncate(date2, Calendar.DAY_OF_MONTH));
    }

    public static boolean isEqualOrAfterIgnoreTime(Date date1, Date date2) {
    	// use consistent logic as in isAfter
        if (date1 == null && date2 == null)
        	return true; // true since equals    	
        else if (date2 == null)
        	return true;
        else if (date1 == null)
            return false;
    	
    	return isSameDay(date1, date2) || isAfter(truncate(date1, Calendar.DAY_OF_MONTH), truncate(date2, Calendar.DAY_OF_MONTH));
    }
    
    public static boolean isEqualOrAfter(Date date1, Date date2) {
    	// use consistent logic as in isAfter
        if (date1 == null && date2 == null)
        	return true; // true since equals    	
        else if (date2 == null)
        	return true;
        else if (date1 == null)
            return false;
    	
    	return isSameDay(date1, date2) || isAfter(date1, date2);
    }    
    
    public static Date getYearStartDate(int year){
        Calendar cal = Calendar.getInstance();
        cal.clear();
        cal.set(year,Calendar.JANUARY,1);
        return cal.getTime();
    } 
    
    public static Date getYearEndDate(int year){
        Calendar cal = Calendar.getInstance();
        cal.clear();
        cal.set(year,11,31);
        return cal.getTime();
    }
    
    public static Date getAddedDate(int field, int amount)
    {
        Calendar time = Calendar.getInstance();
        return getAddedDate(field, amount, time);
            
    }
    
    public static Date getAddedDate(int field, int amount, Calendar time)
    {        
        GregorianCalendar calendar = new GregorianCalendar( time.get( Calendar.YEAR ), 
                                                                  time.get( Calendar.MONTH ), time.get( Calendar.DATE ), 
                                                                  time.get( Calendar.HOUR_OF_DAY ),time.get( Calendar.MINUTE),
                                                                  time.get( Calendar.SECOND ) );
        calendar.add( field, amount );
        return calendar.getTime();
            
    }
    
}