/*
 * $Id: DateUtils.java,v 1.14 2006/11/27 13:02:41 lawern Exp $
 *
 * Copyright (c) Siemens AG 2008 
 *           (c) Universität Paderborn 2008 
 * Licensed under EUPL 1.1. 
 *
 * $Log: DateUtils.java,v $
 * Revision 1.14  2006/11/27 13:02:41  lawern
 * Der Startzeitpunkt der kNN-Parameteroptimierung kann jetzt mit einem Wochentag versehen werden.
 *
 * Revision 1.13  2006/08/31 19:59:10  marbobo
 * + Refactoring
 *
 * Revision 1.12  2006/07/18 11:42:12  lawern
 * Stoppwörter aus den Testvektoren für die Optimierung rausgeschmissen.
 *
 * Revision 1.11  2006/07/17 13:04:01  lawern
 * Neuer typografiegewichteter Index und k-NN-Klassifikation aus meiner Diss.
 *
 * Revision 1.10  2006/05/27 18:34:47  marbobo
 * + Neueste Lucene-Version eingebaut
 *
 * Revision 1.9  2005/08/26 14:55:19  jonyp
 * new date utility method
 *
 * Revision 1.8  2004/11/10 15:02:47  koppy
 * Projektweite Überarbeitung von Error-Logs, Stacktraces,..
 *
 * Revision 1.7  2004/07/20 08:46:27  jonyp
 * added copyright info + cvs log
 *
 *
 */
package vkc.util;

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

public final class DateUtils {
	public static final Date MIN_DATE_FOR_LUCENE;
	public static final Date MAX_DATE_FOR_LUCENE;
	
	static {
		long time = 8000L * 365 * 24 * 60 * 60 * 1000;
		MIN_DATE_FOR_LUCENE = new Date(-time);
		MAX_DATE_FOR_LUCENE = new Date(time);
	}

	private static final SimpleDateFormat[] DATE_FORMATTERS = new SimpleDateFormat[2];

	static {
		DATE_FORMATTERS[0] = new SimpleDateFormat("dd.MM.yy");
		DATE_FORMATTERS[0].setLenient(false);

		DATE_FORMATTERS[1] = new SimpleDateFormat("dd.MM.yyyy");
		DATE_FORMATTERS[1].setLenient(false);
	}

	private static final DateFormat[] TIME_FORMATTERS = new SimpleDateFormat[5];

	static {
		TIME_FORMATTERS[0] = DateFormat.getTimeInstance(DateFormat.SHORT, Locale.GERMANY);
		TIME_FORMATTERS[0].setLenient(false);
        TIME_FORMATTERS[1] = new SimpleDateFormat("EEE, HH:mm:ss", Locale.US);
        TIME_FORMATTERS[1].setLenient(false);
        TIME_FORMATTERS[2] = new SimpleDateFormat("EEE, HH:mm", Locale.US);
        TIME_FORMATTERS[2].setLenient(false);
        TIME_FORMATTERS[3] = new SimpleDateFormat("EE, HH:mm:ss", Locale.GERMANY);
        TIME_FORMATTERS[3].setLenient(false);
        TIME_FORMATTERS[4] = new SimpleDateFormat("EE, HH:mm", Locale.GERMANY);
        TIME_FORMATTERS[4].setLenient(false);
	}

	/**
	 * Method parses a string ( must be in a supported format,
	 * see SIMPLEDATE_FORMATTERS) and converts it into a Date object
	 * <p>
	 * @return date object corresponding to the date string. null if the
	 * date string is in a nonparsable/unsupported format.
	 * @author Hardy Höfer
	 */
	public static Date parseDate(String dateString) {
		for (int i = 0; i < DATE_FORMATTERS.length; i++) {
			try {
				return DATE_FORMATTERS[i].parse(dateString);
			} catch (Exception e) {
			}
		}

		return null;
	}

	/**
	 * Method parses a day time string and converts it into a date object
	 * normalized to 01.01.1970
	 */
	public static Date parseTime(String dateString) {
		for (int i = 0; i < TIME_FORMATTERS.length; i++) {
			try {
				Date d = TIME_FORMATTERS[i].parse(dateString);
				GregorianCalendar cal = new GregorianCalendar();
				cal.setTime(d);
				cal.set(Calendar.YEAR, 1970);
				cal.set(Calendar.DAY_OF_YEAR, 1);

				return cal.getTime();
			} catch (Exception e) {
			}
		}

		return null;
	}
    
