/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 */

package com.queplix.core.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 java.util.Locale;
import java.util.TimeZone;

/**
 * Utility class for date manipulations.
 *
 * @author [ALB] Baranov Andrey
 * @author [ONZ] Oleg N. Zhovtanyuk
 * @version $Revision: 1.2 $ $Date: 2006/01/26 15:00:53 $
 * @todo refactor it:
 * 1. currentTimeMillis() = System.currentTimeMillis()
 * 2. add time zone parameter to formatDate methods
 */
public final class DateHelper {

    // =============================================================== Constants

    /**
     * Milliseconds in day.
     */
    public static final long DAY_MILLIS = 24 * 60 * 60 * 1000;

    // Supported time formats.
    private static final String[] TIME_FORMATS = {
            "hh:mm a",
            "hh:mm:ss a"
    };

    // ========================================================== Public methods

    /**
     * Get the localized date pattern.
     *
     * @param locale locale
     * @return date pattern
     */
    public static String getDatePattern(Locale locale) {
        SimpleDateFormat sdf = (SimpleDateFormat) DateFormat.getDateInstance(
                DateFormat.DEFAULT, locale);
        return sdf.toPattern();
    }

    /**
     * Gets the localized time pattern.
     *
     * @param locale locale
     * @return time pattern
     */
    public static String getTimePattern(Locale locale) {
        SimpleDateFormat sdf = (SimpleDateFormat) DateFormat.getTimeInstance(
                DateFormat.DEFAULT, locale);
        return sdf.toPattern();
    }

    /**
     * Checks whetther the date goes first in pattern.
     *
     * @param datePattern given date pattern
     * @return true if date position first
     */
    public static boolean isDatePatternFirst(String datePattern) {
        return (datePattern.trim().indexOf("d") == 0);
    }

    /**
     * Get system milliseconds
     *
     * @return system milliseconds
     */
    public static long currentTimeMillis() {
        return toSystem(System.currentTimeMillis(), TimeZone.getDefault());
    }

    /**
     * Convert user time to system time
     *
     * @param ms user time
     * @param tz user time zone
     * @return system time
     */
    public static long toSystem(long ms, TimeZone tz) {
        return convert(ms, tz, SystemHelper.SYSTEM_TIMEZONE);
    }

    /**
     * Convert system time to user time
     *
     * @param ms user time
     * @param tz user time zone
     * @return user time
     */
    public static long toUser(long ms, TimeZone tz) {
        return convert(ms, SystemHelper.SYSTEM_TIMEZONE, tz);
    }

    /**
     * Convert time <code>fromMs</code> from <code>fromTz</code> time zone to
     * <code>toTz</code> time zone
     *
     * @param fromMs from time
     * @param fromTz from time zone
     * @param toTz   tom time zone
     * @return system time
     */
    public static long convert(long fromMs, TimeZone fromTz, TimeZone toTz) {

        if(fromTz.equals(toTz)) {
            return fromMs;
        }

        int fromOffset = fromTz.getRawOffset();
        int toOffset = toTz.getRawOffset();
        long utcMs = fromMs - fromOffset;
        long toMs = utcMs + toOffset;

        return toMs;
    }

    /**
     * Return system current date
     *
     * @return system current date
     */
    public static Date getNowDate() {
        return new Date(currentTimeMillis());
    }

    // ----------------------------------------------------------
    // DATE FROMATTERS
    // ----------------------------------------------------------

    /**
     * Format date and return string represents the Date object
     *
     * @param d Date object
     * @return string
     */
    public static String formatDate(Date d) {
        return formatDate(d, "MM/dd/yyyy HH:mm:ss");
    }

    /**
     * Format date and return string represents the Date object
     *
     * @param d       Date object
     * @param pattern pattern string
     * @return string
     */
    public static String formatDate(Date d, String pattern) {
        return formatDate(d, pattern, null);
    }

    /**
     * Format date and return string represents the Date object
     *
     * @param d      Date object
     * @param locale user locale
     * @return string
     */
    public static String formatDate(Date d, Locale locale) {
        return formatDate(d, null, locale);
    }

    /**
     * Format date and return string represents the Date object
     *
     * @param date    Date object
     * @param pattern pattern string
     * @param locale  user locale
     * @return string
     */
    public static String formatDate(Date date, String pattern, Locale locale) {
        DateFormat df = getDateFormat(pattern, locale);
        String dateStr = df.format(date);
        return dateStr;
    }

    /**
     * Format date and return string represents the Date object
     *
     * @param date        Date object
     * @param datePattern date pattern string
     * @param timePattern time pattern string
     * @param locale      user locale
     * @return string
     */
    public static String formatDate(Date date,
                                    String datePattern,
                                    String timePattern,
                                    Locale locale) {

        DateFormat df = getDateFormat("HH:mm:ss", locale);
        if(df.format(date).equals("00:00:00")) {
            return formatDate(date, datePattern, locale);
        } else if(timePattern != null) {
            return formatDate(date, datePattern + " " + timePattern, locale);
        } else {
            return formatDate(date, datePattern, locale);
        }
    }

    /**
     * Format time and return string
     *
     * @param date Date object
     * @return string
     */
    public static String formatTime(Date date) {
        return formatTime(date, "HH:mm:ss");
    }

