/**
 * 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
 */

package org.ajax4jsf.webapp;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Enumeration;

import javax.faces.application.ViewHandler;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.ajax4jsf.Messages;
import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.renderkit.AjaxContainerRenderer;
import org.ajax4jsf.resource.InternetResourceService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Base class for request processing filters, with convert Htmp content to XML
 * for ajax requests, and serve request to application off-page resources
 * 
 * @author shura (latest modification by $Author: alexsmirnov $)
 * @version $Revision: 1.1.2.1 $ $Date: 2007/01/09 18:58:21 $
 * 
 */
public abstract class BaseFilter implements Filter {

	public static final String AJAX_PUSH_KEY_HEADER = "Ajax-Push-Key";

	private static final Log log = LogFactory.getLog(BaseFilter.class);

	public static final boolean DEBUG = true;

	private FilterConfig filterConfig;

	private static final String FUNCTION_NAME_PARAMETER = "function";

	private String function = "alert('Data received');JSHttpRequest.dataReady";

	private String attributesNames;

	private boolean rewriteid = false;

	public static final String REWRITEID_PARAMETER = "rewriteid";

	public static final String STYLESHEET_PARAMETER = "xsl";

	public static final String ABSOLUTE_TAGS_PARAMETER = "absolute-attributes";

	// private WebXml webXml;
	// private String xsl;
	// private Templates xslTemplates;
	/**
	 * 
	 */
	private static final long serialVersionUID = -2295534611886142935L;

	public static final String DATA_PARAMETER = "DATA";

	public static final String DEFAULT_SERVLET_PATH = "/resource";

	public static final String RENDERER_PREFIX = "/renderer";

	public static final String CACHEABLE_PREFIX = "/cache";

	// private static final Pattern rendererPattern =
	// Pattern.compile(RENDERER_PREFIX+"/([^/]+)/([^/]+)/([^/]+)/(.*)");
	// private static final Pattern builderPattern =
	// Pattern.compile(CACHEABLE_PREFIX+"/(.*)");
	public static final String FILTER_PERFORMED = "com.exade.vcp.Filter.done";

	public static final String RESPONSE_WRAPPER_ATTRIBUTE = "com.exade.vcp.Filter.ResponseWrapper";

	protected BaseXMLFilter xmlFilter = null;

	protected InternetResourceService resourceService = null;

	protected PollEventsManager eventsManager;

	/**
	 * Initialize the filter.
	 */
	public void init(FilterConfig config) throws ServletException {
		if (log.isDebugEnabled()) {
			log.debug("Init ajax4jsf filter with nane: "
					+ config.getFilterName());
			Enumeration parameterNames = config.getInitParameterNames();
			StringBuffer parameters = new StringBuffer("Init parameters :\n");
			while (parameterNames.hasMoreElements()) {
				String name = (String) parameterNames.nextElement();
				parameters.append(name).append(" : '").append(
						config.getInitParameter(name)).append('\n');
			}
			log.debug(parameters);
			// log.debug("Stack Trace", new Exception());
		}
		// Save config
		filterConfig = config;
		setFunction((String) nz(filterConfig
				.getInitParameter(FUNCTION_NAME_PARAMETER), getFunction()));
		setAttributesNames(filterConfig
				.getInitParameter(ABSOLUTE_TAGS_PARAMETER));
		xmlFilter.init(config);
		if ("true".equalsIgnoreCase(filterConfig
				.getInitParameter(REWRITEID_PARAMETER))) {
			this.setRewriteid(true);
		}

		String prefix = filterConfig.getServletContext().getRealPath("/");
		String file = filterConfig.getInitParameter("log4j-init-file");
		// if the log4j-init-file is not set, then no point in trying
		if (file != null) {
			Log4JConfigurator log4jconfig = new Log4JConfigurator(prefix);
			log4jconfig.doConfigure(file);
		}
		resourceService = new InternetResourceService();
		// Caching initialization.
		resourceService.init(filterConfig);
		eventsManager = new PollEventsManager();
		eventsManager.init(filterConfig.getServletContext());
	}

	/**
	 * @param httpServletRequest
	 * @throws UnsupportedEncodingException
	 */
	protected void setupRequestEncoding(HttpServletRequest httpServletRequest)
			throws UnsupportedEncodingException {
		String contentType = httpServletRequest.getHeader("Content-Type");

		String characterEncoding = lookupCharacterEncoding(contentType);

		if (characterEncoding == null) {
			HttpSession session = httpServletRequest.getSession(false);

			if (session != null) {
				characterEncoding = (String) session
						.getAttribute(ViewHandler.CHARACTER_ENCODING_KEY);
			}

			if (characterEncoding != null) {
				httpServletRequest.setCharacterEncoding(characterEncoding);
			}
		}
	}

	/**
	 * Detect request encoding from Content-Type header
	 * 
	 * @param contentType
	 * @return - charset, if present.
	 */
	private String lookupCharacterEncoding(String contentType) {
		String characterEncoding = null;

		if (contentType != null) {
			int charsetFind = contentType.indexOf("charset=");
			if (charsetFind != -1) {
				if (charsetFind == 0) {
					// charset at beginning of Content-Type, curious
					characterEncoding = contentType.substring(8);
				} else {
					char charBefore = contentType.charAt(charsetFind - 1);
					if (charBefore == ';' || Character.isWhitespace(charBefore)) {
						// Correct charset after mime type
						characterEncoding = contentType
								.substring(charsetFind + 8);
					}
				}
				if (log.isDebugEnabled())
					log.debug(Messages.getMessage(
							Messages.CONTENT_TYPE_ENCODING, characterEncoding));
			} else {
				if (log.isDebugEnabled())
					log.debug(Messages.getMessage(
							Messages.CONTENT_TYPE_NO_ENCODING, contentType));
			}
		}
		return characterEncoding;
	}

