/**
 * License Agreement.
 *
 * Rich Faces - Natural Ajax for Java Server Faces (JSF)
 *
 * Copyright (C) 2007 Exadel, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */
/*

 * Created on 25.09.2004

 *

 * Copyright 1999-2004 The Apache Software Foundation.

 * 

 * 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 org.ajax4jsf.portlet.context;

import java.net.MalformedURLException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;

import javax.faces.FacesException;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.portlet.faces.Bridge;

import org.ajax4jsf.portlet.AjaxPortletBridge;

/**
 * 
 * @author shura
 * 
 * 
 * 
 * Cocoon specific implementation of <code>ExternalContext</code> Use cocoon
 * 
 * <code>Context</code> for Implement JSF Servlet specific functions Do not
 * 
 * perform redirect - but tell <code>Action</code> for internal pipeline
 * 
 * redirect ? or do normal Cocoon redirect Via Http Environment do not dispanch -
 * 
 * use Cocoon generator source parametr, but perform generation Full
 * 
 * Cocoon-specific realization ;
 * 
 * 
 * 
 */
public abstract class AbstractExternalContext extends ExternalContext {

	/**
	 * Request parameter to store current View Id.
	 */
	public static final String VIEW_ID_PARAMETER = "org.ajax4jsf.portlet.VIEWID";
	/**
	 * Name of request parameter to store namsepace of the current portlet
	 * instance.
	 */
	public static final String NAMESPACE_PARAMETER = "org.ajax4jsf.portlet.NAMESPACE";
	public static final String ACTION__PARAMETER = "org.ajax4jsf.portlet.ACTION_URL";
	public static final Object PORTLET_MODE_PARAMETER = "org.ajax4jsf.portlet.MODE";
	public static final Object PORTLET_NAME_PARAMETER = "org.ajax4jsf.portlet.NAME";

	protected static final String[] EMPTY_STRING_ARRAY = new String[0];
	public static final String PORTLET_CONFIG_ATTRIBUTE = "org.ajax4jsf.portlet.CONFIG";
	public static final String EXCLUDED_PARAMETERS_ATTRIBUTE = "org.ajax4jsf.portlet.REQUEST_PARAMETERS";
	public static final Object RENDER_POLICY_ATTRIBUTE = "org.ajax4jsf.portlet.RENDER_POLICY";
	public static final String PORTAL_USER_PRINCIPAL = "org.ajax4jsf.portlet.USER_PRINCIPAL";
	// TODO - optimization.
	private Map applicationMap;

	private Map initParameterMap;

	private Map requestHeaderMap = null;

	private Map requestHeaderValues;

	private Map requestMap;

	private Map requestParameterMap;

	private Map requestParameterValuesMap;

	private Map sessionMap;

	private Object request;

	private Object response;

	private boolean http;

	private Map actionSettings;

	private Object context;
	
	private boolean hasNavigationRedirect = false;
	public static final Object REDIRECT_VIEW_ID = "javax.faces.portlet.REDIRECT_VIEW_ID";
	public static final String CONVERSATION_ID_PARAMETER = "conversationId";

	/**
	 * @return the hasNavigationRedirect
	 */
	boolean isHasNavigationRedirect() {
		return hasNavigationRedirect;
	}

	/**
	 * @param hasNavigationRedirect the hasNavigationRedirect to set
	 */
	void setHasNavigationRedirect(boolean hasNavigationRedirect) {
		this.hasNavigationRedirect = hasNavigationRedirect;
	}

	/**
	 * 
	 * @param context
	 * @param request
	 * @param response
	 * @param defaultContext -
	 * 
	 * default implementation of <code>ExternalFacesContext</code>.
	 * 
	 */
	public AbstractExternalContext(Object context, Object request,
			Object response) {
		super();
		this.context = context;
		this.request = request;
		this.response = response;
	}

	protected abstract String getNamespace();

	public String encodeNamespace(String name) {

		return getNamespace() + name;
	}

