/* ExecutionResolver.java

	Purpose:
		
	Description:
		
	History:
		Fri Jun 24 12:22:23     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.zk.xel.impl;

import java.util.Collections;

import org.zkoss.util.resource.Labels;
import org.zkoss.xel.VariableResolver;
import org.zkoss.xel.VariableResolverX;
import org.zkoss.xel.XelContext;
import org.zkoss.xel.XelException;
import org.zkoss.xel.util.Evaluators;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Components;
import org.zkoss.zk.ui.Execution;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.ShadowElement;
import org.zkoss.zk.ui.sys.ExecutionCtrl;

/**
 * A variable resolver that is based on the specified execution.
 *
 * @author tomyeh
 * @since 3.0.0
 */
public class ExecutionResolver implements VariableResolverX {
	/** The parent resolver. */
	private final VariableResolver _parent;
	private final Execution _exec;
	private Object _self;

	/** Constructs a resolver with a parent and a page.
	 * @param parent the parent resolver (null means ignored).
	 * @param exec the current execution
	 */
	public ExecutionResolver(Execution exec, VariableResolver parent) {
		if (exec == null)
			throw new NullPointerException();
		_exec = exec;
		_parent = parent;
	}

	/** Sets the self variable.
	 * The self variable also acts as the context to resolve other variables.
	 */
	public void setSelf(Object self) {
		_self = self;
	}

	/** Returns the self variable.
	 */
	public Object getSelf() {
		return _self;
	}

	//-- VariableResolver --//
	public Object resolveVariable(String name) throws XelException {
		return resolveVariable(null, null, name);
	}

	//-- VariableResolverX --//
	public Object resolveVariable(XelContext ctx, Object base, Object onm) {
		return resolveVariable0(_self, ctx, base, onm);
	}