	/**
	 * @param initParameter
	 * @param function2
	 * @return
	 */
	private Object nz(Object param, Object def) {
		return param != null ? param : def;
	}

	/**
	 * Execute the filter.
	 */
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		long startTimeMills = 0;
		// Detect case of request - normal, AJAX, AJAX - JavaScript
		// TODO - detect first processing in filter.
		HttpServletRequest httpServletRequest = (HttpServletRequest) request;
		HttpServletResponse httpServletResponse = (HttpServletResponse) response;
		if (log.isDebugEnabled()) {
			startTimeMills = System.currentTimeMillis();
			log.debug(Messages.getMessage(Messages.FILTER_START_INFO, new Date(
					startTimeMills), httpServletRequest.getRequestURI()));
		}

		if (request.getAttribute(FILTER_PERFORMED) != Boolean.TRUE) {
			// mark - and not processing same request twice.
			try {
				request.setAttribute(FILTER_PERFORMED, Boolean.TRUE);
				String ajaxPushHeader = httpServletRequest
						.getHeader(AJAX_PUSH_KEY_HEADER);
				// check for a push check request.
				if (httpServletRequest.getMethod().equals("HEAD")
						&& null != ajaxPushHeader) {
					PushEventsCounter listener = eventsManager
							.getListener(ajaxPushHeader);
					if (listener.isPerformed()) {
						listener.processed();
						httpServletResponse.setStatus(200);
						if (log.isDebugEnabled()) {
							log
									.debug("Occurs event for a id "
											+ ajaxPushHeader);
						}
					} else {
						// Response code - 'No content'
						httpServletResponse.setStatus(204);
						if (log.isDebugEnabled()) {
							log.debug("No event for a id " + ajaxPushHeader);
						}
					}
				} else
				// check for resource request
				if (!getResourceService().serviceResource(httpServletRequest,
						httpServletResponse)) {
					// Not request to resource - perform filtering.
					// first stage - detect/set encoding of request. Same as in
					// Myfaces External Context.
					setupRequestEncoding(httpServletRequest);
					// check ajax request parameter
					// TODO - check for JSF page.
					if (true) {
						if (log.isDebugEnabled()) {
							log.debug(Messages
									.getMessage(Messages.FILTER_XML_OUTPUT));
						}

						// Execute the rest of the filter chain, including the
						// JSP
						xmlFilter.doXmlFilter(chain, httpServletRequest,
								httpServletResponse);
					} else {
						// normal request, execute chain ...
						if (log.isDebugEnabled()) {
							log.debug(Messages
									.getMessage(Messages.FILTER_NO_XML_CHAIN));
						}
						chain.doFilter(request, response);

					}
				}

			} finally {
				// Remove filter marker from response, to enable sequence calls ( for example, forward to error page )
				request.removeAttribute(FILTER_PERFORMED);
				Object ajaxContext = request.getAttribute(AjaxContext.AJAX_CONTEXT_KEY);
				if(null != ajaxContext && ajaxContext instanceof AjaxContext){
					((AjaxContext)ajaxContext).release();
					request.removeAttribute(AjaxContext.AJAX_CONTEXT_KEY);
				}
			}
		} else {
			if (log.isDebugEnabled()) {
				log.debug(Messages.getMessage(Messages.FILTER_NO_XML_CHAIN_2));
			}
			chain.doFilter(request, response);

		}
		if (log.isDebugEnabled()) {
			startTimeMills = System.currentTimeMillis() - startTimeMills;
			log.debug(Messages.getMessage(Messages.FILTER_STOP_INFO, ""
					+ startTimeMills, httpServletRequest.getRequestURI()));
		}
	}

	/**
	 * @param request
	 * @return
	 */
	protected boolean isAjaxRequest(ServletRequest request) {
		try {
			return null != request
					.getParameter(AjaxContainerRenderer.AJAX_PARAMETER_NAME);
		} catch (Exception e) {
			// OCJ 10 - throw exception for static resources.
			return false;
		}
	}

	/**
	 * Destroy the filter.
	 */
	public void destroy() {
	}

	/**
	 * @return Returns the servletContext.
	 */
	ServletContext getServletContext() {
		return filterConfig.getServletContext();
	}

	/**
	 * @return the resourceService
	 * @throws ServletException
	 */
	protected synchronized InternetResourceService getResourceService()
			throws ServletException {
		// if (resourceService == null) {
		// resourceService = new InternetResourceService();
		// // Caching initialization.
		// resourceService.init(filterConfig);
		//
		// }
		return resourceService;
	}

	/**
	 * @param function
	 *            The function to set.
	 */
	protected void setFunction(String function) {
		this.function = function;
	}

	/**
	 * @return Returns the function.
	 */
	protected String getFunction() {
		return function;
	}

	/**
	 * @param rewriteid
	 *            The rewriteid to set.
	 */
	protected void setRewriteid(boolean rewriteid) {
		this.rewriteid = rewriteid;
	}

	/**
	 * @return Returns the rewriteid.
	 */
	protected boolean isRewriteid() {
		return rewriteid;
	}

	/**
	 * @param attributesNames
	 *            The attributesNames to set.
	 */
	protected void setAttributesNames(String attributesNames) {
		this.attributesNames = attributesNames;
	}

	/**
	 * @return Returns the attributesNames.
	 */
	protected String getAttributesNames() {
		return attributesNames;
	}
}
