//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

function echo(msg) {
    WScript.Echo(msg);
}

function guarded_call(func) {
    try {
        func();
    } catch(e) {
        echo("Exception: " + e.name + " : " + e.message);
    }
}

function dump_args() {
    var args = "";
    for (var i = 0; i < arguments.length; i++) {
        if (i > 0) {
            args += ", ";
        }
        args += arguments[i];
    }
    echo("Called with this: " + typeof this + "[" + this + "], args: [" + args + "]");
}

// 1. If IsCallable(func) is false, throw TypeError
var noncallable = {
    apply: Function.prototype.apply
};
echo("--- f is not callable ---");
guarded_call(function() {
    noncallable.apply();
});
guarded_call(function() {
    noncallable.apply({}, [1, 2, 3]);
});

// 2. If argArray is null or undefined, call func with an empty list of arguments
var o = {};
echo("\n--- f.apply(x) ---");
guarded_call(function() {
    dump_args.apply(o);
});
echo("\n--- f.apply(x, null), f.apply(x, undefined) ---");
guarded_call(function() {
    dump_args.apply(o, null);
});
guarded_call(function() {
    dump_args.apply(o, undefined);
});

// 3. Type(argArray) is invalid
echo("\n--- f.apply(x, 123), f.apply(x, 'string'), f.apply(x, true) ---");
guarded_call(function() {
    dump_args.apply(o, 123);
});
guarded_call(function() {
    dump_args.apply(o, 'string');
});
guarded_call(function() {
    dump_args.apply(o, true);
});

// 5, 7 argArray.length is invalid
echo("\n--- f.apply(x, obj), obj.length is null/undefined/NaN/string/OutOfRange ---");
guarded_call(function() {
    dump_args.apply(o, {length: null});
});
guarded_call(function() {
    dump_args.apply(o, {length: undefined});
});
guarded_call(function() {
    dump_args.apply(o, {length: NaN});
});
guarded_call(function() {
    dump_args.apply(o, {length: 'string'});
});
guarded_call(function() {
    dump_args.apply(o, {length: 4294967295 + 1}); //UINT32_MAX + 1
});
guarded_call(function() {
    dump_args.apply(o, {length: -1});
});

echo("\n--- f.apply(x, arr), arr.length is huge ---");
var huge_array_length = [];
huge_array_length.length = 2147483647; //INT32_MAX
guarded_call(function() {
    dump_args.apply(o, huge_array_length);
});
echo("\n--- f.apply(x, obj), obj.length is huge ---");
guarded_call(function() {
    dump_args.apply(o, {length: 4294967295}); //UINT32_MAX
});

// Normal usage -- argArray tests
echo("\n--- f.apply(x, arr) ---");
dump_args.apply(o, []);
dump_args.apply(o, [1]);
dump_args.apply(o, [2, 3, NaN, null, undefined, false, "hello", o]);

echo("\n--- f.apply(x, arr) arr.length increased ---");
var arr = [1, 2];
arr.length = 5;
dump_args.apply(o, arr);

echo("\n--- f.apply(x, arguments) ---");
function apply_arguments() {
    dump_args.apply(o, arguments);
}
apply_arguments();
apply_arguments(1);
apply_arguments(2, 3, NaN, null, undefined, false, "hello", o);

echo("\n--- f.apply(x, obj) ---");
guarded_call(function() {
    dump_args.apply(o, {
        length: 0
    });
});
guarded_call(function() {
    dump_args.apply(o, {
        length: 1,
        "0": 1
    });
});
guarded_call(function() {
    dump_args.apply(o, {
        length: 8,
        "0": 2,
        "1": 3,
        "2": NaN,
        "3": null,
        "4": undefined,
        "5": false,
        "6": "hello",
        "7": o
    });
});

// Normal usage -- thisArg tests
function f1() {
    this.x1 = "hello";
}

echo("\n--- f.apply(), f.apply(null), f.apply(undefined), global x1 should be changed ---");
f1.apply();
echo("global x1 : " + x1);

x1 = 0;
f1.apply(null);
echo("global x1 : " + x1);

x1 = 0;
f1.apply(undefined);
echo("global x1 : " + x1);

echo("\n--- f.apply(x), global x1 should NOT be changed ---");
var o = {};
x1 = 0;
f1.apply(o);
echo("global x1 : " + x1);
echo("o.x1 : " + o.x1);

// apply on non-objects -- test thisArg
function apply_non_object(func, doEcho) {

    var echo_if = function(msg) {
        if (doEcho) {
            echo(msg);
        }
    };

    guarded_call(function() {
        echo_if(func.apply());
    });
    guarded_call(function() {
        echo_if(func.apply(null));
    });
    guarded_call(function() {
        echo_if(func.apply(undefined));
    });
    guarded_call(function() {
        echo_if(func.apply(123));
    });
    guarded_call(function() {
        echo_if(func.apply(true));
    });
    guarded_call(function() {
        echo_if(func.apply("string"));
    });
}

echo("\n--- f.apply(v), v is missing/null/undefined/123/true/'string' ---");
apply_non_object(dump_args);

//
// ES5: String.prototype.charCodeAt calls CheckObjectCoercible(thisArg). It should throw
// when thisArg is missing/null/undefined.
//
echo("\n--- f.apply(v), v is missing/null/undefined/123/true/'string', f: string.charCodeAt ---");
apply_non_object(String.prototype.charCodeAt, true);

echo("\n--- f.apply(v), v is missing/null/undefined/123/true/'string', f: string.charAt ---");
apply_non_object(String.prototype.charAt, true);

//
// Similarly, test thisArg behavior in Function.prototype.call
//
// call on non-objects -- test thisArg
function call_non_object(func, doEcho) {

    var echo_if = function(msg) {
        if (doEcho) {
            echo(msg);
        }
    };

    guarded_call(function() {
        echo_if(func.call());
    });
    guarded_call(function() {
        echo_if(func.call(null));
    });
    guarded_call(function() {
        echo_if(func.call(undefined));
    });
    guarded_call(function() {
        echo_if(func.call(123));
    });
    guarded_call(function() {
        echo_if(func.call(true));
    });
    guarded_call(function() {
        echo_if(func.call("string"));
    });
}

echo("\n--- f.call(v), v is missing/null/undefined/123/true/'string' ---");
call_non_object(dump_args);

echo("\n--- f.call(v), v is missing/null/undefined/123/true/'string', f: string.charCodeAt ---");
call_non_object(String.prototype.charCodeAt, true);

echo("\n--- f.call(v), v is missing/null/undefined/123/true/'string', f: string.charAt ---");
call_non_object(String.prototype.charAt, true);
