/**
 * 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.application;

import java.io.IOException;
import java.util.Map;

import javax.faces.FacesException;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.servlet.http.HttpServletResponse;

import org.ajax4jsf.Messages;
import org.ajax4jsf.application.ViewHandlerWrapper;
import org.ajax4jsf.component.AjaxViewRoot;
import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.context.ViewIdHolder;
import org.ajax4jsf.renderkit.AjaxContainerRenderer;
import org.ajax4jsf.renderkit.AjaxRendererUtils;
import org.ajax4jsf.renderkit.RendererUtils.HTML;
import org.ajax4jsf.resource.InternetResource;
import org.ajax4jsf.resource.InternetResourceBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author asmirnov@exadel.com (latest modification by $Author: alexsmirnov $)
 * @version $Revision: 1.1.2.5 $ $Date: 2007/02/08 15:02:04 $
 * 
 */
public class AjaxViewHandler extends ViewHandlerWrapper {

	public static final String STATE_MARKER_KEY = "org.ajax4jsf.view.state";

	public static final String SERIALIZED_STATE_KEY = "org.ajax4jsf.view.serializedstate";

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

	public static final String VIEW_EXPIRED = "org.ajax4jsf.view.EXPIRED";

	public static final String VIEW_ID_KEY = "org.ajax4jsf.VIEW_ID";

	/**
	 * @param parent
	 */
	public AjaxViewHandler(ViewHandler parent) {
		super(parent);
		if (_log.isDebugEnabled()) {
			_log.debug("Create instance of Ajax ViewHandler");
		}
	}

