/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.javascript.tests;

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;

import junit.framework.TestCase;

/**
 * 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();
            }
        };
        doTest("function", context -> {
            final Scriptable scope = context.initStandardObjects();
            scope.put("myObj", scope, f);
            return context.evaluateString(scope, "typeof myObj", "test script", 1, null);
        });
    }

    private void testCustomizeTypeOf(final String expected, final Scriptable obj)
    {
        doTest(expected, context -> {
            final Scriptable scope = context.initStandardObjects();
            scope.put("myObj", scope, obj);
            return context.evaluateString(scope, "typeof myObj", "test script", 1, null);
        });
    }

    /**
     * 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)
    {
        doTest(expected, context -> {
            final Scriptable scope = context.initStandardObjects();
            return context.evaluateString(scope, script, "test script", 1, null);
        });
    }

    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(context -> {
            context.setOptimizationLevel(optimizationLevel);
            return Context.toString(action.run(context));
        });
        assertEquals(expected, o);
    }
}