/**
 * 
 */
package org.ajax4jsf.portlet.context;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import javax.faces.FacesException;
import javax.faces.context.FacesContext;
import javax.portlet.PortletSession;
import javax.portlet.PortletSessionUtil;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.portlet.AjaxPortletBridge;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author asmirnov
 * 
 */
public class ServletContextImpl extends AbstractExternalContext {

	private static final Log _log = LogFactory.getLog(ServletContextImpl.class);

	private String namespace;

	/**
	 * @param context
	 * @param request
	 * @param response
	 */
	public ServletContextImpl(ServletContext context,
			HttpServletRequest request, HttpServletResponse response) {
		super(context, request, response);
		ArrayList excludedAttributes = Collections.list(request.getAttributeNames());
		request.setAttribute(EXCLUDED_PARAMETERS_ATTRIBUTE, excludedAttributes);
	}

	public void setResponseCharacterEncoding(String encoding) {

		getHttpResponse().setCharacterEncoding(encoding);
	}

	public String getResponseCharacterEncoding() {
		return getHttpResponse().getCharacterEncoding();
	}

	public String getResponseContentType() {
		return getHttpResponse().getContentType();
	}

	public void setRequestCharacterEncoding(String encoding)
			throws UnsupportedEncodingException {
		getHttpRequest().setCharacterEncoding(encoding);
	}

	public String getRequestCharacterEncoding() {
		return getHttpRequest().getCharacterEncoding();
	}

	public String getRequestContentType() {
		return getHttpRequest().getContentType();
	}

	private HttpServletRequest getHttpRequest() {
		return (HttpServletRequest) getRequest();
	}

	private ServletContext getServletContext() {
		return (ServletContext) getContext();
	}

	private HttpServletResponse getHttpResponse() {
		return (HttpServletResponse) getResponse();
	}

