package org.seasar.jsf.runtime;

import java.io.IOException;

import javax.faces.FactoryFinder;
import javax.faces.application.Application;
import javax.faces.application.NavigationHandler;
import javax.faces.application.ViewHandler;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.el.EvaluationException;
import javax.faces.el.MethodBinding;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;

import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;
import org.seasar.framework.container.servlet.S2ContainerServlet;
import org.seasar.jsf.ErrorPageManager;
import org.seasar.jsf.JsfConfig;
import org.seasar.jsf.JsfContext;
import org.seasar.jsf.TagPool;
import org.seasar.jsf.ViewRenderer;
import org.seasar.jsf.ViewTemplate;
import org.seasar.jsf.ViewTemplateFactory;
import org.seasar.jsf.exception.JspRuntimeException;
import org.seasar.jsf.jsp.PageContextImpl;
import org.seasar.jsf.processor.ViewProcessor;
import org.seasar.jsf.util.InvokeUtil;

/**
 * @author higa
 *  
 */
public class ViewRendererImpl implements ViewRenderer {

	private JsfConfig jsfConfig;

	private ViewTemplateFactory viewTemplateFactory;

	private TagPool tagPool;
	
	private ErrorPageManager errorPageManager;

	public ViewRendererImpl(JsfConfig jsfConfig,
			ViewTemplateFactory viewTemplateFactory, TagPool tagPool) {

		this.jsfConfig = jsfConfig;
		this.viewTemplateFactory = viewTemplateFactory;
		this.tagPool = tagPool;
	}

	public void renderView(String path, HttpServletRequest request,
			HttpServletResponse response) throws IOException {

		ViewTemplate template = viewTemplateFactory.getViewTemplate(path);
		ViewProcessor viewProcessor = (ViewProcessor) template.getRootTagProcessor();
		String initAction = viewProcessor.getInitAction();
		FacesContext context = FacesContext.getCurrentInstance();
		boolean processed = false;
		if (initAction != null) {
			processed = executeInitAction(context, initAction);
		}
		if (!processed) {
			//response.setCharacterEncoding(viewProcessor.getEncoding());
			response.setContentType(viewProcessor.getContentType());
			JsfContext jsfContext = createJsfContext(request, response);
			setupResponseWriter(jsfContext.getPageContext(),
					viewProcessor.getContentType(), viewProcessor.getEncoding());
			try {
				viewProcessor.process(jsfContext, null);
			} catch (JspException ex) {
				throw new JspRuntimeException(ex);
			}
			jsfContext.getPageContext().getOut().flush();
		}
	}

	protected Servlet getServlet() {
		return S2ContainerServlet.getInstance();
	}

	protected ServletConfig getServletConfig() {
		return getServlet().getServletConfig();
	}

	protected ServletContext getServletContext() {
		return getServletConfig().getServletContext();
	}

	protected JsfContext createJsfContext(HttpServletRequest request,
			HttpServletResponse response) throws IOException {

		PageContextImpl pageContext = new PageContextImpl();
		pageContext.initialize(getServlet(), request, response, null);
		return new JsfContextImpl(pageContext, jsfConfig, tagPool);
	}

	protected void setupResponseWriter(PageContext pageContext, String contentType, String encoding) {
		FacesContext context = FacesContext.getCurrentInstance();
		RenderKitFactory renderFactory = getRenderKitFactory();
		RenderKit renderKit = renderFactory.getRenderKit(context, context
				.getViewRoot().getRenderKitId());
		ResponseWriter writer = renderKit.createResponseWriter(
				pageContext.getOut(), 
				contentType,
				encoding);
		context.setResponseWriter(writer);
	}

	protected RenderKitFactory getRenderKitFactory() {
		return (RenderKitFactory) FactoryFinder
				.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
	}
	
	protected boolean executeInitAction(FacesContext context, String initAction) throws IOException {
		Application app = context.getApplication();
		MethodBinding mb = app.createMethodBinding(initAction, null);
		try {
			String outcome = InvokeUtil.invoke(mb, context);
			if (outcome != null && !context.getResponseComplete()) {
				NavigationHandler nh = app.getNavigationHandler();
				nh.handleNavigation(context, initAction, outcome);
				ViewHandler vh = app.getViewHandler();
				vh.renderView(context, context.getViewRoot());
				return true;
			}
			return false;
		} catch (EvaluationException ex) {
			Throwable cause = ex.getCause();
			ExternalContext extContext = context.getExternalContext();
			ErrorPageManager manager = getErrorPageManager();
			if (manager.handleException(cause, extContext)) {
				return true;
			} else {
				throw ex;
			}
		}
	}
	
	protected ErrorPageManager getErrorPageManager() {
		if (errorPageManager != null) {
			return errorPageManager;
		}
		S2Container container = SingletonS2ContainerFactory.getContainer();
		errorPageManager = (ErrorPageManager) container.getComponent(ErrorPageManager.class);
		return errorPageManager;
	}
}