package com.agilex.vamf.utils;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import javax.xml.datatype.XMLGregorianCalendar;

import org.joda.time.DateMidnight;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.Duration;
import org.joda.time.Minutes;
import org.joda.time.Months;
import org.joda.time.Seconds;
import org.joda.time.Years;

/**
 * 
 * @author j.ray
 */
public class DateHelper {
	private static final String DEFAULT_DATE_FORMAT = "MM/dd/yyyy";
	private static final String DEFAULT_DATETIME_FORMAT = "MM/dd/yyyy HH:mm:ss";
	private static final String DEFAULT_DATETIMEMILLIS_FORMAT = "MM-dd-yyyy HH:mm:ss,SSS";
	public static final String VISTA_DATETIME_FORMAT = "yyyyMMdd.HHmmss";
	public static final String VISTA_DOB_DATE_FORMAT = "yyyyMMdd";
	public static final String REPORT_DATE_FORMAT = "MMM dd, yyyy";
	public static final String REPORT_DETAILED_DATE_FORMAT = "dd MMM yyyy @ HHmm";
	public static final String REPORT_DETAILED_DATE_FORMAT_NO_TIME = "dd MMM yyyy";
	private static final String DEFAULT_DATETIME_NOSECONDS_FORMAT = "MM/dd/yyyy HH:mm";
    private static final int VISTA_DATE_OFFSET = 17000000;

	public static Date parseDate(String date) {
		return parse(date, DEFAULT_DATE_FORMAT);
	}

	public static Date parseDateTime(String date) {
		return parse(date, DEFAULT_DATETIME_FORMAT);
	}

	public static Date parseDateTimeWithMilliseconds(String date) {
		return parse(date, DEFAULT_DATETIMEMILLIS_FORMAT);
	}

	public static Date parseVistaDateTimeOrNull(String date) {
		try {
			return parse(date, VISTA_DATETIME_FORMAT);
		} catch (RuntimeException e) {
			return null;
		}
	}

	public static Date parse(String date, String format) {
		SimpleDateFormat df = new SimpleDateFormat(format);
		Date dt = null;
		if (NullChecker.isNotNullish(date)) {
			try {
				dt = df.parse(date);
			} catch (ParseException e) {
				throw new RuntimeException(e);
			}
		}
		return dt;
	}

	public static String formatDate(Date date) {
		return format(date, DEFAULT_DATE_FORMAT);
	}

	public static String formatDateTime(Date date) {
		return format(date, DEFAULT_DATETIME_FORMAT);
	}

	public static String formatDateTimeWithMilliseconds(Date date) {
		return format(date, DEFAULT_DATETIMEMILLIS_FORMAT);
	}

	public static String formatDateTimeInVistaFormat(Date date) {
		return format(date, VISTA_DATETIME_FORMAT);
	}

	public static String formatDateTimeInReportFormat(Date date) {
		return format(date, REPORT_DATE_FORMAT);
	}

	public static String formatDateTimeInReportDetailedFormat(Date date) {
		return format(date, REPORT_DETAILED_DATE_FORMAT);
	}
	
	public static boolean greaterThanOrEqual(Date left, Date right) {
		Date l = DateHelper.beginOfDate(left);
		Date r = DateHelper.beginOfDate(right);
		return l.getTime() >= r.getTime();
	}

	public static String format(Date date, String format) {
		if (date==null) {
			return "";
		}
		SimpleDateFormat df = new SimpleDateFormat(format);
		return df.format(date);
	}

	public static String formatDateTimeNoSeconds(Date date) {
		return format(date, DEFAULT_DATETIME_NOSECONDS_FORMAT);
	}

	public static long dateToSeconds(Date date) {
		long milliseconds = date.getTime();
		long seconds = milliseconds / 1000;
		return seconds;
	}

	public static Date dateFromSeconds(long seconds) {
		long milliseconds = seconds * 1000;
		Date dt = new Date(milliseconds);
		return dt;
	}

	public static Date dateFromSeconds(double seconds) {
		return dateFromSeconds((long) seconds);
	}

	public static Date parseVistaDate(String date) {
		final int vistaDateOffset = 17000000;
		Date dt = null;
		if (NullChecker.isNotNullish(date)) {
			String[] timeParts = date.split("\\.");
			date = timeParts[0];
			int dateAsInt = Integer.parseInt(date);
			if(Integer.toString(dateAsInt).length() == 7)
				dateAsInt += vistaDateOffset;
			date = Integer.toString(dateAsInt);
			dt = parse(date, "yyyyMMdd");
		}
		return dt;
	}

	public static Date getPreviousMonth() {
		Calendar cal = Calendar.getInstance();

		cal.add(Calendar.MONTH, -1);
		Date lastMonth = cal.getTime();

		return lastMonth;
	}

