package org.mozilla.javascript.tests;

import junit.framework.TestCase;

import org.mozilla.javascript.BaseFunction;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextAction;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;

/**
 * Takes care that it's possible to customize the result of the typeof operator.
 * See https://bugzilla.mozilla.org/show_bug.cgi?id=463996
 * Includes fix and test for https://bugzilla.mozilla.org/show_bug.cgi?id=453360
 * @author Marc Guillemot
 */
public class TypeOfTest extends TestCase
{
	public static class Foo extends ScriptableObject {
        private static final long serialVersionUID = -8771045033217033529L;
        private final String typeOfValue_;

        public Foo(final String _typeOfValue)
		{
        	typeOfValue_ = _typeOfValue;
		}
        
        @Override
        public String getTypeOf()
        {
        	return typeOfValue_;
        }

		@Override
		public String getClassName()
		{
			return "Foo";
		}
	}

	/**
	 * ECMA 11.4.3 says that typeof on host object is Implementation-dependent
	 */
	public void testCustomizeTypeOf() throws Exception
	{
		testCustomizeTypeOf("object", new Foo("object"));
		testCustomizeTypeOf("blabla", new Foo("blabla"));
	}

	/**
	 * ECMA 11.4.3 says that typeof on host object is Implementation-dependent
	 */
	public void test0() throws Exception
	{
        final Function f = new BaseFunction()
        {
        	@Override
        	public Object call(Context _cx, Scriptable _scope, Scriptable _thisObj,
        			Object[] _args)
        	{
        		return _args[0].getClass().getName();
        	}
        };
		final ContextAction action = new ContextAction()
		{
			public Object run(final Context context)
			{
				final Scriptable scope = context.initStandardObjects();
				scope.put("myObj", scope, f);
				return context.evaluateString(scope, "typeof myObj", "test script", 1, null);
			}
		};
		doTest("function", action);
	}

	private void testCustomizeTypeOf(final String expected, final Scriptable obj)
	{
		final ContextAction action = new ContextAction()
		{
			public Object run(final Context context)
			{
				final Scriptable scope = context.initStandardObjects();
				scope.put("myObj", scope, obj);
				return context.evaluateString(scope, "typeof myObj", "test script", 1, null);
			}
		};
		doTest(expected, action);
	}

	/**
	 * See https://bugzilla.mozilla.org/show_bug.cgi?id=453360
	 */
	public void testBug453360() throws Exception
	{
		doTest("object", "typeof new RegExp();");
		doTest("object", "typeof /foo/;");
	}

	private void doTest(String expected, final String script)
	{
		final ContextAction action = new ContextAction()
		{
			public Object run(final Context context)
			{
				final Scriptable scope = context.initStandardObjects();
				return context.evaluateString(scope, script, "test script", 1, null);
			}
		};
		doTest(expected, action);
	}

	private void doTest(final String expected, final ContextAction action)
	{
		doTest(-1, expected, action);
		doTest(0, expected, action);
		doTest(1, expected, action);
	}

	private void doTest(final int optimizationLevel, final String expected, final ContextAction action)
	{
		Object o = new ContextFactory().call(new ContextAction()
			{
				public Object run(final Context context)
				{
					context.setOptimizationLevel(optimizationLevel);
					return Context.toString(action.run(context));
				}
			});
		assertEquals(expected, o);
	}
}