	/*
	 * (non-Javadoc) For creating ViewRoot by Application Instead of new . Not
	 * nessesary for MyFaces ( simple copy from it ) or RI 1.2
	 * 
	 * @see javax.faces.application.ViewHandler#createView(javax.faces.context.FacesContext,
	 *      java.lang.String)
	 */
	public UIViewRoot createView(FacesContext facesContext, String viewId) {
		AjaxContext ajaxContext = AjaxContext.getCurrentInstance(facesContext);
		// Check for simple keep new ViewId in navigation cases.
		ViewIdHolder viewIdHolder = ajaxContext.getViewIdHolder();
		UIViewRoot riRoot;
		if (null != viewIdHolder && viewIdHolder.skipNavigation(viewId)) {
			viewIdHolder.setViewId(viewId);
			riRoot = facesContext.getViewRoot();
		} else {
			UIViewRoot root = super.createView(facesContext, viewId);
			// TODO -setup render-kit where instead of Listener ?
			if (null == root || root instanceof AjaxViewRoot) {
				riRoot = root;
			} else {
				// HACK - since only RI 1.1 use new for create ViewRoot instead
				// of Application.createComponent,
				// we can use it as flag for custom creation.
				riRoot = (UIViewRoot) facesContext.getApplication()
						.createComponent(UIViewRoot.COMPONENT_TYPE);
				// fill properties from default.
				riRoot.setViewId(root.getViewId());
				riRoot.setLocale(root.getLocale());
				String renderKitId = root.getRenderKitId();
				// Fix facelets bug - for debug requests renderKitId is null !
				if (null == renderKitId) {
					renderKitId = calculateRenderKitId(facesContext);
				}
				riRoot.setRenderKitId(renderKitId);
			}
			// if (null ==
			// facesContext.getExternalContext().getRequestMap().get(VIEW_EXPIRED))
			// {
			// Clear ajax request flag ( except new view creation )- for navigation states, full page will be
			// send. 
			if(null != facesContext.getViewRoot()){
				ajaxContext.setAjaxRequest(false);
			}
			// }
		}
		return riRoot;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.ajax4jsf.framework.ViewHandlerWrapper#writeState(javax.faces.context.FacesContext)
	 */
	public void writeState(FacesContext context) throws IOException {
		AjaxContext ajaxContext = AjaxContext.getCurrentInstance(context);
		if (ajaxContext.isAjaxRequest(context)) {
			// TODO - detect case of JSF 1.1 + JSP. for all other we need own
			// state marker for
			// self-rendered regions only.
			// write marker html element - input field. Will be parsed in filter
			// and
			// replaced with real state.
			ResponseWriter writer = context.getResponseWriter();
			writer.startElement(HTML.SPAN_ELEM, null);
			writer.writeAttribute(HTML.id_ATTRIBUTE, STATE_MARKER_KEY, null);
			writer.writeAttribute(HTML.NAME_ATTRIBUTE, STATE_MARKER_KEY, null);
			// writer.writeAttribute("style", "display:none;", null);
			if (!ajaxContext.isSelfRender()) {
				super.writeState(context);
			}
			writer.endElement(HTML.SPAN_ELEM);
		} else {
			super.writeState(context);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.ajax4jsf.framework.ViewHandlerWrapper#restoreView(javax.faces.context.FacesContext,
	 *      java.lang.String)
	 */
	public UIViewRoot restoreView(FacesContext context, String viewId) {
		UIViewRoot viewRoot = super.restoreView(context, viewId);
		ExternalContext externalContext = context.getExternalContext();
		Map requestMap = externalContext.getRequestMap();
		if (null == viewRoot) {
			requestMap.put(VIEW_EXPIRED, Messages
					.getMessage(Messages.AJAX_VIEW_EXPIRED));
			if (_log.isDebugEnabled()) {
				_log
						.debug("Detect session expiration in AJAX request - view don't restored for a viewId "
								+ viewId);
			}
		}
		return viewRoot;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.ajax4jsf.framework.ViewHandlerWrapper#getResourceURL(javax.faces.context.FacesContext,
	 *      java.lang.String)
	 */
	public String getResourceURL(FacesContext context, String url) {
		String resourceURL;
		if (url.startsWith(InternetResource.RESOURCE_URI_PREFIX)) {
			InternetResource resource = InternetResourceBuilder
					.getInstance()
					.createResource(
							null,
							url
									.substring(InternetResource.RESOURCE_URI_PREFIX_LENGTH));
			resourceURL = resource.getUri(context, null);
		} else {
			resourceURL = super.getResourceURL(context, url);
		}
		return resourceURL;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.ajax4jsf.framework.ViewHandlerWrapper#renderView(javax.faces.context.FacesContext,
	 *      javax.faces.component.UIViewRoot)
	 */
	public void renderView(FacesContext context, UIViewRoot root)
			throws IOException, FacesException {
		AjaxContext ajaxContext = AjaxContext.getCurrentInstance(context);
		// Broadcast Ajax events before rendering, to setup areas to update.
		if (root instanceof AjaxViewRoot) {
			AjaxViewRoot ajaxRoot = (AjaxViewRoot) root;
			Map requestMap = context.getExternalContext().getRequestMap();
			// broadcast ajax events before render response.
			if (ajaxContext.isAjaxRequest(context)
					&& null == requestMap
							.get(AjaxRendererUtils.AJAX_AREAS_RENDERED)) {
				processAjaxEvents(context, ajaxRoot);
			}
			if (!context.getResponseComplete()) {
				super.renderView(context, root);
				if (ajaxContext.isAjaxRequest(context)
						&& null == requestMap
								.get(AjaxRendererUtils.AJAX_AREAS_RENDERED)) {
					// HACK for MyFaces ( <f:view> tag not call renderers )
					if (_log.isDebugEnabled()) {
						_log
								.debug(Messages
										.getMessage(Messages.AJAX_RESPONSE_NOT_RENDERED_INFO));
					}
					ajaxContext.setSelfRender(true);
					// ServletResponse response = (ServletResponse) context
					// .getExternalContext().getResponse();
					Object response = (Object) context.getExternalContext()
							.getResponse();
					try {
						response.getClass().getDeclaredMethod("reset",
								new Class[0]).invoke(response, new Object[0]);
						// response.reset();
					} catch (Exception e) {
						// Do nothing - we will use directly and reset
						// wrapper
					}
					ajaxContext.renderSubmittedAjaxRegion(context, true);
				}
			}
		} else {
			super.renderView(context, root);
		}
		ajaxContext.processHeadResources(context);
	}

	/**
	 * @param context
	 * @param ajaxRoot
	 */
	private void processAjaxEvents(FacesContext context, AjaxViewRoot ajaxRoot) {
		// First - process reRender from active components.
		if (_log.isDebugEnabled()) {
			_log.debug(Messages.getMessage(Messages.PROCESS_AJAX_EVENTS_INFO));
		}
		ajaxRoot.broadcastAjaxEvents(context);
	}

}