	public static Date get3MonthsFromNow() {
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.MONTH, 3);
		Date threeMonthsFromNow = cal.getTime();
		return threeMonthsFromNow;
	}
	
	public static Date xDaysFromNow(int amount) {
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.DAY_OF_YEAR, amount);
		return cal.getTime();
	}
	
	public static Date xDaysBeforeNow(int amount) {
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.DAY_OF_YEAR, -amount);
		return cal.getTime();
	}

	public static Date get6MonthsAgo() {
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.MONTH, -6);
		Date sixMonthsAgo = cal.getTime();
		return sixMonthsAgo;
	}
    
	public static Date get3MonthsAgo() {
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.MONTH, -3);
		Date threeMonthsAgo = cal.getTime();
		return threeMonthsAgo;
	}

    public static Date getOneYearAgo() {
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.YEAR, -1);
        Date oneYearAgo = cal.getTime();
        return oneYearAgo;
    }
    
	public static Date get30DaysFromNow() {
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.DAY_OF_YEAR, 30);
		Date thirtyDaysFromNow = cal.getTime();
		return thirtyDaysFromNow;
	}

	public static Date get120DaysFromNow() {
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.DAY_OF_YEAR, 120);
		Date oneTwentyDaysFromNow = cal.getTime();
		return oneTwentyDaysFromNow;
	}
	
	public static int calculateDeltaInYears(Date a, Date b) {
		DateTime jodaDateA = new DateTime(a);
		DateTime jodaDateB = new DateTime(b);
		return Years.yearsBetween(jodaDateA, jodaDateB).getYears();
	}

	public static int calculateDeltaInMonth(Date a, Date b) {
		DateTime jodaDateA = new DateTime(a);
		DateTime jodaDateB = new DateTime(b);
		return Months.monthsBetween(jodaDateA, jodaDateB).getMonths();
	}

	public static int calculateDeltaInDays(Date a, Date b) {
		DateTime jodaDateA = new DateTime(a);
		DateTime jodaDateB = new DateTime(b);
		return Days.daysBetween(jodaDateA, jodaDateB).getDays();
	}
	
	public static int calculateDeltaInMinutes(Date a, Date b) {
		DateTime jodaA = new DateTime(a);
		DateTime jodaB = new DateTime(b);
		return Minutes.minutesBetween(jodaA, jodaB).getMinutes();
	}
	
	public static int calculateDeltaInSeconds(Date a, Date b) {
		DateTime jodaA = new DateTime(a);
		DateTime jodaB = new DateTime(b);
		return Seconds.secondsBetween(jodaA, jodaB).getSeconds();
	}

	public static long calculateDeltaInMillis(Date a, Date b) {
		DateTime jodaDateA = new DateTime(a);
		DateTime jodaDateB = new DateTime(b);
		Duration duration = new Duration(jodaDateA, jodaDateB);
		return duration.getMillis();
	}

	public static Date toDate(XMLGregorianCalendar xmlGregorianCalendar) {
		if (xmlGregorianCalendar != null) {
			if (xmlGregorianCalendar.getYear() == 0001) {
				return null;
			}
			return xmlGregorianCalendar.toGregorianCalendar().getTime();
		}
		return null;
	}

	/**
	 * Convert HL7 date/time/zone format to java Date type.
	 * 
	 * HL7 supports following date/time format: YYYYMMDDHHMMSS.SSSS+/-ZZZZ with
	 * the time zone (+/-ZZZZ) represented as +/-HHMM offset from UTC (formerly
	 * Greenwich Mean Time (GMT)), where +0000 or -0000 both represent UTC
	 * (without offset). Example: 19760704010159-0700 where UTC is 7 hours ahead
	 * of PDT time zone.
	 * 
	 */
	public static Date parseHL7DateFormat(String xsdDate) {
		Date jDate = null;
		try {
			// Assume there are zone part in date string, use date/time with
			// zone format
			DateFormat hl7DateTimeWZoneFormat = new SimpleDateFormat("yyyyMMddZ");
			jDate = hl7DateTimeWZoneFormat.parse(xsdDate);
		} catch (ParseException ex) {
			try {
				// If zone is not available, use date/time with no zone format
				DateFormat hl7DateTimeNoZoneFormat = new SimpleDateFormat("yyyyMMddHHmmssZ");
				jDate = hl7DateTimeNoZoneFormat.parse(xsdDate);
			} catch (ParseException ex1) {
				try {
					// No time portion in date string, use date with zone format
					DateFormat hl7DateWZoneFormat = new SimpleDateFormat("yyyyMMddHHmmss");
					jDate = hl7DateWZoneFormat.parse(xsdDate);
				} catch (ParseException ex2) {
					try {
						// No time and zone portion in date string, use date
						// with no time and zone format
						DateFormat hl7DateFormat = new SimpleDateFormat("yyyyMMdd");
						jDate = hl7DateFormat.parse(xsdDate);
					} catch (ParseException ex3) {
						ex3.printStackTrace();
					}
				}
			}
		}
		return jDate;
	}

	public static boolean areEqual(Date dt1, Date dt2) {
		return (dt1.compareTo(dt2) == 0);
	}

	public static Date beginOfDate(Date today) {
		return new DateMidnight(today.getTime()).toDate();
	}

	public static Date endOfDay(Date date) {
		if (date == null)
			return null;
		Date beginningOfDay = beginOfDate(date);
		DateTime jD = new DateTime(beginningOfDay);
		return jD.plusDays(1).minusSeconds(1).toDate();
	}

	public static Date plusDays(Date date, int increment) {
		DateTime dateBeforeIncrement = new DateTime(date);
		return dateBeforeIncrement.plusDays(increment).toDate();
	}

	public static Date plusMinutes(Date date, int minuteIncrement) {
		DateTime dateBeforeIncrement = new DateTime(date);
		return dateBeforeIncrement.plusMinutes(minuteIncrement).toDate();
	}

	public static Date minusMinutes(Date date, int minuteDecrement) {
		DateTime dateBeforeIncrement = new DateTime(date);
		return dateBeforeIncrement.minusMinutes(minuteDecrement).toDate();
	}

	/**
	 * Adds business days to the start of the next day
	 * 
	 * @param date
	 * @param increment
	 * @return
	 */
	public static Date plusBusinessDays(Date date, int increment) {
		Calendar cal = new GregorianCalendar();
		cal.setTime(date);

		int numNonBusinessDays = 0;
		for (int i = 0; i < increment; i++) {
			cal.add(Calendar.DATE, 1);

			if (isWeekend(cal)) {
				numNonBusinessDays++;
			}
		}

		if (numNonBusinessDays > 0) {
			cal.add(Calendar.DATE, numNonBusinessDays);

			if (isWeekend(cal)) {
				cal.add(Calendar.DATE, 1);
			}
		}

		return cal.getTime();
	}

	public static Date minusDays(Date date, int decrement) {
		DateTime dateBeforeDecrement = new DateTime(date);
		return dateBeforeDecrement.minusDays(decrement).toDate();
	}

	public static Date getYesterday() {
		DateTime now = new DateTime();
		return now.minusDays(1).toDate();
	}

	public static Date getToday() {
		DateTime now = new DateTime();
		return now.toDate();
	}

	public static Date getTomorrow() {
		DateTime today = new DateTime();
		return today.plusDays(1).toDate();
	}

	public static Date minusMonths(Date date, int increment) {
		DateTime dateBeforeDecrement = new DateTime(date);
		return dateBeforeDecrement.minusMonths(increment).toDate();
	}

    public static String convertDateToVistaFormat(Date date){
        String convertedDate;
        String vistaFormat = DateHelper.formatDateTimeInVistaFormat(date);
        String[] timeParts = vistaFormat.split("\\.");
        String  datePart = timeParts[0];
        int dateAsInt = Integer.parseInt(datePart);
        if(Integer.toString(dateAsInt).length() == 8)
            dateAsInt -= VISTA_DATE_OFFSET;
        convertedDate = Integer.toString(dateAsInt);
        return convertedDate;
    }

    public static Date parseVistaDateTime(String date) {
        Date dt = null;
        if (NullChecker.isNotNullish(date)) {
            String[] timeParts = date.split("\\.");
            date = timeParts[0];
            int dateAsInt = Integer.parseInt(date);
            if(Integer.toString(dateAsInt).length() == 7)
                dateAsInt += VISTA_DATE_OFFSET;
            date = Integer.toString(dateAsInt);
            dt = parse(date, "yyyyMMdd" );
            if(timeParts[1] != null){
                DateTime dateTime = new DateTime(dt);
                dateTime = dateTime.plusHours(getHours(timeParts[1]));
                dateTime = dateTime.plusMinutes(getMinutes(timeParts[1]));
                dateTime = dateTime.plusSeconds(getSeconds(timeParts[1]));
                dt = dateTime.toDate();
            }
        }
        return dt;
    }

	private static boolean isWeekend(Calendar cal) {
		boolean result = false;
		if (cal.get(Calendar.DAY_OF_WEEK) == 1 || cal.get(Calendar.DAY_OF_WEEK) == 7) {
			result = true;
		}
		return result;
	}

    private static int getHours(String time){
        if(time.length() >= 2){
            String hours = time.substring(0,2);
            return Integer.parseInt(hours);
        }
        return 0;
    }

    private static int getMinutes(String time){
        if(time.length() >= 3){
            String minutes = "0";
            if(time.length() == 3)
                minutes = time.substring(2,3) + "0";
            if(time.length() >= 4)
                minutes = time.substring(2,4);
            return Integer.parseInt(minutes);
        }
        return 0;
    }

    private static int getSeconds(String time){
        if(time.length() >= 5){
            String seconds = "0";
            if(time.length() == 5)
                seconds = time.substring(4,5) + "0";
            if(time.length() == 6)
                seconds = time.substring(4,6);
            return Integer.parseInt(seconds);
        }
        return 0;
    }
}