	protected Object resolveVariable0(Object self, XelContext ctx, Object base, Object onm) {
		if (base != null) {
			Object o = ((ExecutionCtrl) _exec).getExtraXelVariable(ctx, base, onm);
			if (o != null)
				return o;
			final Page page = ((ExecutionCtrl) _exec).getCurrentPage();
			return page != null ? page.getXelVariable(ctx, base, onm, true) : null;
		}

		if (onm == null)
			return null;

		final String name = onm.toString();
		if (name == null || name.length() == 0) //just in case
			return null;

		//Note: we have to access keyword first (rather than component's ns)
		//since 1) BeanShell interpreter will store back variables
		//and page.getZScriptVariable will return the old value
		//2) ZK 5, getAttributeOrFellow doesn't look for variable resolvers and implicit objects 
		if ("arg".equals(name))
			return _exec.getArg();
		if ("componentScope".equals(name)) {
			if (self instanceof Component)
				return ((Component) self).getAttributes(Component.COMPONENT_SCOPE);
			return Collections.EMPTY_MAP;
		}
		if ("desktopScope".equals(name))
			return _exec.getDesktop().getAttributes();
		if ("desktop".equals(name))
			return _exec.getDesktop();
		if ("execution".equals(name))
			return _exec;
		if ("pageScope".equals(name)) {
			if (self instanceof Component)
				return ((Component) self).getAttributes(Component.PAGE_SCOPE);
			if (self instanceof Page)
				return ((Page) self).getAttributes();
			final Page page = ((ExecutionCtrl) _exec).getCurrentPage();
			return page != null ? page.getAttributes() : Collections.EMPTY_MAP;
		}
		if ("page".equals(name)) {
			if (self instanceof Component)
				return Components.getCurrentPage((Component) self);
			if (self instanceof Page)
				return self;
			return ((ExecutionCtrl) _exec).getCurrentPage();
		}
		if ("requestScope".equals(name))
			return _exec.getAttributes();
		if ("self".equals(name))
			return self;
		if ("sessionScope".equals(name))
			return _exec.getDesktop().getSession().getAttributes();
		if ("session".equals(name))
			return _exec.getDesktop().getSession();
		if ("spaceOwner".equals(name)) {
			if (self instanceof Component)
				return ((Component) self).getSpaceOwner();
			if (self instanceof Page)
				return self;
			return null;
		}
		if ("spaceScope".equals(name)) {
			if (self instanceof Component)
				return ((Component) self).getAttributes(Component.SPACE_SCOPE);
			if (self instanceof Page)
				return ((Page) self).getAttributes();
			return Collections.EMPTY_MAP;
		}
		if ("param".equals(name) || "paramValues".equals(name))
			return Evaluators.resolveVariable(_parent, name);
		//Bug 3131983: cannot go through getZScriptVariable

		if (self instanceof Component) {
			final Component comp = (Component) self;

			//We have to look getZScriptVariable first and then namespace
			//so it is in the same order of interpreter
			Page page = null;
			if (comp instanceof ShadowElement) {
				final Component host = ((ShadowElement) comp).getShadowHost();
				page = Components.getCurrentPage(host);
				if (page != null) {
					final Object o = page.getZScriptVariable(host, name);
					if (o != null)
						return o;
				}
			} else {
				page = Components.getCurrentPage(comp);
				if (page != null) {
					final Object o = page.getZScriptVariable(comp, name);
					if (o != null)
						return o;
				}
			}
			Object o = _exec.getAttribute(name);
			if (o != null/* || _exec.hasAttribute(name)*/) //ServletRequest not support hasAttribute
				return o;

			o = comp.getShadowVariable(name, true);
			if (o != null)
				return o;

			o = comp.getAttributeOrFellow(name, true);
			if (o != null)
				return o;

			o = ((ExecutionCtrl) _exec).getExtraXelVariable(name);
			if (o != null)
				return o;

			if (page != null) {
				o = page.getXelVariable(ctx, null, name, true);
				if (o != null)
					return o;
			}
		} else {
			Page page;
			if (self instanceof Page) {
				page = (Page) self;
			} else {
				page = ((ExecutionCtrl) _exec).getCurrentPage();
			}

			if (page != null) {
				Object o = page.getZScriptVariable(name);
				if (o != null)
					return o;

				o = _exec.getAttribute(name);
				if (o != null/* || _exec.hasAttribute(name)*/) //ServletRequest not support hasAttribute
					return o;

				o = page.getAttributeOrFellow(name, true);
				if (o != null)
					return o;

				o = ((ExecutionCtrl) _exec).getExtraXelVariable(name);
				if (o != null)
					return o;

				o = page.getXelVariable(ctx, null, name, true);
				if (o != null)
					return o;
			} else {
				Object o = _exec.getAttribute(name, true);
				if (o != null/* || _exec.hasAttribute(name, true)*/) //ServletRequest not support hasAttribute
					return o;
			}
		}

		Object o = Evaluators.resolveVariable(_parent, name);
		if (o != null)
			return o;

		//lower priority (i.e., user could override it)
		//Reason: they were introduced later, and have to maintain backward comparibility
		if ("labels".equals(name))
			return Labels.getSegmentedLabels();
		return null;
	}

	//Object//
	public String toString() {
		return "[ExecutionResolver: " + _self + ']';
	}

	/**
	 * A wrapper that prevents _self from being changed in the nested resolving process.
	 */
	static class Wrapper implements VariableResolverX {
		private final ExecutionResolver _resolver;
		private final Object _ref;

		Wrapper(ExecutionResolver resolver, Object ref) {
			this._resolver = resolver;
			this._ref = ref;
		}

		public Object resolveVariable(XelContext ctx, Object base, Object name) throws XelException {
			return _resolver.resolveVariable0(_ref, ctx, base, name);
		}

		public Object resolveVariable(String name) throws XelException {
			return _resolver.resolveVariable0(_ref, null, null, name);
		}

		public String toString() {
			return "[ExecutionResolver.Wrapper: " + _ref + ']';
		}
	}
}
