/* ForEach.java

	Purpose:
		
	Description:
		
	History:
		Tue Sep  6 15:33:11     2005, Created by tomyeh

Copyright (C) 2005 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.web.servlet.dsp.action;

import java.io.IOException;
import java.io.StringWriter;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import org.zkoss.web.mesg.MWeb;
import org.zkoss.web.servlet.dsp.DspException;

/**
 * Iterators thru a collection/array of items.
 * 
 * @author tomyeh
 */
public class ForEach extends AbstractAction {
	private String _var, _varStatus;
	private Object _items;
	private int _beg = 0, _end = Integer.MAX_VALUE;
	private boolean _trim;
	private boolean _endSpecified, _itemsSpecified;

	/** Returns the variable name used to iterate thru items. */
	public String getVar() {
		return _var;
	}

	/** Sets the variable name used to iterate thru items. */
	public void setVar(String var) {
		_var = var;
	}

	/** Returns the variable name used to hold the current iteration
	 * status, an instance of {@link LoopStatus}.
	 */
	public String getVarStatus() {
		return _varStatus;
	}

	/** Sets the variable name used to hold the current iteration status.
	 */
	public void setVarStatus(String varStatus) {
		_varStatus = varStatus;
	}

	/** Returns the attribute items. */
	public Object getItems() {
		return _items;
	}

	/** Sets the attribute items. */
	public void setItems(Object items) {
		_items = items;
		_itemsSpecified = true;
	}

	/** Returns the index of the item at which the iteration begins.
	 */
	public int getBegin() {
		return _beg;
	}

	/** Sets the index of the item at which the iteration begins.
	 * <p>Default: 0.
	 */
	public void setBegin(int beg) {
		if (beg < 0)
			throw new IllegalArgumentException("Non-negative only");
		_beg = beg;
	}

	/** Returns the index of the item at which the iteration ends (inclusive).
	 */
	public int getEnd() {
		return _end;
	}

	/** Sets the index of the item at which the iteration ends (inclusive).
	 * <p>Default: Integer.MAX_VALUE.
	 */
	public void setEnd(int end) {
		_end = end;
		_endSpecified = true;
	}

	/** Returns whether to trim the result. */
	public boolean isTrim() {
		return _trim;
	}

	/** Sets whether to trim the result.
	 * <p>Default: true.
	 */
	public void setTrim(boolean trim) {
		_trim = trim;
	}

	//-- Action --//
	public void render(ActionContext ac, boolean nested) throws DspException, IOException {
		//at least items or end must be specified
		if (!nested || (_itemsSpecified && _items == null) || (_endSpecified && _end < _beg)
				|| (!_itemsSpecified && !_endSpecified) || !isEffective())
			return;

		final Object old1 = _var != null ? ac.getAttribute(_var, ac.PAGE_SCOPE) : null;
		final Object old2;
		final Status st;
		if (_varStatus != null) {
			old2 = ac.getAttribute(_varStatus, ac.PAGE_SCOPE);
			ac.setAttribute(_varStatus, st = new Status(), ac.PAGE_SCOPE);
		} else {
			old2 = null;
			st = null;
		}

		if (_items == null) { //use begin and end only
			renderWith(ac, st);
		} else if (_items.getClass().isArray()) {
			if (_items instanceof Object[])
				renderWith(ac, st, (Object[]) _items);
			else if (_items instanceof int[])
				renderWith(ac, st, (int[]) _items);
			else if (_items instanceof short[])
				renderWith(ac, st, (short[]) _items);
			else if (_items instanceof long[])
				renderWith(ac, st, (long[]) _items);
			else if (_items instanceof byte[])
				renderWith(ac, st, (byte[]) _items);
			else if (_items instanceof char[])
				renderWith(ac, st, (char[]) _items);
			else if (_items instanceof double[])
				renderWith(ac, st, (double[]) _items);
			else if (_items instanceof float[])
				renderWith(ac, st, (float[]) _items);
			else
				throw new InternalError("Unknown " + _items.getClass());
		} else if (_beg > 0 && (_items instanceof List)) {
			final List l = (List) _items;
			final int size = l.size();
			renderWith(ac, st, l.listIterator(_beg > size ? size : _beg));
		} else if (_items instanceof Collection) {
			renderWith(ac, st, ((Collection) _items).iterator());
		} else if (_items instanceof Map) {
			renderWith(ac, st, ((Map) _items).entrySet().iterator());
		} else if (_items instanceof Iterator) {
			renderWith(ac, st, (Iterator) _items);
		} else if (_items instanceof Enumeration) {
			renderWith(ac, st, (Enumeration) _items);
		} else if (_items instanceof String) {
			renderWith(ac, st, (String) _items);
		} else {
			throw new DspException(MWeb.DSP_UNKNOWN_ATTRIBUTE_VALUE,
					new Object[] { this, "items", new Integer(ac.getLineNumber()) });
		}

		if (_var != null)
			ac.setAttribute(_var, old1, ac.PAGE_SCOPE);
		if (_varStatus != null)
			ac.setAttribute(_varStatus, old2, ac.PAGE_SCOPE);
	}