    /**
     * Method parses a day time string and converts it into next possible date object.
     */
    public static Date parseNextTime(String dateString) {
        for (int i = 0; i < TIME_FORMATTERS.length; i++) {
            try {
                Date d = TIME_FORMATTERS[i].parse(dateString);
                GregorianCalendar currDate = new GregorianCalendar();
                currDate.setTime(new Date());
                GregorianCalendar cal = new GregorianCalendar();
                cal.setTime(d);
                
                int weekDayOffset = 0;
                boolean dateStringContainsDay = false;
                if (dateString.indexOf(',') > 0) {
                    dateStringContainsDay = true;
                    // Wochentag des dateString ermitteln
                    int weekDay = ((int) (cal.getTimeInMillis() / 86400000L) + 5) % 7;

                    // prüfen ob es sich um heute handelt
                    weekDayOffset = weekDay
                            - currDate.get(Calendar.DAY_OF_WEEK);
                    if (weekDayOffset < 0)
                        weekDayOffset += 7;
                }

                cal.set(Calendar.YEAR, currDate.get(Calendar.YEAR));
                cal.set(Calendar.DAY_OF_YEAR, currDate
                        .get(Calendar.DAY_OF_YEAR));

                if (weekDayOffset == 0) {
                    //heute
                    if (cal.getTime().compareTo(currDate.getTime()) > 0) {
                        return cal.getTime();
                    } else {
                        if (!dateStringContainsDay) {
                            //Datum von morgen wählen, wenn kein Wochentag enthalten
                            return addDays(cal.getTime(), 1);
                        } else {
                            //Datum in nächster Woche wählen, da Wochentag enthalten
                            return addDays(cal.getTime(), 7);
                        }
                    }
                } else {
                    //Tag in der Zukunft
                    return addDays(cal.getTime(), weekDayOffset);
                }

                
            } catch (Exception e) {
            }
        }

        return null;
    }

	/**
	 * Adds the specifed number of day to a date ( may also be negativ )
	 * <p>
	 * @author Hardy Höfer
	 */
	public static Date addDays(Date d, int count) {
		if (d == null) {
			return null;
		}

		GregorianCalendar cal = new GregorianCalendar();
		cal.setTime(d);
		cal.add(Calendar.DAY_OF_YEAR, count);

		return cal.getTime();
	}

	/**
	 * Adds the specifed number of months to a date ( may also be negativ )
	 * <p>
	 * @author Hardy Höfer
	 */
	public static Date addMonths(Date d, int count) {
		if (d == null) {
			return null;
		}

		GregorianCalendar cal = new GregorianCalendar();
		cal.setTime(d);
		cal.add(Calendar.MONTH, count);

		return cal.getTime();
	}

	/**
	 * Adds the specifed number of years to a date ( may also be negativ )
	 * <p>
	 * @author Hardy Höfer
	 */
	public static Date addYears(Date d, int count) {
		if (d == null) {
			return null;
		}

		GregorianCalendar cal = new GregorianCalendar();
		cal.setTime(d);
		cal.add(Calendar.YEAR, count);

		return cal.getTime();
	}

	/**
	 * Adds the specifed number of weeks to a date ( may also be negativ )
	 * <p>
	 * @author Hardy Höfer
	 */
	public static Date addWeeks(Date d, int count) {
		if (d == null) {
			return null;
		}

		if (count < 0) {
			d = getMondayOfWeek(d);
		} else {
			d = getSundayOfWeek(d);
		}

		GregorianCalendar cal = new GregorianCalendar();
		cal.setTime(d);
		cal.add(Calendar.WEEK_OF_YEAR, count);

		return cal.getTime();
	}

	/**
	 * Methods returns a date object associated with the first day of a month
	 * associated with the specified date parameter. The day time of the
	 * returned date object is implicitly minimized which means that the
	 * day time will be set to 00:00:00.000
	 * <p>
	 * @param d - date object
	 */
	public static Date getFirstDayOfMonth(Date d) {
		if (d == null) {
			return d;
		}

		GregorianCalendar cal = new GregorianCalendar();
		cal.setTime(d);
		cal.set(Calendar.HOUR_OF_DAY, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		cal.set(Calendar.DAY_OF_MONTH, 1);

		return cal.getTime();
	}

	/**
	 * Methods returns a date object associated with the last day of a month
	 * associated with the specified date parameter. The day time of the
	 * returned date object is implicitly maximized which means that the
	 * day time will be set to 23:59:59.999
	 * <p>
	 * @param d - date object
	 */
	public static Date getLastDayOfMonth(Date d) {
		if (d == null) {
			return d;
		}

		GregorianCalendar cal = new GregorianCalendar();
		cal.setTime(d);
		cal.set(Calendar.HOUR_OF_DAY, 23);
		cal.set(Calendar.MINUTE, 59);
		cal.set(Calendar.SECOND, 59);
		cal.set(Calendar.MILLISECOND, 999);
		cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));

