/* ForEachImpl.java

	Purpose:
		
	Description:
		
	History:
		Wed Mar  8 14:21:08     2006, Created by tomyeh

Copyright (C) 2006 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
	This program is distributed under LGPL Version 2.1 in the hope that
	it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.zk.ui.util;

import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.zkoss.util.CollectionsX;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.ext.Scope;
import org.zkoss.zk.xel.EvaluatorRef;
import org.zkoss.zk.xel.ExValue;

/**
 * An implementation of {@link ForEach}.
 *
 * <p>Note: the use of {@link ForEachImpl} is different from
 * {@link ConditionImpl}. While you could use the same instance of
 * {@link ConditionImpl} for all evaluation, each instance of
 * {@link ForEachImpl} can be used only once (drop it after {@link #next}
 * returns false).
 *
 * @author tomyeh
 */
public class ForEachImpl implements ForEach {
	private final EvaluatorRef _evalr;
	private final Page _page;
	private final Component _comp;
	private final ExValue[] _expr;
	private final ExValue _begin, _end, _step;
	private Status _status;
	private Iterator _it;
	private Object _oldEach;
	private boolean _done;

	/** Returns an instance that represents the iterator for the
	 * specified collection.
	 *
	 * @param expr an array of expressions. There are two formats.
	 * <ol>
	 * <li>length == 1: it iterates thru the content of expr[0].
	 * For example, if expr[0] is an array, all items in this array will
	 * be iterated.</li>
	 * <li>length > 1, it will iterate thru expr[0]</li>
	 * <li>length == 0 or expr is null, null is returned</li>
	 * </ol>
	 * @since 3.0.6
	 */
	public static ForEach getInstance(EvaluatorRef evalr, Component comp, ExValue[] expr, ExValue begin, ExValue end) {
		return getInstance(evalr, comp, expr, begin, end, null);
	}

	/** Returns an instance that represents the iterator for the
	 * specified collection, or null if expr is null or empty.
	 *
	 * @param expr an array of expressions. There are two formats.
	 * <ol>
	 * <li>length == 1: it iterates thru the content of expr[0].
	 * For example, if expr[0] is an array, all items in this array will
	 * be iterated.</li>
	 * <li>length > 1, it will iterate thru expr[0]</li>
	 * <li>length == 0 or expr is null, null is returned</li>
	 * </ol>
	 * @since 3.0.6
	 */
	public static ForEach getInstance(EvaluatorRef evalr, Page page, ExValue[] expr, ExValue begin, ExValue end) {
		return getInstance(evalr, page, expr, begin, end, null);
	}

	/** Returns an instance that represents the iterator for the
	 * specified collection.
	 *
	 * @param expr an array of expressions. There are two formats.
	 * <ol>
	 * <li>length == 1: it iterates thru the content of expr[0].
	 * For example, if expr[0] is an array, all items in this array will
	 * be iterated.</li>
	 * <li>length > 1, it will iterate thru expr[0]</li>
	 * <li>length == 0 or expr is null, null is returned</li>
	 * </ol>
	 * @since 8.0.0
	 */
	public static ForEach getInstance(EvaluatorRef evalr, Component comp, ExValue[] expr, ExValue begin, ExValue end,
			ExValue step) {
		if (expr == null || expr.length == 0)
			return null;
		return new ForEachImpl(evalr, comp, expr, begin, end, step);
	}

	/** Returns an instance that represents the iterator for the
	 * specified collection, or null if expr is null or empty.
	 *
	 * @param expr an array of expressions. There are two formats.
	 * <ol>
	 * <li>length == 1: it iterates thru the content of expr[0].
	 * For example, if expr[0] is an array, all items in this array will
	 * be iterated.</li>
	 * <li>length > 1, it will iterate thru expr[0]</li>
	 * <li>length == 0 or expr is null, null is returned</li>
	 * </ol>
	 * @since 8.0.0
	 */
	public static ForEach getInstance(EvaluatorRef evalr, Page page, ExValue[] expr, ExValue begin, ExValue end,
			ExValue step) {
		if (expr == null || expr.length == 0)
			return null;
		return new ForEachImpl(evalr, page, expr, begin, end, step);
	}

