package org.seasar.jsf.application;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;

import javax.faces.FactoryFinder;
import javax.faces.application.StateManager;
import javax.faces.application.ViewHandler;
import javax.faces.application.StateManager.SerializedView;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import javax.servlet.ServletResponse;

import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;
import org.seasar.jsf.template.ViewTemplate;
import org.seasar.jsf.template.ViewTemplateFactory;

public class S2ViewHandler extends ViewHandler {

	private static final String STATE_KEY = "org.seasar.jsf.viewState";

	private ViewHandler originalViewHandler_;

	private ViewTemplateFactory viewTemplateFactory_;

	public S2ViewHandler(ViewHandler originalViewHandler) {
		originalViewHandler_ = originalViewHandler;
	}

	public ViewTemplateFactory getViewTemplateFactory() {
		if (viewTemplateFactory_ != null) {
			return viewTemplateFactory_;
		}
		S2Container container = SingletonS2ContainerFactory.getContainer();
		viewTemplateFactory_ = (ViewTemplateFactory) container
				.getComponent(ViewTemplateFactory.class);
		return viewTemplateFactory_;
	}

	public Locale calculateLocale(FacesContext context) {
		return originalViewHandler_.calculateLocale(context);
	}

	public String calculateRenderKitId(FacesContext context) {
		return originalViewHandler_.calculateRenderKitId(context);
	}

	public String getActionURL(FacesContext context, String viewId) {
		return originalViewHandler_.getActionURL(context, viewId);
	}

	public String getResourceURL(FacesContext context, String path) {
		return originalViewHandler_.getResourceURL(context, path);
	}

	public UIViewRoot createView(FacesContext context, String viewId) {
		ViewTemplateFactory factory = getViewTemplateFactory();
		String newViewId = factory.calculateViewId(viewId);
		ViewTemplate template = factory.getViewTemplate(newViewId);
		UIViewRoot viewRoot = template.createView();
		if (viewRoot != null) {
			if (context.getViewRoot() != null) {
				UIViewRoot oldViewRoot = context.getViewRoot();
				viewRoot.setLocale(oldViewRoot.getLocale());
				viewRoot.setRenderKitId(oldViewRoot.getRenderKitId());
			} else {
				viewRoot.setLocale(calculateLocale(context));
				viewRoot.setRenderKitId(calculateRenderKitId(context));
			}
		}
		return viewRoot;
	}

	public void renderView(FacesContext context, UIViewRoot viewRoot)
			throws IOException {

		setupResponseWriter(context);

		StateManager sm = context.getApplication().getStateManager();
		SerializedView state = sm.saveSerializedView(context);
		context.getExternalContext().getRequestMap().put(STATE_KEY, state);

		context.getResponseWriter().startDocument();
		renderResponse(context, viewRoot);
		context.getResponseWriter().endDocument();
	}

	public UIViewRoot restoreView(FacesContext context, String viewId) {
		ViewTemplateFactory factory = getViewTemplateFactory();
		String newViewId = factory.calculateViewId(viewId);
		String renderKitId = context.getApplication().getViewHandler()
				.calculateRenderKitId(context);

		StateManager sm = context.getApplication().getStateManager();
		return sm.restoreView(context, newViewId, renderKitId);
	}

	public void writeState(FacesContext context) throws IOException {
		SerializedView state = (SerializedView) context.getExternalContext()
				.getRequestMap().get(STATE_KEY);
		if (state != null) {
			StateManager sm = context.getApplication().getStateManager();
			sm.writeState(context, state);
		}
	}

	protected void renderResponse(FacesContext context, UIComponent component)
			throws IOException {

		component.encodeBegin(context);
		if (component.getRendersChildren()) {
			component.encodeChildren(context);
		} else {
			Iterator i = component.getChildren().iterator();
			while (i.hasNext()) {
				renderResponse(context, (UIComponent) i.next());
			}
		}
		component.encodeEnd(context);
	}

	protected void setupResponseWriter(FacesContext context) throws IOException {
		ServletResponse response = (ServletResponse) context
				.getExternalContext().getResponse();
		OutputStream os = response.getOutputStream();
		Map headers = context.getExternalContext().getRequestHeaderMap();
		String acceptHeader = (String) headers.get("Accept");
		if (acceptHeader != null
				&& (acceptHeader.indexOf("*/*") != -1 || acceptHeader
						.indexOf("text/*") != -1)) {
			acceptHeader += ",text/html";
		}
		response.setCharacterEncoding(viewTemplateFactory_.getEncoding());
		if (response.getContentType() == null) {
			response.setContentType(viewTemplateFactory_.getContentType());
		}

		RenderKitFactory renderFactory = (RenderKitFactory) FactoryFinder
				.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
		RenderKit renderKit = renderFactory.getRenderKit(context, context
				.getViewRoot().getRenderKitId());
		ResponseWriter writer = renderKit.createResponseWriter(
				new OutputStreamWriter(os), acceptHeader, response.getCharacterEncoding());
		context.setResponseWriter(writer);
	}
}