/**
 * 
 */
package org.richfaces.component;

import java.util.Map;

import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;

import org.ajax4jsf.component.AjaxComponent;
import org.ajax4jsf.event.AjaxEvent;
import org.ajax4jsf.renderkit.AjaxRendererUtils;
import org.richfaces.component.events.TreeEvents;
import org.richfaces.event.DragEvent;
import org.richfaces.event.DragListener;
import org.richfaces.event.DropEvent;
import org.richfaces.event.DropListener;
import org.richfaces.event.NodeExpandedListener;
import org.richfaces.event.NodeSelectedListener;
import org.richfaces.event.TreeListenerEventsProducer;

/**
 * @author Nick Belaevski - nbelaevski@exadel.com created 22.11.2006 Component
 *         class providing concrete representation for tree node
 */

public abstract class UITreeNode extends UIComponentBase implements
TreeListenerEventsProducer, Draggable, Dropzone, AjaxComponent {

	private String dragType;
	private Object acceptedTypes;
	private String dragIndicator;
	
	private boolean  ajaxSingle = false;
	private boolean  bypassUpdates = false;
	private boolean  limitToList = false;
	private Object  reRender = null;
	private String  status = null;
	private String  eventsQueue = null;
	private int  requestDelay = Integer.MIN_VALUE;
	private String  oncomplete = null;
	private String  focus = null;
	private Object  data = null;
	private boolean  ignoreDupResponses = false;
	private int  timeout = Integer.MIN_VALUE;	
	
	private boolean ajaxSingleSet = false;
	private boolean bypassUpdatesSet = false;
	private boolean limitToListSet = false;
	private boolean requestDelaySet = false;
	private boolean ignoreDupResponsesSet = false;
	private boolean timeoutSet = false;
	
	public static final String COMPONENT_TYPE = "org.richfaces.TreeNode";

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

	/**
	 * Attribute name to indicate if this tree node is a virtual
	 * {@link UITreeNode} providing default representation for node. Default
	 * tree node representation should use attributes of its parent
	 * {@link UITree} component
	 */
	protected final static String DEFAULT_NODE_FACE_ATTRIBUTE_NAME = "#defaultNodeFace";

	private final static String ICON_LEAF_FACET_NAME = "iconLeaf";
	private final static String ICON_FACET_NAME = "icon";
	private final static String ICON_EXPANDED_FACET_NAME = "iconExpanded";
	private final static String ICON_COLLAPSED_FACET_NAME = "iconCollapsed";
	
	public abstract String getType();

	public abstract void setType(String type);

	public abstract String getIcon();

	public abstract void setIcon(String icon);

	public abstract String getIconExpanded();

	public abstract void setIconExpanded(String icon);

	public abstract String getIconCollapsed();

	public abstract void setIconCollapsed(String icon);

	public abstract String getIconLeaf();

	public abstract void setIconLeaf(String icon);

	public abstract void setAjaxSubmitSelection(String ajaxSubmitSelection);

	public abstract String getAjaxSubmitSelection();

	public abstract void setHighlightedClass(String selectedClass);

	public abstract String getHighlightedClass();

	public abstract void setSelectedClass(String selectedClass);

	public abstract String getSelectedClass();

	public boolean hasAjaxSubmitSelection() {
		String ajaxSubmitSelection = getAjaxSubmitSelection();
		if ("inherit".equals(ajaxSubmitSelection) || 
				ajaxSubmitSelection == null || ajaxSubmitSelection.length() == 0) {
			return getUITree().isAjaxSubmitSelection();
		} else if ("true".equals(ajaxSubmitSelection)) {
			return true;
		} else if ("false".equals(ajaxSubmitSelection)) {
			return false;
		} else {
			throw new IllegalArgumentException(
			"Property \"ajaxSubmitSelection\" should be \"inherit\", \"true\" or \"false\".");
		}

	}


	public void broadcast(FacesEvent event) throws AbortProcessingException {
		super.broadcast(event);

		FacesContext context = getFacesContext();
		UITree tree = getUITree();

		TreeEvents.invokeListenerBindings(this, event, context);

		//TODO quick fix for UITree to invoke listeners
		if (tree != null) {
			if (event instanceof DragEvent || event instanceof DropEvent) {
				tree.broadcast(event);
			}
		}

		if (event instanceof AjaxEvent) {
			AjaxRendererUtils.addRegionsFromComponent(this, getFacesContext());
		}
	}

	public void addChangeExpandListener(NodeExpandedListener listener) {
		addFacesListener(listener);
	}

	public void addNodeSelectListener(NodeSelectedListener listener) {
		addFacesListener(listener);
	}

	public void removeChangeExpandListener(NodeExpandedListener listener) {
		removeFacesListener(listener);
	}

	public void removeNodeSelectListener(NodeSelectedListener listener) {
		removeFacesListener(listener);
	}

	public NodeExpandedListener[] getChangeExpandListeners() {
		return (NodeExpandedListener[]) getFacesListeners(NodeExpandedListener.class);
	}

	public NodeSelectedListener[] getNodeSelectListeners() {
		return (NodeSelectedListener[]) getFacesListeners(NodeSelectedListener.class);
	}


	/**
	 * Finds direct parent {@link UITree} component or throws
	 * 
	 * @return {@link UITree} instance
	 */
	public UITree getUITree() {
		UIComponent parent = getParent();
		while (parent != null) {
			if (parent instanceof UITree) {
				return (UITree) parent;
			} else {
				parent = parent.getParent();
			}
		}

		return null;
	}

	public void addDropListener(DropListener listener) {
		addFacesListener(listener);
	}

	public DropListener[] getDropListeners() {
		return (DropListener[]) getFacesListeners(DropListener.class);
	}

	public void removeDropListener(DropListener listener) {
		removeFacesListener(listener);
	}


	public void addDragListener(DragListener listener) {
		addFacesListener(listener);
	}

	public DragListener[] getDragListeners() {
		return (DragListener[]) getFacesListeners(DragListener.class);
	}

	public void removeDragListener(DragListener listener) {
		removeFacesListener(listener);
	}

	public Map getAttributes() {
		Map map = super.getAttributes();

		if (Boolean.TRUE.equals(map.get(DEFAULT_NODE_FACE_ATTRIBUTE_NAME))) {
			return getUITree().getAttributes();
		}

		return map;
	}

	public void setAcceptedTypes(Object types) {
		this.acceptedTypes = types;
	}

	/*
	 * List of drag types to be processd by the current drop zone.
	 * Getter for acceptedTypes
	 * @return acceptedTypes value from local variable or value bindings
	 */
	public Object getAcceptedTypes(  ){
		if (null != this.acceptedTypes)
		{
			return this.acceptedTypes;
		}
		ValueBinding vb = getValueBinding("acceptedTypes");
		if (null != vb){
			return (Object)vb.getValue(getFacesContext());
		} else {
			UITree tree = getUITree();
			if (tree != null) {
				return tree.getAcceptedTypes();
			}

			return null;
		}
	}

	public void setDragType(String dragType) {
		this.dragType = dragType;
	}

	/*
	 * Key of a drag object. It's used to define a necessity of processing the current dragged element on the drop zone side
	 * Getter for dragType
	 * @return dragType value from local variable or value bindings
	 */
	public String getDragType(  ){
		if (null != this.dragType)
		{
			return this.dragType;
		}
		ValueBinding vb = getValueBinding("dragType");
		if (null != vb){
			return (String)vb.getValue(getFacesContext());
		} else {
			UITree tree = getUITree();
			if (tree != null) {
				return tree.getDragType();
			}
			
			return null;
		}
	}

	private UIComponent getTreeFacet(String facetName) {
		UIComponent facet = getFacet(facetName);
		if (facet == null || !facet.isRendered()) {
			UIComponent parentFacet = getUITree().getFacet(facetName);
			if (facet == null || parentFacet != null && parentFacet.isRendered()) {
				facet = parentFacet;
			}
		}
		
		return facet;
	}
	
	public UIComponent getIconFacet() {
		return getTreeFacet(ICON_FACET_NAME);
	}

	public UIComponent getIconLeafFacet() {
		return getTreeFacet(ICON_LEAF_FACET_NAME);
	}
	
	public UIComponent getIconExpandedFacet() {
		return getTreeFacet(ICON_EXPANDED_FACET_NAME);
	}
	
	public UIComponent getIconCollapsedFacet() {
		return getTreeFacet(ICON_COLLAPSED_FACET_NAME);
	}

	public Object saveState(FacesContext context) {
		Object[] state = new Object[4];
		state[0] = super.saveState(context);
		state[1] = this.dragType;
		state[2] = this.acceptedTypes;
		state[3] = this.dragIndicator;
		
		return state;
	}

	public void restoreState(FacesContext context, Object state) {
		Object[] _state = (Object[]) state;
		super.restoreState(context, _state[0]);
		this.dragType = (String) _state[1];
		this.acceptedTypes = _state[2];
		this.dragIndicator = (String) _state[3];
	}
	
	public void setDragIndicator(String dragIndicator) {
		this.dragIndicator = dragIndicator;
	}
	
	protected String getLocalDragIndicator() {
		if (dragIndicator != null) {
			return dragIndicator;
		}
		
		ValueBinding vb = getValueBinding("dragIndicator");
		if (vb != null) {
			return (String) vb.getValue(getFacesContext());
		}
		
		return null;
	}
	
	public String getDragIndicator() {
		String localDragIndicator = getLocalDragIndicator();
		if (localDragIndicator == null) {
			UITree tree = getUITree();
			if (tree != null) {
				return tree.getDragIndicator();
			} else {
				return null;
			}
		} else {
			return localDragIndicator;
		}
	}
	
	public String getResolvedDragIndicator(FacesContext facesContext) {
		String indicatorId = getLocalDragIndicator();
		UITree tree = getUITree();
		if (tree != null) {
			if (indicatorId != null) {
				//tree is naming container
				UIComponent parent = tree.getParent();
				if (parent != null) {
					UIComponent indicator = parent.findComponent(indicatorId);
					if (indicator != null) {
						return indicator.getClientId(facesContext);
					}
				}
			} else {
				return tree.getResolvedDragIndicator(facesContext);
			}
		}
		
		return null;
	}
	
	protected String getDefaultOndragend() {
		//tag invokes read method on component creation
		//we shouldn't fail with NPE
		UITree tree = getUITree();
		if (tree == null) {
			return null;
		}
		return tree.getOndragend();
	}
	
	protected String getDefaultOndragenter() {
		UITree tree = getUITree();
		if (tree == null) {
			return null;
		}
		return tree.getOndragenter();
	}
	
	protected String getDefaultOndragexit() {
		UITree tree = getUITree();
		if (tree == null) {
			return null;
		}
		return tree.getOndragexit();
	}
	
	protected String getDefaultOndragstart() {
		UITree tree = getUITree();
		if (tree == null) {
			return null;
		}
		return tree.getOndragstart();
	}
	
	protected String getDefaultOndrop() {
		UITree tree = getUITree();
		if (tree == null) {
			return null;
		}
		return tree.getOndrop();
	}
	
	protected String getDefaultOndropend() {
		UITree tree = getUITree();
		if (tree == null) {
			return null;
		}
		return tree.getOndropend();
	}
	
	 /**
	 * if "true", submits ONLY one field/link, instead of all form controls
	 * Setter for ajaxSingle
	 * @param ajaxSingle - new value
	 */
	 public void setAjaxSingle( boolean  __ajaxSingle ){
		this.ajaxSingle = __ajaxSingle;
	 	this.ajaxSingleSet = true;
	 }
	
	/**
	 * if "true", submits ONLY one field/link, instead of all form controls
	 * Getter for ajaxSingle
	 * @return ajaxSingle value from local variable or value bindings
	 */
	 public boolean isAjaxSingle(  ){
		 if(this.ajaxSingleSet){
			return this.ajaxSingle;
		 }
    	ValueBinding vb = getValueBinding("ajaxSingle");
    	if (vb != null) {
    	    Boolean value = (Boolean) vb.getValue(getFacesContext());
    	    if (null == value) {
    	    	UITree tree = getUITree();
    			if (tree != null) {
    				return tree.isAjaxSingle();
    			}
    			return this.ajaxSingle;
    	    }
    	    return (value.booleanValue());
    	} else {
    		UITree tree = getUITree();
			if (tree != null) {
				return tree.isAjaxSingle();
			}
			return this.ajaxSingle;
    	}
	 }
	 
	 /**
	 * If "true", after process validations phase it skips updates of model beans on a force render response. It can be used for validating components input
	 * Setter for bypassUpdates
	 * @param bypassUpdates - new value
	 */
	 public void setBypassUpdates( boolean  __bypassUpdates ){
		this.bypassUpdates = __bypassUpdates;
	 	this.bypassUpdatesSet = true;
	 }
	 
	 /**
	 * If "true", after process validations phase it skips updates of model beans on a force render response. It can be used for validating components input
	 * Getter for bypassUpdates
	 * @return bypassUpdates value from local variable or value bindings
	 */
	public boolean isBypassUpdates() {
		if (this.bypassUpdatesSet) {
			return this.bypassUpdates;
		}
		ValueBinding vb = getValueBinding("bypassUpdates");
		if (vb != null) {
			Boolean value = (Boolean) vb.getValue(getFacesContext());
			if (null == value) {
				UITree tree = getUITree();
				if (tree != null) {
					return tree.isBypassUpdates();
				}
				return this.bypassUpdates;
			}
			return (value.booleanValue());
		} else {
			UITree tree = getUITree();
			if (tree != null) {
				return tree.isBypassUpdates();
			}
			return this.bypassUpdates;
		}
	}
	
	 /**
	 * If "true", updates on client side ONLY elements from this 'reRender' property. If "false" (default) updates all rendered by ajax region components
	 * Setter for limitToList
	 * @param limitToList - new value
	 */
	public void setLimitToList(boolean __limitToList) {
		this.limitToList = __limitToList;
		this.limitToListSet = true;
	}

	/**
	 * If "true", updates on client side ONLY elements from this 'reRender' property. If "false" (default) updates all rendered by ajax region components
	 * Getter for limitToList
	 * @return limitToList value from local variable or value bindings
	 */
	public boolean isLimitToList() {
		if (this.limitToListSet) {
			return this.limitToList;
		}
		ValueBinding vb = getValueBinding("limitToList");
		if (vb != null) {
			Boolean value = (Boolean) vb.getValue(getFacesContext());
			if (null == value) {
				UITree tree = getUITree();
				if (tree != null) {
					return tree.isLimitToList();
				}
				return this.limitToList;
			}
			return (value.booleanValue());
		} else {
			UITree tree = getUITree();
			if (tree != null) {
				return tree.isLimitToList();
			}
			return (this.limitToList);
		}
	}
	
	/**
	 * Id['s] (in format of call  UIComponent.findComponent()) of components, rendered in case of AjaxRequest  caused by this component. Can be single id, comma-separated list of Id's, or EL Expression  with array or Collection
	 * Setter for reRender
	 * @param reRender - new value
	 */
	public void setReRender(Object __reRender) {
		this.reRender = __reRender;
	}

	/**
	 * Id['s] (in format of call  UIComponent.findComponent()) of components, rendered in case of AjaxRequest  caused by this component. Can be single id, comma-separated list of Id's, or EL Expression  with array or Collection
	 * Getter for reRender
	 * @return reRender value from local variable or value bindings
	 */
	public Object getReRender() {
		if (null != this.reRender) {
			return this.reRender;
		}
		ValueBinding vb = getValueBinding("reRender");
		if (null != vb) {
			return (Object) vb.getValue(getFacesContext());
		} else {
			UITree tree = getUITree();
			if (tree != null) {
				return tree.getReRender();
			}
			return null;
		}
	}
	
	/**
	 * ID (in format of call UIComponent.findComponent()) of Request status component
	 * Setter for status
	 * @param status - new value
	 */
	public void setStatus(String __status) {
		this.status = __status;
	}

	/**
	 * ID (in format of call UIComponent.findComponent()) of Request status component
	 * Getter for status
	 * @return status value from local variable or value bindings
	 */
	public String getStatus() {
		if (null != this.status) {
			return this.status;
		}
		ValueBinding vb = getValueBinding("status");
		if (null != vb) {
			return (String) vb.getValue(getFacesContext());
		} else {
			UITree tree = getUITree();
			if (tree != null) {
				return tree.getStatus();
			}
			return null;
		}
	}
	
	/**
	 * Name of requests queue to avoid send next request before complete other from same event. Can be used to reduce number of requests of frequently events (key press, mouse move etc.)
	 * Setter for eventsQueue
	 * @param eventsQueue - new value
	 */
	public void setEventsQueue(String __eventsQueue) {
		this.eventsQueue = __eventsQueue;
	}

	/**
	 * Name of requests queue to avoid send next request before complete other from same event. Can be used to reduce number of requests of frequently events (key press, mouse move etc.)
	 * Getter for eventsQueue
	 * @return eventsQueue value from local variable or value bindings
	 */
	public String getEventsQueue() {
		if (null != this.eventsQueue) {
			return this.eventsQueue;
		}
		ValueBinding vb = getValueBinding("eventsQueue");
		if (null != vb) {
			return (String) vb.getValue(getFacesContext());
		} else {
			UITree tree = getUITree();
			if (tree != null) {
				return tree.getEventsQueue();
			}
			return null;
		}
	}
	
	/**
	 * Attribute defines the time (in ms.) that the request will be wait in the queue before it is ready to send.
	 When the delay time is over, the request will be sent to the server or removed if the newest 'similar' request is in a queue already
	 * Setter for requestDelay
	 * @param requestDelay - new value
	 */
	public void setRequestDelay(int __requestDelay) {
		this.requestDelay = __requestDelay;
		this.requestDelaySet = true;
	}

	/**
	 * Attribute defines the time (in ms.) that the request will be wait in the queue before it is ready to send.
	 When the delay time is over, the request will be sent to the server or removed if the newest 'similar' request is in a queue already
	 * Getter for requestDelay
	 * @return requestDelay value from local variable or value bindings
	 */
	public int getRequestDelay() {
		if (this.requestDelaySet) {
			return this.requestDelay;
		}
		ValueBinding vb = getValueBinding("requestDelay");
		if (vb != null) {
			Integer value = (Integer) vb.getValue(getFacesContext());
			if (null == value) {
				UITree tree = getUITree();
				if (tree != null) {
					return tree.getRequestDelay();
				}
				return this.requestDelay;
			}
			return (value.intValue());
		} else {
			UITree tree = getUITree();
			if (tree != null) {
				return tree.getRequestDelay();
			}
			return (this.requestDelay);
		}
	}
	
	/**
	 * JavaScript code for call after request completed on client side
	 * Setter for oncomplete
	 * @param oncomplete - new value
	 */
	public void setOncomplete(String __oncomplete) {
		this.oncomplete = __oncomplete;
	}

	/**
	 * JavaScript code for call after request completed on client side
	 * Getter for oncomplete
	 * @return oncomplete value from local variable or value bindings
	 */
	public String getOncomplete() {
		if (null != this.oncomplete) {
			return this.oncomplete;
		}
		ValueBinding vb = getValueBinding("oncomplete");
		if (null != vb) {
			return (String) vb.getValue(getFacesContext());
		} else {
			UITree tree = getUITree();
			if (tree != null) {
				return tree.getOncomplete();
			}
			return null;
		}
	}
	
	/**
	 * id of element to set focus after request completed on client side
	 * Setter for focus
	 * @param focus - new value
	 */
	public void setFocus(String __focus) {
		this.focus = __focus;
	}

	/**
	 * id of element to set focus after request completed on client side
	 * Getter for focus
	 * @return focus value from local variable or value bindings
	 */
	public String getFocus() {
		if (null != this.focus) {
			return this.focus;
		}
		ValueBinding vb = getValueBinding("focus");
		if (null != vb) {
			return (String) vb.getValue(getFacesContext());
		} else {
			UITree tree = getUITree();
			if (tree != null) {
				return tree.getFocus();
			}
			return null;
		}
	}
	
	 /**
	 * Serialized (on default with JSON) data passed on the client by a developer on AJAX request. It's accessible via "data.foo" syntax
	 * Setter for data
	 * @param data - new value
	 */
	public void setData(Object __data) {
		this.data = __data;
	}

	/**
	 * Serialized (on default with JSON) data passed on the client by a developer on AJAX request. It's accessible via "data.foo" syntax
	 * Getter for data
	 * @return data value from local variable or value bindings
	 */
	public Object getData() {
		if (null != this.data) {
			return this.data;
		}
		ValueBinding vb = getValueBinding("data");
		if (null != vb) {
			return (Object) vb.getValue(getFacesContext());
		} else {
			UITree tree = getUITree();
			if (tree != null) {
				return tree.getData();
			}
			return null;
		}
	}
	
	/**
	 * Attribute allows to ignore an Ajax Response produced by a request if the newest 'similar' request is
	 in a queue already. ignoreDupResponses="true" does not cancel the request while it is processed on the server,
	 but just allows to avoid unnecessary updates on the client side if the response isn't actual now
	 * Setter for ignoreDupResponses
	 * @param ignoreDupResponses - new value
	 */
	public void setIgnoreDupResponses(boolean __ignoreDupResponses) {
		this.ignoreDupResponses = __ignoreDupResponses;
		this.ignoreDupResponsesSet = true;
	}

	/**
	 * Attribute allows to ignore an Ajax Response produced by a request if the newest 'similar' request is
	 in a queue already. ignoreDupResponses="true" does not cancel the request while it is processed on the server,
	 but just allows to avoid unnecessary updates on the client side if the response isn't actual now
	 * Getter for ignoreDupResponses
	 * @return ignoreDupResponses value from local variable or value bindings
	 */
	public boolean isIgnoreDupResponses() {
		if (this.ignoreDupResponsesSet) {
			return this.ignoreDupResponses;
		}
		ValueBinding vb = getValueBinding("ignoreDupResponses");
		if (vb != null) {
			Boolean value = (Boolean) vb.getValue(getFacesContext());
			if (null == value) {
				UITree tree = getUITree();
				if (tree != null) {
					return tree.isIgnoreDupResponses();
				}
				return this.ignoreDupResponses;
			}
			return (value.booleanValue());
		} else {
			UITree tree = getUITree();
			if (tree != null) {
				return tree.isIgnoreDupResponses();
			}
			return (this.ignoreDupResponses);
		}
	}
	
	 /**
	 * Response waiting time on a particular request. If a response is not received during this time, the request is aborted
	 * Setter for timeout
	 * @param timeout - new value
	 */
	public void setTimeout(int __timeout) {
		this.timeout = __timeout;
		this.timeoutSet = true;
	}

	/**
	 * Response waiting time on a particular request. If a response is not received during this time, the request is aborted
	 * Getter for timeout
	 * @return timeout value from local variable or value bindings
	 */
	public int getTimeout() {
		if (this.timeoutSet) {
			return this.timeout;
		}
		ValueBinding vb = getValueBinding("timeout");
		if (vb != null) {
			Integer value = (Integer) vb.getValue(getFacesContext());
			if (null == value) {
				UITree tree = getUITree();
				if (tree != null) {
					return tree.getTimeout();
				}
				return this.timeout;
			}
			return (value.intValue());
		} else {
			UITree tree = getUITree();
			if (tree != null) {
				return tree.getTimeout();
			}
			return (this.timeout);
		}
	}
}
