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

import java.io.PrintWriter;
import java.security.Principal;
import java.util.Map;
import java.util.ResourceBundle;

import javax.faces.FacesException;
import javax.faces.context.FacesContext;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletConfig;
import javax.portlet.PortletContext;
import javax.portlet.PortletException;
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.BridgeDefaultViewNotSpecifiedException;
import javax.portlet.faces.BridgeException;

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

/**
 * @author asmirnov
 * 
 */
public class AjaxPortletBridge extends AbstractAjaxBridge implements Bridge {

	private static final Log log = LogFactory.getLog(AjaxPortletBridge.class);
	private static final String EXCEPTION_HANDLER_CLASS_PARAMETER = ExceptionHandler.class.getName();
	private PortletConfig portletConfig;
	
	private ExceptionHandler exceptionHandler;

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.portlet.faces.Bridge#destroy()
	 */
	public void destroy() {
		if (log.isDebugEnabled()) {
			log.debug("Destroy portlet " + getPortletConfig().getPortletName());
		}
		super.destroy();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.portlet.faces.Bridge#init(javax.portlet.PortletConfig)
	 */
	public void init(PortletConfig config) throws PortletException {
		try {
			if (log.isDebugEnabled()) {
				log.debug("Start portlet initialisation for "
						+ config.getPortletName());
			}
			this.portletConfig = config;
			PortletContext portletContext = config.getPortletContext();
			synchronized (portletContext) {
				init();
			}
		} catch (FacesException e) {
			throw new PortletException("Initialization error", e);
		}
		exceptionHandler = createExceptionHandler(config);
		if (log.isDebugEnabled()) {
			log.debug("Done portlet initialisation for "
					+ config.getPortletName());
		}
		// getPortletContext().setAttribute(PORTLET_CONFIG, config);
	}

	protected ExceptionHandler createExceptionHandler(PortletConfig config) {
		String exceptionHandlerClassName = config.getInitParameter(EXCEPTION_HANDLER_CLASS_PARAMETER);
		ExceptionHandler handler = null;
		if(null != exceptionHandlerClassName){
			ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
			if(null == classLoader){
				classLoader = this.getClass().getClassLoader();
			}
			try {
				Class<ExceptionHandler> exceptionHandlerClass = (Class<ExceptionHandler>) classLoader.loadClass(exceptionHandlerClassName);
				handler = exceptionHandlerClass.newInstance();
			} catch (ClassNotFoundException e) {				
				handler = new ExceptionHandlerImpl();
			} catch (InstantiationException e) {
				handler = new ExceptionHandlerImpl();
			} catch (IllegalAccessException e) {
				handler = new ExceptionHandlerImpl();
			}
		} else {
			handler = new ExceptionHandlerImpl();			
		}
		return handler;
	}

	public void doFacesRequest(ActionRequest request, ActionResponse response)
			throws BridgeException {
		initRequest(request, response, Bridge.PortletPhase.ActionPhase);
		FacesContext facesContext = getFacesContext(request, response);
		PortletViewState windowState = PortletStateHolder.getInstance(
				facesContext).getWindowState(facesContext);
		windowState.restoreRequest(facesContext, false);
		// request.setAttribute(Bridge.IS_POSTBACK_ATTRIBUTE,
		// Boolean.valueOf(null != windowState.getViewId()));
		try {
			execute(facesContext);
			facesContext.getApplication().getStateManager().saveSerializedView(
					facesContext);
			// save request scope variables and Faces Messages.
			if (!facesContext.getResponseComplete()) {
				windowState.saveRequest(facesContext);

			} // saveView(facesContext);
		} catch (Exception e) {
			// TODO - handle exception.
			log.error("Error processing execute lifecycle", e);
			exceptionHandler.processActionException(facesContext, windowState, e);
		} finally {
			facesContext.release();
		}
	}

	public void doFacesRequest(RenderRequest request, RenderResponse response)
			throws BridgeException {
		initRequest(request, response, Bridge.PortletPhase.RenderPhase);
		FacesContext facesContext = getFacesContext(request, response);
		PortletViewState windowState = PortletStateHolder.getInstance(
				facesContext).getWindowState(facesContext);
		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);

			}
			// set portlet title if its set.
			ResourceBundle bundle = portletConfig.getResourceBundle(request
					.getLocale());
			if (bundle != null) {
				String title = null;
				try {
					title = bundle.getString("javax.portlet.title");
					response.setTitle(title);
				} catch (Exception e) {
					// Ignore MissingResourceException
				}
			}
			String namespace = response.getNamespace();
			windowState.restoreRequest(facesContext, true);
			if (null == facesContext.getViewRoot()) {
				execute(facesContext);
			}
			//
			AjaxContext ajaxContext = AjaxContext
					.getCurrentInstance(facesContext);
			Map commonAjaxParameters = ajaxContext.getCommonAjaxParameters();
			String actionURL = facesContext.getApplication().getViewHandler().getActionURL(facesContext, facesContext.getViewRoot().getViewId());
			commonAjaxParameters.put(AbstractExternalContext.ACTION__PARAMETER,
					facesContext.getExternalContext().encodeActionURL(actionURL));
			commonAjaxParameters.put(
					AbstractExternalContext.PORTLET_MODE_PARAMETER, request
							.getPortletMode().toString());
			commonAjaxParameters.put(
					AbstractExternalContext.NAMESPACE_PARAMETER, namespace);
			commonAjaxParameters.put(
					AbstractExternalContext.PORTLET_NAME_PARAMETER,
					getPortletConfig().getPortletName());
			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);
			Principal userPrincipal = request.getUserPrincipal();
			// TODO - get user roles, defined in the portlet.xml ( ??? parse it ??? ), and store all values for a "isUserInRole(roleName)" calls
			portletSession.setAttribute(AbstractExternalContext.PORTAL_USER_PRINCIPAL, userPrincipal, PortletSession.APPLICATION_SCOPE);
			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) {
			response.reset();
			// TODO - handle exception
			log.error("Error processing execute lifecycle", e);
			exceptionHandler.processRenderException(facesContext, windowState, e);
		} finally {
			facesContext.release();
		}
	}

	/**
	 * @param request
	 * @param response
	 * @param actionPhase
	 * @throws BridgeException
	 */
	protected void initRequest(PortletRequest request,
			PortletResponse response, PortletPhase actionPhase)
			throws BridgeException {
		if (null == request.getAttribute(Bridge.DEFAULT_VIEWID)) {
			throw new BridgeDefaultViewNotSpecifiedException();
		}
		request.setAttribute(Bridge.PORTLET_LIFECYCLE_PHASE, actionPhase);
		request.setAttribute(AbstractExternalContext.PORTLET_CONFIG_ATTRIBUTE,
				getPortletConfig());
	}

	/**
	 * @return the portletConfig
	 */
	protected PortletConfig getPortletConfig() {
		return portletConfig;
	}

	@Override
	protected Object getContext() {
		// TODO Auto-generated method stub
		return portletConfig.getPortletContext();
	}

	@Override
	protected String getInitParameter(String name) {
		String initParameter = portletConfig.getInitParameter(name);
		if (null == initParameter) {
			initParameter = portletConfig.getPortletContext().getInitParameter(
					name);
		}
		return initParameter;
	}

}
