package com.n2bb.util;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.Locale;
import java.util.Date;
import java.util.Calendar;

/**
 * Utility methods for dates.
 *
 * @author kmatsuoka
 */
public class DateUtil {

    /**
     * Gets localized date format with four-digit year, if available.
     *
     * @return localized date format with four-digit year
     */
    public static DateFormat getLongYearDateFormat(Locale locale) {
        return getLongYearDateFormat(locale, false);
    }

    /**
     * Gets localized date format with four-digit year, if available.
     *
     * @return localized date format with four-digit year
     */
    public static DateFormat getLongYearDateFormat(Locale locale, boolean time) {
        DateFormat dateFormat = time ?
                DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale) :
                DateFormat.getDateInstance(DateFormat.SHORT, locale);
        if (dateFormat instanceof SimpleDateFormat) {
            String pattern = ((SimpleDateFormat) dateFormat).toPattern();
            pattern = pattern.replaceFirst("y+", "yyyy");
            dateFormat = new SimpleDateFormat(pattern, locale);
        }
        return dateFormat;
    }

    /**
     * Gets date from date string, converting 1- or 2-digit years
     * (0-99) to years in the current century.
     *
     * @param dateString                date string
     * @param currentCenturyStartYear   start of current century (minus one, actually)
     * @return                          date
     * @throws ParseException           if invalid date string given
     */
    public static Date getCurrentCenturyDate(Locale locale, String dateString, int currentCenturyStartYear) throws ParseException {
        Calendar cal = Calendar.getInstance();
        DateFormat format = getLongYearDateFormat(locale);
        // disallow nonstandard date formats (months > 12, etc.)
        format.setLenient(false);
        cal.setTime(format.parse(dateString.trim()));
        if (cal.get(Calendar.YEAR) < 100) {
            cal.add(Calendar.YEAR, currentCenturyStartYear);
        }
        return cal.getTime();
    }

    /**
     * Gets pattern string for date format, assuming it's a <code>SimpleDateFormat</code>.
     * This pattern string is for display, not for interpretation by <code>DateFormat</code>
     *
     * @param dateFormat    date format
     * @return              pattern string for date format
     * @throws ClassCastException if date format isn't a <code>SimpleDateFormat</code>
     */
    public static String getPatternDisplayString(DateFormat dateFormat) throws ClassCastException {
        String pattern = ((SimpleDateFormat) dateFormat).toPattern().toUpperCase();
        // HACK: to satisfy requirement (for English locale) that pattern display as MM/DD/YYYY
        pattern = pattern.replaceFirst("^M+/D+/", "MM/DD/");
        return pattern;
    }

    /**
     * Gets date parsed according to default locale and format.
     *
     * @param dateString    date string
     * @return              date
     * @throws ParseException   if unable to parse
     */
    public static ParseDateResult parseDate(String dateString) {
        Locale locale = Locale.getDefault();
        DateFormat shortDateFormat = getLongYearDateFormat(locale);

        try {
            Date date = getCurrentCenturyDate(locale, dateString, 2000);
            return new ParseDateResult(date);
        }
        catch (ParseException e) {
            String formatString = "";
            try {
                formatString = getPatternDisplayString(shortDateFormat);
            }
            catch (ClassCastException ex) {}
            return new ParseDateResult(formatString);
        }
    }

    /**
     * Gets next day in calendar, to convert end-of-day date in UI
     * to actual date with time = 00:00:00.
     *
     * @param date      date with implied end-of-day time
     * @return          date with actual end-of-day time, corresponding to next day
     */
    public static Date getNextDay(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.add(Calendar.DAY_OF_YEAR, 1);
        return cal.getTime();
    }

    /**
     * Gets date corresponding to previous day, so that UI can display
     * "expires on [date] at end of day".
     *
     * @param date  date
     * @return      date corresponding to previous day
     */
    public static Date getPreviousDay(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.add(Calendar.DAY_OF_YEAR, -1);
        return cal.getTime();
    }

    /**
     * Result for <code>checkDate</code> method.
     * If date string is valid, date object is filled in.
     * Otherwise, format pattern string is filled in.
     */
    public static class ParseDateResult {
        Date date;
        boolean valid;
        String pattern;

        public ParseDateResult(Date date) {
            this.date = date;
            this.valid = true;
        }

        public ParseDateResult(String pattern) {
            this.valid = false;
            this.pattern = pattern;
        }

        public Date getDate() {
            return date;
        }

        public boolean isValid() {
            return valid;
        }

        public String getPattern() {
            return pattern;
        }
    }

}
