// double a number or concatenate a string with itself
function retTwice(val, key, obj) {
    print(typeof this, this, typeof val, val, typeof key, key, typeof obj, obj);
    return val + val;
}

function test(this_value, args) {
    var t;

    try {
        t = Array.prototype.map.apply(this_value, args);
        print(typeof t, t);
    } catch (e) {
        print(e.name);
    }
}

/*===
basic
object 
object [object global] number 1 number 0 object 1
object 2
object [object global] number 1 number 0 object 1,2
object [object global] number 2 number 1 object 1,2
object 2,4
object [object global] number 1 number 0 object 1,2,3,4,5
object [object global] number 2 number 1 object 1,2,3,4,5
object [object global] number 3 number 2 object 1,2,3,4,5
object [object global] number 4 number 3 object 1,2,3,4,5
object [object global] number 5 number 4 object 1,2,3,4,5
object 2,4,6,8,10
object [object global] number 1 number 0 object 1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3
object [object global] number 2 number 50 object 1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3
object [object global] number 3 number 100 object 1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3
object 2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6
object [object global] string foo number 0 object [object Object]
object [object global] string bar number 5 object [object Object]
object [object global] string quux number 20 object [object Object]
object foofoo,,,,,barbar,,,,,,,,,,,,,,,quuxquux
callback 1
CallbackError
nonstrict object
nonstrict object
nonstrict object
object 101,102,103
nonstrict object
nonstrict object
nonstrict object
object 101,102,103
nonstrict object
nonstrict object
nonstrict object
object 101,102,103
strict undefined undefined
strict undefined undefined
strict undefined undefined
object 101,102,103
strict object null
strict object null
strict object null
object 101,102,103
strict string foo
strict string foo
strict string foo
object 101,102,103
callback 1 0 1,2,3,4,5,6,7,8,9,10
callback 2 1 1,2,3,4,5,6,7,8,9,10
callback 3 2 1,2,3,4,5,6,7,8,9,10
callback 4 3 1,2,3,4,5,6,7,8,9,10
callback 5 4 1,2,3,4,5,6,7,8,9,10
callback 6 5 1,2,3,4,5,6,7,8,9,10
callback 7 6 1,2,3,4,5,6,7,8,9,10
callback 8 7 1,2,3,4,5,6,7,8,9,10
callback 9 8 1,2,3,4,5,6,7,8,9,10
callback 10 9 1,2,3,4,5,6,7,8,9,10
object ,,true,false,123,foo,1,2,[object Object],,
===*/

print('basic');

function basicTest() {
    var obj;

    // simple cases

    test([], [ retTwice ]);
    test([1], [ retTwice ]);
    test([1,2], [ retTwice ]);

    // dense

    test([1,2,3,4,5], [ retTwice ]);

    // sparse

    obj = [1];
    obj[100] = 3;
    obj[50] = 2;
    test(obj, [ retTwice ]);

    // non-array
    // (Note that trailing non-existent elements do not affect result length)

    obj = { '0': 'foo', '5': 'bar', '20': 'quux', '100': 'baz', length: 35 };
    test(obj, [ retTwice ]);

    // error in callback propagates outwards

    test([1,2,3], [ function(val, key, obj) {
        var e;
        print('callback', val);
        e = new Error('callback error');
        e.name = 'CallbackError';
        throw e;
    }]);

    // this binding, non-strict callbacks gets a coerced binding

    test([1,2,3], [ function(val, key, obj) {
        print('nonstrict', typeof this);
        return val + 100;
    }]);

    test([1,2,3], [ function(val, key, obj) {
        print('nonstrict', typeof this);
        return val + 100;
    }, null]);

    test([1,2,3], [ function(val, key, obj) {
        print('nonstrict', typeof this);
        return val + 100;
    }, 'foo']);

    test([1,2,3], [ function(val, key, obj) {
        'use strict';
        print('strict', typeof this, this);
        return val + 100;
    }]);

    test([1,2,3], [ function(val, key, obj) {
        'use strict';
        print('strict', typeof this, this);
        return val + 100;
    }, null]);  // Note: typeof null -> 'object'

    test([1,2,3], [ function(val, key, obj) {
        'use strict';
        print('strict', typeof this, this);
        return val + 100;
    }, 'foo']);

    // return value is used as is; note that undefined and null both print
    // as empty string when array toString() is called

    var testvalues = [ undefined, null, true, false, 123, 'foo', [1,2], { foo: 1, bar: 2 } ];
    test([1,2,3,4,5,6,7,8,9,10], [ function (val, key, obj) {
        print('callback', val, key, obj);
        return testvalues[key];
    } ]);
}

try {
    basicTest();
} catch (e) {
    print(e);
}

