package com.srbenoit.util;

import java.util.Calendar;
import java.util.Date;

/**
 * A thread-safe static class to perform calendar operations. This is needed as the <code>
 * Calendar</code> class is not thread safe.
 */
public final class TimeConverter {

    /** the default calendar */
    private final static Calendar CAL;

    static {
        CAL = Calendar.getInstance();
    }

    /**
     * Private constructor to enforce singleton model.
     */
    private TimeConverter() {

        /* No action. */
    }

    /**
     * Takes only the time component of a timestamp and converts it to the same time on January 1,
     * 1970 (the "epoch").
     *
     * @param   timestamp  the timestamp whose time is to be extracted
     * @return  the extracted timestamp
     */
    public static long compressToFirstDayOfEpoch(final long timestamp) {

        long time;

        synchronized (CAL) {
            CAL.setTimeInMillis(timestamp);
            CAL.set(Calendar.YEAR, 1970);
            CAL.set(Calendar.DAY_OF_YEAR, 1);
            time = CAL.getTimeInMillis();
        }

        return time;
    }

    /**
     * Takes only the time component of a timestamp and converts it to the same time on January 1,
     * 1970 (the "epoch").
     *
     * @param   timestamp  the timestamp whose time is to be extracted
     * @return  the extracted timestamp
     */
    public static Long compressToFirstDayOfEpoch(final Long timestamp) {

        Long time;

        if (timestamp == null) {
            time = null;
        } else {
            time = Long.valueOf(compressToFirstDayOfEpoch(timestamp.longValue()));
        }

        return time;
    }

    /**
     * Takes only the time component of a timestamp and converts it to the same time on January 1,
     * 1970 (the "epoch").
     *
     * @param   timestamp  the timestamp whose time is to be extracted
     * @return  the extracted timestamp
     */
    public static Long compressToFirstDayOfEpoch(final Date timestamp) {

        Long time;

        if (timestamp == null) {
            time = null;
        } else {
            time = Long.valueOf(compressToFirstDayOfEpoch(timestamp.getTime()));
        }

        return time;
    }

    /**
     * Given a year, month (1-12) and day of month (1-31), gets the timestamp in milliseconds of
     * midnight at the beginning of that day.
     *
     * @param   year        the year
     * @param   month       the month
     * @param   dayOfMonth  the day of the month
     * @return  the millisecond value
     */
    public static long toMillis(final int year, final int month, final int dayOfMonth) {

        return toMillis(year, month, dayOfMonth, 0, 0, 0, 0);
    }

    /**
     * Given the hours, minutes, and seconds, gets the timestamp in milliseconds of that time on
     * January 1, 1970.
     *
     * @param   hourOfDay  the hour of the day
     * @param   minute     the minute
     * @param   second     the second
     * @param   millis     the milliseconds within the second
     * @return  the millisecond value
     */
    public static long toMillis(final int hourOfDay, final int minute, final int second,
        final int millis) {

        return toMillis(1970, 1, 1, hourOfDay, minute, second, millis);
    }

    /**
     * Given a year, month (1-12), day of month (1-31), hours, minutes, and seconds, gets the
     * timestamp in milliseconds.
     *
     * @param   year        the year
     * @param   month       the month
     * @param   dayOfMonth  the day of the month
     * @param   hourOfDay   the hour of the day
     * @param   minute      the minute
     * @param   second      the second
     * @param   millis      the milliseconds within the second
     * @return  the millisecond value
     */
    public static long toMillis(final int year, final int month, final int dayOfMonth,
        final int hourOfDay, final int minute, final int second, final int millis) {

        synchronized (CAL) {
            CAL.set(year, month - 1, dayOfMonth, hourOfDay, minute, second);
            CAL.set(Calendar.MILLISECOND, millis);

            return CAL.getTimeInMillis();
        }
    }

    /**
     * Converts a timestamp (in milliseconds) into an integer that represents the year * 1000 + the
     * day (this is an integer value that will be different for every day and is monotonically
     * increasing; useful for comparing two timestamps to see if they were on the same day or if
     * not, which occurred later).
     *
     * @param   timestamp  the timestamp to convert
     * @return  the integer day value
     */
    public static int toIntegerDay(final long timestamp) {

        int day;

        synchronized (CAL) {
            CAL.setTimeInMillis(timestamp);
            day = (1000 * CAL.get(Calendar.YEAR)) + CAL.get(Calendar.DAY_OF_YEAR);
        }

        return day;
    }

    /**
     * Converts a date into an integer that represents the year * 1000 + the day (this is an
     * integer value that will be different for every day and is monotonically increasing; useful
     * for comparing two timestamps to see if they were on the same day or if not, which occurred
     * later).
     *
     * @param   date  the date to convert
     * @return  the integer day value
     */
    public static int toIntegerDay(final Date date) {

        int day;

        synchronized (CAL) {
            CAL.setTimeInMillis(date.getTime());
            day = (1000 * CAL.get(Calendar.YEAR)) + CAL.get(Calendar.DAY_OF_YEAR);
        }

        return day;
    }

    /**
     * Converts a timestamp (in milliseconds) into an integer that represents the minute of the day
     * (0 for the first minute after midnight, and 1439 during the last minute before midnight).
     *
     * @param   timestamp  the timestamp to convert
     * @return  the integer minute value
     */
    public static int toMinuteOfDay(final long timestamp) {

        int minute;

        synchronized (CAL) {
            CAL.setTimeInMillis(timestamp);
            minute = (60 * CAL.get(Calendar.HOUR_OF_DAY)) + CAL.get(Calendar.MINUTE);
        }

        return minute;
    }

    /**
     * Converts a date into an integer that represents the minute of the day (0 for the first
     * minute after midnight, and 1439 during the last minute before midnight).
     *
     * @param   date  the date to convert
     * @return  the integer minute value
     */
    public static int toMinuteOfDay(final Date date) {

        int minute;

        synchronized (CAL) {
            CAL.setTimeInMillis(date.getTime());
            minute = (60 * CAL.get(Calendar.HOUR_OF_DAY)) + CAL.get(Calendar.MINUTE);
        }

        return minute;
    }
}
