/********************************************************************
 * Copyright © 2008 VHA. All rights reserved
 ********************************************************************/
package gov.va.med.fw.util.date;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.TimeZone;

import gov.va.med.fw.util.DateUtils;
import gov.va.med.fw.util.StringUtils;

/**
 * Supports TimeZone functions/manipulation/conversion
 * 
 * Sep 29, 2008
 * 
 * @author DNS   BOHMEG
 */
public class TimeZoneUtils {
	public static final String TIMEZONE_FORMAT = DateUtils.MMDDYYYYHHMM_EDIT + " zzz";

	public static final String GMT_PREFIX = "GMT";

	public static TimeZone getGMTTimeZone(int rawOffsetWithoutDaylightSaving) {
		return TimeZone.getTimeZone(GMT_PREFIX + rawOffsetWithoutDaylightSaving);
	}

	/**
	 * Does a best attempt to identify TimeZone. It can not be exact without the
	 * true TimeZone name (eg, America/New York) due to differing Daylight
	 * Saving time rules that can be shared within the same GMT offset. Gives
	 * higher weight to US TimeZones.
	 * 
	 * @param rawOffsetWithoutDaylightSaving
	 * @param observesDaylightSaving
	 * @return
	 */
	public static TimeZone getTimeZone(int rawOffsetWithoutDaylightSaving,
			boolean observesDaylightSaving) {
		TimeZone targetTimeZone = null;
		Set<TimeZone> matchingTimeZones = doGetTimeZones(rawOffsetWithoutDaylightSaving,
				observesDaylightSaving);
		for (TimeZone timeZone : matchingTimeZones) {
			if (targetTimeZone == null) {
				targetTimeZone = timeZone;
			}

			if (timeZone.getID().startsWith("US/")) {
				targetTimeZone = timeZone;
				break;
			}
		}
		return targetTimeZone;
	}

	/**
	 * For application standardization across domestic timezones, gives higher
	 * weight to "US" TimeZones over "America" TimeZones.
	 * 
	 * @return
	 */
	public static TimeZone getTimeZone() {
		TimeZone current = TimeZone.getDefault();
		return getTimeZone(current.getRawOffset() / 3600000, current.getDSTSavings() != 0 ? true
				: false);
	}

	public static String convertDateToTimeZone(Date date, TimeZone timeZone) {
		if (date == null)
			return StringUtils.EMPTY;

		SimpleDateFormat sdf = new SimpleDateFormat(TIMEZONE_FORMAT);
		if (timeZone != null)
			sdf.setTimeZone(timeZone);
		return sdf.format(date);
	}

	public static String convertDateToUTC(Date date, boolean includeTimeZoneInString) {
		if (date == null)
			return StringUtils.EMPTY;

		SimpleDateFormat sdf = new SimpleDateFormat(includeTimeZoneInString ? TIMEZONE_FORMAT
				: DateUtils.MMDDYYYYHHMM_EDIT);
		sdf.setTimeZone(TimeZone.getTimeZone(GMT_PREFIX));
		return sdf.format(date);
	}

	public static String convertDateToTimeZone(DateWithTimeZone date) {
		if (date == null)
			return StringUtils.EMPTY;
		return convertDateToTimeZone(date.getDate(), date.getTimeZone());
	}

	private static Set<TimeZone> doGetTimeZones(int rawOffsetWithoutDaylightSaving,
			boolean observesDaylightSaving) {
		Set<TimeZone> targetTimeZones = new HashSet<TimeZone>();
		String[] matchingZones = TimeZone.getAvailableIDs(rawOffsetWithoutDaylightSaving * 3600000);
		TimeZone timeZone = null;
		for (int i = 0; i < matchingZones.length; i++) {

			timeZone = TimeZone.getTimeZone(matchingZones[i]);
			if (timeZone.useDaylightTime() == observesDaylightSaving) {
				targetTimeZones.add(timeZone);
			}
		}

		return targetTimeZones;
	}

	/**
	 * This function assumes input strings are generally in the form of
	 * MM/dd/yyyy HH:mm [zzz], where zzz represents an optional TimeZone
	 * 
	 * @param str
	 * @param timeZone
	 * @return
	 * @throws ParseException
	 */
	public static Date parseDate(String str, TimeZone timeZone) throws ParseException {
		if (StringUtils.isEmpty(str))
			return null;
		String[] tokens = str.split(" ");
		String pattern = TIMEZONE_FORMAT;
		if (tokens.length == 2)
			pattern = DateUtils.MMDDYYYYHHMM_EDIT;
		return DateUtils.parseDate(str, pattern, timeZone);
	}

	public static Date convertUTCDateToSpecifiedTimeZone(Date date, TimeZone tz) {
		int offset = (tz.getOffset(date.getTime()));
		return new Date(date.getTime() + offset);
	}

	public static Date convertCurrentDateToSpecifiedTimeZone(TimeZone tz) {
		Date curDate = new Date();
		TimeZone tz1 = TimeZone.getDefault();
		int offset = (tz.getOffset(curDate.getTime()) - tz1.getOffset(curDate.getTime()));
		return new Date(curDate.getTime() + offset);
	}

	/*
	 * ThreadLocal used to pass current timezon through long stqack of calls and
	 * exceptrions
	 */
	private static ThreadLocal<TimeZone> currentTimeZone = new ThreadLocal<TimeZone>();

	public static TimeZone getThreadCurrentTimezone() {
		return currentTimeZone.get();
	}

	public static void setThreadCurrentTimezone(TimeZone tz) {
		currentTimeZone.set(tz);
	}

	public static void removeThreadCurrentTimezone() {
		currentTimeZone.remove();
	}

}