/*===
mutation
foo 0 foo,bar,quux
bar 1 foo,bar,quux,baz
quux 2 foo,bar,quux,baz
object mapped-foo,mapped-bar,mapped-quux
foo 0 foo,bar,quux
quux 2 foo,,quux
object mapped-foo,,mapped-quux
foo 0 [object Object]
bar 1 [object Object]
quux 2 [object Object]
object mapped-foo,mapped-bar,mapped-quux
foo 0 [object Object]
quux 2 [object Object]
object mapped-foo,,mapped-quux
===*/

print('mutation');

function mutationTest() {
    var obj;

    // added element not recognized

    obj = [ 'foo', 'bar', 'quux' ];
    test(obj, [ function (val, key, obj) {
        print(val, key, obj);
        obj[3] = 'baz';
        return 'mapped-' + val;
    }]);

    // deleted element not processed

    obj = [ 'foo', 'bar', 'quux' ];
    test(obj, [ function (val, key, obj) {
        print(val, key, obj);
        delete obj[1];
        return 'mapped-' + val;
    }]);

    // same for non-array

    obj = { '0': 'foo', '1': 'bar', '2': 'quux', '3': 'baz', length: 3 };
    test(obj, [ function (val, key, obj) {
        print(val, key, obj);
        obj[4] = 'quuux';
        obj.length = 10;
        return 'mapped-' + val;
    }]);

    obj = { '0': 'foo', '1': 'bar', '2': 'quux', '3': 'baz', length: 3 };
    test(obj, [ function (val, key, obj) {
        print(val, key, obj);
        delete obj[3]; delete obj[1];
        obj.length = 0;
        return 'mapped-' + val;
    }]);
}

try {
    mutationTest();
} catch (e) {
    print(e);
}

/*===
coercion
TypeError
TypeError
object 
object 
object 
object [object global] string f number 0 object foo
object [object global] string o number 1 object foo
object [object global] string o number 2 object foo
object ff,oo,oo
object [object global] number 1 number 0 object 1,2,3
object [object global] number 2 number 1 object 1,2,3
object [object global] number 3 number 2 object 1,2,3
object 2,4,6
object 
object [object global] string foo number 0 object [object Object]
object [object global] string bar number 1 object [object Object]
object [object global] string quux number 2 object [object Object]
object foofoo,barbar,quuxquux
object [object global] string foo number 0 object [object Object]
object [object global] string bar number 1 object [object Object]
object [object global] string quux number 2 object [object Object]
object foofoo,barbar,quuxquux
object [object global] string foo number 0 object [object Object]
object [object global] string bar number 1 object [object Object]
object [object global] string quux number 2 object [object Object]
object [object global] string baz number 3 object [object Object]
object foofoo,barbar,quuxquux,bazbaz
length valueOf
object [object global] string foo number 0 object [object Object]
object [object global] string bar number 1 object [object Object]
object [object global] string quux number 2 object [object Object]
object foofoo,barbar,quuxquux
length valueOf
TypeError
===*/

print('coercion');

function coercionTest() {
    var obj;

    // this

    test(undefined, [ retTwice ]);
    test(null, [ retTwice ]);
    test(true, [ retTwice ]);
    test(false, [ retTwice ]);
    test(123, [ retTwice ]);
    test('foo', [ retTwice ]);
    test([1,2,3], [ retTwice ]);
    test({ foo: 1, bar: 2 }, [ retTwice ]);

    // length

    obj = { '0': 'foo', '1': 'bar', '2': 'quux', '3': 'baz', '4': 'quux', length: '3.9' };
    test(obj, [ retTwice ]);
    obj = { '0': 'foo', '1': 'bar', '2': 'quux', '3': 'baz', '4': 'quux', length: 256*256*256*256 + 3.9 };  // coerces to 3
    test(obj, [ retTwice ]);
    obj = { '0': 'foo', '1': 'bar', '2': 'quux', '3': 'baz', '4': 'quux', length: -256*256*256*256 + 3.9 };  // coerces to 4
    test(obj, [ retTwice ]);

    obj = { '0': 'foo', '1': 'bar', '2': 'quux', 'length': {
        toString: function() {
            print('length toString');
            return 4;
        },
        valueOf: function() {
            print('length valueOf');
            return 3;
        }
    }};
    test(obj, [ retTwice ]);

    // callable check is done after length coercion

    obj = { '0': 'foo', '1': 'bar', '2': 'quux', 'length': {
        toString: function() {
            print('length toString');
            return 4;
        },
        valueOf: function() {
            print('length valueOf');
            return 3;
        }
    }};
    test(obj, [ null ]);
}

try {
    coercionTest();
} catch (e) {
    print(e);
}

