// FIXME: shared test utils

function formatValue(v) {
    if (typeof v === 'function') {
        return 'function';
    }
    return typeof(v) + ':' + String(v);
}

function printDescriptor(obj, key) {
    var pd = Object.getOwnPropertyDescriptor(obj, key);
    if (!pd) {
        print('key=' + key, 'nonexistent');
        return;
    }

    print('key=' + key,
          'value=' + formatValue(pd.value),
          'writable=' + formatValue(pd.writable),
          'enumerable=' + formatValue(pd.enumerable),
          'configurable=' + formatValue(pd.configurable),
          'get=' + formatValue(pd.get),
          'set=' + formatValue(pd.set));
}

function dumpValue(x) {
    var i, n, clipped;
    var tmp=[];

    n = x.length;
    if (n > 1000) {
        n = 1000; 
        clipped=true;
    }
    for (i = 0; i < n; i++) {
        if (x.hasOwnProperty(i)) {
            tmp.push(String(x[i]));
        } else {
            tmp.push('nonexistent');
        }
    }
    if (clipped) {
        tmp.push('...');
    }
    return tmp.join(',');
}

function test(this_value, args, suppress_print) {
    var t;
    if (args === undefined) {
        args = [];
    }

    try {
        t = Array.prototype.reverse.apply(this_value, args);
        if (suppress_print) {
            print('success');
        } else {
            if (t.length === undefined) {
                print(typeof t, 'nolength');
            } else {
                print(typeof t, t.length, dumpValue(t));
            }
        }
    } catch (e) {
        print(e.name);
    }
}

/*===
basic
object 0 
object 1 1
object 2 2,1
object 3 3,2,1
object 4 4,3,2,1
object 5 5,4,3,2,1
object 10 10,9,8,7,6,5,4,3,2,1
object 11 11,10,9,8,7,6,5,4,3,2,1
object 101 3,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,2,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,1
object 100 3,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,2,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,nonexistent,1
10000001 1 2
success
10000001 2 1
object 4 4,3,2,1
object 4 4,3,2,1
5 6
object 8 nonexistent,4,nonexistent,3,nonexistent,nonexistent,2,1
===*/

print('basic');

function basicTest() {
    var obj;

    // basic dense array tests (odd and even lengths)

    test([]);
    test([1]);
    test([1,2]);
    test([1,2,3]);
    test([1,2,3,4]);
    test([1,2,3,4,5]);
    test([1,2,3,4,5,6,7,8,9,10]);
    test([1,2,3,4,5,6,7,8,9,10,11]);

    // sparse

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

    obj = [1];
    obj[99] = 3;
    obj[50] = 2;
    test(obj);

    // Extremely sparse array.  The standard algorithm spends a lot of
    // time on this.

    obj = [];
    obj[0] = 1;
    obj[10000000] = 2;
    print(obj.length, obj[0], obj[10000000]);
    test(obj, undefined, true);  // suppress printing
    print(obj.length, obj[0], obj[10000000]);

    // non-array

    obj = { '0': 1, '1': 2, '2': 3, '3': 4, length: 4 };
    test(obj);

    obj = { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5, '5': 6, length: 4 };  // '4' and '5' are ignored
    test(obj);
    print(obj['4'], obj['5']);

    // combinations of upper/lower exists
    // exists: true true false false true false true false
    // reverse pairs: (true,false) (true,true) (false,false) (false,true)

    obj=[];
    obj[0] = 1;
    obj[1] = 2;
    obj[4] = 3;
    obj[6] = 4;
    obj.length = 8;
    test(obj);
}

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

