/**
 * License Agreement.
 *
 * Rich Faces - Natural Ajax for Java Server Faces (JSF)
 *
 * Copyright (C) 2007 Exadel, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

package org.ajax4jsf.component;

import java.io.IOException;
import java.util.NoSuchElementException;

import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.el.MethodBinding;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;
import javax.faces.event.PhaseId;

import org.ajax4jsf.Messages;
import org.ajax4jsf.application.AjaxSingleException;
import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.context.AjaxContextImpl;
import org.ajax4jsf.context.InvokerCallback;
import org.ajax4jsf.context.ViewIdHolder;
import org.ajax4jsf.event.AjaxListener;
import org.ajax4jsf.event.EventsQueue;
import org.ajax4jsf.renderkit.AjaxContainerRenderer;

/**
 * Custom ViewRoot for support render parts of tree for Ajax requests. Main
 * difference from default ViewRoot - store events queue and unique id counter
 * in request-scope variables. In common realisation, store request-scope
 * variables in component produce errors in case of concurrent requests to same
 * view.
 * 
 * @author asmirnov@exadel.com (latest modification by $Author: alexsmirnov $)
 * @version $Revision: 1.1.2.4 $ $Date: 2007/02/28 17:01:01 $
 * 
 */
public class AjaxViewRoot extends UIViewRoot implements AjaxContainer {

    public static final String ROOT_ID = "_viewRoot";

    private AjaxRegionBrige _brige;

    /**
         * 
         */
    public AjaxViewRoot() {
	super();
	super.setId(ROOT_ID);
	_brige = new AjaxRegionBrige(this);
    }

    /*
         * (non-Javadoc)
         * 
         * @see javax.faces.component.UIComponentBase#getId()
         */
    // public String getId() {
    // return ROOT_ID;
    // }
    public String getRendererType() {
	return (COMPONENT_FAMILY);
    }

    /*
         * (non-Javadoc)
         * 
         * @see org.ajax4jsf.framework.ajax.AjaxViewBrige#broadcast(javax.faces.event.FacesEvent)
         */
    public void broadcast(FacesEvent event) throws AbortProcessingException {
	super.broadcast(event);
	getBrige().broadcast(event);
    }

    /*
         * (non-Javadoc)
         * 
         * @see org.ajax4jsf.framework.ajax.AjaxViewBrige#getAjaxListener()
         */
    public MethodBinding getAjaxListener() {
	return getBrige().getAjaxListener();
    }

    /*
         * (non-Javadoc)
         * 
         * @see org.ajax4jsf.framework.ajax.AjaxViewBrige#isImmediate()
         */
    public boolean isImmediate() {
	return getBrige().isImmediate();
    }

    /*
         * (non-Javadoc)
         * 
         * @see org.ajax4jsf.framework.ajax.AjaxViewBrige#isSubmitted()
         */
    public boolean isSubmitted() {
	return getBrige().isSubmitted();
    }

    /*
         * (non-Javadoc)
         * 
         * @see javax.faces.component.UIComponent#queueEvent(javax.faces.event.FacesEvent)
         */
    public void queueEvent(FacesEvent event) {
	if (event == null)
	    throw new NullPointerException(Messages
		    .getMessage(Messages.NULL_EVENT_SUBMITTED_ERROR));
	if (event.getPhaseId().compareTo(PhaseId.RENDER_RESPONSE) == 0) {
	    // HACK - Special case - Ajax Events to RenderResponse phase
	    // queue.
	    // in future, with JSF 1.2 it must be done by invokeOnComponent
	    // method.
	    // In 1.1 , events to RENDER_RESPONSE phase not used.
	    getAjaxEventsQueue(getFacesContext()).add(event);
	} else {
	    getEventsQueue(getFacesContext(), event.getPhaseId()).add(event);
	}
    }

