package org.seasar.jsf.application;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.faces.FactoryFinder;
import javax.faces.application.StateManager;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import javax.faces.render.ResponseStateManager;

import org.seasar.framework.util.ClassUtil;

public class S2StateManager extends StateManager {

	private static final String SERIALIZED_VIEW_ATTR = S2StateManager.class
			.getName()
			+ ".SERIALIZED_VIEW";

	private RenderKitFactory renderKitFactory;

	public S2StateManager() {
	}

	public SerializedView saveSerializedView(FacesContext context)
			throws IllegalStateException {

		ExternalContext externalContext = context.getExternalContext();
		String viewId = context.getViewRoot().getViewId();
		SerializedView serializedView = getSerializedViewFromSession(
				externalContext, viewId);
		if (serializedView == null) {
			Object struct = getTreeStructureToSave(context);
			Object state = getComponentStateToSave(context);
			serializedView = new S2SerializedView(struct, state);
		}
		if (isSavingStateInClient(context)) {
			return serializedView;
		} else {
			saveSerializedViewToSession(externalContext, context.getViewRoot()
					.getViewId(), serializedView);
			return null;
		}
	}

	protected SerializedView getSerializedViewFromSession(
			ExternalContext externalContext, String viewId) {

		return (SerializedView) externalContext.getSessionMap().get(
				SERIALIZED_VIEW_ATTR + "-" + viewId);
	}

	protected void saveSerializedViewToSession(ExternalContext externalContext,
			String viewId, SerializedView serializedView) {

		externalContext.getSessionMap().put(
				SERIALIZED_VIEW_ATTR + "-" + viewId, serializedView);
	}

	protected Object getTreeStructureToSave(FacesContext context) {
		UIViewRoot viewRoot = context.getViewRoot();
		if (viewRoot.isTransient()) {
			return null;
		}
		return buildTreeStructure(viewRoot);
	}

	protected Object getComponentStateToSave(FacesContext context) {
		UIViewRoot viewRoot = context.getViewRoot();
		if (viewRoot.isTransient()) {
			return null;
		}
		return viewRoot.processSaveState(context);
	}

	protected TreeStructure buildTreeStructure(UIComponent component) {
		TreeStructure struct = new TreeStructure(
				component.getClass().getName(), component.getId());
		struct.setChildren(buildChildrenTreeStructure(component));
		struct.setFacets(buildFacetsTreeStructure(component));
		return struct;
	}

	protected TreeStructure[] buildChildrenTreeStructure(UIComponent component) {
		if (component.getChildCount() > 0) {
			List children = component.getChildren();
			List structs = new ArrayList();
			for (int i = 0; i < children.size(); ++i) {
				UIComponent child = (UIComponent) children.get(i);
				if (!child.isTransient()) {
					TreeStructure childStruct = buildTreeStructure(child);
					structs.add(childStruct);
				}
			}
			return (TreeStructure[]) structs.toArray(new TreeStructure[structs
					.size()]);
		} else {
			return null;
		}
	}

	protected Object[] buildFacetsTreeStructure(UIComponent component) {
		Map facets = component.getFacets();
		if (!facets.isEmpty()) {
			List structs = new ArrayList();
			for (Iterator it = facets.entrySet().iterator(); it.hasNext();) {
				Map.Entry entry = (Map.Entry) it.next();
				UIComponent child = (UIComponent) entry.getValue();
				if (!child.isTransient()) {
					String facetName = (String) entry.getKey();
					TreeStructure struct = buildTreeStructure(child);
					structs.add(new Object[] { facetName, struct });
				}
			}
			return structs.toArray();
		} else {
			return null;
		}
	}

	public void writeState(FacesContext context, SerializedView serializedView)
			throws IOException {

		UIViewRoot viewRoot = context.getViewRoot();
		RenderKit renderKit = getRenderKit(context, viewRoot.getRenderKitId());
		renderKit.getResponseStateManager().writeState(context, serializedView);
	}

	protected RenderKit getRenderKit(FacesContext context, String renderKitId) {
		return getRenderKitFactory().getRenderKit(context, renderKitId);
	}