/*===
attributes
true true false false false true false true
key=0 value=undefined:undefined writable=undefined:undefined enumerable=boolean:false configurable=boolean:true get=function set=function
key=1 value=undefined:undefined writable=undefined:undefined enumerable=boolean:false configurable=boolean:true get=function set=function
key=2 nonexistent
key=3 nonexistent
key=4 nonexistent
key=5 value=undefined:undefined writable=undefined:undefined enumerable=boolean:true configurable=boolean:true get=function set=function
key=6 nonexistent
key=7 value=undefined:undefined writable=undefined:undefined enumerable=boolean:true configurable=boolean:true get=function set=function
get 0
get 7
set 0 val7
set 7 val0
get 1
get 5
true false true false false false true true
key=0 value=undefined:undefined writable=undefined:undefined enumerable=boolean:false configurable=boolean:true get=function set=function
key=1 nonexistent
key=2 value=string:val5 writable=boolean:true enumerable=boolean:true configurable=boolean:true get=undefined:undefined set=undefined:undefined
key=3 nonexistent
key=4 nonexistent
key=5 nonexistent
key=6 value=string:val1 writable=boolean:true enumerable=boolean:true configurable=boolean:true get=undefined:undefined set=undefined:undefined
key=7 value=undefined:undefined writable=undefined:undefined enumerable=boolean:true configurable=boolean:true get=function set=function
key=0 value=string:foo writable=boolean:true enumerable=boolean:false configurable=boolean:true get=undefined:undefined set=undefined:undefined
key=1 nonexistent
key=0 value=string:foo writable=boolean:true enumerable=boolean:false configurable=boolean:true get=undefined:undefined set=undefined:undefined
key=1 nonexistent
===*/

print('attributes');

function attributesTest() {
    var t;

    // Since [[Get]] and [[Put]] are used, getter/setter calls are made normally.
    // When swapping two elements:
    //   - If both exists, call order is: lower get, upper get, lower set, upper set
    //   - If only lower exists: lower get; no other side effects (lower is deleted, upper is [[Put]]'d as a plain property)
    //   - If only upper exists: upper get; no other side effects (upper is deleted, lower is [[Put]]'d as a plain property)
    //   - If neither exists, no side effects
    //
    // Here:
    //   - 0 and 7: both exist
    //   - 1 and 6: 1 exists, 6 does not
    //   - 2 and 5: 5 exists, 2 does not
    //   - 3 and 4: neither exists

    t = [];
    t.my0 = 'val0';
    t.my1 = 'val1';
    t.my5 = 'val5';
    t.my7 = 'val7';
    Object.defineProperties(t, {
        '0': {
            enumerable: false,
            configurable: true,
            get: function() { print('get 0'); return this.my0; },
            set: function(v) { print('set 0', v); this.my0 = v; }
        },
        '1': {
            enumerable: false,
            configurable: true,
            get: function() { print('get 1'); return this.my1; },
            set: function(v) { print('set 1', v); this.my1 = v; }
        },
        // '2' skipped
        // '3' skipped
        // '4' skipped
        '5': {
            enumerable: true,
            configurable: true,
            get: function() { print('get 5'); return this.my5; },
            set: function(v) { print('set 5', v); this.my5 = v; }
        },
        // '6' skipped
        '7': {
            enumerable: true,
            configurable: true,
            get: function() { print('get 7'); return this.my7; },
            set: function(v) { print('set 7', v); this.my7 = v; }
        },
    });
    print(t.hasOwnProperty(0), t.hasOwnProperty(1),
          t.hasOwnProperty(2), t.hasOwnProperty(3),
          t.hasOwnProperty(4), t.hasOwnProperty(5),
          t.hasOwnProperty(6), t.hasOwnProperty(7));

    for (i = 0; i < 8; i++) {
        printDescriptor(t, String(i));
    }

    t.reverse();

    print(t.hasOwnProperty(0), t.hasOwnProperty(1),
          t.hasOwnProperty(2), t.hasOwnProperty(3),
          t.hasOwnProperty(4), t.hasOwnProperty(5),
          t.hasOwnProperty(6), t.hasOwnProperty(7));

    for (i = 0; i < 8; i++) {
        printDescriptor(t, String(i));
    }

    // Here, '0' is not enumerable, but when it gets swapped to position 1,
    // the specification algorithm calls [[Put]] with key "1" (and [[Delete]]
    // with key "0").  The property "1" will have default properties, and will
    // be enumerable.

    t = [];
    Object.defineProperties(t, {
        '0': { value: 'foo', writable: true, enumerable: false, configurable: true },
        'length': { value: 2 }
    });
    printDescriptor(t, '0');
    printDescriptor(t, '1');
    [].reverse();
    printDescriptor(t, '0');
    printDescriptor(t, '1');
}

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

/*===
protected
1 2
TypeError
2 2
1 2
TypeError
1 2
1 1
TypeError
1 1
1 undefined
TypeError
1 undefined
undefined 1
TypeError
1 1
1 undefined
TypeError
undefined undefined
undefined 1
TypeError
undefined 1
===*/

/* All [[Get]], [[Put]], [[Delete]] operations have Throw=true */

print('protected');

