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

import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.portlet.application.ComponentsLoader;
import org.ajax4jsf.portlet.application.ComponentsLoaderImpl;
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 {
    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;
    
    private ComponentsLoader componentLoader;


    /*
         * (non-Javadoc)
         * 
         * @see javax.portlet.GenericPortlet#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
	    LifecycleFactory factory = (LifecycleFactory) FactoryFinder
		    .getFactory(FactoryFinder.LIFECYCLE_FACTORY);
	    String 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);
	}
	super.init(config);
	if (log.isDebugEnabled()) {
	    log.debug("Done portlet initialisation for "
		    + config.getPortletName());
	}
//	getPortletContext().setAttribute(PORTLET_CONFIG, config);
	// TODO - use only one loader instance.
	componentLoader = new ComponentsLoaderImpl();

    }

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

    /*
         * (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());
	}
	FacesContext facesContext = getFacesContext(request, response);
	try {
//	    setViewId(facesContext);
	    execute(facesContext);
//	    saveView(facesContext);
	} catch (Exception e) {
	    log.error("Error processing execute lifecycle", e);
	    throw new PortletException("Error processing execute lifecycle ", e);
	} finally {
	    facesContext.release();
	}
    }


    /*
         * (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());
	}
	FacesContext facesContext = getFacesContext(request, response);
	try {
	    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();
//	    restoreView(facesContext);
	    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 PortletException("Error processing execute lifecycle ", e);
	} finally {
	    facesContext.release();
	}
    }


    /*
         * (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());
	}
    }

    /*
         * (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());
	}
    }

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

    /**
     * @return the componentLoader
     */
    public ComponentsLoader getComponentLoader() {
	// TODO - share loader instance with a StateManager
        return componentLoader;
    }
}
