/* AttributesInfo.java

	Purpose:
		
	Description:
		
	History:
		Thu Apr 13 09:06:16     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.metainfo;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Components;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.util.ConditionImpl;
import org.zkoss.zk.xel.Evaluator;
import org.zkoss.zk.xel.impl.Utils;

/**
 * Represents a map of custom attributes of a component definition
 * ({@link ComponentDefinition}).
 * It is equivalent to the custom-attributes element.
 *
 * <p>Note: it is serializable.
 *
 * @author tomyeh
 */
public class AttributesInfo extends ConditionLeafInfo {
	/** Map(String name, ExValue/ExValue[]/Map value). */
	private final Map<String, Object> _attrs;
	private final int _composite;
	private final int _scope;

	/** Constructor.
	 * @param attrs the custom attributes (String name, String value).
	 * Once called, the caller shall not access attrs again -- it belongs
	 * to this object.
	 * @param scope specifies the scope.
	 * @param composite indicates the composite type.
	 * It can be one of "none", "list" or "map".
	 * If null or empty, "none" is assumed.
	 * @exception IllegalArgumentException if the composite type is illegal.
	 * @since 3.0.6
	 */
	public AttributesInfo(NodeInfo parent, Map<String, String> attrs, String scope, String composite,
			ConditionImpl cond) {
		super(parent, cond);

		if (composite == null || composite.length() == 0 || composite.equals("none"))
			_composite = Utils.SCALAR;
		else if (composite.equals("list"))
			_composite = Utils.LIST;
		else if (composite.equals("map"))
			_composite = Utils.MAP;
		else
			throw new IllegalArgumentException("Unkonwn composite: " + composite);

		if (attrs != null && !attrs.isEmpty()) {
			_attrs = new LinkedHashMap<String, Object>();
			for (Map.Entry<String, String> me : attrs.entrySet()) {
				_attrs.put(me.getKey(), Utils.parseComposite(me.getValue(), Object.class, _composite));
			}
		} else {
			_attrs = null;
		}

		_scope = scope == null ? -1 : Components.getScope(scope);
	}

	/** The same as AttributesInfo(parent, attrs, scope, "none", cond).
	 *
	 * @param attrs the custom attributes (String name, String value).
	 * Once called, the caller shall not access attrs again -- it belongs
	 * to this object.
	 * @param scope specifies the scope.
	 */
	public AttributesInfo(NodeInfo parent, Map<String, String> attrs, String scope, ConditionImpl cond) {
		this(parent, attrs, scope, null, cond);
	}

	/** Returns the scope, or null if it is not associated with a scope.
	 * <p>Notice that, prior to 5.0.8, "component" is returned if
	 * it is not associated with a scope (which is not correct since
	 * this info might be associated with a page).
	 * @since 3.0.6
	 */
	public String getScope() {
		return _scope != -1 ? Components.scopeToString(_scope) : null;
	}

	/** Returns the composite type: "none", "list" or "map".
	 * @since 3.0.6
	 */
	public String getComposite() {
		return _composite == Utils.LIST ? "list" : _composite == Utils.MAP ? "map" : "none";
	}

	/** Applies the custom attributes.
	 * <p>Note: this method does nothing if {@link #isEffective} returns false.
	 */
	public void apply(Component comp) {
		if (_attrs != null && isEffective(comp)) {
			final Evaluator eval = getEvaluator();
			for (Map.Entry<String, Object> me : _attrs.entrySet()) {
				final String name = me.getKey();
				final Object value = me.getValue();
				comp.setAttribute(name, Utils.evaluateComposite(eval, comp, value),
						_scope != -1 ? _scope : Component.COMPONENT_SCOPE);

				//bug zk-1298, handle special composerName, see Components#wireController
				if ("composerName".equals(name)) {
					Object ctrlnm = comp.getAttribute(name);
					if (ctrlnm instanceof String) {
						Object controller = comp.removeAttribute("_$composer$_"); //get from special attribute
						if (controller != null && comp.getAttribute((String) ctrlnm) == null) {
							comp.setAttribute((String) ctrlnm, controller);
						}
					}
				}

			}
		}
	}

	/** Applies the custom attributes.
	 * <p>Note: this method does nothing if {@link #isEffective} returns false.
	 */
	public void apply(Page page) {
		if (_attrs != null && isEffective(page)) {
			final Evaluator eval = getEvaluator();
			for (Map.Entry<String, Object> me : _attrs.entrySet()) {
				final String name = me.getKey();
				final Object value = me.getValue();
				page.setAttribute(name, Utils.evaluateComposite(eval, page, value),
						_scope != -1 ? _scope : Component.PAGE_SCOPE);
			}
		}
	}

	/**
	 * Returns the internal attributes if any.
	 * @since 10.0.0
	 */
	public Map<String, Object> getAttrs() {
		return _attrs != null ? _attrs : Collections.emptyMap();
	}

	//Object//
	public String toString() {
		final StringBuffer sb = new StringBuffer(40).append("[custom-attributes:");
		if (_attrs != null)
			for (String name : _attrs.keySet())
				sb.append(' ').append(name);
		return sb.append(']').toString();
	}
}
