/*
 * Copyright 2011 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * 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.gitblit.utils;

import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.ResourceBundle;
import java.util.TimeZone;

/**
 * Utility class of time functions.
 *
 * @author James Moger
 *
 */
public class TimeUtils {
	public static final long MIN = 1000 * 60L;

	public static final long HALFHOUR = MIN * 30L;

	public static final long ONEHOUR = HALFHOUR * 2;

	public static final long ONEDAY = ONEHOUR * 24L;

	public static final long ONEYEAR = ONEDAY * 365L;

	private final ResourceBundle translation;

	private final TimeZone timezone;

	public TimeUtils() {
		this(null, null);
	}

	public TimeUtils(ResourceBundle translation, TimeZone timezone) {
		this.translation = translation;
		this.timezone = timezone;
	}

	/**
	 * Returns true if date is today.
	 *
	 * @param date
	 * @return true if date is today
	 */
	public static boolean isToday(Date date, TimeZone timezone) {
		Date now = new Date();
		SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
		if (timezone != null) {
			df.setTimeZone(timezone);
		}
		return df.format(now).equals(df.format(date));
	}

	/**
	 * Returns true if date is yesterday.
	 *
	 * @param date
	 * @return true if date is yesterday
	 */
	public static boolean isYesterday(Date date, TimeZone timezone) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(new Date());
		cal.add(Calendar.DATE, -1);
		SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
		if (timezone != null) {
			df.setTimeZone(timezone);
		}
		return df.format(cal.getTime()).equals(df.format(date));
	}

	/**
	 * Returns the string representation of the duration as days, months and/or
	 * years.
	 *
	 * @param days
	 * @return duration as string in days, months, and/or years
	 */
	public String duration(int days) {
		if (days <= 60) {
			return (days > 1 ? translate(days, "gb.duration.days", "{0} days") : translate("gb.duration.oneDay", "1 day"));
		} else if (days < 365) {
			int rem = days % 30;
			return translate(((days / 30) + (rem >= 15 ? 1 : 0)), "gb.duration.months", "{0} months");
		} else {
			int years = days / 365;
			int rem = days % 365;
			String yearsString = (years > 1 ? translate(years, "gb.duration.years", "{0} years") : translate("gb.duration.oneYear", "1 year"));
			if (rem < 30) {
				if (rem == 0) {
					return yearsString;
				} else {
					return yearsString + (rem >= 15 ? (", " + translate("gb.duration.oneMonth", "1 month")): "");
				}
			} else {
				int months = rem / 30;
				int remDays = rem % 30;
				if (remDays >= 15) {
					months++;
				}
				String monthsString = yearsString + ", "
						+ (months > 1 ? translate(months, "gb.duration.months", "{0} months") : translate("gb.duration.oneMonth", "1 month"));
				return monthsString;
			}
		}
	}

	/**
	 * Returns the number of minutes ago between the start time and the end
	 * time.
	 *
	 * @param date
	 * @param endTime
	 * @param roundup
	 * @return difference in minutes
	 */
	public static int minutesAgo(Date date, long endTime, boolean roundup) {
		long diff = endTime - date.getTime();
		int mins = (int) (diff / MIN);
		if (roundup && (diff % MIN) >= 30) {
			mins++;
		}
		return mins;
	}

	/**
	 * Return the difference in minutes between now and the date.
	 *
	 * @param date
	 * @param roundup
	 * @return minutes ago
	 */
	public static int minutesAgo(Date date, boolean roundup) {
		return minutesAgo(date, System.currentTimeMillis(), roundup);
	}

	/**
	 * Return the difference in hours between now and the date.
	 *
	 * @param date
	 * @param roundup
	 * @return hours ago
	 */
	public static int hoursAgo(Date date, boolean roundup) {
		long diff = System.currentTimeMillis() - date.getTime();
		int hours = (int) (diff / ONEHOUR);
		if (roundup && (diff % ONEHOUR) >= HALFHOUR) {
			hours++;
		}
		return hours;
	}

	/**
	 * Return the difference in days between now and the date.
	 *
	 * @param date
	 * @return days ago
	 */
	public static int daysAgo(Date date) {
		long today = ONEDAY * (System.currentTimeMillis()/ONEDAY);
		long day = ONEDAY * (date.getTime()/ONEDAY);
		long diff = today - day;
		int days = (int) (diff / ONEDAY);
		return days;
	}

	public String today() {
		return translate("gb.time.today", "today");
	}

	public String yesterday() {
		return translate("gb.time.yesterday", "yesterday");
	}

	/**
	 * Returns the string representation of the duration between now and the
	 * date.
	 *
	 * @param date
	 * @return duration as a string
	 */
	public String timeAgo(Date date) {
		return timeAgo(date, false);
	}

	/**
	 * Returns the CSS class for the date based on its age from Now.
	 *
	 * @param date
	 * @return the css class
	 */
	public String timeAgoCss(Date date) {
		return timeAgo(date, true);
	}

	/**
	 * Returns the string representation of the duration OR the css class for
	 * the duration.
	 *
	 * @param date
	 * @param css
	 * @return the string representation of the duration OR the css class
	 */
	private String timeAgo(Date date, boolean css) {
		if (isToday(date, timezone) || isYesterday(date, timezone)) {
			int mins = minutesAgo(date, true);
			if (mins >= 120) {
				if (css) {
					return "age1";
				}
				int hours = hoursAgo(date, true);
				if (hours > 23) {
					return yesterday();
				} else {
					return translate(hours, "gb.time.hoursAgo", "{0} hours ago");
				}
			}
			if (css) {
				return "age0";
			}
			if (mins > 2) {
				return translate(mins, "gb.time.minsAgo", "{0} mins ago");
			}
			return translate("gb.time.justNow", "just now");
		} else {
			int days = daysAgo(date);
			if (css) {
				if (days <= 7) {
					return "age2";
				} if (days <= 30) {
					return "age3";
				} else {
					return "age4";
				}
			}
			if (days < 365) {
				if (days <= 30) {
					return translate(days, "gb.time.daysAgo", "{0} days ago");
				} else if (days <= 90) {
					int weeks = days / 7;
					if (weeks == 12) {
						return translate(3, "gb.time.monthsAgo", "{0} months ago");
					} else {
						return translate(weeks, "gb.time.weeksAgo", "{0} weeks ago");
					}
				}
				int months = days / 30;
				int weeks = (days % 30) / 7;
				if (weeks >= 2) {
					months++;
				}
				return translate(months, "gb.time.monthsAgo", "{0} months ago");
			} else if (days == 365) {
				return translate("gb.time.oneYearAgo", "1 year ago");
			} else {
				int yr = days / 365;
				days = days % 365;
				int months = (yr * 12) + (days / 30);
				if (months > 23) {
					return translate(yr, "gb.time.yearsAgo", "{0} years ago");
				} else {
					return translate(months, "gb.time.monthsAgo", "{0} months ago");
				}
			}
		}
	}

	public String inFuture(Date date) {
		long diff = date.getTime() - System.currentTimeMillis();
		if (diff > ONEDAY) {
			double days = ((double) diff)/ONEDAY;
			return translate((int) Math.round(days), "gb.time.inDays", "in {0} days");
		} else {
			double hours = ((double) diff)/ONEHOUR;
			if (hours > 2) {
				return translate((int) Math.round(hours), "gb.time.inHours", "in {0} hours");
			} else {
				int mins = (int) (diff/MIN);
				return translate(mins, "gb.time.inMinutes", "in {0} minutes");
			}
		}
	}

	private String translate(String key, String defaultValue) {
		String value = defaultValue;
		if (translation != null && translation.containsKey(key)) {
			String aValue = translation.getString(key);
			if (!StringUtils.isEmpty(aValue)) {
				value = aValue;
			}
		}
		return value;
	}

	private String translate(int val, String key, String defaultPattern) {
		String pattern = defaultPattern;
		if (translation != null && translation.containsKey(key)) {
			String aValue = translation.getString(key);
			if (!StringUtils.isEmpty(aValue)) {
				pattern = aValue;
			}
		}
		return MessageFormat.format(pattern, val);
	}

	/**
	 * Convert a frequency string into minutes.
	 *
	 * @param frequency
	 * @param minimumMins
	 * @return minutes
	 */
	public static int convertFrequencyToMinutes(String frequency, int minimumMins) {
		// parse the frequency
		frequency = frequency.toLowerCase();
		int mins = minimumMins;
		if (!StringUtils.isEmpty(frequency)) {
			try {
				String str = frequency.trim();
				if (frequency.indexOf(' ') > -1) {
					str = str.substring(0, str.indexOf(' ')).trim();
				}
				mins = (int) Float.parseFloat(str);
			} catch (NumberFormatException e) {
			}
			if (mins < minimumMins) {
				mins = minimumMins;
			}
			if (frequency.indexOf("day") > -1) {
				// convert to minutes
				mins *= 1440;
			} else if (frequency.indexOf("hour") > -1) {
				// convert to minutes
				mins *= 60;
			}
		}
		return mins;
	}
}