	public UIViewRoot restoreView(FacesContext context, String viewId,
			String renderKitId) {

		UIViewRoot viewRoot = restoreTreeStructure(context, viewId, renderKitId);
		if (viewRoot != null) {
			viewRoot.setViewId(viewId);
			restoreComponentState(context, viewRoot, renderKitId);
		}
		return viewRoot;
	}

	protected UIViewRoot restoreTreeStructure(FacesContext context,
			String viewId, String renderKitId) {

		if (isSavingStateInClient(context)) {
			return restoreTreeStructureFromClient(context, viewId, renderKitId);
		} else {
			return restoreTreeStructureFromServer(context, viewId);
		}
	}

	protected UIViewRoot restoreTreeStructureFromClient(FacesContext context,
			String viewId, String renderKitId) {

		RenderKit renderKit = getRenderKit(context, renderKitId);
		ResponseStateManager responseStateManager = renderKit
				.getResponseStateManager();
		TreeStructure struct = (TreeStructure) responseStateManager
				.getTreeStructureToRestore(context, viewId);
		if (struct == null) {
			return null;
		}
		return (UIViewRoot) restoreTreeStructure(struct);
	}

	protected UIViewRoot restoreTreeStructureFromServer(FacesContext context,
			String viewId) {

		SerializedView serializedView = getSerializedViewFromSession(context
				.getExternalContext(), viewId);
		if (serializedView == null) {
			return null;
		}
		TreeStructure struct = (TreeStructure) serializedView.getStructure();
		return (UIViewRoot) restoreTreeStructure(struct);
	}

	protected UIComponent restoreTreeStructure(TreeStructure struct) {
		String className = struct.getComponentClassName();
		String id = struct.getComponentId();
		UIComponent component = (UIComponent) ClassUtil.newInstance(className);
		component.setId(id);
		restoreChildrenTreeStructure(component, struct.getChildren());
		restoreFacetsTreeStructure(component, struct.getFacets());
		return component;
	}

	protected void restoreChildrenTreeStructure(UIComponent component,
			TreeStructure[] structs) {
		if (structs != null) {
			for (int i = 0; i < structs.length; ++i) {
				UIComponent child = restoreTreeStructure(structs[i]);
				component.getChildren().add(child);
			}
		}
	}

	protected void restoreFacetsTreeStructure(UIComponent component,
			Object[] facets) {
		if (facets != null) {
			for (int i = 0, len = facets.length; i < len; i++) {
				Object[] array = (Object[]) facets[i];
				String facetName = (String) array[0];
				TreeStructure struct = (TreeStructure) array[1];
				UIComponent child = restoreTreeStructure(struct);
				component.getFacets().put(facetName, child);
			}
		}
	}

	protected void restoreComponentState(FacesContext context,
			UIViewRoot viewRoot, String renderKitId) {

		if (viewRoot.getRenderKitId() == null) {
			viewRoot.setRenderKitId(renderKitId);
		}
		if (isSavingStateInClient(context)) {
			restoreComponentStateFromClient(context, viewRoot, renderKitId);
		} else {
			restoreComponentStateFromServer(context, viewRoot);
		}
	}

	protected void restoreComponentStateFromClient(FacesContext context,
			UIViewRoot viewRoot, String renderKitId) {

		RenderKit renderKit = getRenderKit(context, renderKitId);
		ResponseStateManager responseStateManager = renderKit
				.getResponseStateManager();
		Object state = responseStateManager.getComponentStateToRestore(context);
		viewRoot.processRestoreState(context, state);
	}
	
	protected void restoreComponentStateFromServer(FacesContext context,
			UIViewRoot viewRoot) {

		SerializedView serializedView = getSerializedViewFromSession(
				context.getExternalContext(), viewRoot.getViewId());
		if (serializedView == null) {
			return;
		}
		Object state = serializedView.getState();
		if (state == null) {
			return;
		}
		viewRoot.processRestoreState(context, state);
	}

	protected RenderKitFactory getRenderKitFactory() {
		if (renderKitFactory == null) {
			renderKitFactory = (RenderKitFactory) FactoryFinder
					.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
		}
		return renderKitFactory;
	}

	public class S2SerializedView extends SerializedView implements
			Serializable {
		
		public S2SerializedView(Object structure, Object state) {
			super(structure, state);
		}
	}
}