	/** Constructor.
	 * In most cases, use {@link #getInstance(EvaluatorRef, Component, ExValue[], ExValue, ExValue, ExValue)}
	 * instead of this constructor.
	 * @exception IllegalArgumentException if comp or evalr is null
	 * @since 8.0.0
	 */
	public ForEachImpl(EvaluatorRef evalr, Component comp, ExValue[] expr, ExValue begin, ExValue end, ExValue step) {
		if (comp == null || evalr == null)
			throw new IllegalArgumentException("null");

		_evalr = evalr;
		_page = null;
		_comp = comp;
		_expr = expr;
		_begin = begin;
		_end = end;
		_step = step;
	}

	/** Constructor.
	 * In most cases, use {@link #getInstance(EvaluatorRef, Component, ExValue[], ExValue, ExValue, ExValue)}
	 * instead of this constructor.
	 * @exception IllegalArgumentException if page or evalr is null
	 * @since 8.0.0
	 */
	public ForEachImpl(EvaluatorRef evalr, Page page, ExValue[] expr, ExValue begin, ExValue end, ExValue step) {
		if (page == null || evalr == null)
			throw new IllegalArgumentException();

		_evalr = evalr;
		_page = page;
		_comp = null;
		_expr = expr;
		_begin = begin;
		_end = end;
		_step = step;
	}

	/** Constructor.
	 * In most cases, use {@link #getInstance(EvaluatorRef, Component, ExValue[], ExValue, ExValue)}
	 * instead of this constructor.
	 * @exception IllegalArgumentException if comp or evalr is null
	 * @since 3.0.6
	 */
	public ForEachImpl(EvaluatorRef evalr, Component comp, ExValue[] expr, ExValue begin, ExValue end) {
		if (comp == null || evalr == null)
			throw new IllegalArgumentException("null");

		_evalr = evalr;
		_page = null;
		_comp = comp;
		_expr = expr;
		_begin = begin;
		_end = end;
		_step = null;
	}

	/** Constructor.
	 * In most cases, use {@link #getInstance(EvaluatorRef, Component, ExValue[], ExValue, ExValue)}
	 * instead of this constructor.
	 * @exception IllegalArgumentException if page or evalr is null
	 * @since 3.0.6
	 */
	public ForEachImpl(EvaluatorRef evalr, Page page, ExValue[] expr, ExValue begin, ExValue end) {
		if (page == null || evalr == null)
			throw new IllegalArgumentException();

		_evalr = evalr;
		_page = page;
		_comp = null;
		_expr = expr;
		_begin = begin;
		_end = end;
		_step = null;
	}

	/** Returns an instance that represents the iterator for the
	 * specified collection, or null if expr is null or empty.
	 *
	 * @param expr an EL expression that shall return a collection of objects.
	 * @see #getInstance(EvaluatorRef, Component, ExValue[], ExValue, ExValue)
	 */
	public static ForEach getInstance(EvaluatorRef evalr, Component comp, String expr, String begin, String end) {
		if (expr == null || expr.length() == 0)
			return null;
		return new ForEachImpl(evalr, comp, expr, begin, end);
	}

	/** Returns an instance that represents the iterator for the
	 * specified collection, or null if expr is null or empty.
	 *
	 * @param expr an EL expression that shall return a collection of objects.
	 * @since 3.0.0
	 * @see #getInstance(EvaluatorRef, Page, ExValue[], ExValue, ExValue)
	 */
	public static ForEach getInstance(EvaluatorRef evalr, Page page, String expr, String begin, String end) {
		if (expr == null || expr.length() == 0)
			return null;
		return new ForEachImpl(evalr, page, expr, begin, end);
	}

	/** Constructor.
	 * In most cases, use {@link #getInstance(EvaluatorRef, Component, String, String, String)}
	 * instead of this constructor.
	 * @exception IllegalArgumentException if comp or evalr is null
	 * @since 3.0.0
	 * @see #ForEachImpl(EvaluatorRef, Component, ExValue[], ExValue, ExValue)
	 */
	public ForEachImpl(EvaluatorRef evalr, Component comp, String expr, String begin, String end) {
		if (comp == null || evalr == null)
			throw new IllegalArgumentException();

		_evalr = evalr;
		_page = null;
		_comp = comp;
		_expr = expr != null ? new ExValue[] { new ExValue(expr, Object.class) } : null;
		_begin = begin != null && begin.length() > 0 ? new ExValue(begin, Integer.class) : null;
		_end = end != null && end.length() > 0 ? new ExValue(end, Integer.class) : null;
		_step = null;
	}