    /**
         * Broadcast events for specified Phase
         * 
         * @param context
         * @param phaseId -
         *                phase, for wich events must be processed.
         */
    void broadcastEvents(FacesContext context, PhaseId phaseId) {
	EventsQueue[] events = getEvents(context);
	EventsQueue anyPhaseEvents = events[PhaseId.ANY_PHASE.getOrdinal()];
	EventsQueue phaseEvents = events[phaseId.getOrdinal()];
	if (phaseEvents.isEmpty() && anyPhaseEvents.isEmpty())
	    return;
	// FacesEvent event = null;
	boolean haveAnyPhaseEvents = !anyPhaseEvents.isEmpty();
	boolean havePhaseEvents = !phaseEvents.isEmpty();
	do {
	    // ANY_PHASE first
	    processEvents(anyPhaseEvents, haveAnyPhaseEvents);

	    processEvents(phaseEvents, havePhaseEvents);
	    // Events can queued in other events processing
	    haveAnyPhaseEvents = !anyPhaseEvents.isEmpty();
	    havePhaseEvents = !phaseEvents.isEmpty();
	} while (haveAnyPhaseEvents || havePhaseEvents);
	if (context.getRenderResponse() || context.getResponseComplete()) {
	    clearEvents(context);
	}

    }

    /**
         * @param phaseEventsQueue
         * @param havePhaseEvents
         */
    public void processEvents(EventsQueue phaseEventsQueue, boolean havePhaseEvents) {
	FacesEvent event;
	while (havePhaseEvents) {
	    try {
		event = (FacesEvent) phaseEventsQueue.remove();
		UIComponent source = event.getComponent();
		try {
		    source.broadcast(event);
		} catch (AbortProcessingException e) {
		    // abort event processing
		    // Page 3-30 of JSF 1.1 spec: "Throw an
		    // AbortProcessingException, to tell the JSF
		    // implementation
		    // that no further broadcast of this event, or any
		    // further
		    // events, should take place."
		    // clearEvents();
		    // return;
		}
	    } catch (NoSuchElementException e) {
		havePhaseEvents = false;
	    }
	}
    }

    public void broadcastAjaxEvents(FacesContext context) {
	EventsQueue queue = getAjaxEventsQueue(context);
	processEvents(queue, !queue.isEmpty());
    }

    private EventsQueue[] events;

    private EventsQueue ajaxEvents = new EventsQueue();

    /**
         * Use FIFO buffers for hold Faces Events. Hold this buffers in Request
         * scope parameters for support any concurrent requests for same
         * component tree ( since RI hold component tree in session ).
         * 
         * @param context
         * @param phase
         * @return
         */
    protected EventsQueue getEventsQueue(FacesContext context, PhaseId phase) {
	return getEvents(context)[phase.getOrdinal()];
    }

    /**
         * @return
         */
    protected EventsQueue[] getEvents(FacesContext context) {
	if (events == null) {
	    clearEvents(context);
	}
	return events;
    }

    /**
         * Special Fifo Buffer for ajax events to Render Responce phase.
         * 
         * @param context
         * @return
         */
    protected EventsQueue getAjaxEventsQueue(FacesContext context) {

	return ajaxEvents;
    }

    public void clearEvents(FacesContext context) {
	int len;
	events = new EventsQueue[len = PhaseId.VALUES.size()];
	for (int i = 0; i < len; i++) {
	    events[i] = new EventsQueue();
	}
    }

    private InvokerCallback _decodeInvoker = new InvokerCallback() {

	public void invoke(FacesContext context, UIComponent component) {
	    component.processDecodes(context);
	}

	public void invokeRoot(FacesContext context) {
	    AjaxViewRoot.super.processDecodes(context);
	}

    };

    private UIComponent singleComponent = null;

    /*
         * (non-Javadoc)
         * 
         * @see org.ajax4jsf.framework.ajax.AjaxViewBrige#processDecodes(javax.faces.context.FacesContext)
         */
    public void processDecodes(FacesContext context) {
	if (context == null)
	    throw new NullPointerException("context");
	singleComponent = null;
	AjaxContextImpl.invokeOnRegionOrRoot(this, context, _decodeInvoker);
	try {
	    broadcastEvents(context, PhaseId.APPLY_REQUEST_VALUES);
	} catch (AjaxSingleException e) {
	    singleComponent = e.getComponent();
	}
    }

