/**
 * 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.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.faces.FacesException;
import javax.faces.application.FacesMessage;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.NamingContainer;
import javax.faces.component.StateHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;
import javax.faces.event.FacesListener;
import javax.faces.event.PhaseId;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;
import javax.faces.render.Renderer;

import org.ajax4jsf.model.DataComponentState;
import org.ajax4jsf.model.DataVisitor;
import org.ajax4jsf.model.ExtendedDataModel;
import org.ajax4jsf.model.SerializableDataModel;
import org.ajax4jsf.renderkit.AjaxChildrenRenderer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Base class for iterable components, like dataTable, Tomahawk dataList,
 * Facelets repeat, tree etc., with support for partial rendering on AJAX
 * responces for one or more selected iterations.
 * 
 * @author shura
 * 
 */
public abstract class UIDataAdaptor extends UIData implements AjaxDataEncoder {

	/**
	 * 
	 */
	public static final String COMPONENT_STATE_ATTRIBUTE = "componentState";

	public final static DataModel EMPTY_MODEL = new ListDataModel(
			Collections.EMPTY_LIST);
	
	private static final Log _log = LogFactory.getLog(UIDataAdaptor.class);

	/**
	 * Base class for visit data model at phases decode, validation and update
	 * model
	 * 
	 * @author shura
	 * 
	 */
	protected abstract class ComponentVisitor implements DataVisitor {

		public void process(FacesContext context, Object rowKey, Object argument)
				throws IOException {
			setRowKey(context, rowKey);
			if (isRowAvailable()) {
				Iterator childIterator = dataChildren();
				while (childIterator.hasNext()) {
					UIComponent component = (UIComponent) childIterator.next();
					processComponent(context, component, argument);
				}

			}
		}

		public abstract void processComponent(FacesContext context,
				UIComponent c, Object argument) throws IOException;

	}

	/**
	 * Visitor for process decode on children components.
	 */
	protected ComponentVisitor decodeVisitor = new ComponentVisitor() {

		public void processComponent(FacesContext context, UIComponent c,
				Object argument) {
			c.processDecodes(context);
		}

	};

	/**
	 * Visitor for process validation phase
	 */
	protected ComponentVisitor validateVisitor = new ComponentVisitor() {

		public void processComponent(FacesContext context, UIComponent c,
				Object argument) {
			c.processValidators(context);
		}

	};

	/**
	 * Visitor for process update model phase.
	 */
	protected ComponentVisitor updateVisitor = new ComponentVisitor() {

		public void processComponent(FacesContext context, UIComponent c,
				Object argument) {
			c.processUpdates(context);
		}

	};

	/**
	 * Base client id's of this component, for wich invoked encode... methods.
	 * Component will save state and serialisable models for this keys only.
	 */
	private Set _encoded;

	/**
	 * Storage for data model instances with different client id's of this
	 * component. In case of child for UIData component, this map will keep data
	 * models for different iterations between phases.
	 */
	private Map _modelsMap = new HashMap();

	/**
	 * Reference for curent data model
	 */
	private ExtendedDataModel _currentModel = null;

	/**
	 * States of this component for diferent iterations, same as for models.
	 */
	private Map _statesMap = new HashMap();

	/**
	 * Reference for current component state.
	 */
	private DataComponentState _currentState = null;

	/**
	 * Name of EL variable for current component state.
	 */
	private String _stateVar;

	private String _rowKeyVar;

	/**
	 * Key for current value in model.
	 */
	private Object _rowKey = null;

	/**
	 * Values of row keys, encoded on ajax response rendering.
	 */
	private Set _ajaxKeys = null;

	/**
	 * Internal set of row keys, encoded on ajax response rendering and cleared after response complete
	 */
	private Set _ajaxRequestKeys = null;

	private Object _ajaxRowKey = null;

	private Map _ajaxRowKeysMap = new HashMap();

	/**
	 * Get name of EL variable for component state.
	 * 
	 * @return the varState
	 */
	public String getStateVar() {
		return _stateVar;
	}

	/**
	 * @param varStatus
	 *            the varStatus to set
	 */
	public void setStateVar(String varStatus) {
		this._stateVar = varStatus;
	}

	/**
	 * @return the rowKeyVar
	 */
	public String getRowKeyVar() {
		return this._rowKeyVar;
	}

