/* I18Ns.java

{{IS_NOTE
	Purpose:
		
	Description:
		
	History:
		Thu Jul 13 16:53:47     2006, Created by tomyeh
}}IS_NOTE

Copyright (C) 2006 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
}}IS_RIGHT
*/
package org.zkoss.zk.ui.http;

import java.util.TimeZone;

import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.zkoss.lang.Classes;
import org.zkoss.lang.Library;
import org.zkoss.text.DateFormatInfo;
import org.zkoss.text.DateFormats;
import org.zkoss.util.TimeZones;
import org.zkoss.web.Attributes;
import org.zkoss.web.servlet.Charsets;
import org.zkoss.zk.ui.Session;
import org.zkoss.zk.ui.sys.SessionsCtrl;

/**
 * Internationalization utilities.
 *
 * <p>Typical use:
 * <pre><code>
 Object old = I18Ns.setup(session, request, response);
 try {
   ...
 } finally {
   I18Ns.cleanup(old);
 }
 *</code></pre>
 * @author tomyeh
 */
public class I18Ns {
	private static final Logger log = LoggerFactory.getLogger(I18Ns.class);
	private static final String ATTR_SETUP = "org.zkoss.zk.ui.http.charset.setup";

	/** Sets up the internationalization attributes, including locale
	 * and time zone.
	 *
	 * @param sess the session. It cannot be null.
	 * @param charset the response's charset. If null or empty,
	 * response.setCharacterEncoding won't be called, i.e., the container's
	 * default is used.
	 */
	public static final Object setup(Session sess, ServletRequest request, ServletResponse response, String charset) {
		return setup((Object) sess, request, response, charset);
	}

	/** Sets up the internationalization attributes, including locale
	 * and time zone.
	 *
	 * <p>This method is used only for requests that don't
	 * count on {@link Session}.
	 *
	 * <ol>
	 * <li>This method first checks if any session attribute called
	 * {@link Attributes#PREFERRED_LOCALE} and
	 * {@link Attributes#PREFERRED_TIME_ZONE} are set with the preferred
	 * locale and timezone. If so, use it as the default.</li>
	 * <li>Then, it checks if any servlet context attribute called
	 * {@link Attributes#PREFERRED_LOCALE} and
	 * {@link Attributes#PREFERRED_TIME_ZONE} are set with the preferred
	 * locale and timezone. If so, use it as the default.</li>
	 * <li>Then, it checks if any library properties called
	 * {@link Attributes#PREFERRED_LOCALE} and
	 * {@link Attributes#PREFERRED_TIME_ZONE} are set with the preferred
	 * locale and timezone. If so, use it as the default.</li>
	 * <li>Otherwise, it depends the setting and location of the browser
	 * (by checking the request's header).</li>
	 * </ol>
	 *
	 * @param sess the HTTP session. It cannot be null.
	 * @param charset the response's charset. If null or empty,
	 * response.setCharacterEncoding won't be called, i.e., the container's
	 * default is used.
	 * @since 3.6.2
	 */
	public static final Object setup(HttpSession sess, ServletRequest request, ServletResponse response,
			String charset) {
		return setup((Object) sess, request, response, charset);
	}

	private static final Object setup(Object sess, ServletRequest request, ServletResponse response, String charset) {
		final Object[] old;
		if (request.getAttribute(ATTR_SETUP) != null) { //has been setup
			old = null;
		} else {
			final HttpSession hsess;
			if (sess instanceof Session) {
				final Session se = (Session) sess;
				//Invoke the request interceptors
				se.getWebApp().getConfiguration().invokeRequestInterceptors(se, request, response);

				hsess = (HttpSession) se.getNativeSession();
			} else {
				hsess = (HttpSession) sess;
			}

			final Object ol = Charsets.setup(hsess, request, response, charset);
			//Charsets will handle PREFERRED_LOCALE

			//time zone
			final TimeZone tz = getTimeZone(hsess);
			final Object otz = TimeZones.setThreadLocal(tz);

			//date format info
			final DateFormatInfo dfinfo = getDateFormatInfo(hsess);
			final Object odi = DateFormats.setLocalFormatInfo(dfinfo);
			request.setAttribute(ATTR_SETUP, Boolean.TRUE); //mark as setup
			old = new Object[] { ol, otz, odi };
		}

		if (sess instanceof Session)
			SessionsCtrl.setCurrent((Session) sess);
		return old;
	}

