/* Utils.java

	Purpose:
		
	Description:
		
	History:
		Wed Jun  4 09:39:33     2008, Created by tomyeh

Copyright (C) 2008 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.xel.impl;

import static org.zkoss.lang.Generics.cast;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.zkoss.util.CollectionsX;
import org.zkoss.util.Maps;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.xel.Evaluator;
import org.zkoss.zk.xel.ExValue;

/**
 * Utilities to handle the metainfo.
 *
 * @author tomyeh
 * @since 3.0.6
 */
public class Utils {
	/** Used with {@link #parseComposite} to indicate the expression
	 * is a scalar value.
	 * In other words, it won't handle it specially. If a
	 */
	public static final int SCALAR = 0;
	/** Used with {@link #parseComposite} to indicate the expression
	 * is a vector, i.e., a list separated with comma.
	 * Example: "first, ${second}".
	 */
	public static final int LIST = 1;
	/** Used with {@link #parseComposite} to indicate the expression
	 * is a map, i.e., a map paired with equal and
	 * separated with comma.
	 * Example: "one=first, two=${second}".
	 */
	public static final int MAP = 2;

	/** Parses a list of expressions that is separated by comma.
	 * For example, parseList("${a}, b, ${c}", Object.class)
	 * will return a three-element array.
	 *
	 * @param ignoreEmpty whether to return null if expr is an empty expression.
	 * @return an array of the parsed expressions (at least with one element),
	 * or null if expr is null.
	 * @see #parseComposite
	 */
	public static ExValue[] parseList(String expr, Class expcls, boolean ignoreEmpty) {
		if (expr == null)
			return null;

		if (expr.length() != 0) {
			List<ExValue> dst = new LinkedList<ExValue>();
			Collection<String> src = CollectionsX.parse(null, expr, ',', true, true);
			for (String s : src)
				if (!ignoreEmpty || s.length() > 0)
					dst.add(new ExValue(s, expcls));

			if (!dst.isEmpty())
				return dst.toArray(new ExValue[dst.size()]);
		}

		return ignoreEmpty ? null : new ExValue[] { new ExValue(expr, expcls) };
	}

	/** Parses an expression which could a scalar, vector or map,
	 * depending on the specified type.
	 *
	 * <p>If type is {@link #SCALAR}, it is a simple expression and
	 * no special parsing happens. For example, "a, ${b}" will be evaluated
	 * to "a, boy" if b is "boy" if {@link #evaluateComposite} is called.</p>
	 *
	 * <p>If type is {@link #LIST}, the expression is a list separated
	 * with comma. For example, "a, ${b}" will be evaluated
	 * to a list with two elements: "a" and "boy".
	 * It is similar to {@link #parseList}.</p>
	 *
	 * <p>If type is {@link #MAP}, the expression is a list of paired
	 * entries. For example, "a=apple, b=${b}" will be evaluated
	 * to a map with two entries, ("a", "apple") and ("b", "boy").
	 *
	 * @param type one of {@link #SCALAR}, {@link #LIST} and {@link #MAP}.
	 * @return ExValue if {@link #SCALAR}, ExValue[] if {@link #LIST},
	 * Map(String, ExValue) if {@link #MAP}, or null if expr is null.
	 * To evaluate it, invoke {@link #evaluateComposite} by passing
	 * the returned value as its expr argument.
	 */
	public static Object parseComposite(String expr, Class expcls, int type) {
		if (expr == null)
			return null;

		if (type == LIST) {
			return parseList(expr, expcls, false);
		} else if (type == MAP) {
			Map<String, Object> dst = new LinkedHashMap<String, Object>();
			Maps.parse(dst, expr, ',', '\'', false, true);
			for (Map.Entry<String, Object> me : dst.entrySet()) {
				me.setValue(new ExValue((String) me.getValue(), expcls));
			}
			return dst;
		} else {
			return new ExValue(expr, expcls);
		}
	}

	/** Evaluates the composite expression parsed by
	 * {@link #parseComposite} against a component.
	 */
	public static Object evaluateComposite(Evaluator eval, Component comp, Object expr) {
		if (expr == null) {
			return null;
		} else if (expr instanceof ExValue) {
			return ((ExValue) expr).getValue(eval, comp);
		} else if (expr instanceof ExValue[]) {
			ExValue[] src = (ExValue[]) expr;
			Object[] dst = new Object[src.length];
			for (int j = 0; j < src.length; ++j)
				dst[j] = src[j].getValue(eval, comp);
			return dst;
		} else {
			Map<?, ExValue> src = cast((Map) expr);
			Map<Object, Object> dst = new LinkedHashMap<Object, Object>(src.size());
			for (Map.Entry<?, ExValue> me : src.entrySet()) {
				dst.put(me.getKey(), me.getValue().getValue(eval, comp));
			}
			return dst;
		}
	}

	/** Evaluates the composite expression parsed by
	 * {@link #parseComposite} against a page.
	 */
	public static Object evaluateComposite(Evaluator eval, Page page, Object expr) {
		if (expr == null) {
			return null;
		} else if (expr instanceof ExValue) {
			return ((ExValue) expr).getValue(eval, page);
		} else if (expr instanceof ExValue[]) {
			ExValue[] src = (ExValue[]) expr;
			Object[] dst = new Object[src.length];
			for (int j = 0; j < src.length; ++j)
				dst[j] = src[j].getValue(eval, page);
			return dst;
		} else {
			Map<?, ExValue> src = cast((Map) expr);
			Map<Object, Object> dst = new LinkedHashMap<Object, Object>(src.size());
			for (Map.Entry<?, ExValue> me : src.entrySet()) {
				dst.put(me.getKey(), me.getValue().getValue(eval, page));
			}
			return dst;
		}
	}
}
