package nl.graphit.util.jsf.history;

import java.io.Serializable;

import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.application.TreeStructureManager;

/**
 * Holds all information for a specific page with all its state.
 * 
 * @author Nico Krijnen <nico.krijnen@graphit.nl>
 */
public class ViewState implements Serializable {

    private static final Log log = LogFactory.getLog(ViewState.class);

    private static final long serialVersionUID = 5724368737664787237L;

    private String marker;

    private String viewId;

    private Object treeStruct;

    private Object compStates;

    public ViewState(FacesContext facesContext) {
        store(facesContext);
    }

    public void store(FacesContext facesContext) {
        UIViewRoot viewRoot = facesContext.getViewRoot();
        viewId = viewRoot.getViewId();

        if (log.isDebugEnabled())
            log.debug("storing ViewState for view: " + viewId);

        if (!viewRoot.isTransient()) {
            TreeStructureManager tsm = new TreeStructureManager();
            treeStruct = tsm.buildTreeStructureToSave(viewRoot);
            compStates = viewRoot.processSaveState(facesContext);
        }
    }

    public void restore(FacesContext facesContext) {
        if (log.isDebugEnabled())
            log.debug("restoring ViewState with view: " + viewId);

        // ValueBinding binding = FacesUtils.getValueBinding("#{processScope}");
        // Object processScope = binding.getValue(facesContext);

        UIViewRoot uiViewRoot = restoreView(facesContext);
        facesContext.renderResponse();
        facesContext.setViewRoot(uiViewRoot);

        // binding.setValue(facesContext, processScope);
    }

    /*
     * 
     */

    public String getMarker() {
        return marker;
    }

    public void setMarker(String marker) {
        this.marker = marker;
    }

    public String getViewId() {
        return viewId;
    }

    /*
     * 
     */

    private UIViewRoot restoreView(FacesContext facesContext) {

        UIViewRoot uiViewRoot = restoreTreeStructure(facesContext);
        if (uiViewRoot != null) {
            uiViewRoot.setViewId(viewId);
            restoreComponentState(facesContext, uiViewRoot);
            String restoredViewId = uiViewRoot.getViewId();
            if (restoredViewId == null || !(restoredViewId.equals(viewId))) {
                return null;
            }
        } else {
            ViewHandler viewHandler = facesContext.getApplication()
                    .getViewHandler();
            uiViewRoot = viewHandler.createView(facesContext, viewId);
        }

        facesContext.renderResponse();

        return uiViewRoot;
    }

    private UIViewRoot restoreTreeStructure(FacesContext facesContext) {

        Object treeStructure = treeStruct;
        if (treeStructure == null) {
            if (log.isDebugEnabled()) {
                log.debug("No tree structure state found in client request");
            }
            return null;
        }

        TreeStructureManager tsm = new TreeStructureManager();
        UIViewRoot uiViewRoot = tsm
                .restoreTreeStructure((TreeStructureManager.TreeStructComponent) treeStructure);
        if (log.isTraceEnabled()) {
            log.trace("Tree structure restored from client request");
        }

        return uiViewRoot;
    }

    private void restoreComponentState(FacesContext facesContext,
            UIViewRoot uiViewRoot) {

        if (compStates == null) {
            log.error("No serialized component state found!");
            // mark UIViewRoot invalid by resetting view id
            uiViewRoot.setViewId(null);
            return;
        }

        if (uiViewRoot.getRenderKitId() == null) {
            // Just to be sure...
            // uiViewRoot.setRenderKitId(renderKitId);
            throw new RuntimeException("FIX ME IF THIS HAPPENS");
        }

        uiViewRoot.processRestoreState(facesContext, compStates);
    }
}