		return cal.getTime();
	}

	public static Date getMondayOfWeek(Date d) {
		if (d == null) {
			return d;
		}

		GregorianCalendar cal = new GregorianCalendar();
		cal.setTime(d);
		cal.set(Calendar.HOUR_OF_DAY, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);

		return cal.getTime();
	}

	public static Date getSundayOfWeek(Date d) {
		if (d == null) {
			return d;
		}

		GregorianCalendar cal = new GregorianCalendar();
		cal.setTime(d);
		cal.set(Calendar.HOUR_OF_DAY, 23);
		cal.set(Calendar.MINUTE, 59);
		cal.set(Calendar.SECOND, 59);
		cal.set(Calendar.MILLISECOND, 999);
		cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);

		return cal.getTime();
	}

	/**
	 * Method normalizes a date object to 00:00h of the specified day
	 */
	public static Date getMinimizedDayTime(Date theDate) {
		if (theDate == null) {
			return theDate;
		}

		GregorianCalendar cal = new GregorianCalendar();
		cal.setTime(theDate);
		cal.set(Calendar.HOUR_OF_DAY, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);

		return cal.getTime();
	}

	/**
	 * Method normalizes a date object to last possible millisecond
	 * of the specified day
	 */
	public static Date getMaximizedDayTime(Date theDate) {
		if (theDate == null) {
			return theDate;
		}

		GregorianCalendar cal = new GregorianCalendar();
		cal.setTime(theDate);
		cal.set(Calendar.HOUR_OF_DAY, 23);
		cal.set(Calendar.MINUTE, 59);
		cal.set(Calendar.SECOND, 59);
		cal.set(Calendar.MILLISECOND, 999);

		return cal.getTime();
	}

	/**
	 * Returns the associated day of week constant from java.util.Calendar
	 * for a date object
	 * <p>
	 * @author Hardy Höfer
	 */
	public static int getDayOfWeek(Date d) {
		GregorianCalendar gc = new GregorianCalendar();
		gc.setTime(d);

		return gc.get(Calendar.DAY_OF_WEEK);
	}

	/**
	 * Method returns the number of milliseconds elapsed
	 * for the date since 00:00h.
	 * <p>
	 * @author Hardy Höfer
	 */
	public static long getElapsedDayMillis(Date d) {
		Date refDate = getMinimizedDayTime(d);

		return d.getTime() - refDate.getTime();
	}

	/**
	 * Method returns the index of the week day associated with
	 * a date. Monday is equivalent to index 0 and sunday
	 * equivalent to index 6;
	 * <p>
	 * @author Hardy Höfer
	 */
	public static int getDayOfWeekIndex(Date d) {
		GregorianCalendar gc = new GregorianCalendar();
		gc.setTime(d);

		int day = gc.get(Calendar.DAY_OF_WEEK);

		if (day == Calendar.MONDAY) {
			return 0;
		} else if (day == Calendar.TUESDAY) {
			return 1;
		} else if (day == Calendar.WEDNESDAY) {
			return 2;
		} else if (day == Calendar.THURSDAY) {
			return 3;
		} else if (day == Calendar.FRIDAY) {
			return 4;
		} else if (day == Calendar.SATURDAY) {
			return 5;
		}

		return 6;
	}

	/**
	 * Gets a HH.MM.YYY - string from a Date-object
	 * <p>
	 * @author Hardy Höfer
	 */
	public static String getDD_MM_YYYY(Date date, char separator) {
		if (date == null) {
			return "00" + separator + "00" + separator + "0000";
		}

		DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.GERMANY);

		return dateFormat.format(date);
	}

	public static String getDD_MM_YYYY(Date date) {
		return getDD_MM_YYYY(date, '.');
	}

	public static String getYYYY_MM_DD(Date date) {
		DateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd");

		return (dateFormat.format(date));
	}

	public static String getYYYY_MM_DD_HH_MM_SS(Date date) {
		DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");

		return (dateFormat.format(date));
	}

	/**
	 * Gets a HH:MM - string from a Date-object
	 * <p>
	 * @return a string formatted as: HH + separator + MM. If the date
	 * references to null the "00" + separator + "00" is returned.
	 * @author Hardy Höfer
	 */
	public static String getHH_MM(Date date, char separator) {
		if (date == null) {
			return "00" + separator + "00";
		}

		GregorianCalendar cal = new GregorianCalendar();
		cal.setTime(date);

		int val = cal.get(Calendar.HOUR_OF_DAY);
		String hh = val < 10 ? "0" + val : "" + val;

		val = cal.get(Calendar.MINUTE);

		String mm = val < 10 ? "0" + val : "" + val;

		return hh + separator + mm;
	}

	public static String getHH_MM(Date date) {
		return getHH_MM(date, ':');
	}

	/**
	 * Gets a formatted Date-String in the given local format from a Date-object
	 * <p>
	 * @param date The Date Object
	 * @param locale The Locale Object
	 * @return a string formatted in the local format, for example "11.09.2003" for locale "DE" or "Sep 11, 2003" for locale "EN"
	 * @author vchris
	 */
	public static String getDateInLocalFormat(Date date, Locale locale) {
		if (date == null) {
			return "00-00-0000";
		}
		
		DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM, locale);

		return dateFormat.format(date);
	}

	/**
	 * Gets a formatted Time-String in the given local format from a Date-Object
	 * <p>
	 * @param date The Date Object
	 * @param locale The Locale Object
	 * @return a string formatted in the local format, for example "23:11" for locale "DE" or "11:11 p" for locale "EN"
	 * @author vchris
	 */
	public static String getTimeInLocalFormat(Date date, Locale locale) {
		if (date == null) {
			return "00:00";
		}
		
		DateFormat dateFormat = DateFormat.getTimeInstance(DateFormat.SHORT, locale);

		return dateFormat.format(date);
	}

	/**
	 * Gets a HH:MM:SS - string from a Date-object
	 * <p>
	 * @return a string formatted as: HH + separator + MM. If the date
	 * references to null the "00" + separator + "00" is returned.
	 */
	public static String getHH_MM_SS(Date date, char separator) {
		if (date == null) {
			return "00" + separator + "00";
		}

		GregorianCalendar cal = new GregorianCalendar();
		cal.setTime(date);

		int val = cal.get(Calendar.HOUR_OF_DAY);
		String hh = val < 10 ? "0" + val : "" + val;

		val = cal.get(Calendar.MINUTE);

		String mm = val < 10 ? "0" + val : "" + val;

		val = cal.get(Calendar.SECOND);

		String ss = val < 10 ? "0" + val : "" + val;

		return hh + separator + mm + separator + ss;
	}

	public static String getHH_MM_SS(Date date) {
		return getHH_MM_SS(date, ':');
	}

	/**
	 * Returns the current HOUR_OF_DAY
	 * @return the current HOUR_OF_DAY
	 */
	public static final int getCurrentHourOfDay() {
		Calendar calendar = Calendar.getInstance();
		calendar.setTimeInMillis(System.currentTimeMillis());

		return calendar.get(Calendar.HOUR_OF_DAY);
	}

	/**
	 * Formats the number of millis in human readable
	 * representation.
	 * 
	 * @param diffInMills number of millis
	 * @return human readable representation
	 */
	public static final String formatDuration(long diffInMills) {
		long sec = 1000;
		long min = sec * 60;
		long hour = min * 60;
		long day = hour * 24;
		long month = day * 30;
		long year = 365 * day;

		if (diffInMills < min) {
			return String.valueOf(diffInMills / sec) + "[s]";
		} else if (diffInMills < hour) {
			long m = diffInMills / min;
			long s = (diffInMills - m * min) / sec;

			return String.valueOf(m) + "[min] " + String.valueOf(s) + "[s]";
		} else if (diffInMills < day) {
			long h = diffInMills / hour;
			long m = (diffInMills - h * hour) / min;
			long s = (diffInMills - h * hour - m * min) / sec;
			
			return String.valueOf(h) + "[h] " + String.valueOf(m) + "[min] "
					+ String.valueOf(s) + "[s]";
		} else if (diffInMills < month) {
			long d = diffInMills / day;
			long h = (diffInMills - d * day) / hour;
			long m = (diffInMills - d * day - h * hour) / min;
			long s = (diffInMills - d * day - h * hour - m * min) / sec;
			
			return String.valueOf(d) + "[d] " + String.valueOf(h) + "[h] "
					+ String.valueOf(m) + "[min] " + String.valueOf(s) + "[s]";
		} else if (diffInMills < year) {
			long mn = diffInMills / month;
			long d = (diffInMills - mn * month) / day;
			long h = (diffInMills - mn * month - d * day) / hour;
			long m = (diffInMills - mn * month - d * day - h * hour) / min;
			long s = (diffInMills - mn * month - d * day - h * hour - m * min) / sec;
			
			return String.valueOf(mn) + "[m] " + String.valueOf(d) + "[d] "
					+ String.valueOf(h) + "[h] " + String.valueOf(m) + "[min] "
					+ String.valueOf(s) + "[s]";
		} else { //if (diffInMills>=year)
			long y = diffInMills / year;
			long mn = (diffInMills - y * year) / month;
			long d = (diffInMills - y * year - mn * month) / day;
			long h = (diffInMills - y * year - mn * month - d * day) / hour;
			long m = (diffInMills - y * year - mn * month - d * day - h * hour) / min;
			long s = (diffInMills - y * year - mn * month - d * day - h * hour - m * min) / sec;
			
			return String.valueOf(y) + "[y] " + String.valueOf(mn) + "[m] "
					+ String.valueOf(d) + "[d] " + String.valueOf(h) + "[h] "
					+ String.valueOf(m) + "[min] " + String.valueOf(s) + "[s]";
		}
	}
}