	protected String getNamespace() {
		if (null == namespace) {
			Object requestParameter = getRequestParameter(NAMESPACE_PARAMETER);
			if (null != requestParameter) {
				namespace = (String) requestParameter;
				if (_log.isDebugEnabled()) {
					_log.debug("Namespace for a portlet instance is "
							+ namespace);
				}
			} else {
				throw new FacesException(
						"AJAX call to portlet without namespace parameter");
			}
		}
		return namespace;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.context.ExternalContext#getInitParameter(java.lang.String)
	 */
	public String getInitParameter(String name) {
		return getServletContext().getInitParameter(name);
	}

	protected Enumeration getInitParametersNames() {
		return getServletContext().getInitParameterNames();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.context.ExternalContext#getResource(java.lang.String)
	 */
	public URL getResource(String path) throws MalformedURLException {
		return getServletContext().getResource(path);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.context.ExternalContext#getResourceAsStream(java.lang.String)
	 */
	public InputStream getResourceAsStream(String path) {
		return getServletContext().getResourceAsStream(path);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.context.ExternalContext#getResourcePaths(java.lang.String)
	 */
	public Set getResourcePaths(String path) {
		return getServletContext().getResourcePaths(path);
	}

	protected Object getContextAttribute(String name) {
		return getServletContext().getAttribute(name);
	}

	protected Enumeration getContextAttributeNames() {
		return getServletContext().getAttributeNames();
	}

	protected void setContextAttribute(String name, Object value) {
		getServletContext().setAttribute(name, value);
	}

	public String getAuthType() {
		return getHttpRequest().getAuthType();
	}

	public String getRemoteUser() {
		Principal userPrincipal = getUserPrincipal();
		if( null != userPrincipal){
			return userPrincipal.getName();
		}
		return getHttpRequest().getRemoteUser();
	}

	public String getRequestContextPath() {
		return getHttpRequest().getContextPath();
	}

	public String getRequestPathInfo() {
		return getHttpRequest().getPathInfo();
	}

	public String getRequestServletPath() {
		return getHttpRequest().getServletPath();
	}

	protected Enumeration enumerateRequestParameterNames() {
		return getHttpRequest().getParameterNames();
	}

	protected Object getRequestAttribute(String name) {
		return getHttpRequest().getAttribute(name);
	}

	protected Enumeration getRequestAttributeNames() {
		return getHttpRequest().getAttributeNames();
	}

	protected Object getRequestParameterValues(String name) {
		return getHttpRequest().getParameterValues(name);
	}

	protected Object getRequestHeader(String name) {
		return getHttpRequest().getHeader(name);
	}

	protected Enumeration getRequestHeaderNames() {
		return getHttpRequest().getHeaderNames();
	}

	protected String[] getRequestHeaderValues(String name) {
		Enumeration values = getHttpRequest().getHeaders(name);
		ArrayList valuesList = new ArrayList();
		while (values.hasMoreElements()) {
			valuesList.add(values.nextElement());
		}
		return (String[]) valuesList.toArray(EMPTY_STRING_ARRAY);
	}

	protected Object getRequestParameter(String name) {
		return getHttpRequest().getParameter(name);
	}

	private String sessionPrefix;

	private String getSessionPrefix() {
		if (sessionPrefix == null) {
			HttpSession session = getHttpRequest().getSession(false);
			String namespase = getNamespace();
			if (null == namespase || null == session) {
				throw new FacesException(
						"JSF request called without portlet namespace parameter");
			}
			Enumeration attributeNames = session.getAttributeNames();
			while (attributeNames.hasMoreElements() && null == sessionPrefix) {
				String name = (String) attributeNames.nextElement();
				Object attribute = session.getAttribute(name);
				if (PortletSessionUtil.decodeScope(name) == PortletSession.PORTLET_SCOPE
						&& PortletSessionUtil.decodeAttributeName(name).equals(
								NAMESPACE_PARAMETER)
						&& namespase.equals(attribute)) {
					sessionPrefix = name.substring(0, name.length()
							- NAMESPACE_PARAMETER.length());
					if (_log.isDebugEnabled()) {
						_log
								.debug("Prefix for a PORTLET_SCOPE session attributes: "
										+ sessionPrefix);
					}
				}
			}
			if (null == sessionPrefix) {
				throw new FacesException(
						"Prefix for attributes in portlet session scope not found");
			}
		}
		return sessionPrefix;
	}

	protected Object getSessionAttribute(String name) {
		return getHttpRequest().getSession(true).getAttribute(
				getSessionPrefix() + name);
	}

	protected Enumeration getSessionAttributeNames() {
		return new SessionAttributesNames(getHttpRequest().getSession(true)
				.getAttributeNames(), getSessionPrefix());
	}

	protected void removeSessionAttribute(String name) {
		getHttpRequest().getSession(true).removeAttribute(
				getSessionPrefix() + name);
	}

	protected void setSessionAttribute(String name, Object value) {
		getHttpRequest().getSession(true).setAttribute(
				getSessionPrefix() + name, value);
	}

	protected void removeContextAttribute(String name) {
		getHttpRequest().getSession(true).removeAttribute(name);
	}

	protected void removeRequestAttribute(String name) {
		getHttpRequest().removeAttribute(name);
	}

	protected void setRequestAttribute(String name, Object value) {
		getHttpRequest().setAttribute(name, value);
	}

	public void dispatch(String path) throws IOException {
		RequestDispatcher requestDispatcher = getHttpRequest()
				.getRequestDispatcher(path);
		if (requestDispatcher == null) {
			(getHttpResponse()).sendError(HttpServletResponse.SC_NOT_FOUND);
			return;
		}
		try {
			requestDispatcher.forward(getHttpRequest(), getHttpResponse());
		} catch (IOException ioe) {
			throw ioe;
		} catch (ServletException se) {
			throw new FacesException(se);
		}

	}

	@Override
	protected String createActionUrl(Map<String, String> parameters) {
		String url = getHttpRequest().getParameter(ACTION__PARAMETER);
		if(null == url){
			throw new FacesException("No portal action url availible");
		}
		PortalActionURL portalUrl;
		try {
			portalUrl = new PortalActionURL(url);
		} catch (MalformedURLException e) {
			throw new FacesException("Malformed Portal Action URL "+url);
		}
		for (Entry<String, String> parameterEntry : parameters.entrySet()) {
			portalUrl.addParameter(parameterEntry.getKey(), parameterEntry.getValue());
		}
		return portalUrl.toString();
	}

	public String encodeResourceURL(String url) {
		return encodeURL(url);
	}

	/**
	 * @param url
	 * @return
	 */
	protected String encodeURL(String url) {
		return getHttpResponse().encodeURL(url);
	}

	public Locale getRequestLocale() {
		return getHttpRequest().getLocale();
	}

	public Iterator getRequestLocales() {
		return new EnumerationIterator(getHttpRequest().getLocales());
	}

	public Object getSession(boolean create) {
		HttpSession session = getHttpRequest().getSession(create);
		if (null != session) {
			session = new ServletSessionWrapper(session, getSessionPrefix());
		}
		return session;
	}

	public Principal getUserPrincipal() {
        Principal userPrincipal = getHttpRequest().getUserPrincipal();
		HttpSession httpSession = getHttpRequest().getSession(false);
		if(null != httpSession){
			Principal portalPrincipal = (Principal) httpSession.getAttribute(PORTAL_USER_PRINCIPAL);
			if(null != portalPrincipal){
				userPrincipal = portalPrincipal;
			}
		}
		return userPrincipal;
	}

	public boolean isUserInRole(String role) {
		return getHttpRequest().isUserInRole(role);
	}

	public void log(String message) {
		getServletContext().log(message);
	}

	public void log(String message, Throwable exception) {
		getServletContext().log(message, exception);
	}

	public void redirect(String url) throws IOException {
		getHttpResponse().sendRedirect(url);
		FacesContext.getCurrentInstance().responseComplete();
	}

}
