/**
 * 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 javax.faces.application.Application;
import javax.faces.component.StateHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import javax.faces.el.MethodBinding;
import javax.faces.el.ValueBinding;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;

import org.ajax4jsf.Messages;
import org.ajax4jsf.component.AjaxContainerBase;
import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.event.AjaxEvent;
import org.ajax4jsf.event.AjaxListener;
import org.ajax4jsf.event.AjaxRenderEvent;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * @author shura
 * 
 * Brige design pattern for any UIComponent append support ajax partial rendering. For AJAX request, apply
 * ActionListeniers, construct list of components for render - by direct setting
 * from Listenier or Components bean methods. 
 * For use in concrete component, in constructor must be instantiated to class field as :
 * private AjaxRegionBrige brige;
 * ....
 * Constructor(){ 
 * brige = new AjaxRegionBrige(this);
 * }.
 * And delegate all brige public methods. 
 * @See <code>AJAXConatiner</code> interface , or from configuration - by
 *      <ajax:forupdate>jsp tag's of this or nested components, or from all
 *      <code>UIForm</code> components instances with <code>isSubmitted</code>
 *      set to <code>true</code>
 *  
 */
public class AjaxRegionBrige  implements AjaxContainerBase, StateHolder{


    // Private Fields
    private static final Log log = LogFactory.getLog(AjaxRegionBrige.class);

    /**
     * Listener for call on Ajax Requests
     */
    private MethodBinding ajaxListener = null;

    /**
     * Flag for immediate call listeners 
     */
    private boolean immediate = false;
    private boolean immediateSet = false;

    private boolean selfRendered = false;
    private boolean selfRenderedSet = false;



    private boolean transientFlag = false;
    
    private UIComponent component ;
    
    private boolean submitted = false;
    

    // Interface implementation

    /**
	 * @param component
	 */
	public AjaxRegionBrige(UIComponent component) {
		this.component = component;
	}