    private InvokerCallback _updatesInvoker = new InvokerCallback() {

	public void invoke(FacesContext context, UIComponent component) {
	    component.processUpdates(context);
	}

	public void invokeRoot(FacesContext context) {
	    AjaxViewRoot.super.processUpdates(context);
	}

    };

    /*
         * (non-Javadoc)
         * 
         * @see org.ajax4jsf.framework.ajax.AjaxViewBrige#processUpdates(javax.faces.context.FacesContext)
         */
    public void processUpdates(FacesContext context) {
	if (context == null)
	    throw new NullPointerException("context");
	if (null == singleComponent) {
	    AjaxContextImpl.invokeOnRegionOrRoot(this, context, _updatesInvoker);
	} else {
	    singleComponent.processUpdates(context);
	}
	broadcastEvents(context, PhaseId.UPDATE_MODEL_VALUES);
    }

    private InvokerCallback _validatorsInvoker = new InvokerCallback() {

	public void invoke(FacesContext context, UIComponent component) {
	    component.processValidators(context);
	}

	public void invokeRoot(FacesContext context) {
	    AjaxViewRoot.super.processValidators(context);
	}

    };

    /*
         * (non-Javadoc)
         * 
         * @see org.ajax4jsf.framework.ajax.AjaxViewBrige#processValidators(javax.faces.context.FacesContext)
         */
    public void processValidators(FacesContext context) {
	if (context == null)
	    throw new NullPointerException("context");
	if (null == singleComponent) {
	    AjaxContextImpl.invokeOnRegionOrRoot(this, context, _validatorsInvoker);
	} else {
	    singleComponent.processValidators(context);
	}
	broadcastEvents(context, PhaseId.PROCESS_VALIDATIONS);
    }

    /*
         * (non-Javadoc)
         * 
         * @see javax.faces.component.UIViewRoot#processApplication(javax.faces.context.FacesContext)
         */
    public void processApplication(FacesContext context) {
	if (context == null)
	    throw new NullPointerException("context");
	// UIComponent component = getSubmittedRegion(context);
	// TODO - process JSF 1.2 listeners
	broadcastEvents(context, PhaseId.INVOKE_APPLICATION);
    }

    /*
         * (non-Javadoc)
         * 
         * @see javax.faces.component.UIViewRoot#encodeBegin(javax.faces.context.FacesContext)
         */
    // public void encodeBegin(FacesContext context) throws IOException {
    // UIComponent submittedComponent = getSubmittedRegion(context);
    // if(null == submittedComponent){
    // super.encodeBegin(context);
    // } else {
    // submittedComponent.encodeBegin(context);
    // }
    // }
    private InvokerCallback _ajaxInvoker = new InvokerCallback() {

	public void invoke(FacesContext context, UIComponent component) {
	    try {
		if (component instanceof AjaxContainer) {
		    AjaxContainer ajax = (AjaxContainer) component;
		    ajax.encodeAjax(context);
		} else {
		    // Container not found, use Root for encode.
		    encodeAjax(context);
		}
	    } catch (IOException e) {
		throw new FacesException(e);
	    }
	}

	public void invokeRoot(FacesContext context) {
	    try {
		encodeAjax(context);
	    } catch (IOException e) {
		throw new FacesException(e);
	    }
	}

    };

    /*
         * (non-Javadoc)
         * 
         * @see javax.faces.component.UIComponentBase#encodeChildren(javax.faces.context.FacesContext)
         */
    public void encodeChildren(FacesContext context) throws IOException {
	if (!isHavePage()
		&& AjaxContext.getCurrentInstance(context).isAjaxRequest(
			context)) {
	    AjaxContextImpl.invokeOnRegionOrRoot(this, context, _ajaxInvoker);
	} else {
	    super.encodeChildren(context);
	}
    }

    /*
         * (non-Javadoc)
         * 
         * @see javax.faces.component.UIComponentBase#encodeEnd(javax.faces.context.FacesContext)
         */
    // public void encodeEnd(FacesContext context) throws IOException {
    // UIComponent submittedComponent = getSubmittedRegion(context);
    // if (null == submittedComponent) {
    // super.encodeEnd(context);
    // } else {
    // submittedComponent.encodeEnd(context);
    // }
    // }
    public void restoreState(FacesContext context, Object state) {
	Object[] mystate = (Object[]) state;
	super.restoreState(context, mystate[0]);
	getBrige().restoreState(context, mystate[1]);
    }