	private void renderWith(ActionContext ac, Status st) throws DspException, IOException {
		final StringWriter out = getFragmentOut(ac, _trim);
		for (int j = _beg; j <= _end; ++j) {
			final Object val = new Integer(j);
			if (_var != null)
				ac.setAttribute(_var, val, ac.PAGE_SCOPE);
			if (st != null)
				st.update(j, val);
			renderFragment(ac, out, _trim);
		}
		if (out != null)
			ac.getOut().write(out.toString());
	}

	private void renderWith(ActionContext ac, Status st, ListIterator it) throws DspException, IOException {
		final StringWriter out = getFragmentOut(ac, _trim);
		for (int j = 0, cnt = _end - _beg + 1; it.hasNext() && --cnt >= 0; ++j) {
			final Object val = it.next();
			if (_var != null)
				ac.setAttribute(_var, val, ac.PAGE_SCOPE);
			if (st != null)
				st.update(j, val);
			renderFragment(ac, out, _trim);
		}
		if (out != null)
			ac.getOut().write(out.toString());
	}

	private void renderWith(ActionContext ac, Status st, Iterator it) throws DspException, IOException {
		final StringWriter out = getFragmentOut(ac, _trim);

		for (int j = 0; ++j <= _beg && it.hasNext();) //skip
			it.next();

		for (int j = 0, cnt = _end - _beg + 1; it.hasNext() && --cnt >= 0; ++j) {
			final Object val = it.next();
			if (_var != null)
				ac.setAttribute(_var, val, ac.PAGE_SCOPE);
			if (st != null)
				st.update(j, val);
			renderFragment(ac, out, _trim);
		}
		if (out != null)
			ac.getOut().write(out.toString());
	}

	private void renderWith(ActionContext ac, Status st, Enumeration enm) throws DspException, IOException {
		final StringWriter out = getFragmentOut(ac, _trim);

		for (int j = 0; ++j <= _beg && enm.hasMoreElements();) //skip
			enm.nextElement();

		for (int j = 0, cnt = _end - _beg + 1; enm.hasMoreElements() && --cnt >= 0; ++j) {
			final Object val = enm.nextElement();
			if (_var != null)
				ac.setAttribute(_var, val, ac.PAGE_SCOPE);
			if (st != null)
				st.update(j, val);
			renderFragment(ac, out, _trim);
		}
		if (out != null)
			ac.getOut().write(out.toString());
	}

	private void renderWith(ActionContext ac, Status st, Object[] ary) throws DspException, IOException {
		final StringWriter out = getFragmentOut(ac, _trim);
		for (int j = _beg; j < ary.length && j <= _end; ++j) {
			final Object val = ary[j];
			if (_var != null)
				ac.setAttribute(_var, val, ac.PAGE_SCOPE);
			if (st != null)
				st.update(j, val);
			renderFragment(ac, out, _trim);
		}
		if (out != null)
			ac.getOut().write(out.toString());
	}

	private void renderWith(ActionContext ac, Status st, int[] ary) throws DspException, IOException {
		final StringWriter out = getFragmentOut(ac, _trim);
		for (int j = _beg; j < ary.length && j <= _end; ++j) {
			final Object val = new Integer(ary[j]);
			if (_var != null)
				ac.setAttribute(_var, val, ac.PAGE_SCOPE);
			if (st != null)
				st.update(j, val);
			renderFragment(ac, out, _trim);
		}
		if (out != null)
			ac.getOut().write(out.toString());
	}

	private void renderWith(ActionContext ac, Status st, short[] ary) throws DspException, IOException {
		final StringWriter out = getFragmentOut(ac, _trim);
		for (int j = _beg; j < ary.length && j <= _end; ++j) {
			final Object val = new Short(ary[j]);
			if (_var != null)
				ac.setAttribute(_var, val, ac.PAGE_SCOPE);
			if (st != null)
				st.update(j, val);
			renderFragment(ac, out, _trim);
		}
		if (out != null)
			ac.getOut().write(out.toString());
	}

