/* RhinoInterpreter.java

	Purpose:
		
	Description:
		
	History:
		Fri Feb  9 00:23:47     2007, Created by tomyeh

Copyright (C) 2007 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.scripting.rhino;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.ImporterTopLevel;
import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.Undefined;

import org.zkoss.zk.scripting.util.GenericInterpreter;
import org.zkoss.zk.ui.Page;

/**
 * Rhino-based JavaScript interpreter.
 *
 * @author tomyeh
 */
public class RhinoInterpreter extends GenericInterpreter {
	private Scriptable _global;

	public RhinoInterpreter() {
	}

	/** Returns the top-level scope.
	 */
	/*package*/ Scriptable getGlobalScope() {
		return _global;
	}

	/** Returns the native interpreter, or null if it is not initialized
	 * or destroyed.
	 * From application's standpoint, it never returns null, and the returned
	 * object must be an instance of {@link org.mozilla.javascript.Scriptable}
	 * @since 3.0.2
	 */
	public Object getNativeInterpreter() {
		return _global;
	}

	//GenericInterpreter//
	protected void exec(String script) {
		Context.getCurrentContext().evaluateString(_global, script, "zk", 1, null);
	}

	protected boolean contains(String name) {
		return _global.has(name, _global);
	}

	protected Object get(String name) {
		final Object val = _global.get(name, _global);
		if (val == Scriptable.NOT_FOUND || val == Undefined.instance || val == null)
			return null;

		return Context.getCurrentContext().jsToJava(val, ScriptRuntime.ObjectClass);
	}

	protected void set(String name, Object value) {
		_global.put(name, _global, toJS(value));
	}

	private Object toJS(Object value) {
		return value == null || (value instanceof Number) || (value instanceof String) || (value instanceof Boolean)
				? value : Context.toObject(value, _global);
	}

	protected void unset(String name) {
		_global.delete(name);
	}

	protected void beforeExec() {
		enterContext();
	}

	protected void afterExec() {
		exitContext();
	}

	private Context enterContext() {
		return ContextFactory.getGlobal().enterContext();
	}

	private void exitContext() {
		Context.exit();
	}

	//Interpreter//
	public void init(Page owner, String zslang) {
		super.init(owner, zslang);

		final Context ctx = enterContext();
		try {
			_global = new GlobalScope(ctx);
		} finally {
			exitContext();
		}
	}

	public void destroy() {
		_global = null;
		super.destroy();
	}

	/**TODO: feasible but need to read manual/source first
	public Class getClass(String clsnm) {
	}
	*/
	/** Returns the method.
	 * <p>Note: JavaScript identifies a function with the name only.
	 */
	public org.zkoss.xel.Function getFunction(String name, Class[] argTypes) {
		enterContext();
		try {
			final Object val = _global.get(name, _global);
			if (!(val instanceof Function))
				return null;
			return new RhinoFunction((Function) val);
		} finally {
			exitContext();
		}
	}

	//supporting class//
	/** Extends ImporterTopLevel to support ZK namespaces.
	 */
	private class GlobalScope extends ImporterTopLevel {
		private GlobalScope(Context ctx) {
			super(ctx);
		}

		/* Not sure the side effect yet, so disable it
		public boolean has(String name, Scriptable start) {
			return super.has(name, start) || getFromNamespace(name) != UNDEFINED;
		}*/
		public Object get(String name, Scriptable start) {
			final Object val = super.get(name, start);
			if (val == Scriptable.NOT_FOUND || val == Undefined.instance) {
				final Object v = getFromNamespace(name);
				if (v != UNDEFINED)
					return toJS(v);
			}
			return val;
		}
	}

	private class RhinoFunction implements org.zkoss.xel.Function {
		private final Function _func;

		private RhinoFunction(Function func) {
			if (func == null)
				throw new IllegalArgumentException("null");
			_func = func;
		}

		//-- Function --//
		public Class[] getParameterTypes() {
			return new Class[0];
		}

		public Class getReturnType() {
			return Object.class;
		}

		public Object invoke(Object obj, Object[] args) throws Exception {
			final Context ctx = enterContext();
			try {
				final Scriptable scope = getGlobalScope();
				return _func.call(ctx, scope, scope, args);
			} finally {
				exitContext();
			}
		}

		public java.lang.reflect.Method toMethod() {
			return null;
		}
	}
}