    /**
     * Format time and return string
     *
     * @param time    Date object
     * @param pattern time pattern string
     * @return string
     */
    public static String formatTime(Date time,
                                    String pattern) {

        DateFormat df = getTimeFormat(pattern);
        String timeStr = df.format(time);
        return timeStr;
    }

    // ----------------------------------------------------------
    // DATE PARSERS
    // ----------------------------------------------------------

    /**
     * Parse date and construct Date object
     *
     * @param str date string
     * @return date object
     * @throws ParseException
     */
    public static Date parseDate(String str)
            throws ParseException {
        return parseDate(str, "dd/MM/yyyy HH:mm:ss");
    }

    /**
     * Parse date and construct Date object
     *
     * @param str     date string
     * @param pattern pattern string
     * @return date object
     * @throws ParseException
     */
    public static Date parseDate(String str, String pattern)
            throws ParseException {
        return parseDate(str, pattern, null);
    }

    /**
     * Parse date and construct Date object
     *
     * @param str    date string
     * @param locale user locale
     * @return date object
     * @throws ParseException
     */
    public static Date parseDate(String str, Locale locale)
            throws ParseException {
        return parseDate(str, null, locale);
    }

    /**
     * Parse date and construct Date object
     *
     * @param str     date string
     * @param pattern pattern string
     * @param locale  user locale
     * @return date object
     * @throws ParseException
     */
    public static Date parseDate(String str, String pattern, Locale locale)
            throws ParseException {

        DateFormat df = getDateFormat(pattern, locale);
        try {
            return df.parse(str);
        } catch (ParseException ex) {
            throw new ParseException(
                    ex.getMessage() + ". Pattern '" + pattern + "'. Date '"
                            + str + "'",
                    ex.getErrorOffset());
        }
    }

    /**
     * Parse time and return count of seconds
     *
     * @param str    time string
     * @param strict strict or not
     * @return seconds
     * @throws ParseException
     */
    public static int parseTime(String str, boolean strict)
            throws ParseException {

        String stime = str.trim();
        boolean find = false;
        int h = 0;
        int m = 0;
        int s = 0;

        // step first (try to pick up format)
        GregorianCalendar gc = new GregorianCalendar();
        for(int i = 0; i < TIME_FORMATS.length; i++) {
            try {
                SimpleDateFormat df = new SimpleDateFormat(TIME_FORMATS[i]);
                Date date = df.parse(str);

                gc.setTime(date);
                h = gc.get(Calendar.HOUR_OF_DAY);
                m = gc.get(Calendar.MINUTE);
                s = gc.get(Calendar.SECOND);
                find = true;
                break;

            } catch (ParseException ex) {
                // can`t parse
            }
        }

        // step second (parse by the hands)
        if(!find) {
            try {
                int pos1 = stime.indexOf(":");
                if(pos1 >= 0) {
                    h = Integer.parseInt(stime.substring(0, pos1));
                    int pos2 = stime.indexOf(":", pos1 + 1);
                    if(pos2 >= 0) {
                        m = Integer.parseInt(stime.substring(pos1 + 1, pos2));
                        s = Integer.parseInt(stime.substring(pos2 + 1));
                    } else {
                        m = Integer.parseInt(stime.substring(pos1 + 1));
                    }
                } else {
                    throw new ParseException("Can't parse time '" + str + "'",
                            0);
                }

            } catch (NumberFormatException ex) {
                throw new ParseException("Can't parse time '" + str + "'", 0);
            }
        }

        if(h < 0 || (strict && h > 24)) {
            throw new ParseException("Hours out of range", 0);
        }

        if(m < 0 || (strict && (m >= 60 || (m != 0 && h == 24)))) {
            throw new ParseException("Minutes out of range", 0);
        }

        if(s < 0 || (strict && (s >= 60 || (s != 0 && h == 24)))) {
            throw new ParseException("Seconds out of range", 0);
        }

        return ((h * 60 + m) * 60 + s);
    }

    // -------------------------------------------------------- Private methods

    /**
     * Get DateFormat object for date
     *
     * @param pattern pattern string
     * @param locale  user locale
     * @return DateFormat object
     */
    private static DateFormat getDateFormat(String pattern, Locale locale) {

        if(locale == null) {
            locale = SystemHelper.SYSTEM_LOCALE;
        }

        DateFormat df;
        if(pattern == null) {
            df = DateFormat.getDateTimeInstance(DateFormat.DEFAULT,
                    DateFormat.DEFAULT, locale);
        } else {
            df = new SimpleDateFormat(pattern, locale);
        }

        df.setLenient(true);
        return df;

    }

    /**
     * Get DateFormat object fot time
     *
     * @param pattern pattern string
     * @return DateFormat object
     */
    private static DateFormat getTimeFormat(String pattern) {

        DateFormat df;
        if(pattern == null) {
            df = DateFormat.getTimeInstance(DateFormat.DEFAULT,
                    SystemHelper.SYSTEM_LOCALE);
        } else {
            df = new SimpleDateFormat(pattern, SystemHelper.SYSTEM_LOCALE);
        }
        df.setLenient(true);
        return df;
    }

    //
    // Main method (only for testing)
    //
    public static void main(String[] args) {
        System.out.println("Process time: " + new Date(
                System.currentTimeMillis()));
        System.out.println("System time: " + getNowDate());
        System.out.println("Default time: " + new Date(toUser(
                currentTimeMillis(), SystemHelper.DEFAULT_TIMEZONE)));
    }

} // end of class
