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

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

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();
	}

	/**
	 * Parse the given string into a Date object.
	 * 
	 * @param date
	 * @param pattern
	 * @return
	 * @throws ParseException
	 */
	public static Date parseDate(String date, String pattern) throws ParseException {
		String[] patterns = new String[1];
		patterns[0] = pattern;
		return DateUtils.parseDate(date, patterns);
	}

	public static Date parseDate(String date, String pattern, String timeZone)
			throws ParseException {
		String[] patterns = new String[1];
		patterns[0] = pattern + "z";
		return DateUtils.parseDate(date + " " + timeZone, patterns);
	}

	public static Date parseDate(String str, String pattern, TimeZone timeZone)
			throws ParseException {
		String[] parsePatterns = new String[1];
		parsePatterns[0] = pattern;

		if (str == null || parsePatterns == null)
			throw new IllegalArgumentException("Date and Patterns must not be null");
		SimpleDateFormat parser = null;
		ParsePosition pos = new ParsePosition(0);
		for (int i = 0; i < parsePatterns.length; i++) {
			if (i == 0) {
				parser = new SimpleDateFormat(parsePatterns[0]);
				parser.setTimeZone(timeZone);
			} else
				if (parser != null) {
					parser.applyPattern(parsePatterns[i]);
				}
			pos.setIndex(0);
			Date date = (parser != null) ? parser.parse(str, pos) : null;
			if (date != null && pos.getIndex() == str.length())
				return date;
		}

		throw new ParseException("Unable to parse the date: " + str, -1);
	}

	/**
	 * 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);
	}

	/*
	 * had to copy this from commons-lang 2.4 DateUtils (addDays, addWeeks, etc)
	 * due to WebLogic being a POC and requiring 2.1
	 */
	public static Date add(Date date, int calendarField, int amount) {
		if (date == null)
			throw new IllegalArgumentException("The date must not be null");

		Calendar c = Calendar.getInstance();
		c.setTime(date);
		c.add(calendarField, amount);
		return c.getTime();
	}

	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();

	}

	public static Date clearTime(Date date) {
		if (date == null)
			return null;

		Calendar calendar = Calendar.getInstance();
		calendar.clear();
		calendar.setTime(date);
		calendar.clear(Calendar.HOUR);
		calendar.clear(Calendar.MINUTE);
		calendar.clear(Calendar.SECOND);
		calendar.clear(Calendar.MILLISECOND);
		return calendar.getTime();
	}

	public static Date getDateTodayPlusDays(int days) {
		Calendar calendar = Calendar.getInstance();
		int year = calendar.get(Calendar.YEAR);
		int month = calendar.get(Calendar.MONTH);
		int day = calendar.get(Calendar.DATE) + days;
		calendar.clear();
		calendar.set(year, month, day);
		return calendar.getTime();
	}

}