/**
 *
 */

package org.richfaces.component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.component.UIInput;
import javax.faces.component.UIMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;
import javax.faces.event.PhaseId;

import org.ajax4jsf.component.AjaxComponent;
import org.ajax4jsf.component.AjaxContainer;
import org.ajax4jsf.component.AjaxSupport;
import org.ajax4jsf.component.EventValueExpression;
import org.ajax4jsf.context.AjaxContext;
import org.ajax4jsf.event.AjaxEvent;
import org.ajax4jsf.event.AjaxListener;
import org.ajax4jsf.renderkit.AjaxRendererUtils;
import org.ajax4jsf.webapp.taglib.MethodExpressionAjaxListener;
import org.richfaces.event.ValidationEvent;

import com.sun.facelets.FaceletException;

/**
 * JSF component class
 * 
 */
public abstract class UIBeanValidator extends UIComponentBase implements AjaxComponent, AjaxSupport, AjaxContainer {

    public static final String COMPONENT_TYPE = "org.richfaces.BeanValidator";

    public static final String COMPONENT_FAMILY = "org.richfaces.BeanValidator";

    public static final String BEAN_VALIDATOR_FACET = "org.richfaces.validator.";

    @Override
    public void setParent(UIComponent parent) {
        super.setParent(parent);

        if (null != parent && parent instanceof EditableValueHolder) {
            setParentProperties(parent);
        }
    }

    /**
     * @param parent
     * @throws FaceletException
     * @throws FacesException
     */
    public void setParentProperties(UIComponent parent) throws FaceletException, FacesException {
        if (!(parent instanceof EditableValueHolder)) {
            throw new FaceletException("Parent component must be an EditableValueHolder");
        }

        if (null != getEvent()) {
            ValueExpression binding = new EventValueExpression(this);
            parent.setValueExpression(getEvent(), binding);
        }
    }

    @Override
    public void broadcast(FacesEvent event) throws AbortProcessingException {
        super.broadcast(event);
        if (event.getComponent() == this) {
            FacesContext context = getFacesContext();
            if (event instanceof ValidationEvent) {
                // ByPass UpdateModelValue
                context.renderResponse();
            } else if (event instanceof AjaxEvent) {
                // TODO - find all UIMessages components for a parent UIInput.
                // for an ajaxSingle component, re-render only target message.
                AjaxContext ajaxContext = AjaxContext.getCurrentInstance(context);
                ajaxContext.setSubmittedRegionClientId(this.getClientId(context));
            }
        }
    }

    @Override
    public void queueEvent(FacesEvent event) {
        if (event instanceof ValidationEvent && event.getComponent() == this) {
            UIComponent parent = getParent();
            if (parent instanceof UIInput) {
                UIInput input = (UIInput) parent;
                if (input.isImmediate()) {
                    event.setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
                } else {
                    event.setPhaseId(PhaseId.PROCESS_VALIDATIONS);
                }
            }
        }
        super.queueEvent(event);
    }

    public String getEventString() {
        StringBuffer buildOnEvent = new StringBuffer();
        String onsubmit = getOnsubmit();
        // Insert script to call before submit ajax request.
        if (null != onsubmit) {
            buildOnEvent.append(onsubmit).append(";");
        }
        buildOnEvent.append(AjaxRendererUtils.buildOnEvent(this, getFacesContext(), getEvent()));
        String script = buildOnEvent.toString();
        return script;
    }

    public UIComponent getSingleComponent() {
        return getParent();
    }

    public void encodeAjax(FacesContext context) throws IOException {
        AjaxContext ajaxContext = AjaxContext.getCurrentInstance(context);
        Set<String> renderedAreas = ajaxContext.getAjaxRenderedAreas();
        List<UIMessage> messages = getMessages(context);
        for (UIMessage message : messages) {
            if (message.isRendered()) {
                message.encodeAll(context);
                renderedAreas.add(message.getClientId(context));
            }
        }
        // Write information about encoded areas after submission.
        AjaxRendererUtils.encodeAreas(context, this);
    }

    public List<UIMessage> getMessages(FacesContext context) {
        ArrayList<UIMessage> messages = new ArrayList<UIMessage>();
        UIComponent form = AjaxRendererUtils.getNestingForm(this);
        if (null == form) {
            form = context.getViewRoot();
        }
        findMessages(form, messages);
        return messages;
    }

    /**
     * Recursive search messages for the parent component.
     * 
     * @param component
     * @param messages
     */
    protected void findMessages(UIComponent component, ArrayList<UIMessage> messages) {
        // TODO create more intellectual search, symmenric for an UIMessage
        // algoritm.
        Iterator<UIComponent> facetsAndChildren = component.getFacetsAndChildren();
        while (facetsAndChildren.hasNext()) {
            UIComponent child = (UIComponent) facetsAndChildren.next();
            if (child instanceof UIMessage) {
                UIMessage message = (UIMessage) child;
                String messageFor = message.getFor();
                if (null != messageFor && messageFor.equals(getParent().getId())) {
                    messages.add(message);
                }
            } else {
                findMessages(child, messages);
            }
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.ajax4jsf.event.AjaxSource#addAjaxListener(org.ajax4jsf.event.AjaxListener)
     */
    public void addAjaxListener(AjaxListener listener) {
    	addFacesListener(listener);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.ajax4jsf.event.AjaxSource#getAjaxListeners()
     */
    public AjaxListener[] getAjaxListeners() {
        return (AjaxListener[]) getFacesListeners(AjaxListener.class);
    }

    /* (non-Javadoc)
     * @see org.ajax4jsf.event.AjaxSource#removeAjaxListener(org.ajax4jsf.event.AjaxListener)
     */
    public void removeAjaxListener(AjaxListener listener) {
    	removeFacesListener(listener);
    }

    public void setAjaxListener(MethodExpression listener) {
    	AjaxListener[] ajaxListeners = getAjaxListeners();
    	for (int i = 0; i < ajaxListeners.length; i++) {
			AjaxListener ajaxListener = ajaxListeners[i];
			if (ajaxListener.getClass().equals(MethodExpressionAjaxListener.class)) {
				MethodExpressionAjaxListener expressionListener = (MethodExpressionAjaxListener) ajaxListener;
				if (expressionListener.getExpression() != listener) {
					removeAjaxListener(ajaxListener);

					if (listener == null) {

						return;
					} else {

						break;
					}
				}
			}
    	}
    	
    	addAjaxListener(new MethodExpressionAjaxListener(listener));
    }
    
    public MethodExpression getAjaxListener() {
    	AjaxListener[] ajaxListeners = getAjaxListeners();
    	for (int i = 0; i < ajaxListeners.length; i++) {
			AjaxListener ajaxListener = ajaxListeners[i];
			if (ajaxListener.getClass().equals(MethodExpressionAjaxListener.class)) {
				MethodExpressionAjaxListener expressionListener = (MethodExpressionAjaxListener) ajaxListener;
				return expressionListener.getExpression();
			}
    	}
    	
    	return null;
    }
}