	/** Constructor.
	 * In most cases, use {@link #getInstance(EvaluatorRef, Component, String, String, String)}
	 * instead of this constructor.
	 * @exception IllegalArgumentException if page or evalr is null
	 * @since 3.0.0
	 * @see #ForEachImpl(EvaluatorRef, Page, ExValue[], ExValue, ExValue)
	 */
	public ForEachImpl(EvaluatorRef evalr, Page page, String expr, String begin, String end) {
		if (page == null || evalr == null)
			throw new IllegalArgumentException();

		_evalr = evalr;
		_page = page;
		_comp = null;
		_expr = expr != null ? new ExValue[] { new ExValue(expr, Object.class) } : null;
		_begin = begin != null && begin.length() > 0 ? new ExValue(begin, Integer.class) : null;
		_end = end != null && end.length() > 0 ? new ExValue(end, Integer.class) : null;
		_step = null;
	}

	private Object eval(ExValue value) {
		return value == null ? null : _comp != null ? value.getValue(_evalr, _comp) : value.getValue(_evalr, _page);
	}

	//-- ForEach --//
	public boolean next() {
		if (_done)
			throw new IllegalStateException("Iterate twice not allowed");

		if (_status == null) {
			//Bug 2188572: we have to evaluate _expr before setupStatus
			final Object o;
			if (_expr == null || _expr.length == 0) {
				o = null;
			} else if (_expr.length == 1) {
				o = eval(_expr[0]);
			} else {
				Object[] ary = new Object[_expr.length];
				for (int j = 0; j < _expr.length; ++j)
					ary[j] = eval(_expr[j]);
				o = ary;
			}

			if (o == null) {
				_done = true;
				return false;
			}

			//Bug 1786154: we have to prepare _status first since _expr,
			//_begin or _end might depend on it
			setupStatus();

			Integer ibeg = (Integer) eval(_begin);
			int vbeg = ibeg != null ? ibeg.intValue() : 0;
			if (vbeg < 0)
				ibeg = new Integer(vbeg = 0);
			_status.setBegin(ibeg);
			_status.setEnd((Integer) eval(_end));
			_status.setStep((Integer) eval(_step));

			prepare(o, vbeg); //prepare iterator
		}

		if (_status.count > 0 && _status.getStep() != null) {
			_status.index += _status.getStep().intValue() - 1;
		}
		if ((_status.end == null || _status.index < _status.end.intValue()) && _it.hasNext()) {
			++_status.index;
			_status.each = _it.next();
			_status.count++;
			getScope().setAttribute("each", _status.each);

			if (_status.getStep() != null)
				discard(_status.getStep().intValue() - 1);
			calibrateLast();
			return true;
		}

		//restore
		_done = true;
		restoreStatus();
		return false;
	}

	private void discard(int n) {
		if (n < 0) {
			throw new IllegalArgumentException("'step' cannot less than 1");
		}
		while (n-- > 0) {
			if (_it.hasNext()) {
				_it.next();
			} else {
				break;
			}
		}
	}

	private void calibrateLast() {
		_status.last = !_it.hasNext() && (_status.end == null || _status.index + _status.begin
				+ (_status.getStep() == null ? 1 : _status.getStep().intValue()) == _status.end.intValue());
	}

	private void setupStatus() {
		final Scope scope = getScope();
		_oldEach = scope.getAttribute("each", true);
		_status = new Status(scope.getAttribute("forEachStatus", true));
		scope.setAttribute("forEachStatus", _status);
	}

	private void restoreStatus() {
		final Scope scope = getScope();
		if (_status.previous != null)
			scope.setAttribute("forEachStatus", _status.previous);
		else
			scope.removeAttribute("forEachStatus");
		if (_oldEach != null)
			scope.setAttribute("each", _oldEach);
		else
			scope.removeAttribute("each");
		_it = null;
		_status = null; //recycle (just in case)
	}

	private Scope getScope() {
		return _comp != null ? (Scope) _comp : _page;
	}

