/* RequestXelResolver.java

	Purpose:
		
	Description:
		
	History:
		Tue Mar 29 18:45:46     2005, Created by tomyeh

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

{{IS_RIGHT
	This program is distributed under LGPL Version 2.1 in the hope that
	it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.web.servlet.xel;

import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.zkoss.xel.ExpressionFactory;
import org.zkoss.xel.VariableResolver;
import org.zkoss.xel.XelException;

/**
 * A XEL variable resolver that is based on Servlet request, response,
 * and EL.
 *
 * @author tomyeh
 * @since 3.0.0
 */
public abstract class RequestXelResolver implements VariableResolver {
	private final ServletContext _ctx;
	private final ServletRequest _request;
	private final ServletResponse _response;
	private HttpSession _sess;
	private Map<String, Object> _reqScope, _sessScope, _appScope;
	/** A fake page context implementation. */
	private PageContext _pc;
	/** cached cookies. */
	private Map<String, Cookie> _cookies;

	/** Request-based resolver.
	 * @param ctx the context; which might be null
	 * @param request the request. It cannot be null.
	 * @param response the response, which might bell.
	 * @exception IllegalArgumentException if request is null.
	 */
	public RequestXelResolver(ServletContext ctx, ServletRequest request, ServletResponse response) {
		_ctx = ctx;
		_request = request;
		_response = response;
	}

	public RequestXelResolver(ServletContext ctx, ServletRequest request) {
		this(ctx, request, null);
	}

	public RequestXelResolver(ServletRequest request) {
		this(null, request, null);
	}

	//-- extra --//
	/** Returns the expression factory (never null).
	 * <p>The deriving class must override it.
	 */
	public abstract ExpressionFactory getExpressionFactory();

	/** Returns the page context. */
	public PageContext getPageContext() {
		if (_pc == null)
			_pc = new PageContextImpl();
		return _pc;
	}

	/** Returns the request. */
	public ServletRequest getRequest() {
		return _request;
	}

	/** Returns the response. */
	public ServletResponse getResponse() {
		return _response;
	}

	/** Returns the context. */
	public ServletContext getServletContext() {
		return _ctx;
	}

	//-- VariableResovler --//
	@SuppressWarnings("unchecked")
	public Object resolveVariable(String name) throws XelException {
		if ("pageContext".equals(name)) {
			return getPageContext();
		} else if ("pageScope".equals(name)) {
			return Collections.EMPTY_MAP;
		} else if ("requestScope".equals(name)) {
			return getRequestScope();
		} else if ("sessionScope".equals(name)) {
			return getSessionScope();
		} else if ("applicationScope".equals(name)) {
			return getApplicationScope();
		} else if ("param".equals(name)) {
			return _request != null ? new ParameterMap(_request) : Collections.EMPTY_MAP;
		} else if ("paramValues".equals(name)) {
			return _request != null ? _request.getParameterMap() : Collections.EMPTY_MAP;
		} else if ("header".equals(name)) {
			if (!(_request instanceof HttpServletRequest))
				return Collections.EMPTY_MAP;

			final HttpServletRequest hreq = (HttpServletRequest) _request;
			return new AttributesMap() {
				protected Enumeration getKeys() {
					return hreq.getHeaderNames();
				}

				protected Object getValue(String key) {
					return hreq.getHeader(key);
				}

				protected void setValue(String key, Object val) {
					throw new UnsupportedOperationException("readonly");
				}

				protected void removeValue(String key) {
					throw new UnsupportedOperationException("readonly");
				}
			};
		} else if ("headerValues".equals(name)) {
			if (!(_request instanceof HttpServletRequest))
				return Collections.EMPTY_MAP;

			final HttpServletRequest hreq = (HttpServletRequest) _request;
			return new AttributesMap() {
				//It is OK to cache because it is readonly
				private final Map _values = new HashMap();

				protected Enumeration getKeys() {
					return hreq.getHeaderNames();
				}

				protected Object getValue(String key) {
					Object o = _values.get(key);
					if (o == null) {
						final Enumeration e = hreq.getHeaders(key);
						if (e == null || !e.hasMoreElements())
							return null;

						final List l = new LinkedList();
						do {
							l.add(e.nextElement());
						} while (e.hasMoreElements());
						o = l.toArray(new String[l.size()]);
						_values.put(key, o);
					}
					return o;
				}

				protected void setValue(String key, Object val) {
					throw new UnsupportedOperationException("readonly");
				}

				protected void removeValue(String key) {
					throw new UnsupportedOperationException("readonly");
				}
			};
		} else if ("initParam".equals(name)) {
			if (_ctx == null)
				return Collections.EMPTY_MAP;

			return new AttributesMap() {
				protected Enumeration getKeys() {
					return _ctx.getInitParameterNames();
				}

				protected Object getValue(String key) {
					return _ctx.getInitParameter(key);
				}

				protected void setValue(String key, Object val) {
					throw new UnsupportedOperationException("readonly");
				}

				protected void removeValue(String key) {
					throw new UnsupportedOperationException("readonly");
				}
			};
		} else if ("cookie".equals(name)) {
			if (_cookies != null)
				return _cookies;

			final Cookie[] cookies;
			if (!(_request instanceof HttpServletRequest)
					|| (cookies = ((HttpServletRequest) _request).getCookies()) == null || cookies.length == 0)
				return Collections.EMPTY_MAP;

			_cookies = new HashMap<String, Cookie>();
			for (int j = cookies.length; --j >= 0;)
				_cookies.put(cookies[j].getName(), cookies[j]);
		}

		return findAttribute(name);
		//according EL spec, we have to search attribute
	}