	/*
	 * 
	 * (non-Javadoc)
	 * 
	 * 
	 * 
	 * @see javax.faces.context.ExternalContext#dispatch(java.lang.String)
	 * 
	 */
	public Map getApplicationMap() {
		if (this.applicationMap == null) {
			this.applicationMap = new ContextAttributesMap() {
				protected Enumeration getEnumeration() {
					return getContextAttributeNames();
				}

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

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

				protected void removeAttribute(String name) {
					removeContextAttribute(name);
				}
			};
		}
		return this.applicationMap;
	}

	protected abstract void removeContextAttribute(String name);

	protected abstract void setContextAttribute(String name, Object value);

	protected abstract Object getContextAttribute(String name);

	protected abstract Enumeration getContextAttributeNames();

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.context.ExternalContext#getAuthType()
	 */
	public Object getContext() {
		return this.context;
	}

	/**
	 * @param context
	 */
	public void setContext(Object context) {
		this.context = context;
		this.applicationMap = null;
		this.initParameterMap = null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.context.ExternalContext#getInitParameter(java.lang.String)
	 */
	public Map getInitParameterMap() {
		if (this.initParameterMap == null) {
			this.initParameterMap = new ContextAttributesMap() {
				protected Object getAttribute(String name) {
					return getInitParameter(name);
				}

				protected void setAttribute(String name, Object value) {
					throw new UnsupportedOperationException();
				}

				protected Enumeration getEnumeration() {
					return getInitParametersNames();
				}
			};
		}
		return this.initParameterMap;
	}

	/**
	 * Hoock method for initialization parameters.
	 * 
	 * @return
	 */
	protected abstract Enumeration getInitParametersNames();

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.context.ExternalContext#getRequest()
	 */
	public Object getRequest() {
		return this.request;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.context.ExternalContext#setRequest(java.lang.Object)
	 */
	public void setRequest(Object request) {
		this.requestHeaderMap = null;
		this.requestHeaderValues = null;
		this.requestMap = null;
		this.requestParameterMap = null;
		this.requestParameterValuesMap = null;
		this.sessionMap = null;
		this.request = request;
	}

	public Map getRequestCookieMap() {
		// Portlet environment don't have methods to use cookies.
		return Collections.EMPTY_MAP;
	}

	/*
	 * 
	 * (non-Javadoc)
	 * 
	 * 
	 * 
	 * @see javax.faces.context.ExternalContext#getRequestHeaderMap()
	 * 
	 */
	public Map getRequestHeaderMap() {
		if (this.requestHeaderMap == null) {
			this.requestHeaderMap = new ContextAttributesMap() {
				protected Enumeration getEnumeration() {
					return getRequestHeaderNames();
				}

				protected Object getAttribute(String name) {
					return getRequestHeader(name);
				}

				protected void setAttribute(String name, Object value) {
					throw new UnsupportedOperationException();
				}
			};
		}
		return this.requestHeaderMap;
	}

	protected abstract Object getRequestHeader(String name);

	protected abstract Enumeration getRequestHeaderNames();

	public Map getRequestHeaderValuesMap() {
		// 
		if (this.requestHeaderValues == null) {
			this.requestHeaderValues = new ContextAttributesMap() {
				protected Enumeration getEnumeration() {
					return getRequestHeaderNames();
				}

				protected Object getAttribute(String name) {
					return getRequestHeaderValues(name);
				}

				protected void setAttribute(String name, Object value) {
					throw new UnsupportedOperationException();
				}
			};
		}
		return this.requestHeaderValues;
	}

	protected abstract String[] getRequestHeaderValues(String name);

	public Map getRequestMap() {
		if (this.requestMap == null) {
			this.requestMap = new ContextAttributesMap() {
				protected Enumeration getEnumeration() {
					return getRequestAttributeNames();
				}

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

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

				protected void removeAttribute(String name) {
					removeRequestAttribute(name);
				}
			};
		}
		return this.requestMap;
	}

	protected abstract void removeRequestAttribute(String name);

	protected abstract void setRequestAttribute(String name, Object value);

	protected abstract Object getRequestAttribute(String name);

	protected abstract Enumeration getRequestAttributeNames();

	public Map getRequestParameterMap() {
		// 
		if (this.requestParameterMap == null) {
			this.requestParameterMap = new ContextAttributesMap() {
				protected Enumeration getEnumeration() {
					return enumerateRequestParameterNames();
				}

				protected Object getAttribute(String name) {
					return getRequestParameter(name);
				}

				protected void setAttribute(String name, Object value) {
					throw new UnsupportedOperationException();
				}
			};
		}
		return this.requestParameterMap;
	}

	protected abstract Object getRequestParameter(String name);

	protected abstract Enumeration enumerateRequestParameterNames();

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.context.ExternalContext#getRequestParameterNames()
	 * 
	 */
	public Iterator getRequestParameterNames() {
		return new EnumerationIterator(enumerateRequestParameterNames());
	}

	/*
	 * 
	 * (non-Javadoc)
	 * 
	 * 
	 * 
	 * @see javax.faces.context.ExternalContext#getRequestParameterValuesMap()
	 * 
	 */
	public Map getRequestParameterValuesMap() {
		if (this.requestParameterValuesMap == null) {
			this.requestParameterValuesMap = new ContextAttributesMap() {

				protected Enumeration getEnumeration() {
					return enumerateRequestParameterNames();
				}

				protected Object getAttribute(String name) {
					return getRequestParameterValues(name);
				}

				protected void setAttribute(String name, Object value) {
					throw new UnsupportedOperationException();
				}
			};
		}
		return this.requestParameterValuesMap;
	}

	protected abstract Object getRequestParameterValues(String name);

	/*
	 * 
	 * (non-Javadoc)
	 * 
	 * 
	 * 
	 * @see javax.faces.context.ExternalContext#getResponse()
	 * 
	 */
	public void setResponse(Object response) {
		this.response = response;
	}

	public Object getResponse() {
		return this.response;
	}

	/*
	 * 
	 * (non-Javadoc)
	 * 
	 * 
	 * 
	 * @see javax.faces.context.ExternalContext#getSessionMap()
	 * 
	 */
	public Map getSessionMap() {
		if (this.sessionMap == null) {
			this.sessionMap = new ContextAttributesMap() {

				protected Enumeration getEnumeration() {
					return getSessionAttributeNames();
				}

				protected Object getAttribute(String name) {
					return getSessionAttribute(name);
				}

				protected void setAttribute(String name, Object value) {
					setSessionAttribute(name, value);
				}

				protected void removeAttribute(String name) {
					removeSessionAttribute(name);
				}

			};
		}
		return this.sessionMap;
	}

	protected abstract void removeSessionAttribute(String name);

	protected abstract void setSessionAttribute(String name, Object value);

	protected abstract Object getSessionAttribute(String name);

	protected abstract Enumeration getSessionAttributeNames();

	protected abstract String createActionUrl(Map<String, String> parameters);

	public String encodeActionURL(String url) {
		if (null == url) {
			throw new NullPointerException();
		}
		String actionUrl = url;
		if (!actionUrl.startsWith("#")) {
			try {
				PortalActionURL portalUrl = new PortalActionURL(url);
				String directLink = portalUrl.getParameter(Bridge.DIRECT_LINK);
				boolean inContext = portalUrl
						.isInContext(getRequestContextPath());
				if (inContext
						&& (null == directLink || false == Boolean
								.parseBoolean(directLink))) {
					if (null != directLink) {
						portalUrl.removeParameter(Bridge.DIRECT_LINK);

					}
					String viewId = portalUrl.getParameter(VIEW_ID_PARAMETER);
					return createActionUrl(portalUrl.getParameters());
				}
			} catch (MalformedURLException e) {
				throw new FacesException(e);
			}
		}
		return encodeURL(actionUrl);
	}

	protected abstract String encodeURL(String actionUrl);

	public String encodeResourceURL(String url) {
		try {
			PortalActionURL portalUrl = new PortalActionURL(url);
			boolean inContext = portalUrl
					.isInContext(getRequestContextPath());
			// For resources in the portlet application context add namespace as URL parameter, to restore portlet session.
			if (inContext) {
				portalUrl.addParameter(NAMESPACE_PARAMETER, getNamespace());
				url=portalUrl.toString();
			}
		} catch (MalformedURLException e) {
			throw new FacesException(e);
		}
		return encodeURL(url);
	}
}