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

import java.io.IOException;
import java.io.PrintWriter;

import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextFactory;
import javax.faces.lifecycle.Lifecycle;
import javax.faces.lifecycle.LifecycleFactory;
import javax.faces.webapp.FacesServlet;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletConfig;
import javax.portlet.PortletException;
import javax.portlet.PortletMode;
import javax.portlet.PortletRequest;
import javax.portlet.PortletResponse;
import javax.portlet.PortletSession;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.faces.Bridge;
import javax.portlet.faces.BridgeException;
import javax.servlet.ServletResponse;

import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.portlet.application.PortletStateHolder;
import org.ajax4jsf.portlet.context.AbstractExternalContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author asmirnov
 * 
 */
public class AjaxFacesPortlet extends GenericPortlet implements Bridge {
	private static final Log log = LogFactory.getLog(AjaxFacesPortlet.class);

	/**
	 * <p>
	 * The key used to store/retrive the PortletConfig object.
	 * </p>
	 */
	public static final String PORTLET_CONFIG = "javax.portlet.PortletConfig";

	private Lifecycle lifecycle;

	private FacesContextFactory facesContextFactory;

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.portlet.GenericPortlet#init(javax.portlet.PortletConfig)
	 */
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.ajax4jsf.portlet.Brige#init(javax.portlet.PortletConfig)
	 */
	public void init(PortletConfig config) throws PortletException {
		super.init(config);
		try {
			if (log.isDebugEnabled()) {
				log.debug("Start portlet initialisation for "
						+ config.getPortletName());
			}
			// get faces lifecycle instance. Name of the Lifecycle can be
			// changed by the init parameter, as described in the JSR 301 PLT
			// 3.2
			LifecycleFactory factory = (LifecycleFactory) FactoryFinder
					.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
			String lifecycleId = config
					.getInitParameter(FacesServlet.LIFECYCLE_ID_ATTR);
			if (null == lifecycleId) {
				lifecycleId = config.getPortletContext().getInitParameter(
						FacesServlet.LIFECYCLE_ID_ATTR);
				if (null == lifecycleId) {
					lifecycleId = LifecycleFactory.DEFAULT_LIFECYCLE;

				}
			}
			if (log.isDebugEnabled()) {
				log.debug("Create instance of a JSF lifecycle " + lifecycleId);
			}
			this.lifecycle = factory.getLifecycle(lifecycleId);
			// get faces context factory instance
			this.facesContextFactory = (FacesContextFactory) FactoryFinder
					.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
		} catch (FacesException e) {
			throw new PortletException("Initialization error", e);
		}
		if (log.isDebugEnabled()) {
			log.debug("Done portlet initialisation for "
					+ config.getPortletName());
		}
		// getPortletContext().setAttribute(PORTLET_CONFIG, config);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.portlet.GenericPortlet#destroy()
	 */
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.ajax4jsf.portlet.Brige#destroy()
	 */
	public void destroy() {
		if (log.isDebugEnabled()) {
			log.debug("Destroy portlet " + getPortletConfig().getPortletName());
		}
		this.lifecycle = null;
		this.facesContextFactory = null;
		super.destroy();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.ajax4jsf.portlet.Brige#doFacesRequest(javax.portlet.ActionRequest,
	 *      javax.portlet.ActionResponse)
	 */
	public void doFacesRequest(ActionRequest request, ActionResponse response)
			throws BridgeException {
		initRequest(request,response,Bridge.PortletPhase.ActionPhase);
		FacesContext facesContext = getFacesContext(request, response);
		try {
			execute(facesContext);
			// saveView(facesContext);
		} catch (Exception e) {
			log.error("Error processing execute lifecycle", e);
			throw new BridgeException("Error processing execute lifecycle ", e);
		} finally {
			facesContext.release();
		}
	}



	/*
	 * (non-Javadoc)
	 * 
	 * @see org.ajax4jsf.portlet.Brige#doFacesRequest(javax.portlet.RenderRequest,
	 *      javax.portlet.RenderResponse)
	 */
	public void doFacesRequest(RenderRequest request, RenderResponse response)
			throws BridgeException {
		initRequest(request,response,Bridge.PortletPhase.RenderPhase);

		FacesContext facesContext = getFacesContext(request, response);
		try {
			if (null == response.getContentType()) {
				String contenttype = request.getResponseContentType();
				if (contenttype == null) {
					contenttype = "text/html";
				}
				String encoding = response.getCharacterEncoding();
				if (encoding == null) {
					encoding = "UTF-8";
				}
				response.setContentType(contenttype);

			}
			PrintWriter writer = response.getWriter();
			String namespace = response.getNamespace();
			// Write anchor for update portlet pages by ajax.
			// TODO - configure html element and style/class.
			writer.println("<div id='" + namespace + "'>");
			// writer.flush();
			AjaxContext ajaxContext = AjaxContext
					.getCurrentInstance(facesContext);
			ajaxContext.getCommonAjaxParameters().put(
					AbstractExternalContext.ACTION__PARAMETER,
					response.createActionURL().toString());
			ajaxContext.getCommonAjaxParameters().put(
					AbstractExternalContext.PORTLET_MODE_PARAMETER,
					request.getPortletMode().toString());
			ajaxContext.getCommonAjaxParameters().put(
					AbstractExternalContext.NAMESPACE_PARAMETER, namespace);
			render(facesContext);
			writer.println("</div>");
			PortletSession portletSession = request.getPortletSession(true);
			// Store namespace value in portlet scope session.
			// ServletContext, in case of ajax requests,
			// will restore session prefiv by it's value.
			portletSession.setAttribute(
					AbstractExternalContext.NAMESPACE_PARAMETER, namespace);
			if (log.isDebugEnabled()) {
				log
						.debug("Finish rendering portlet for namespace "
								+ namespace);
			}
			// Disable portlet caching.
			// TODO - detect ajax components on page, static views can be
			// cached.
			response.setProperty(RenderResponse.EXPIRATION_CACHE, "0");
		} catch (Exception e) {
			log.error("Error processing execute lifecycle", e);
			throw new BridgeException("Error processing execute lifecycle ", e);
		} finally {
			facesContext.release();
		}
	}

	/**
	 * @param request
	 * @param response
	 * @param actionPhase
	 * @throws BridgeException
	 */
	protected void initRequest(PortletRequest request, PortletResponse response,
			PortletPhase actionPhase) throws BridgeException {
		
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.portlet.GenericPortlet#processAction(javax.portlet.ActionRequest,
	 *      javax.portlet.ActionResponse)
	 */
	public void processAction(ActionRequest request, ActionResponse response)
			throws PortletException, IOException {
		if (log.isDebugEnabled()) {
			log.debug("Process action in portlet "
					+ getPortletConfig().getPortletName() + " for mode "
					+ request.getPortletMode());
		}
		doFacesRequest(request, response);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.portlet.GenericPortlet#doView(javax.portlet.RenderRequest,
	 *      javax.portlet.RenderResponse)
	 */
	protected void doView(RenderRequest request, RenderResponse response)
			throws PortletException, IOException {
		if (log.isDebugEnabled()) {
			log.debug("Process do view in portlet "
					+ getPortletConfig().getPortletName());
		}
		doFacesRequest(request, response);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.portlet.GenericPortlet#doEdit(javax.portlet.RenderRequest,
	 *      javax.portlet.RenderResponse)
	 */
	protected void doEdit(RenderRequest arg0, RenderResponse arg1)
			throws PortletException, IOException {
		if (log.isDebugEnabled()) {
			log.debug("Process do edit in portlet "
					+ getPortletConfig().getPortletName());
		}
		doFacesRequest(arg0, arg1);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.portlet.GenericPortlet#doHelp(javax.portlet.RenderRequest,
	 *      javax.portlet.RenderResponse)
	 */
	protected void doHelp(RenderRequest arg0, RenderResponse arg1)
			throws PortletException, IOException {
		if (log.isDebugEnabled()) {
			log.debug("Process do help in portlet "
					+ getPortletConfig().getPortletName());
		}
		doFacesRequest(arg0, arg1);
	}

	/**
	 * Get currenf JSF lifecycle instance.
	 * 
	 * @return
	 */
	protected Lifecycle getLifecycle() {
		return this.lifecycle;
	}

	/**
	 * Create new faces context instance.
	 * 
	 * @param request
	 * @param response
	 * @return new instance of faces context.
	 */
	protected FacesContext getFacesContext(PortletRequest request,
			PortletResponse response) {
		FacesContext facesContext = this.facesContextFactory.getFacesContext(
				getPortletConfig().getPortletContext(), request, response,
				getLifecycle());
		// Store portlet config in the Request parameter.
		facesContext.getExternalContext().getRequestMap().put(PORTLET_CONFIG,
				getPortletConfig());
		return facesContext;
	}

	protected void execute(FacesContext context) throws FacesException {
		getLifecycle().execute(context);
	}

	protected void render(FacesContext context) throws FacesException {
		getLifecycle().render(context);
	}

	protected void setViewId(FacesContext context) {
		PortletStateHolder portletStateHolder = PortletStateHolder
				.getInstance(context);
		String viewId = portletStateHolder.getViewId(context);
		context.getExternalContext().getRequestMap().put(
				AbstractExternalContext.VIEW_ID_PARAMETER, viewId);
		UIViewRoot viewRoot = context.getViewRoot();
		if (null != viewRoot) {
			viewRoot.setViewId(viewId);
		}
	}

	/**
	 * @param facesContext
	 * @return
	 */
	public static String getPortletNamespace(FacesContext facesContext) {
		ExternalContext externalContext = facesContext.getExternalContext();
		Object response = externalContext.getResponse();
		String namespace = null;
		if (response instanceof RenderResponse) {
			namespace = ((RenderResponse) response).getNamespace();
		} else if (response instanceof ServletResponse) {
			namespace = (String) externalContext.getRequestParameterMap().get(
					AbstractExternalContext.NAMESPACE_PARAMETER);
		} else {
			throw new IllegalStateException(
					"portlet Namespace not availible at this phase");
		}
		return namespace;
	}

	/**
	 * @param facesContext
	 * @return
	 */
	public static PortletMode getPortletMode(FacesContext facesContext) {
		ExternalContext externalContext = facesContext.getExternalContext();
		Object request = externalContext.getRequest();
		PortletMode mode = null;
		if (request instanceof PortletRequest) {
			mode = ((PortletRequest) request).getPortletMode();
		} else if (null != externalContext.getRequestParameterMap().get(
				AbstractExternalContext.PORTLET_MODE_PARAMETER)) {
			String modeName = (String) externalContext.getRequestParameterMap()
					.get(AbstractExternalContext.PORTLET_MODE_PARAMETER);
			if (null != modeName) {
				mode = new PortletMode(modeName);
			}
		}
		return mode;
	}

}