	private HttpSession getSession() {
		if (_sess != null)
			return _sess;

		if (!(_request instanceof HttpServletRequest))
			return null;
		return _sess = ((HttpServletRequest) _request).getSession(false);
	}

	private Object findAttribute(String name) {
		Object o = getRequestScope().get(name);
		if (o != null)
			return o;
		o = getSessionScope().get(name);
		return o != null ? o : getApplicationScope().get(name);
	}

	private Map<String, Object> getRequestScope() {
		if (_reqScope != null)
			return _reqScope;
		if (_request == null)
			return Collections.emptyMap();
		return _reqScope = new RequestScope(_request);
	}

	private Map<String, Object> getSessionScope() {
		if (_sessScope != null)
			return _sessScope;
		final HttpSession sess = getSession();
		if (sess == null)
			return _sessScope = Collections.emptyMap();
		return _sessScope = new AttributesMap() {
			@SuppressWarnings("unchecked")
			protected Enumeration<String> getKeys() {
				return sess.getAttributeNames();
			}

			protected Object getValue(String key) {
				return sess.getAttribute(key);
			}

			protected void setValue(String key, Object val) {
				sess.setAttribute(key, val);
			}

			protected void removeValue(String key) {
				sess.removeAttribute(key);
			}
		};
	}

	private Map<String, Object> getApplicationScope() {
		if (_appScope != null)
			return _appScope;
		if (_ctx == null)
			return _appScope = Collections.emptyMap();
		return _appScope = new AttributesMap() {
			@SuppressWarnings("unchecked")
			protected Enumeration<String> getKeys() {
				return _ctx.getAttributeNames();
			}

			protected Object getValue(String key) {
				return _ctx.getAttribute(key);
			}

			protected void setValue(String key, Object val) {
				_ctx.setAttribute(key, val);
			}

			protected void removeValue(String key) {
				_ctx.removeAttribute(key);
			}
		};
	}

	/** An implementation of PageContext. */
	private class PageContextImpl implements PageContext {
		public ServletRequest getRequest() {
			return _request;
		}

		public ServletResponse getResponse() {
			return _response;
		}

		public ServletConfig getServletConfig() {
			return null;
		}

		public ServletContext getServletContext() {
			return _ctx;
		}

		public HttpSession getSession() {
			return RequestXelResolver.this.getSession();
		}

		public VariableResolver getVariableResolver() {
			return RequestXelResolver.this;
		}
	}
}