    /*
         * (non-Javadoc)
         * 
         * @see org.ajax4jsf.framework.ajax.AjaxViewBrige#saveState(javax.faces.context.FacesContext)
         */
    public Object saveState(FacesContext context) {
	Object[] state = new Object[2];
	state[0] = super.saveState(context);
	state[1] = getBrige().saveState(context);
	return state;
    }

    public String getViewId() {
	ViewIdHolder viewIdHolder = AjaxContext.getCurrentInstance()
		.getViewIdHolder();
	String viewId;
	if (null != viewIdHolder) {
	    viewId = viewIdHolder.getViewId();
	} else {
	    viewId = super.getViewId();
	}
	return viewId;
    }

    /*
         * (non-Javadoc)
         * 
         * @see org.ajax4jsf.framework.ajax.AjaxViewBrige#setAjaxListener(javax.faces.el.MethodBinding)
         */
    public void setAjaxListener(MethodBinding ajaxListener) {
	getBrige().setAjaxListener(ajaxListener);
    }

    /*
         * (non-Javadoc)
         * 
         * @see org.ajax4jsf.framework.ajax.AjaxViewBrige#setImmediate(boolean)
         */
    public void setImmediate(boolean immediate) {
	getBrige().setImmediate(immediate);
    }

    /*
         * (non-Javadoc)
         * 
         * @see org.ajax4jsf.framework.ajax.AjaxViewBrige#setSubmitted(boolean)
         */
    public void setSubmitted(boolean submitted) {
	getBrige().setSubmitted(submitted);
    }

    public void addAjaxListener(AjaxListener listener) {
	addFacesListener(listener);
    }

    public AjaxListener[] getAjaxListeners() {
	return (AjaxListener[]) getFacesListeners(AjaxListener.class);
    }

    public void removeAjaxListener(AjaxListener listener) {
	removeFacesListener(listener);

    }

    /*
         * (non-Javadoc)
         * 
         * @see org.ajax4jsf.framework.ajax.AjaxViewBrige#isSelfRendered()
         */
    public boolean isSelfRendered() {
	return false;// _brige.isSelfRendered();
    }

    /*
         * (non-Javadoc)
         * 
         * @see org.ajax4jsf.framework.ajax.AjaxViewBrige#setSelfRendered(boolean)
         */
    public void setSelfRendered(boolean selfRendered) {
	// _brige.setSelfRendered(selfRendered);
    }

    /**
         * Check for AjaxContainer component in childreh. This is workaround for
         * MyFaces JSP tag implementation, not called any encode... methods in
         * <f:view> tag. If only one AjaxContainer present as children for
         * viewRoot, decoding methods delegated to them.
         * 
         * @return Returns the havePage.
         */
    public boolean isHavePage() {
	return (getChildCount() == 1 && getChildren().get(0) instanceof AjaxContainer);
    }

    /*
         * (non-Javadoc)
         * 
         * @see javax.faces.component.UIComponentBase#getRendersChildren()
         */
    public boolean getRendersChildren() {
	FacesContext context = FacesContext.getCurrentInstance();
	// For non Ajax request, view root not render children
	if (!AjaxContext.getCurrentInstance(context).isAjaxRequest(context)) {
	    return false;
	}
	// Also, if have page component as clild - delegate rendering of
	// children to it.
	if (isHavePage()) {
	    return false;
	}
	// Ajax Request. Control all output.
	return true;
    }

    public boolean isRenderRegionOnly() {
	// for viewroot it not applicable.
	return false;
    }

    public void setRenderRegionOnly(boolean reRenderPage) {
	// TODO Auto-generated method stub

    }

    public void encodeAjax(FacesContext context) throws IOException {
	String rendererType = getRendererType();
	if (rendererType != null) {
	    ((AjaxContainerRenderer) getRenderer(context)).encodeAjax(context,
		    this);
	}

    }

    /**
         * @return the brige
         */
    protected AjaxRegionBrige getBrige() {
	return _brige;
    }

}