	@SuppressWarnings("unchecked")
	private void prepare(Object o, final int begin) {
		if (begin > 0 && (o instanceof List)) {
			final List l = (List) o;
			final int size = l.size();
			_it = l.listIterator(begin > size ? size : begin);
		} else if (o instanceof Collection) {
			_it = ((Collection) o).iterator();
			forward(begin);
		} else if (o instanceof Map) {
			_it = ((Map) o).entrySet().iterator();
			forward(begin);
		} else if (o instanceof Iterator) {
			_it = (Iterator) o;
			forward(begin);
		} else if (o instanceof Enumeration) {
			_it = new CollectionsX.EnumerationIterator((Enumeration) o);
			forward(begin);
		} else if (o instanceof Object[]) {
			final Object[] ary = (Object[]) o;
			_it = new Iterator() {
				private int _j = begin;

				public boolean hasNext() {
					return _j < ary.length;
				}

				public Object next() {
					return ary[_j++];
				}

				public void remove() {
					throw new UnsupportedOperationException();
				}
			};
		} else if (o instanceof int[]) {
			final int[] ary = (int[]) o;
			_it = new Iterator() {
				private int _j = begin;

				public boolean hasNext() {
					return _j < ary.length;
				}

				public Object next() {
					return new Integer(ary[_j++]);
				}

				public void remove() {
					throw new UnsupportedOperationException();
				}
			};
		} else if (o instanceof long[]) {
			final long[] ary = (long[]) o;
			_it = new Iterator() {
				private int _j = begin;

				public boolean hasNext() {
					return _j < ary.length;
				}

				public Object next() {
					return new Long(ary[_j++]);
				}

				public void remove() {
					throw new UnsupportedOperationException();
				}
			};
		} else if (o instanceof short[]) {
			final short[] ary = (short[]) o;
			_it = new Iterator() {
				private int _j = begin;

				public boolean hasNext() {
					return _j < ary.length;
				}

				public Object next() {
					return new Short(ary[_j++]);
				}

				public void remove() {
					throw new UnsupportedOperationException();
				}
			};
		} else if (o instanceof byte[]) {
			final byte[] ary = (byte[]) o;
			_it = new Iterator() {
				private int _j = begin;

				public boolean hasNext() {
					return _j < ary.length;
				}

				public Object next() {
					return new Byte(ary[_j++]);
				}

				public void remove() {
					throw new UnsupportedOperationException();
				}
			};
		} else if (o instanceof double[]) {
			final double[] ary = (double[]) o;
			_it = new Iterator() {
				private int _j = begin;

				public boolean hasNext() {
					return _j < ary.length;
				}

				public Object next() {
					return new Double(ary[_j++]);
				}

				public void remove() {
					throw new UnsupportedOperationException();
				}
			};
		} else if (o instanceof float[]) {
			final float[] ary = (float[]) o;
			_it = new Iterator() {
				private int _j = begin;

				public boolean hasNext() {
					return _j < ary.length;
				}

				public Object next() {
					return new Float(ary[_j++]);
				}

				public void remove() {
					throw new UnsupportedOperationException();
				}
			};
		} else if (o instanceof char[]) {
			final char[] ary = (char[]) o;
			_it = new Iterator() {
				private int _j = begin;

				public boolean hasNext() {
					return _j < ary.length;
				}

				public Object next() {
					return new Character(ary[_j++]);
				}

				public void remove() {
					throw new UnsupportedOperationException();
				}
			};
		} else if (o instanceof boolean[]) {
			final boolean[] ary = (boolean[]) o;
			_it = new Iterator() {
				private int _j = begin;

				public boolean hasNext() {
					return _j < ary.length;
				}

				public Object next() {
					return Boolean.valueOf(ary[_j++]);
				}

				public void remove() {
					throw new UnsupportedOperationException();
				}
			};
		} else {
			_it = new CollectionsX.OneIterator(o);
			forward(begin);
		}
	}

	private void forward(int begin) {
		while (--begin >= 0 && _it.hasNext())
			_it.next();
	}

	private static class Status implements ForEachStatus {
		private final Object previous;
		private Object each;
		private int index;
		private int count = 0;
		private boolean last = false;
		private Integer begin, end, step;

		private Status(Object previous) {
			this.previous = previous;
			this.index = -1;
		}

		private void setBegin(Integer begin) {
			this.begin = begin;
			this.index = begin != null ? begin.intValue() - 1 : -1;
		}

		private void setEnd(Integer end) {
			this.end = end;
		}

		private void setStep(Integer step) {
			this.step = step;
		}

		public ForEachStatus getPrevious() {
			return this.previous instanceof ForEachStatus ? (ForEachStatus) this.previous : null;
		}

		public Object getEach() {
			return getCurrent();
		}

		public int getIndex() {
			return this.index;
		}

		public Integer getBegin() {
			return this.begin;
		}

		public Integer getEnd() {
			return this.end;
		}

		public String toString() {
			return "[index=" + index + ']';
		}

		public Object getCurrent() {
			return this.each;
		}

		public boolean isFirst() {
			return getCount() == 1;
		}

		public boolean isLast() {
			return last;
		}

		public Integer getStep() {
			return step;
		}

		public int getCount() {
			return count;
		}
	}
}