function protectedTest() {
    var obj;

    // non-writable property; '1' is not writable but '0' must still get updated
    // ([[Put]] "0" happens before [[Put]] "1")

    obj = {};
    Object.defineProperties(obj, {
        '0': { value: 1, writable: true, enumerable: true, configurable: true },
        '1': { value: 2, writable: false, enumerable: true, configurable: true }
    });
    obj.length = 2;
    print(obj['0'], obj['1']);
    test(obj);
    print(obj['0'], obj['1']);

    // non-writable property; '0' is not writable, '1' must not get updated
    // ([[Put]] "0" happens before [[Put]] "1")

    obj = {};
    Object.defineProperties(obj, {
        '0': { value: 1, writable: false, enumerable: true, configurable: true },
        '1': { value: 2, writable: true, enumerable: true, configurable: true }
    });
    obj.length = 2;
    print(obj['0'], obj['1']);
    test(obj);
    print(obj['0'], obj['1']);

    // non-writable property; even though values match with SameValue, [[CanPut]]
    // should TypeError

    obj = {};
    Object.defineProperties(obj, {
        '0': { value: 1, writable: false, enumerable: true, configurable: true },
        '1': { value: 1, writable: false, enumerable: true, configurable: true }
    });
    obj.length = 2;
    print(obj['0'], obj['1']);
    test(obj);
    print(obj['0'], obj['1']);

    // non-configurable property, cannot be deleted; [[Delete]] for '0' happens
    // before [[Put]] for '1', so '1' should not be set

    obj = {};
    Object.defineProperties(obj, {
        '0': { value: 1, writable: true, enumerable: true, configurable: false }
    });
    obj.length = 2;
    print(obj['0'], obj['1']);
    test(obj);
    print(obj['0'], obj['1']);

    // non-configurable property, cannot be deleted; [[Put]] for '0' happens
    // before [[Delete]] for '1', so '1' *SHOULD* be set

    obj = {};
    Object.defineProperties(obj, {
        '1': { value: 1, writable: true, enumerable: true, configurable: false }
    });
    obj.length = 2;
    print(obj['0'], obj['1']);
    test(obj);
    print(obj['0'], obj['1']);

    // non-extensible object, new property cannot be added; [[Delete]] '0' happens
    // before [[Put]] '1', so [[Delete]] should succeed

    obj = {};
    Object.defineProperties(obj, {
        '0': { value: 1, writable: true, enumerable: true, configurable: true }
    });
    obj.length = 2;
    Object.preventExtensions(obj);
    print(obj['0'], obj['1']);
    test(obj);
    print(obj['0'], obj['1']);

    // non-extensible object, new property cannot be added; [[Put]] '0' happens
    // before [[Delete]] '1'; since [[Put]] fails, also won't [[Delete]]

    obj = {};
    Object.defineProperties(obj, {
        '1': { value: 1, writable: true, enumerable: true, configurable: true }
    });
    obj.length = 2;
    Object.preventExtensions(obj);
    print(obj['0'], obj['1']);
    test(obj);
    print(obj['0'], obj['1']);
}

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

/*===
coercion
TypeError
TypeError
object nolength
object nolength
object nolength
object 0 
TypeError
object 3 3,2,1
object nolength
object 3 quux,bar,foo
success
3.9 quux bar foo baz
success
4294967299.9 quux bar foo baz
success
-4294967292.1 baz quux bar foo
success
4 quux bar foo baz
===*/

print('coercion');

function coercionTest() {
    var obj;

    // this coercion

    test(undefined);
    test(null);
    test(true);
    test(false);
    test(123);
    test('');     // length = 0 -> no action
    test('foo');  // has numeric properties but they're not writable/configurable -> TypeError
    test([1,2,3]);
    test({ foo: 1, bar: 2 });
    test({ '0': 'foo', '1': 'bar', '2': 'quux', '3': 'baz', length: 3 });

    // length coercion

    obj = { '0': 'foo', '1': 'bar', '2': 'quux', '3': 'baz', length: '3.9' };
    test(obj, undefined, true);
    print(obj.length, obj['0'], obj['1'], obj['2'], obj['3']);

    obj = { '0': 'foo', '1': 'bar', '2': 'quux', '3': 'baz', length: 256*256*256*256 + 3.9 };
    test(obj, undefined, true);
    print(obj.length, obj['0'], obj['1'], obj['2'], obj['3']);

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

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

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