	private void renderWith(ActionContext ac, Status st, long[] ary) throws DspException, IOException {
		final StringWriter out = getFragmentOut(ac, _trim);
		for (int j = _beg; j < ary.length && j <= _end; ++j) {
			final Object val = new Long(ary[j]);
			if (_var != null)
				ac.setAttribute(_var, val, ac.PAGE_SCOPE);
			if (st != null)
				st.update(j, val);
			renderFragment(ac, out, _trim);
		}
		if (out != null)
			ac.getOut().write(out.toString());
	}

	private void renderWith(ActionContext ac, Status st, char[] ary) throws DspException, IOException {
		final StringWriter out = getFragmentOut(ac, _trim);
		for (int j = _beg; j < ary.length && j <= _end; ++j) {
			final Object val = new Character(ary[j]);
			if (_var != null)
				ac.setAttribute(_var, val, ac.PAGE_SCOPE);
			if (st != null)
				st.update(j, val);
			renderFragment(ac, out, _trim);
		}
		if (out != null)
			ac.getOut().write(out.toString());
	}

	private void renderWith(ActionContext ac, Status st, byte[] ary) throws DspException, IOException {
		final StringWriter out = getFragmentOut(ac, _trim);
		for (int j = _beg; j < ary.length && j <= _end; ++j) {
			final Object val = new Byte(ary[j]);
			if (_var != null)
				ac.setAttribute(_var, val, ac.PAGE_SCOPE);
			if (st != null)
				st.update(j, val);
			renderFragment(ac, out, _trim);
		}
		if (out != null)
			ac.getOut().write(out.toString());
	}

	private void renderWith(ActionContext ac, Status st, float[] ary) throws DspException, IOException {
		final StringWriter out = getFragmentOut(ac, _trim);
		for (int j = _beg; j < ary.length && j <= _end; ++j) {
			final Object val = new Float(ary[j]);
			if (_var != null)
				ac.setAttribute(_var, val, ac.PAGE_SCOPE);
			if (st != null)
				st.update(j, val);
			renderFragment(ac, out, _trim);
		}
		if (out != null)
			ac.getOut().write(out.toString());
	}

	private void renderWith(ActionContext ac, Status st, double[] ary) throws DspException, IOException {
		final StringWriter out = getFragmentOut(ac, _trim);
		for (int j = _beg; j < ary.length && j <= _end; ++j) {
			final Object val = new Double(ary[j]);
			if (_var != null)
				ac.setAttribute(_var, val, ac.PAGE_SCOPE);
			if (st != null)
				st.update(j, val);
			renderFragment(ac, out, _trim);
		}
		if (out != null)
			ac.getOut().write(out.toString());
	}

	private void renderWith(ActionContext ac, Status st, String txt) throws DspException, IOException {
		final StringBuffer sb = new StringBuffer();
		int idx = 0;
		final StringWriter out = getFragmentOut(ac, _trim);
		for (int j = _beg, len = txt.length(); j < len && j <= _end; ++j) {
			char cc = txt.charAt(j);
			if (cc == ',') {
				final Object val = sb.toString();
				if (_var != null)
					ac.setAttribute(_var, val, ac.PAGE_SCOPE);
				if (st != null)
					st.update(idx++, val);
				renderFragment(ac, out, _trim);
				sb.setLength(0);
			} else if (cc == '\\' && j + 1 < len) {
				cc = txt.charAt(j + 1);
				switch (cc) {
				case 'n':
					cc = '\n';
					break;
				case 'r':
					cc = '\r';
					break;
				case 't':
					cc = '\t';
					break;
				case 'b':
					cc = '\b';
					break;
				}
			}
			sb.append(cc);
		}
		if (sb.length() > 0) {
			final Object val = sb.toString();
			if (_var != null)
				ac.setAttribute(_var, val, ac.PAGE_SCOPE);
			if (st != null)
				st.update(idx++, val);
			renderFragment(ac, out, _trim);
		}
		if (out != null)
			ac.getOut().write(out.toString());
	}

	//-- Object --//
	public String toString() {
		return "forEach";
	}

	private static class Status implements LoopStatus {
		private int _j;
		private Object _cur;

		public int getIndex() {
			return _j;
		}

		public Object getCurrent() {
			return _cur;
		}

		private void update(int j, Object cur) {
			_j = j;
			_cur = cur;
		}
	}
}