	/**
	 * @param rowKeyVar
	 *            the rowKeyVar to set
	 */
	public void setRowKeyVar(String rowKeyVar) {
		this._rowKeyVar = rowKeyVar;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.component.UIData#getRowCount()
	 */
	public int getRowCount() {
		return getExtendedDataModel().getRowCount();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.component.UIData#getRowData()
	 */
	public Object getRowData() {
		return getExtendedDataModel().getRowData();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.component.UIData#isRowAvailable()
	 */
	public boolean isRowAvailable() {
		return this.getExtendedDataModel().isRowAvailable();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.component.UIData#setRowIndex(int)
	 */
	public void setRowIndex(int index) {
		FacesContext faces = FacesContext.getCurrentInstance();
		ExtendedDataModel localModel = getExtendedDataModel();
		
		boolean rowAvailable = isRowAvailable();
		
		// if(key == localModel.getRowIndex()){
		// return;
		// }
		
		if (rowAvailable) {
			// save child state
			this.saveChildState(faces);
		}

		// Set current model row by int, but immediately get value from model.
		// for compability, complex models must provide values map between
		// integer and key value.
		localModel.setRowIndex(index);
		rowAvailable = isRowAvailable();
		
		this._rowKey = localModel.getRowKey();
		this._clientId = null;
		
		boolean rowSelected = this._rowKey != null && rowAvailable;

		setupVariable(faces, localModel, rowSelected);
		
		if (rowAvailable) {
			// restore child state
			this.restoreChildState(faces);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.component.UIData#getRowIndex()
	 */
	public int getRowIndex() {
		return getExtendedDataModel().getRowIndex();
	}

	/**
	 * Same as for int index, but for complex model key.
	 * 
	 * @return
	 */
	public Object getRowKey() {
		return this._rowKey;
	}

	public void setRowKey(Object key) {
		setRowKey(FacesContext.getCurrentInstance(), key);
	}

	/**
	 * Setup current roy by key. Perform same functionality as
	 * {@link UIData#setRowIndex(int)}, but for key object - it may be not only
	 * row number in sequence data, but, for example - path to current node in
	 * tree.
	 * 
	 * @param faces -
	 *            current FacesContext
	 * @param key
	 *            new key value.
	 */
	public void setRowKey(FacesContext faces, Object key) {
		ExtendedDataModel localModel = getExtendedDataModel();
		
		boolean rowAvailable = isRowAvailable();
		
		if (rowAvailable) {
			// save child state
			this.saveChildState(faces);
		}

		this._rowKey = key;
		this._clientId = null;
		
		localModel.setRowKey(key);
		rowAvailable = isRowAvailable();

		boolean rowSelected = key != null && rowAvailable;

		//XXX check for row availability
		setupVariable(faces, localModel, rowSelected);
		
		if (rowAvailable) {
			// restore child state
			this.restoreChildState(faces);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.ajax4jsf.ajax.repeat.AjaxDataEncoder#getAjaxKeys()
	 */
	public Set getAjaxKeys() {
		Set keys = null;
		if (this._ajaxKeys != null) {
			keys = (this._ajaxKeys);
		} else {
			ValueBinding vb = getValueBinding("ajaxKeys");
			if (vb != null) {
				keys = (Set) (vb.getValue(getFacesContext()));
			} else if (null != _ajaxRowKey) {
				// If none of above exist , use row with submitted AjaxComponent
				keys = new HashSet(1);
				keys.add(_ajaxRowKey);
			}
		}
		return keys;
	}
	
	public Set getAllAjaxKeys() {
		Set ajaxKeys = getAjaxKeys();
		
		Set allAjaxKeys = null;
		if (ajaxKeys != null) {
			allAjaxKeys = new HashSet();
			allAjaxKeys.addAll(ajaxKeys);
		}
		
		if (_ajaxRequestKeys != null) {
			if (allAjaxKeys == null) {
				allAjaxKeys = new HashSet();
			}
			
			allAjaxKeys.addAll(_ajaxRequestKeys);
		}

		return allAjaxKeys;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.ajax4jsf.ajax.repeat.AjaxDataEncoder#setAjaxKeys(java.util.Set)
	 */
	public void setAjaxKeys(Set ajaxKeys) {
		this._ajaxKeys = ajaxKeys;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.ajax4jsf.framework.ajax.AjaxChildrenEncoder#encodeAjaxChild(javax.faces.context.FacesContext,
	 *      java.lang.String, java.util.Set, java.util.Set)
	 */
	public void encodeAjaxChild(FacesContext context, String path,
			final Set ids, final Set renderedAreas) throws IOException {
		resetDataModel();

		Renderer renderer = getRenderer(context);
		if (null != renderer && renderer instanceof AjaxChildrenRenderer) {
			// If renderer support partial encoding - call them.
			if(_log.isDebugEnabled()){
				_log.debug("Component "+getClientId(context)+" has delegated Encode children components by AjaxChildrenRenderer for path "+path);
			}
			AjaxChildrenRenderer childrenRenderer = (AjaxChildrenRenderer) renderer;
			childrenRenderer.encodeAjaxChildren(context, this, path, ids,
					renderedAreas);
		} else {
			if(_log.isDebugEnabled()){
				_log.debug("Component "+getClientId(context)+"  do Encode children components  for path "+path);
			}
			// Use simple ajax children encoding for iterate other keys.
			final AjaxChildrenRenderer childrenRenderer = getChildrenRenderer();
			final String childrenPath = path + getId()
					+ NamingContainer.SEPARATOR_CHAR;
			ComponentVisitor ajaxVisitor = new ComponentVisitor() {

				public void processComponent(FacesContext context,
						UIComponent c, Object argument) throws IOException {
					childrenRenderer.encodeAjaxComponent(context, c,
							childrenPath, ids, renderedAreas);
				}

			};
			Set ajaxKeys = getAllAjaxKeys();
			if (null != ajaxKeys) {
				if(_log.isDebugEnabled()){
					_log.debug("Component "+getClientId(context)+"  Encode children components for a keys "+ajaxKeys);
				}
				captureOrigValue();
				Object savedKey = getRowKey();
				setRowKey(context, null);
				Iterator fixedChildren = fixedChildren();
				while (fixedChildren.hasNext()) {
					UIComponent component = (UIComponent) fixedChildren.next();
					ajaxVisitor.processComponent(context, component, null);
				}
				for (Iterator iter = ajaxKeys.iterator(); iter.hasNext();) {
					Object key = (Object) iter.next();
					ajaxVisitor.process(context, key, null);
				}
				setRowKey(context,savedKey);
				restoreOrigValue(context);
			} else {
				if(_log.isDebugEnabled()){
					_log.debug("Component "+getClientId(context)+" children components  for all rows");
				}
				iterate(context, ajaxVisitor, null);
			}
		}
	}

	/**
	 * Instance of default renderer in ajax responses.
	 */
	private AjaxChildrenRenderer _childrenRenderer = null;

	/**
	 * getter for simple {@link AjaxChildrenRenderer} instance in case of ajax
	 * responses. If default renderer not support search of children for encode
	 * in ajax response, component will use this instance by default.
	 * 
	 * @return
	 */
	protected AjaxChildrenRenderer getChildrenRenderer() {
		if (_childrenRenderer == null) {
			_childrenRenderer = new AjaxChildrenRenderer() {

				protected Class getComponentClass() {
					return UIDataAdaptor.class;
				}

			};

		}

		return _childrenRenderer;
	}

	/**
	 * @return Set of values for clientId's of this component, for wich was
	 *         invoked "encode" methods.
	 */
	protected Set getEncodedIds() {
		if (_encoded == null) {
			_encoded = new HashSet();
		}

		return _encoded;
	}

	/**
	 * Setup EL variable for different iteration. Value of row data and
	 * component state will be put into request scope attributes with names
	 * given by "var" and "varState" bean properties.
	 * 
	 * Changed: does not check for row availability now
	 * 
	 * @param faces
	 *            current faces context
	 * @param localModel
	 * @param rowSelected
	 */
	protected void setupVariable(FacesContext faces, DataModel localModel,
			boolean rowSelected) {
		Map attrs = faces.getExternalContext().getRequestMap();
		if (rowSelected/*&& isRowAvailable()*/) {
			// Current row data.
			setupVariable(getVar(), attrs, localModel.getRowData());
			// Component state variable.
			setupVariable(getStateVar(), attrs, getComponentState());
			// Row key Data variable.
			setupVariable(getRowKeyVar(), attrs, getRowKey());

		} else {
			removeVariable(getVar(), attrs);
			removeVariable(getStateVar(), attrs);
			removeVariable(getRowKeyVar(), attrs);
		}
	}

	/**
	 * @param var
	 * @param attrs
	 * @param rowData
	 */
	private void setupVariable(String var, Map attrs, Object rowData) {
		if (var != null) {
			attrs.put(var, rowData);
		}
	}

	/**
	 * @param var
	 * @param attrs
	 * @param rowData
	 */
	private void removeVariable(String var, Map attrs) {
		if (var != null) {
			attrs.remove(var);
		}
	}

	/**
	 * Reset data model. this method must be called twice per request - before
	 * decode phase and before component encoding.
	 */
	protected void resetDataModel() {
		this.setExtendedDataModel(null);
	}

	/**
	 * Set data model. Model value will be stored in Map with key as current
	 * clientId for this component, to keep models between phases for same
	 * iteration in case if this component child for other UIData
	 * 
	 * @param model
	 */
	protected void setExtendedDataModel(ExtendedDataModel model) {
		this._currentModel = model;
		this._modelsMap.put(getBaseClientId(getFacesContext()), model);
	}

	/**
	 * Get current data model, or create it by {@link #createDataModel()}
	 * method. For different iterations in ancestor UIData ( if present ) will
	 * be returned different models.
	 * 
	 * @return current data model.
	 */
	protected ExtendedDataModel getExtendedDataModel() {
		if (this._currentModel == null) {
			String baseClientId = getBaseClientId(getFacesContext());
			ExtendedDataModel model;
			model = (ExtendedDataModel) this._modelsMap.get(baseClientId);
			if (null == model) {
				model = createDataModel();
				this._modelsMap.put(baseClientId, model);
			}
			this._currentModel = model;
		}
		return this._currentModel;
	}

	/**
	 * Hook mathod for create data model in concrete implementations.
	 * 
	 * @return
	 */
	protected abstract ExtendedDataModel createDataModel();

	/**
	 * Set current state ( at most cases, visual representation ) of this
	 * component. Same as for DataModel, component will keep states for
	 * different iterations.
	 * 
	 * @param state
	 */
	public void setComponentState(DataComponentState state) {
		this._currentState = state;
		this._statesMap.put(getBaseClientId(getFacesContext()),
				this._currentState);
	}

	/**
	 * @return current state of this component.
	 */
	public DataComponentState getComponentState() {
		DataComponentState state = null;
		if (this._currentState == null) {
			// Check for binding state to user bean.
			ValueBinding valueBinding = getValueBinding(UIDataAdaptor.COMPONENT_STATE_ATTRIBUTE);
			FacesContext facesContext = getFacesContext();
			if (null != valueBinding) {
				state = (DataComponentState) valueBinding
						.getValue(facesContext);
				if (null == state) {
					// Create default state
					state = createComponentState();
					if (!valueBinding.isReadOnly(facesContext)) {
						// Store created state in user bean.
						valueBinding.setValue(facesContext, state);
					}
				}
			} else {
				// Check for stored state in map for parent iterations
				String baseClientId = getBaseClientId(facesContext);
				state = (DataComponentState) this._statesMap.get(baseClientId);
				if (null == state) {
					// Create default component state
					state = createComponentState();
					this._statesMap.put(baseClientId, state);
				}
				this._currentState = state;
			}
		} else {
			state = this._currentState;
		}
		return state;
	}

	/**
	 * Hook method for create default state in concrete implementations.
	 * 
	 * @return
	 */
	protected abstract DataComponentState createComponentState();

	private String _clientId = null;

	public String getClientId(FacesContext faces) {
		if (null == _clientId) {
			StringBuffer id = new StringBuffer(getBaseClientId(faces));
			Object rowKey = getRowKey();
			if (rowKey != null) {
				id.append(NamingContainer.SEPARATOR_CHAR).append(
						rowKey.toString());
			}
			Renderer renderer;
			if (null != (renderer = getRenderer(faces))) {
				_clientId = renderer.convertClientId(faces, id.toString());
			} else {
				_clientId = id.toString();
			}

		}
		return _clientId;
	}

	private String _baseClientId = null;

	/**
	 * Get base clietntId of this component ( withowt iteration part )
	 * 
	 * @param faces
	 * @return
	 */
	public String getBaseClientId(FacesContext faces) {
		// Return any previously cached client identifier
		if (_baseClientId == null) {

			// Search for an ancestor that is a naming container
			UIComponent ancestorContainer = this;
			StringBuffer parentIds = new StringBuffer();
			while (null != (ancestorContainer = ancestorContainer.getParent())) {
				if (ancestorContainer instanceof NamingContainer) {
					parentIds.append(ancestorContainer.getClientId(faces))
							.append(NamingContainer.SEPARATOR_CHAR);
					break;
				}
			}
			String id = getId();
			if (null != id) {
				_baseClientId = parentIds.append(id).toString();
			} else {
				_baseClientId = parentIds.append(
						faces.getViewRoot().createUniqueId()).toString();
			}
		}
		return (_baseClientId);

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.component.UIComponentBase#setId(java.lang.String)
	 */
	public void setId(String id) {
		// If component created by restoring tree or JSP, initial Id is null.
		boolean haveId = null != super.getId();
		String baseClientId;
//		baseClientId = haveId ? getBaseClientId(getFacesContext())
//				: null;
		super.setId(id);
		_baseClientId = null;
		_clientId = null;
		if (haveId) {
			// parent UIData ( if present ) will be set same Id at iteration
			// -
			// we use it for
			// switch to different model and state.
			// Step one - save old values.
//			this._statesMap.put(baseClientId, this._currentState);
//			this._modelsMap.put(baseClientId, this._currentModel);
//			this._ajaxRowKeysMap.put(baseClientId, this._ajaxRowKey);
			// Step two - restore values for a new clientId.
			baseClientId = getBaseClientId(getFacesContext());
			this._currentState = (DataComponentState) this._statesMap
					.get(baseClientId);
			this._currentModel = (ExtendedDataModel) this._modelsMap
					.get(baseClientId);
			if (null != this._currentModel) {
				this._rowKey = this._currentModel.getRowKey();
				// restoreChildState();
			}
			// Restore value for row with submitted AjaxComponent.
			this._ajaxRowKey = _ajaxRowKeysMap.get(baseClientId);
		}
	}

	private Object origValue;

	/**
	 * Save current state of data variable.
	 */
	public void captureOrigValue() {
		captureOrigValue(FacesContext.getCurrentInstance());
	}

	/**
	 * Save current state of data variable.
	 * 
	 * @param faces
	 *            current faces context
	 */
	public void captureOrigValue(FacesContext faces) {
		String var = getVar();
		if (var != null) {
			Map attrs = faces.getExternalContext().getRequestMap();
			this.origValue = attrs.get(var);
		}
	}

	/**
	 * Restore value of data variable after processing phase.
	 */
	public void restoreOrigValue() {
		restoreOrigValue(FacesContext.getCurrentInstance());
	}

	/**
	 * Restore value of data variable after processing phase.
	 * 
	 * @param faces
	 *            current faces context
	 */
	public void restoreOrigValue(FacesContext faces) {
		String var = getVar();
		if (var != null) {
			Map attrs = faces.getExternalContext().getRequestMap();
			if (this.origValue != null) {
				attrs.put(var, this.origValue);
			} else {
				attrs.remove(var);
			}
		}
	}

	/**
	 * Saved values of {@link EditableValueHolder} fields per iterations.
	 */
	private Map childState;

	/**
	 * @param faces
	 * @return Saved values of {@link EditableValueHolder} fields per
	 *         iterations.
	 */
	protected Map getChildState(FacesContext faces) {
		if (this.childState == null) {
			this.childState = new HashMap();
		}
		String baseClientId = getBaseClientId(faces);
		Map currentChildState = (Map) childState.get(baseClientId);
		if (null == currentChildState) {
			currentChildState = new HashMap();
			childState.put(baseClientId, currentChildState);
		}
		return currentChildState;
	}

	/**
	 * Save values of {@link EditableValueHolder} fields before change current
	 * row.
	 * 
	 * @param faces
	 */
	protected void saveChildState(FacesContext faces) {

		Iterator itr = dataChildren();
		while (itr.hasNext()) {
			Map childState = this.getChildState(faces);
			this.saveChildState(faces, (UIComponent) itr.next(), childState);
		}
	}

	/**
	 * Recursive method for Iterate on children for save
	 * {@link EditableValueHolder} fields states.
	 * 
	 * @param faces
	 * @param c
	 * @param childState
	 */
	private void saveChildState(FacesContext faces, UIComponent c,
			Map childState) {

		if (c instanceof EditableValueHolder && !c.isTransient()) {
			String clientId = c.getClientId(faces);
			SavedState ss = (SavedState) childState.get(clientId);
			if (ss == null) {
				ss = new SavedState();
				childState.put(clientId, ss);
			}
			ss.populate((EditableValueHolder) c);
		}

		// continue hack
		Iterator itr = c.getChildren().iterator();
		while (itr.hasNext()) {
			saveChildState(faces, (UIComponent) itr.next(), childState);
		}
		itr = c.getFacets().values().iterator();
		while (itr.hasNext()) {
			saveChildState(faces, (UIComponent) itr.next(), childState);
		}
	}

	/**
	 * Restore values of {@link EditableValueHolder} fields after change current
	 * row.
	 * 
	 * @param faces
	 */
	protected void restoreChildState(FacesContext faces) {

		Iterator itr = dataChildren();
		while (itr.hasNext()) {
			Map childState = this.getChildState(faces);
			this.restoreChildState(faces, (UIComponent) itr.next(), childState);
		}
	}

	/**
	 * Recursive part of
	 * {@link #restoreChildState(FacesContext, UIComponent, Map)}
	 * 
	 * @param faces
	 * @param c
	 * @param childState
	 * 
	 */
	private void restoreChildState(FacesContext faces, UIComponent c,
			Map childState) {
		// reset id
		String id = c.getId();
		c.setId(id);

		// hack
		if (c instanceof EditableValueHolder) {
			EditableValueHolder evh = (EditableValueHolder) c;
			String clientId = c.getClientId(faces);
			SavedState ss = (SavedState) childState.get(clientId);
			if (ss != null) {
				ss.apply(evh);
			} else {
				NullState.apply(evh);
			}
		}

		// continue hack
		Iterator itr = c.getChildren().iterator();
		while (itr.hasNext()) {
			restoreChildState(faces, (UIComponent) itr.next(), childState);
		}
		itr = c.getFacets().values().iterator();
		while (itr.hasNext()) {
			restoreChildState(faces, (UIComponent) itr.next(), childState);
		}
	}

	/**
	 * Check for validation errors on children components. If true, saved values
	 * must be keep on render phase
	 * 
	 * @param context
	 * @return
	 */
	private boolean keepSaved(FacesContext context) {

		Iterator clientIds = this.getChildState(context).keySet().iterator();
		while (clientIds.hasNext()) {
			String clientId = (String) clientIds.next();
			Iterator messages = context.getMessages(clientId);
			while (messages.hasNext()) {
				FacesMessage message = (FacesMessage) messages.next();
				if (message.getSeverity()
						.compareTo(FacesMessage.SEVERITY_ERROR) >= 0) {
					return (true);
				}
			}
		}
		return false;
	}

	/**
	 * Perform iteration on all children components and all data rows with given
	 * visitor.
	 * 
	 * @param faces
	 * @param visitor
	 */
	protected void iterate(FacesContext faces, ComponentVisitor visitor,
			Object argument) {

		// stop if not rendered
		if (!this.isRendered()) {
			return;
		}
		// reset rowIndex
		this.captureOrigValue(faces);
		this.setRowKey(faces, null);
		try {
			Iterator fixedChildren = fixedChildren();
			while (fixedChildren.hasNext()) {
				UIComponent component = (UIComponent) fixedChildren.next();
				visitor.processComponent(faces, component, argument);
			}

			walk(faces, visitor, argument);
		} catch (Exception e) {
			throw new FacesException(e);
		} finally {
			this.setRowKey(faces, null);
			this.restoreOrigValue(faces);
		}
	}

	/**
	 * Walk ( visit ) this component on all data-avare children for each row.
	 * 
	 * @param faces
	 * @param visitor
	 * @throws IOException
	 */
	public void walk(FacesContext faces, DataVisitor visitor, Object argument)
			throws IOException {
		getExtendedDataModel().walk(faces, visitor,
				getComponentState().getRange(), argument);
	}

	protected void processDecodes(FacesContext faces, Object argument) {
		if (!this.isRendered())
			return;
		this.iterate(faces, decodeVisitor, argument);
		this.decode(faces);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.component.UIData#processDecodes(javax.faces.context.FacesContext)
	 */
	public void processDecodes(FacesContext faces) {
		processDecodes(faces, null);
	}

	/**
	 * Reset per-request fields in component.
	 * 
	 * @param faces
	 * 
	 */
	protected void resetComponent(FacesContext faces) {
		// resetDataModel();
		if (null != this.childState) {
			childState.remove(getBaseClientId(faces));
		}
		this._encoded = null;
	}

	protected void processUpdates(FacesContext faces, Object argument) {
		if (!this.isRendered())
			return;
		this.iterate(faces, updateVisitor, argument);
		ExtendedDataModel dataModel = getExtendedDataModel();
		// If no validation errors, update values for serializable model,
		// restored from view.
		if (dataModel instanceof SerializableDataModel && (!keepSaved(faces))) {
			SerializableDataModel serializableModel = (SerializableDataModel) dataModel;
			serializableModel.update();
		}

	}

	public void processUpdates(FacesContext faces) {
		processUpdates(faces, null);
		// resetComponent(faces);
	}

	protected void processValidators(FacesContext faces, Object argument) {
		if (!this.isRendered())
			return;
		this.iterate(faces, validateVisitor, argument);
	}

	public void processValidators(FacesContext faces) {
		processValidators(faces, null);
	}

	public void encodeBegin(FacesContext context) throws IOException {
		resetDataModel();
		if (!keepSaved(context)) {
			childState.remove(getBaseClientId(context));
		}
		// Mark component as used, if parent UIData change own range states not
		// accessed at
		// encode phase must be unsaved.
		getEncodedIds().add(getBaseClientId(context));
		// getComponentState().setUsed(true);
		super.encodeBegin(context);
	}

	/**
	 * This method must create iterator for all non-data avare children of this
	 * component ( header/footer facets for components and columns in dataTable,
	 * facets for tree etc.
	 * 
	 * @return iterator for all components not sensitive for row data.
	 */
	protected abstract Iterator fixedChildren();

	/**
	 * This method must create iterator for all children components, processed
	 * "per row" It can be children of UIColumn in dataTable, nodes in tree
	 * 
	 * @return iterator for all components processed per row.
	 */
	protected abstract Iterator dataChildren();

	private final static SavedState NullState = new SavedState();

	// from RI
	/**
	 * This class keep values of {@link EditableValueHolder} row-sensitive
	 * fields.
	 * 
	 * @author shura
	 * 
	 */
	private final static class SavedState implements Serializable {

		private Object submittedValue;

		private static final long serialVersionUID = 2920252657338389849L;

		Object getSubmittedValue() {
			return (this.submittedValue);
		}

		void setSubmittedValue(Object submittedValue) {
			this.submittedValue = submittedValue;
		}

		private boolean valid = true;

		boolean isValid() {
			return (this.valid);
		}

		void setValid(boolean valid) {
			this.valid = valid;
		}

		private Object value;

		Object getValue() {
			return (this.value);
		}

		public void setValue(Object value) {
			this.value = value;
		}

		private boolean localValueSet;

		boolean isLocalValueSet() {
			return (this.localValueSet);
		}

		public void setLocalValueSet(boolean localValueSet) {
			this.localValueSet = localValueSet;
		}

		public String toString() {
			return ("submittedValue: " + submittedValue + " value: " + value
					+ " localValueSet: " + localValueSet);
		}

		public void populate(EditableValueHolder evh) {
			this.value = evh.getLocalValue();
			this.valid = evh.isValid();
			this.submittedValue = evh.getSubmittedValue();
			this.localValueSet = evh.isLocalValueSet();
		}

		public void apply(EditableValueHolder evh) {
			evh.setValue(this.value);
			evh.setValid(this.valid);
			evh.setSubmittedValue(this.submittedValue);
			evh.setLocalValueSet(this.localValueSet);
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.component.UIData#queueEvent(javax.faces.event.FacesEvent)
	 */
	public void queueEvent(FacesEvent event) {
		if (event.getComponent() != this) {
			event = new IndexedEvent(this, event, getRowKey());
		}
		// Send event directly to parent, to avoid wrapping in superclass.
		UIComponent parent = getParent();
		if (parent == null) {
			throw new IllegalStateException(
					"No parent component for queue event");
		} else {
			parent.queueEvent(event);
		}
	}

	public void broadcast(FacesEvent event) throws AbortProcessingException {

		if (!(event instanceof IndexedEvent)) {
			if (!broadcastLocal(event)) {
				super.broadcast(event);
			}
			return;
		}

		// Set up the correct context and fire our wrapped event
		IndexedEvent revent = (IndexedEvent) event;
		Object oldRowKey = getRowKey();
		FacesContext faces = FacesContext.getCurrentInstance();
		captureOrigValue(faces);
		Object eventRowKey = revent.getKey();
		setRowKey(faces, eventRowKey);
		FacesEvent rowEvent = revent.getTarget();
		rowEvent.getComponent().broadcast(rowEvent);
		// For Ajax events, keep row value.
		if (!(rowEvent.getPhaseId() == PhaseId.RENDER_RESPONSE)) {
			this._ajaxRowKey = eventRowKey;
			// this._ajaxRowKeysMap.put(getBaseClientId(faces), eventRowKey);
		}
		setRowKey(faces, oldRowKey);
		restoreOrigValue(faces);
		// }
		return;
	}

	/**
	 * Process events targetted for concrete implementation. Hook method called
	 * from {@link #broadcast(FacesEvent)}
	 * 
	 * @param event -
	 *            processed event.
	 * @return true if event processed, false if component must continue
	 *         processing.
	 */
	protected boolean broadcastLocal(FacesEvent event) {
		return false;
	}

	/**
	 * Wrapper for event from child component, with value of current row key.
	 * 
	 * @author shura
	 * 
	 */
	protected static final class IndexedEvent extends FacesEvent {

		private static final long serialVersionUID = -8318895390232552385L;

		private final FacesEvent target;

		private final Object key;

		public IndexedEvent(UIDataAdaptor owner, FacesEvent target, Object key) {
			super(owner);
			this.target = target;
			this.key = key;
		}

		public PhaseId getPhaseId() {
			return (this.target.getPhaseId());
		}

		public void setPhaseId(PhaseId phaseId) {
			this.target.setPhaseId(phaseId);
		}

		public boolean isAppropriateListener(FacesListener listener) {
			return this.target.isAppropriateListener(listener);
		}

		public void processListener(FacesListener listener) {
			UIDataAdaptor owner = (UIDataAdaptor) this.getComponent();
			Object prevIndex = owner._rowKey;
			try {
				owner.setRowKey(this.key);
				this.target.processListener(listener);
			} finally {
				owner.setRowKey(prevIndex);
			}
		}

		public Object getKey() {
			return key;
		}

		public FacesEvent getTarget() {
			return target;
		}

	}

	/**
	 * "memento" pattern class for state of component.
	 * 
	 * @author shura
	 * 
	 */
	private static class DataState implements Serializable {

		/**
		 * 
		 */
		private static final long serialVersionUID = 17070532L;

		private Object superState;

		private Map componentStates = new HashMap();

		private Set ajaxKeys;

		public String rowKeyVar;

		public String stateVar;

		private Map childStates;

	}

	/**
	 * Serialisable model and component state per iteration of parent UIData.
	 * 
	 * @author shura
	 * 
	 */
	private static class PerIdState implements Serializable {
		/**
		 * 
		 */
		private static final long serialVersionUID = 9037454770537726418L;

		/**
		 * Flag setted to true if componentState implements StateHolder
		 */
		private boolean stateInHolder = false;

		/**
		 * Serializable componentState or
		 */
		private Object componentState;

		private SerializableDataModel model;
	}

	public void restoreState(FacesContext faces, Object object) {
		DataState state = (DataState) object;
		super.restoreState(faces, state.superState);
		this._ajaxKeys = state.ajaxKeys;
		this._statesMap = new HashMap();
		this._rowKeyVar = state.rowKeyVar;
		this._stateVar = state.stateVar;
		this.childState = state.childStates;
		// Restore serializable models and component states for all rows of
		// parent UIData ( single if this
		// component not child of iterable )
		for (Iterator iter = state.componentStates.entrySet().iterator(); iter
				.hasNext();) {
			Map.Entry stateEntry = (Map.Entry) iter.next();
			PerIdState idState = (PerIdState) stateEntry.getValue();
			DataComponentState compState;
			if (idState.stateInHolder) {
				// TODO - change RichFaces Tree component, for remove reference
				// to component from state.
				compState = createComponentState();
				((StateHolder) compState).restoreState(faces,
						idState.componentState);
			} else {
				compState = (DataComponentState) idState.componentState;
			}
			Object key = stateEntry.getKey();
			this._statesMap.put(key, compState);
			this._modelsMap.put(key, idState.model);
		}
	}

	public Object saveState(FacesContext faces) {
		DataState state = new DataState();
		state.superState = super.saveState(faces);
		state.ajaxKeys = this._ajaxKeys;
		state.rowKeyVar = this._rowKeyVar;
		state.stateVar = this._stateVar;
		state.childStates = this.childState;
		Set encodedIds = getEncodedIds();
		// Save all states of component and data model for all valies of
		// clientId, encoded in this request.
//		this._statesMap.put(getBaseClientId(faces), this._currentState);
//		this._modelsMap.put(getBaseClientId(faces), this._currentModel);
		for (Iterator iter = this._statesMap.entrySet().iterator(); iter
				.hasNext();) {
			Map.Entry stateEntry = (Map.Entry) iter.next();
			DataComponentState dataComponentState = ((DataComponentState) stateEntry
					.getValue());
			Object stateKey = stateEntry.getKey();
			if (encodedIds.isEmpty() || encodedIds.contains(stateKey)) {
				PerIdState idState = new PerIdState();
				// Save component state , depended if implemented interfaces.
				if (null == dataComponentState) {
					idState.componentState = null;
				} else {
					if (dataComponentState instanceof Serializable) {
						idState.componentState = dataComponentState;
					} else if (dataComponentState instanceof StateHolder) {
						idState.componentState = ((StateHolder) dataComponentState)
								.saveState(faces);
						idState.stateInHolder = true;
					}
					ExtendedDataModel extendedDataModel = (ExtendedDataModel) this._modelsMap
							.get(stateKey);
					if (null != extendedDataModel) {
						idState.model = extendedDataModel
								.getSerializableModel(dataComponentState
										.getRange());

					}
				}
				if (null != idState.model || null != idState.componentState) {
					state.componentStates.put(stateKey, idState);
				}
			}
		}
		return state;
	}

	public void setParent(UIComponent parent) {
		super.setParent(parent);
		this._clientId = null;
		this._baseClientId = null;
	}

	/**
	 * Adds argument key to AJAX internal request keys set
	 * @param key key to add
	 */
	public void addRequestKey(Object key) {
		if (_ajaxRequestKeys == null) {
			_ajaxRequestKeys = new HashSet();
		}

		_ajaxRequestKeys.add(key);
	}

	/**
	 * Removes argument key from AJAX internal request keys set
	 * @param key key to remove
	 */
	public void removeRequestKey(Object key) {
		if (_ajaxRequestKeys != null && key != null) {
			_ajaxRequestKeys.remove(key);
		}
	}
	
	/**
	 * Checks whether AJAX internal request keys set contains argument key
	 * @param key key to check
	 * @return <code>true</code> if set contains key, <code>false</code> - otherwise
	 */
	public boolean containsRequestKey(Object key) {
		if (_ajaxRequestKeys != null && key != null) {
			return _ajaxRequestKeys.contains(key);
		}
		
		return false;
	}
	
	/**
	 * Clears AJAX internal request keys set
	 */
	public void clearRequestKeysSet() {
		_ajaxRequestKeys = null;
	}

	/**
	 * Getter for value
	 */
	public Object getValue() {
		return super.getValue();
	}
	
	/**
	 * Setter for value resetting local model
	 */
	public void setValue(Object value) {
		setExtendedDataModel(null);
		super.setValue(value);
	} 
}