	/*
     * (non-Javadoc)
     * 
     * @see org.ajax4jsf.components.custom.ajax.AjaxContainer#getAjaxListener()
     */
    public MethodBinding getAjaxListener() {
        return this.ajaxListener;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ogr.apache.myfaces.custom.ajax.AjaxContainer#setAjaxListener(javax.faces.el.MethodBinding)
     */
    public void setAjaxListener(MethodBinding ajaxListener) {
        // 
        if (log.isDebugEnabled())
        {
            log.debug(Messages.getMessage(Messages.SET_AJAX_LISTENER, ajaxListener.getExpressionString()));
        }
        this.ajaxListener = ajaxListener;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ogr.apache.myfaces.custom.ajax.AjaxContainer#isImmediate()
     */
    public boolean isImmediate() {
        if (this.immediateSet) {
            return (this.immediate);
        }
        ValueBinding vb = component.getValueBinding("immediate");
        if (vb != null) {
            return (Boolean.TRUE.equals(vb.getValue(FacesContext.getCurrentInstance())));
        } else {
            return (this.immediate);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see ogr.apache.myfaces.custom.ajax.AjaxContainer#setImmediate(boolean)
     */
    public void setImmediate(boolean immediate) {
        if (immediate != this.immediate) {
            this.immediate = immediate;
        }
        this.immediateSet = true;

    }

    /* (non-Javadoc)
	 * @see org.ajax4jsf.framework.ajax.AjaxContainer#isSelfRendered()
	 */
	public boolean isSelfRendered() {
        if (this.selfRenderedSet) {
            return (this.selfRendered);
        }
        ValueBinding vb = component.getValueBinding("selfRendered");
        if (vb != null) {
            return (Boolean.TRUE.equals(vb.getValue(FacesContext.getCurrentInstance())));
        } else {
            return (this.selfRendered);
        }
	}

	/* (non-Javadoc)
	 * @see org.ajax4jsf.framework.ajax.AjaxContainer#setSelfRendered(boolean)
	 */
	public void setSelfRendered(boolean selfRendered) {
		this.selfRendered = selfRendered;
		this.selfRenderedSet = true;
	}

	/**
     * @return Returns the submitted.
     */
    public boolean isSubmitted()
    {
    	// TODO - more correct check for submitted state 
        return this.submitted;
    }

    /**
	     * @param submitted The submitted to set.
	     */
	    public void setSubmitted(boolean submitted)
	    {
	        // Important - decoder must set submitted AFTER setAjaxRequest !!!
	        if (log.isDebugEnabled() && submitted  && (!isSubmitted())) {
	            log.debug(Messages.getMessage(Messages.SUBMITTED_AJAX_REQUEST));
	        }
	        this.submitted = submitted;
	    }

	/**
	     * <p>
	     * In addition to to the default {@link UIComponent#broadcast}processing,
	     * pass the {@link AjaxEvent}being broadcast to the method referenced by
	     * <code>AjaxListener</code> (if any), and to the default
	     * {@link AjaxListener}registered on the {@link Application}.
	     * </p>
	     * 
	     * @param event
	     *            {@link FacesEvent}to be broadcast
	     * 
	     * @exception AbortProcessingException
	     *                Signal the JavaServer Faces implementation that no further
	     *                processing on the current event should be performed
	     * @exception IllegalArgumentException
	     *                if the implementation class of this {@link FacesEvent}is
	     *                not supported by this component
	     * @exception NullPointerException
	     *                if <code>event</code> is <code>null</code>
	     */
	    public void broadcast(FacesEvent event) throws AbortProcessingException {
	
	        // Perform standard superclass processing
//	        component.broadcast(event);
	
	        if (event instanceof AjaxEvent) {
	            if (log.isDebugEnabled())
	            {
	                log.debug(Messages.getMessage(Messages.SEND_EVENT_TO_AJAX_LISTENER, component.getId()));
	            }
	
	            // Notify the specified action listener method (if any)
	            MethodBinding mb = getAjaxListener();
	            if (mb != null) {
		            FacesContext context = FacesContext.getCurrentInstance();
	                mb.invoke(context, new Object[] { event });
	            }
	            // TODO - perform special rendering, withowt RENDER_VIEW phase.
	            if(isSelfRendered()) {
	                // queue new event to end of queue to perform child elements
	            	// events.
		            if (log.isDebugEnabled())
		            {
		                log.debug("Queue AjaxRenderEvent for self-render of  AjaxContainer with Id "
		                        + component.getId());
		            }
		            FacesEvent renderEvent = new AjaxRenderEvent(component);
		            renderEvent.setPhaseId(event.getPhaseId());
	            	component.queueEvent(renderEvent);
	            }
	        }
	        if (event instanceof AjaxRenderEvent) {
	        		FacesContext context = FacesContext.getCurrentInstance();
	                AjaxContext.getCurrentInstance(context).renderAjaxRegion(context, component, false);
	            }
	        }

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.component.StateHolder#restoreState(javax.faces.context.FacesContext,
	 *      java.lang.Object)
	 */
	public void restoreState(FacesContext context, Object state) {
	    if (log.isDebugEnabled()) {
			log.debug(Messages.getMessage(Messages.RESTORE_AJAX_COMPONENT_STATE, component.getId()));
		}
	    Object values[] = (Object[]) state;
//	    super.restoreState(context, values[0]);
	    this.immediate = ((Boolean) values[0]).booleanValue();
	    this.immediateSet = ((Boolean) values[1]).booleanValue();
	    this.ajaxListener = (MethodBinding) UIComponentBase.restoreAttachedState(context, values[2]);
	    this.selfRendered = ((Boolean) values[3]).booleanValue();
	    this.selfRenderedSet = ((Boolean) values[4]).booleanValue();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.component.StateHolder#saveState(javax.faces.context.FacesContext)
	 */
	public Object saveState(FacesContext context) {
	    // 
	    if (log.isDebugEnabled()) {
			log.debug(Messages.getMessage(Messages.SAVE_AJAX_COMPONENT_STATE, component.getId()));
		}
	    Object[] values = new Object[5];
//	    values[0] = super.saveState(context);
	    values[0] = Boolean.valueOf(immediate);
	    values[1] = Boolean.valueOf(immediateSet);
	    values[2] = UIComponentBase.saveAttachedState(context, ajaxListener);
	    values[3] = Boolean.valueOf(selfRendered);
	    values[4] = Boolean.valueOf(selfRenderedSet);
	    return values;
	}

/*	public void processDecodes(javax.faces.context.FacesContext context)
    {
        if (context == null) throw new NullPointerException("context");
        // due SUN ri design, clear current sattus.
        if(! component.isRendered()) return;
        if(log.isDebugEnabled()){
            log.debug("Process decodes of AjaxContainer with Id "+component.getId());
        }
        component.decode(context);
        // If it is AJAX request for different area - skip decoding childs.
        if (isAjaxRequest()&& !isSubmitted()){
            if(log.isDebugEnabled()){
                log.debug("Skip Decoders for childrens of AjaxContainer with Id "+component.getId());
            }
        	return;
        }
        for (Iterator it = component.getFacetsAndChildren(); it.hasNext(); )
        {
            UIComponent childOrFacet = (UIComponent)it.next();
            childOrFacet.processDecodes(context);
        }
    }

    public void processValidators(javax.faces.context.FacesContext context)
    {
        if (context == null) throw new NullPointerException("context");
        if(! component.isRendered()) return;
        if (isAjaxRequest()&& !isSubmitted()){
            if(log.isDebugEnabled()){
                log.debug("Skip Validators for childrens of AjaxContainer with Id "+component.getId());
            }
        	return;
        }
        
        for (Iterator it = component.getFacetsAndChildren(); it.hasNext(); )
        {
            UIComponent childOrFacet = (UIComponent)it.next();
            childOrFacet.processValidators(context);
        }
    }

    public void processUpdates(javax.faces.context.FacesContext context)
    {
        if (context == null) throw new NullPointerException("context");
        if(! component.isRendered()) return;
        if (isAjaxRequest()&& !isSubmitted()){
            if(log.isDebugEnabled()){
                log.debug("Skip updates for childrens of AjaxContainer with Id "+component.getId());
            }
        	return;
        }
        for (Iterator it = component.getFacetsAndChildren(); it.hasNext(); )
        {
            UIComponent childOrFacet = (UIComponent)it.next();
            childOrFacet.processUpdates(context);
        }
    }
*/    
    public boolean isTransient() {
		return transientFlag;
	}

	public void setTransient(boolean transientFlag) {
		this.transientFlag = transientFlag;
	}
	
	}
    
