/*
 * AbstractColumnsTag.java		Date created: 26.11.2007
 * Last modified by: $Author$
 * $Revision$	$Date$
 */

package org.richfaces.taglib;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import javax.el.ELContext;
import javax.el.ELException;
import javax.el.ELResolver;
import javax.el.FunctionMapper;
import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.el.VariableMapper;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.webapp.UIComponentClassicTagBase;
import javax.faces.webapp.UIComponentTag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.IterationTag;

import org.richfaces.component.UIColumn;
import org.richfaces.iterator.ForEachIterator;
import org.richfaces.iterator.SimpleForEachIterator;

/**
 * Class provides implementation for columns tag
 * 
 * @author "Andrey Markavtsov"
 * 
 */
/**
 * @author Andrey
 *
 */
public class ColumnsTag extends UIComponentClassicTagBase implements
		IterationTag {

	/** Component type */
	private static final String COLUMN_COMPONENT_TYPE = UIColumn.COMPONENT_TYPE;

	/** Prefix before id to be assigned for column */
	private static final String COLUMN_ID_PREFIX = "rf";

	/** Current column counter */
	private Integer index = -1;

	/** Data table */
	private UIComponent dataTable;

	private Pattern varPattern = null;

	private Pattern indexPattern = null;

	/**
	 * <p>
	 * The {@link UIComponent} that is being encoded by this tag, if any.
	 * </p>
	 */
	private UIComponent component = null;

	/** Flag indicates if columns are already created */
	private boolean created = false;

	/** Iterator for columns's tag value attribute */
	protected ForEachIterator items; // our 'digested' items

	/** Value attribute value */
	protected Object rawItems; // our 'raw' items

	/** End attribute - defines count of column if value attr hasn't been defined */
	private ValueExpression columns;

	/** Begin attribute - defines the first iteration item */
	private ValueExpression begin;

	/** Begin attribute - defines the last iteration item */
	private ValueExpression end;

	/** Index attr - defines page variable for current column counter */
	private ValueExpression _index;

	/** Var attr - defines page variable for current item */
	private String indexId;

	/** Integer value of end attr. */
	private Integer _columns;

	/** Integer value of begin attr. */
	private Integer _begin;

	/** Integer value of end attr. */
	private Integer _end;

	/** String value of var attr */
	private String itemId = null;

	/** Expression for var item */
	private IteratedExpression iteratedExpression;

	/** Column incrementer */
	private Integer counter = 0;

	/**
	 * <p>
	 * The <code>Lst</code> of {@link UIComponent} ids created or located by
	 * nested {@link UIComponentTag}s while processing the current request.
	 * </p>
	 */
	// public List<String> createdComponents = null;
	/**
	 * Caches the nearest enclosing {@link UIComponentRichClassicTagBase} of
	 * this tag. This is used for duplicate id detection.
	 */
	private UIComponentClassicTagBase parentTag = null;

	/**
	 * <p>
	 * Return the {@link UIComponent} instance that is associated with this tag
	 * instance. This method is designed to be used by tags nested within this
	 * tag, and only returns useful results between the execution of
	 * <code>doStartTag()</code> and <code>doEndTag()</code> on this tag
	 * instance.
	 * </p>
	 */
	public UIComponent getComponentInstance() {

		return (this.component);

	}

	/**
	 * <p>
	 * The request scope attribute under which a component tag stack for the
	 * current request will be maintained.
	 * </p>
	 */
	private static final String COMPONENT_TAG_STACK_ATTR = "javax.faces.webapp.COMPONENT_TAG_STACK";

	/**
	 * <p>
	 * The value binding expression (if any) used to wire up this component to a
	 * {@link UIComponent} property of a JavaBean class.
	 * </p>
	 */
	private ValueExpression binding = null;

	/**
	 * <p>
	 * An override for the rendered attribute associated with our
	 * {@link UIComponent}.
	 * </p>
	 */
	private ValueExpression rendered = null;

	/**
	 * Dir attr
	 */
	private ValueExpression _dir;

	/**
	 * Sets dir attr
	 * 
	 * @param dir
	 */
	public void setDir(ValueExpression dir) {
		_dir = dir;
	}

	/**
	 * style CSS style(s) is/are to be applied when this component is rendered
	 */
	private ValueExpression _style;

	/**
	 * CSS style(s) is/are to be applied when this component is rendered Setter
	 * for style
	 * 
	 * @param style -
	 *            new value
	 */
	public void setStyle(ValueExpression __style) {
		this._style = __style;
	}

	/**
	 * breakBefore if "true" next column begins from the first row
	 */
	private ValueExpression _breakBefore;

	/**
	 * if "true" next column begins from the first row Setter for breakBefore
	 * 
	 * @param breakBefore -
	 *            new value
	 */
	public void setBreakBefore(ValueExpression __breakBefore) {
		this._breakBefore = __breakBefore;
	}

	/**
	 * colspan Corresponds to the HTML colspan attribute
	 */
	private ValueExpression _colspan;

	/**
	 * Corresponds to the HTML colspan attribute Setter for colspan
	 * 
	 * @param colspan -
	 *            new value
	 */
	public void setColspan(ValueExpression __colspan) {
		this._colspan = __colspan;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.webapp.UIComponentClassicTagBase#createComponent(javax.faces.context.FacesContext,
	 *      java.lang.String)
	 */
	@Override
	protected UIComponent createComponent(FacesContext context, String newId)
			throws JspException {
		// TODO Auto-generated method stub
		return context.getApplication().createComponent(COLUMN_COMPONENT_TYPE);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.webapp.UIComponentTagBase#getComponentType()
	 */
	@Override
	public String getComponentType() {
		// TODO Auto-generated method stub
		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.webapp.UIComponentTagBase#getRendererType()
	 */
	@Override
	public String getRendererType() {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * footerClass Space-separated list of CSS style class(es) that are be
	 * applied to any footer generated for this table
	 */
	private ValueExpression _footerClass;

	/**
	 * Space-separated list of CSS style class(es) that are be applied to any
	 * footer generated for this table Setter for footerClass
	 * 
	 * @param footerClass -
	 *            new value
	 */
	public void setFooterClass(ValueExpression __footerClass) {
		this._footerClass = __footerClass;
	}

	/**
	 * headerClass Space-separated list of CSS style class(es) that are be
	 * applied to any header generated for this table
	 */
	private ValueExpression _headerClass;

	/**
	 * Space-separated list of CSS style class(es) that are be applied to any
	 * header generated for this table Setter for headerClass
	 * 
	 * @param headerClass -
	 *            new value
	 */
	public void setHeaderClass(ValueExpression __headerClass) {
		this._headerClass = __headerClass;
	}

	/**
	 * rowspan Corresponds to the HTML rowspan attribute
	 */
	private ValueExpression _rowspan;

	/**
	 * Corresponds to the HTML rowspan attribute Setter for rowspan
	 * 
	 * @param rowspan -
	 *            new value
	 */
	public void setRowspan(ValueExpression __rowspan) {
		this._rowspan = __rowspan;
	}

	/**
	 * sortExpression Attribute defines a bean property which is used for
	 * sorting of a column
	 */
	private ValueExpression _sortExpression;

	/**
	 * Attribute defines a bean property which is used for sorting of a column
	 * Setter for sortExpression
	 * 
	 * @param sortExpression -
	 *            new value
	 */
	public void setSortExpression(ValueExpression __sortExpression) {
		this._sortExpression = __sortExpression;
	}

	/**
	 * sortable Boolean attribute. If "true" it's possible to sort the column
	 * content after click on the header. Default value is "true"
	 */
	private ValueExpression _sortable;

	/*
	 * sortBy Attribute defines a bean property which is used for sorting of a
	 * column
	 */
	private ValueExpression _sortBy;

	/**
	 * Attribute defines a bean property which is used for sorting of a column
	 * Setter for sortBy
	 * 
	 * @param sortBy -
	 *            new value
	 */
	public void setSortBy(ValueExpression __sortBy) {
		this._sortBy = __sortBy;
	}

	/**
	 * sortOrder SortOrder is an enumeration of the possible sort orderings.
	 */
	private ValueExpression _sortOrder;

	/**
	 * SortOrder is an enumeration of the possible sort orderings. Setter for
	 * sortOrder
	 * 
	 * @param sortOrder -
	 *            new value
	 */
	public void setSortOrder(ValueExpression __sortOrder) {
		this._sortOrder = __sortOrder;
	}

	/**
	 * selfSorted
	 * 
	 */
	private ValueExpression _selfSorted;

	/**
	 * 
	 * Setter for selfSorted
	 * 
	 * @param selfSorted -
	 *            new value
	 */
	public void setSelfSorted(ValueExpression __selfSorted) {
		this._selfSorted = __selfSorted;
	}

	/**
	 * comparator
	 * 
	 */
	private ValueExpression _comparator;

	/**
	 * 
	 * Setter for comparator
	 * 
	 * @param comparator -
	 *            new value
	 */
	public void setComparator(ValueExpression __comparator) {
		this._comparator = __comparator;
	}

	/*
	 * filterBy
	 * 
	 */
	private ValueExpression _filterBy;

	/**
	 * 
	 * Setter for filterBy
	 * 
	 * @param filterBy -
	 *            new value
	 */
	public void setFilterBy(ValueExpression __filterBy) {
		this._filterBy = __filterBy;
	}

	/*
	 * filterDefaultLabel
	 * 
	 */
	private ValueExpression _filterDefaultLabel;

	/**
	 * 
	 * Setter for filterDefaultLabel
	 * 
	 * @param filterDefaultLabel -
	 *            new value
	 */
	public void setFilterDefaultLabel(ValueExpression __filterDefaultLabel) {
		this._filterDefaultLabel = __filterDefaultLabel;
	}

	/*
	 * filterEvent Event for filter input that forces the filtration (default =
	 * onchange)
	 */
	private ValueExpression _filterEvent;

	/**
	 * Event for filter input that forces the filtration (default = onchange)
	 * Setter for filterEvent
	 * 
	 * @param filterEvent -
	 *            new value
	 */
	public void setFilterEvent(ValueExpression __filterEvent) {
		this._filterEvent = __filterEvent;
	}

	/*
	 * filterExpression Attribute defines a bean property which is used for
	 * filtering of a column
	 */
	private ValueExpression _filterExpression;

	/**
	 * Attribute defines a bean property which is used for filtering of a column
	 * Setter for filterExpression
	 * 
	 * @param filterExpression -
	 *            new value
	 */
	public void setFilterExpression(ValueExpression __filterExpression) {
		this._filterExpression = __filterExpression;
	}

	/*
	 * filterMethod
	 * 
	 */
	private MethodExpression _filterMethod;

	/**
	 * 
	 * Setter for filterMethod
	 * 
	 * @param filterMethod -
	 *            new value
	 */
	public void setFilterMethod(MethodExpression __filterMethod) {
		this._filterMethod = __filterMethod;
	}

	/*
	 * filterValue
	 * 
	 */
	private ValueExpression _filterValue;

	/**
	 * 
	 * Setter for filterValue
	 * 
	 * @param filterValue -
	 *            new value
	 */
	public void setFilterValue(ValueExpression __filterValue) {
		this._filterValue = __filterValue;
	}

	/**
	 * Boolean attribute. If "true" it's possible to sort the column content
	 * after click on the header. Default value is "true" Setter for sortable
	 * 
	 * @param sortable -
	 *            new value
	 */
	public void setSortable(ValueExpression __sortable) {
		this._sortable = __sortable;
	}

	/**
	 * styleClass Corresponds to the HTML class attribute
	 */
	private ValueExpression _styleClass;

	/**
	 * Corresponds to the HTML class attribute Setter for styleClass
	 * 
	 * @param styleClass -
	 *            new value
	 */
	public void setStyleClass(ValueExpression __styleClass) {
		this._styleClass = __styleClass;
	}

	/**
	 * value The current value for this component
	 */
	private ValueExpression _value;

	/**
	 * The current value for this component Setter for value
	 * 
	 * @param value -
	 *            new value
	 */
	public void setValue(ValueExpression __value) {
		this._value = __value;
	}

	/**
	 * var A request-scope attribute via which the data object for the current
	 * row will be used when iterating
	 */
	private ValueExpression _var;

	/**
	 * A request-scope attribute via which the data object for the current row
	 * will be used when iterating Setter for var
	 * 
	 * @param var -
	 *            new value
	 */
	public void setVar(ValueExpression __var) {
		this._var = __var;
	}

	/*
	 * width Attribute defines width of column. Default value is "100px".
	 */
	private ValueExpression _width;

	/**
	 * Attribute defines width of column. Default value is "100px". Setter for
	 * width
	 * 
	 * @param width -
	 *            new value
	 */
	public void setWidth(ValueExpression __width) {
		this._width = __width;
	}

	/**
	 * Default constructor for AbstractColumnsTag class
	 */
	public ColumnsTag() {
		super();
	}

	/**
	 * <p>
	 * Set an override for the rendered attribute.
	 * </p>
	 * 
	 * @param rendered
	 *            The new value for rendered attribute
	 */
	public void setRendered(ValueExpression rendered) {
		this.rendered = rendered;
	}

	/**
	 * <p>
	 * Set the value expression for our component.
	 * </p>
	 * 
	 * @param binding
	 *            The new value expression
	 * 
	 * @throws JspException
	 *             if an error occurs
	 */
	public void setBinding(ValueExpression binding) throws JspException {
		this.binding = binding;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.jsp.tagext.BodyTagSupport#doAfterBody()
	 */
	@Override
	public int doAfterBody() throws JspException {

		if (hasNext()) {
			exposeVariables();
			loop();
		} else
			return EVAL_BODY_INCLUDE;

		exposeVariables();

		return EVAL_BODY_AGAIN;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.webapp.UIComponentClassicTagBase#addFacet(java.lang.String)
	 */
	protected void addFacet(String name) {
		super.addFacet(name);

		// add created component in facet in childrenList to reduce duplicated
		// Id exp.
		if (this.component != null && this.component.getFacets() != null) {
			Map<String, UIComponent> facets = this.component.getFacets();
			Iterator<UIComponent> it = facets.values().iterator();
			while (it.hasNext()) {
				super.addChild(it.next());
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.jsp.tagext.BodyTagSupport#doInitBody()
	 */
	@Override
	public void doInitBody() throws JspException {
		// TODO Auto-generated method stub
		super.doInitBody();

	}

	/**
	 * Removes any attributes that this LoopTagSupport set.
	 * 
	 * <p>
	 * These attributes are intended to support scripting variables with NESTED
	 * scope, so we don't want to pollute attribute space by leaving them lying
	 * around.
	 */
	public void doFinally() {
		/*
		 * Make sure to un-expose variables, restoring them to their prior
		 * values, if applicable.
		 */
		unExposeVariables();
	}

	/**
	 * Removes page attributes that we have exposed and, if applicable, restores
	 * them to their prior values (and scopes).
	 */
	private void unExposeVariables() {
		// "nested" variables are now simply removed
		if (itemId != null) {
			pageContext.removeAttribute(itemId, PageContext.PAGE_SCOPE);
			VariableMapper vm = pageContext.getELContext().getVariableMapper();
			if (vm != null)
				vm.setVariable(itemId, null);
		}
		if (indexId != null) {
			pageContext.removeAttribute(indexId, PageContext.PAGE_SCOPE);
			VariableMapper vm = pageContext.getELContext().getVariableMapper();
			if (vm != null)
				vm.setVariable(indexId, null);
		}
	}

	/**
	 * Method is invoking by each iteration of body again rendering
	 * 
	 * @throws JspTagException
	 */
	private void loop() throws JspTagException {
		if (!atFirst()) {
			popUIComponentClassicTagBase();
		}
		UIComponent component = createColumn();
		pushUIComponentClassicTagBase(this);

		dataTable.getChildren().add(component);

		next();
	}

	private String generateColumnId() {
		return COLUMN_ID_PREFIX + Integer.toString(counter++);
	}

	/**
	 * Creates column instance
	 * 
	 * @return
	 */
	private UIComponent createColumn() {
		UIComponent component = getFacesContext().getApplication()
				.createComponent(COLUMN_COMPONENT_TYPE);
		component.setId(generateColumnId());

		initColumnByThis(component);

		this.component = component;
		this.parentTag = getParentUIComponentClassicTagBase(pageContext);

		return component;
	}

	/**
	 * Inits column component by columns tag attrs
	 * 
	 * @param column
	 */
	void initColumnByThis(UIComponent column) {

		ELContext elContext = getContext(getELContext());
		Field[] fields = this.getClass().getDeclaredFields();
		for (Field field : fields) {
			try {
				Object o = field.get(this);
				if ((o != null) && (o instanceof ValueExpression)) {
					String fieldName = field.getName();
					String attributeName = fieldName.replace("_", "");
					ValueExpression ex = (ValueExpression) o;
					column.setValueExpression(attributeName,
							createValueExpression(elContext, ex));

				}
			} catch (Exception e) {
				continue;
			}
		}
	}

	/**
	 * Creates value expression to be out into column 
	 * @param context
	 * @param orig
	 * @return
	 */
	private ValueExpression createValueExpression(ELContext context,
			ValueExpression orig) {
		ValueExpression vexpr = getFacesContext().getApplication()
				.getExpressionFactory().createValueExpression(
						getContext(context), orig.getExpressionString(),
						orig.getExpectedType());
		return vexpr;
	}

	/**
	 * Create custom context with VariableMapper override.
	 * @param context
	 * @return
	 */
	private ELContext getContext(final ELContext context) {
		return new ELContext() {

			@Override
			public ELResolver getELResolver() {
				// TODO Auto-generated method stub
				return context.getELResolver();
			}

			@Override
			public FunctionMapper getFunctionMapper() {
				// TODO Auto-generated method stub
				return context.getFunctionMapper();
			}

			@Override
			public VariableMapper getVariableMapper() {
				return new VariableMapper() {

					@Override
					public ValueExpression resolveVariable(String variable) {
						// TODO Auto-generated method stub
						if (variable.equals(itemId)) {
							return new IndexedValueExpression(_value, index + 1);
						} else if (variable.equals(indexId)) {
							return new IteratedIndexExpression(index + 1);
						}
						return context.getVariableMapper().resolveVariable(
								variable);
					}

					@Override
					public ValueExpression setVariable(String variable,
							ValueExpression expression) {
						// TODO Auto-generated method stub
						return context.getVariableMapper().setVariable(
								variable, expression);
					}

				};
			}

		};
	}

	/**
	 * Returns true if this is the first loop of columns tag
	 * 
	 * @return
	 */
	private boolean atFirst() {
		return (index == -1);
	}

	/**
	 * Deletes dynamic rich columns created before
	 */
	private void deleteRichColumns() {
		List<UIComponent> children = dataTable.getChildren();
		Iterator<UIComponent> it = children.iterator();
		List<UIComponent> forDelete = new ArrayList<UIComponent>();
		Integer i = 0;
		while (it.hasNext()) {
			UIComponent child = it.next();
			String id = child.getId();
			if (id != null && id.startsWith(COLUMN_ID_PREFIX)) {
				forDelete.add(child);
			}
			i++;
		}
		it = forDelete.iterator();
		while (it.hasNext()) {
			UIComponent elem = it.next();
			children.remove(elem);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.jsp.tagext.BodyTagSupport#doStartTag()
	 */
	@Override
	public int doStartTag() throws JspException {

		prepare();

		if (created) {
			deleteRichColumns();
			created = false;
		}

		// get the item we're interested in
		if (!hasNext()) {
			return SKIP_BODY;
		}

		exposeVariables();

		loop();

		exposeVariables();

		return EVAL_BODY_INCLUDE;
	}

	/**
	 * Method prepares all we need for starting of tag rendering
	 * 
	 * @throws JspTagException
	 */
	private void prepare() throws JspTagException {
		dataTable = getParentUIComponentClassicTagBase(pageContext)
				.getComponentInstance();
		created = (dataTable.getChildCount() > 0);

		initVariables();

		// produce the right sort of ForEachIterator
		if (_value != null) {
			// If this is a deferred expression, make a note and get
			// the 'items' instance.
			if (_value instanceof ValueExpression) {
				rawItems = _value.getValue(pageContext.getELContext());
			}
			// extract an iterator over the 'items' we've got
			items = SimpleForEachIterator
					.supportedTypeForEachIterator(rawItems);
		} else {
			// no 'items', so use 'begin' and 'end'
			items = SimpleForEachIterator.beginEndForEachIterator(_columns - 1);
		}

		correctFirst();

		/*
		 * ResultSet no more supported in <c:forEach> // step must be 1 when
		 * ResultSet is passed in if (rawItems instanceof ResultSet && step !=
		 * 1) throw new JspTagException(
		 * Resources.getMessage("FOREACH_STEP_NO_RESULTSET"));
		 */

	}

	/**
	 * Extracts integer value from end attr
	 */
	private void initColumnsCount() {
		if (columns != null) {
			if (columns instanceof ValueExpression)
				try {
					String t = (String) columns.getValue(getELContext());
					_columns = Integer.parseInt(t);
					if (_columns < 0) {
						_columns = 0; // If end is negative set up zero
					}
				} catch (Exception e) {
					_columns = 0;
				}
		} else {
			_columns = 0;
		}
	}

	/**
	 * Extracts string value from var attr
	 */
	private void initVar() {
		if (_var != null) {
			try {
				itemId = (String) _var.getValue(getELContext());
				varPattern = Pattern.compile("[\\W]+" + itemId + "[\\W]+");
			} catch (ClassCastException e) {
				itemId = null;
			}

		}
	}

	/**
	 * Extracts string value from index attr
	 */
	private void initIndex() {
		if (_index != null) {
			try {
				indexId = (String) _index.getValue(getELContext());
				indexPattern = Pattern.compile("[\\W]+" + indexId + "[\\W]+");
			} catch (ClassCastException e) {
				indexId = null;
			}

		}
	}

	/**
	 * Extracts string value from index attr
	 */
	private void initBegin() {
		_begin = 0;
		if (begin != null) {
			try {
				String t = (String) begin.getValue(getELContext());
				_begin = Integer.parseInt(t);
				_begin--; // correct begin value
				if (_begin < 0) {
					_begin = 0;
				}
			} catch (ClassCastException e) {
				_begin = 0;
			}

		}
	}

	/**
	 * Extracts string value from index attr
	 */
	private void initEnd() {
		_end = 0;
		if (end != null) {
			try {
				String t = (String) end.getValue(getELContext());
				_end = Integer.parseInt(t);
				if (_end < 0) {
					_end = 0;
				}
			} catch (ClassCastException e) {
				_end = 0;
			}

		}
	}

	/**
	 * Extracts tags attributes values
	 */
	private void initVariables() {
		initColumnsCount();
		initIndex();
		initVar();
		initBegin();
		initEnd();
	}

	/**
	 * Return true if we didn't complete column's count
	 * 
	 * @return
	 * @throws JspTagException
	 */
	private boolean hasNext() throws JspTagException {
		if (_end != 0) {
			return (index < (_end - 1)) ? items.hasNext() : false;
		} else {
			return items.hasNext();
		}
	}

	/**
	 * Iterate to next column
	 * 
	 * @return
	 * @throws JspTagException
	 */
	private Object next() throws JspTagException {
		Object o = items.next();
		index++;
		return o;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.jsp.tagext.BodyTagSupport#getBodyContent()
	 */
	@Override
	public BodyContent getBodyContent() {
		// TODO Auto-generated method stub
		return super.getBodyContent();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.webapp.UIComponentClassicTagBase#setProperties(javax.faces.component.UIComponent)
	 */
	protected void setProperties(UIComponent component) {

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.webapp.UIComponentClassicTagBase#hasBinding()
	 */
	protected boolean hasBinding() {
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.faces.webapp.UIComponentClassicTagBase#doEndTag()
	 */
	@Override
	public int doEndTag() throws JspException {
		if (!atFirst()) {
			popUIComponentClassicTagBase();
		}
		return EVAL_PAGE;
	}

	/**
	 * Sets page request variables
	 * 
	 * @throws JspTagException
	 */
	private void exposeVariables() throws JspTagException {

		/*
		 * We need to support null items returned from next(); we do this simply
		 * by passing such non-items through to the scoped variable as
		 * effectively 'null' (that is, by calling removeAttribute()).
		 * 
		 * Also, just to be defensive, we handle the case of a null 'status'
		 * object as well.
		 * 
		 * We call getCurrent() and getLoopStatus() (instead of just using
		 * 'item' and 'status') to bridge to subclasses correctly. A subclass
		 * can override getCurrent() or getLoopStatus() but still depend on our
		 * doStartTag() and doAfterBody(), which call this method
		 * (exposeVariables()), to expose 'item' and 'status' correctly.
		 */

		// Set up var variable
		if (itemId != null) {
			if (index == null)
				pageContext.removeAttribute(itemId, PageContext.PAGE_SCOPE);
			else if (_value != null) {
				VariableMapper vm = // getFacesContext().getELContext().getVariableMapper();
				pageContext.getELContext().getVariableMapper();
				if (vm != null) {
					ValueExpression ve = getVarExpression(_value);
					vm.setVariable(itemId, ve);
					getELContext().getVariableMapper().setVariable(itemId, ve);
				}
			} else
				pageContext.setAttribute(itemId, index);
		}

		// Set up index variable

		if (indexId != null) {
			if (index == null)
				pageContext.removeAttribute(indexId, PageContext.PAGE_SCOPE);
			else {
				IteratedIndexExpression indexExpression = new IteratedIndexExpression(
						index - _begin);
				VariableMapper vm = pageContext.getELContext()
						.getVariableMapper();
				if (vm != null) {
					vm.setVariable(indexId, indexExpression);
					getELContext().getVariableMapper().setVariable(indexId,
							indexExpression);
				}
				pageContext.setAttribute(indexId, index - _begin);
			}
		}

	}

	@Override
	public void setPageContext(PageContext pageContext) {
		super.setPageContext(pageContext);
	}

	/**
	 * Return expression for page variables
	 * 
	 * @param expr
	 * @return
	 */
	private ValueExpression getVarExpression(ValueExpression expr) {
		Object o = expr.getValue(pageContext.getELContext());
		if (o.getClass().isArray() || o instanceof List) {
			return new IndexedValueExpression(_value, index);
		}

		if (o instanceof Collection || o instanceof Iterator
				|| o instanceof Enumeration || o instanceof Map
				|| o instanceof String) {

			if (iteratedExpression == null) {
				iteratedExpression = new IteratedExpression(_value, getDelims());
			}
			return new IteratedValueExpression(iteratedExpression, index);
		}

		throw new ELException("FOREACH_BAD_ITEMS");
	}

	/*
	 * Get the delimiter for string tokens. Used only for constructing the
	 * deferred expression for it.
	 */
	protected String getDelims() {
		return ",";
	}

	/**
	 * Inits first iteration item
	 */
	private void correctFirst() {
		try {
			if (items != null) {
				if (_begin > 0 && (index < (_begin - 1))) {
					while ((index < (_begin - 1)) && hasNext()) {
						next();
					}
				}
			}
		} catch (Exception e) {
			// TODO: handle exception
		}
	}

	/**
	 * <p>
	 * Pop the top {@link UIComponentTag} instance off of our component tag
	 * stack, deleting the stack if this was the last entry.
	 * </p>
	 */
	private void popUIComponentClassicTagBase() {
		FacesContext context = FacesContext.getCurrentInstance();
		Map<String, Object> requestMap = context.getExternalContext()
				.getRequestMap();
		List list = (List) requestMap.get(COMPONENT_TAG_STACK_ATTR);
		if (list != null) {
			list.remove(list.size() - 1);
			if (list.size() < 1) {
				requestMap.remove(COMPONENT_TAG_STACK_ATTR);
			}
		}

	}

	/**
	 * <p>
	 * Push the specified {@link UIComponentTag} instance onto our component tag
	 * stack, creating a stack if necessary.
	 * </p>
	 */
	private void pushUIComponentClassicTagBase(UIComponentClassicTagBase tag) {
		FacesContext context = FacesContext.getCurrentInstance();
		Map<String, Object> requestMap = context.getExternalContext()
				.getRequestMap();
		List<UIComponentClassicTagBase> list = TypedCollections
				.dynamicallyCastList((List) requestMap
						.get(COMPONENT_TAG_STACK_ATTR),
						UIComponentClassicTagBase.class);
		if (list == null) {
			// noinspection CollectionWithoutInitialCapacity
			list = new ArrayList<UIComponentClassicTagBase>();
			requestMap.put(COMPONENT_TAG_STACK_ATTR, list);
		}
		list.add(tag);

	}

	/**
	 * @return the begin
	 */
	public ValueExpression getBegin() {
		return begin;
	}

	/**
	 * @param begin
	 *            the begin to set
	 */
	public void setBegin(ValueExpression begin) {
		this.begin = begin;
	}

	/**
	 * @return the columns
	 */
	public ValueExpression getColumns() {
		return columns;
	}

	/**
	 * @param columns
	 *            the columns to set
	 */
	public void setColumns(ValueExpression columns) {
		this.columns = columns;
	}

	/**
	 * @return the index
	 */
	public ValueExpression getIndex() {
		return _index;
	}

	/**
	 * @param index
	 *            the index to set
	 */
	public void setIndex(ValueExpression index) {
		this._index = index;
	}

	/**
	 * @return the end
	 */
	public ValueExpression getEnd() {
		return end;
	}

	/**
	 * @param end
	 *            the end to set
	 */
	public void setEnd(ValueExpression end) {
		this.end = end;
	}

}