	/** Returns the time zone of the given session, or null if not set.
	 */
	private static TimeZone getTimeZone(HttpSession hsess) {
		TimeZone tz = checkTimeZone(hsess.getAttribute(Attributes.PREFERRED_TIME_ZONE));
		if (tz != null)
			return tz;

		tz = checkTimeZone(hsess.getAttribute(PX_PREFERRED_TIME_ZONE)); //backward compatible (prior to 5.0.3)
		if (tz != null)
			return tz;

		tz = checkTimeZone(hsess.getServletContext().getAttribute(Attributes.PREFERRED_TIME_ZONE));
		if (tz != null)
			return tz;

		tz = checkTimeZone(hsess.getServletContext().getAttribute(PX_PREFERRED_TIME_ZONE)); //backward compatible (prior to 5.0.3)
		if (tz != null)
			return tz;

		String s = Library.getProperty(Attributes.PREFERRED_TIME_ZONE);
		return s != null ? TimeZone.getTimeZone(s) : null;
	}

	/** Returns the format info of the given session, or null if not set.
	 */
	private static DateFormatInfo getDateFormatInfo(HttpSession hsess) {
		DateFormatInfo fi = checkDateFormatInfo(hsess.getAttribute(Attributes.PREFERRED_DATE_FORMAT_INFO));
		if (fi != null)
			return fi;

		fi = checkDateFormatInfo(hsess.getServletContext().getAttribute(Attributes.PREFERRED_DATE_FORMAT_INFO));
		if (fi != null)
			return fi;

		String s = Library.getProperty(Attributes.PREFERRED_DATE_FORMAT_INFO);
		if (s != null)
			try {
				return checkDateFormatInfo(Classes.newInstanceByThread(s));
			} catch (Throwable ex) {
				log.error("Failed to instantiate " + s, ex);
			}
		return null;
	}

	/** The previous attribute name (backward compatible prior to 5.0.3. */
	private static final String PX_PREFERRED_TIME_ZONE = "px_preferred_time_zone";

	private static TimeZone checkTimeZone(Object tz) {
		if (tz != null && !(tz instanceof TimeZone)) {
			log.warn(Attributes.PREFERRED_TIME_ZONE + " ignored. TimeZone required, not " + tz.getClass());
			return null;
		}
		return (TimeZone) tz;
	}

	private static DateFormatInfo checkDateFormatInfo(Object o) {
		if (o == null || (o instanceof DateFormatInfo))
			return (DateFormatInfo) o;

		try {
			if (o instanceof String)
				o = Classes.forNameByThread((String) o);
			if (o instanceof Class)
				return (DateFormatInfo) ((Class) o).newInstance();
		} catch (Throwable ex) {
			log.warn(Attributes.PREFERRED_DATE_FORMAT_INFO + " ignored. Failed to instantiate " + o, ex);
			return null;
		}

		log.warn(Attributes.PREFERRED_DATE_FORMAT_INFO + " ignored. DateFormatInfo required, not " + o.getClass());
		return null;
	}

	/* Cleans up the internationalization attributes.
	 *
	 * @param old which must be the value returned by setup.
	 */
	public static final void cleanup(ServletRequest request, Object old) {
		if (old != null) {
			request.removeAttribute(ATTR_SETUP);

			SessionsCtrl.setCurrent((Session) null);

			final Object[] op = (Object[]) old;
			TimeZones.setThreadLocal((TimeZone) op[1]);
			DateFormats.setLocalFormatInfo((DateFormatInfo) op[2]);
			Charsets.cleanup(request, op[0]);
		}
	}

	/** Sets the preferred timezone for the specified session.
	 * It is the default timezone for the whole Web session.
	 * <p>Default: null (no preferred timezone -- depending on browser's location).
	 * @param timezone the preferred time zone. If null, it means no preferred timezone
	 * @see #setup
	 * @since 3.6.3
	 */
	public static final void setPreferredTimeZone(HttpSession hsess, TimeZone timezone) {
		if (timezone != null) {
			hsess.setAttribute(Attributes.PREFERRED_TIME_ZONE, timezone);
		} else {
			hsess.removeAttribute(Attributes.PREFERRED_TIME_ZONE);
			hsess.removeAttribute(PX_PREFERRED_TIME_ZONE);
		}
	}

	/** Sets the preferred timezone for the specified servlet context.
	 * It is the default timezone for the whole Web application.
	 * <p>Default: null (no preferred timezone -- depending on browser's location).
	 * @param timezone the preferred time zone. If null, it means no preferred timezone
	 * @see #setup
	 * @since 3.6.3
	 */
	public static final void setPreferredTimeZone(ServletContext ctx, TimeZone timezone) {
		if (timezone != null) {
			ctx.setAttribute(Attributes.PREFERRED_TIME_ZONE, timezone);
		} else {
			ctx.removeAttribute(Attributes.PREFERRED_TIME_ZONE);
			ctx.removeAttribute(PX_PREFERRED_TIME_ZONE);
		}
	}
}
