
// FIXME: util
function printDesc(obj, key) {
    var pd = Object.getOwnPropertyDescriptor(obj, key);
    if (!pd) {
        print(key, 'nonexistent');
        return;
    }
    print(key,
          'value=' + pd.value,
          'writable=' + pd.writable,
          'enumerable=' + pd.enumerable,
          'configurable=' + pd.configurable,
          'get=' + typeof pd.get,
          'set=' + typeof pd.set);
}

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

    if (v === undefined) {
        return 'undefined';
    } else if (v === null) {
        return 'null';
    }

    n = Math.floor(v.length);
    for (i = 0; i < n; i++) {
        if (v.hasOwnProperty(i)) {
            tmp.push(v[i]);
        } else {
            tmp.push('nonexistent');
        }
    }
    return typeof v + ' ' + v.length + ' ' + tmp.join(',');
}

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

    try {
        if (!no_pre_post) {
            print('pre', dumpValue(this_value));
        }
        t = Array.prototype.unshift.apply(this_value, args);
        print(typeof t, t);
        if (!no_pre_post) {
            print('post', dumpValue(this_value));
        }
    } catch (e) {
        print(e.name);
    }
}

/*===
basic
pre object 0 
number 0
post object 0 
pre object 1 1
number 1
post object 1 1
pre object 2 1,2
number 2
post object 2 1,2
pre object 3 1,2,3
number 3
post object 3 1,2,3
pre object 0 
number 1
post object 1 foo
pre object 1 1
number 2
post object 2 foo,1
pre object 2 1,2
number 3
post object 3 foo,1,2
pre object 3 1,2,3
number 4
post object 4 foo,1,2,3
pre object 0 
number 2
post object 2 foo,bar
pre object 1 1
number 3
post object 3 foo,bar,1
pre object 2 1,2
number 4
post object 4 foo,bar,1,2
pre object 3 1,2,3
number 5
post object 5 foo,bar,1,2,3
pre object 0 
number 3
post object 3 foo,bar,quux
pre object 1 1
number 4
post object 4 foo,bar,quux,1
pre object 2 1,2
number 5
post object 5 foo,bar,quux,1,2
pre object 3 1,2,3
number 6
post object 6 foo,bar,quux,1,2,3
pre object 101 1,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,3
number 103
post object 103 foo,bar,1,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,3
pre object 101 1,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,3
number 102
post object 102 foo,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,baz,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,quux,1,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,3
pre object 3 1,2,3
number 5
post object 5 foo,bar,1,2,3
pre object 3 1,2,3
number 5
post object 5 foo,bar,1,2,3
pre object 10 nonexistent,nonexistent,3,nonexistent,nonexistent,6,nonexistent,nonexistent,nonexistent,nonexistent
number 12
post object 12 foo,bar,nonexistent,nonexistent,3,nonexistent,nonexistent,6,nonexistent,nonexistent,nonexistent,nonexistent
false undefined
pre object undefined 
number 0
post object 0 
true 0
===*/

print('basic');

function basicTest() {
    var obj, obj2;

    // basic dense array tests

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

    test([], ['foo']);
    test([1], ['foo']);
    test([1,2], ['foo']);
    test([1,2,3], ['foo']);

    test([], ['foo','bar']);
    test([1], ['foo','bar']);
    test([1,2], ['foo','bar']);
    test([1,2,3], ['foo','bar']);

    test([], ['foo','bar','quux']);
    test([1], ['foo','bar','quux']);
    test([1,2], ['foo','bar','quux']);
    test([1,2,3], ['foo','bar','quux']);

    // sparse array tests

    obj=[1]; obj[100]=3; obj[50]=2;
    test(obj, ['foo', 'bar']);

    obj=[1]; obj[100]=3; obj[50]=2;
    obj2=['foo']; obj2[100]='quux'; obj2[50]='baz';
    test(obj, [obj2]);  // obj2 is inserted "as is"

    // non-array tests

    obj = { '0': '1', '1': '2', '2': '3', length: 3 };  // basic
    test(obj, ['foo','bar']);

    obj = { '0': '1', '1': '2', '2': '3', '3': 'out-4', '4': 'out-5', '5': 'out-6', length: 3 };  // out of length range elements
    test(obj, ['foo','bar']);

    obj = { '2': '3', '5': '6', length: 10 };  // sparse this
    test(obj, ['foo','bar']);

    // if a non-array has no 'length', it will be treated like zero length
    // (ToUint32(undefined) = 0); in particular, step 10 writes a length field
    // to the object

    obj = {};
    print(obj.hasOwnProperty('length'), obj.length);
    test(obj);
    print(obj.hasOwnProperty('length'), obj.length);
}

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

/*===
attributes
2 getter
3 getter
pre object 4 1,2,3,4
3 getter
2 getter
3 setter 2
2 setter 1
number 6
2 getter
3 getter
post object 6 foo,bar,1,2,3,4
2 getter
3 getter
6 foo bar 1 2 3 4
length value=6 writable=true enumerable=true configurable=true get=undefined set=undefined
0 value=foo writable=true enumerable=false configurable=false get=undefined set=undefined
1 value=bar writable=true enumerable=true configurable=true get=undefined set=undefined
2 value=undefined writable=undefined enumerable=false configurable=false get=function set=function
3 value=undefined writable=undefined enumerable=true configurable=true get=function set=function
4 value=3 writable=true enumerable=true configurable=true get=undefined set=undefined
5 value=4 writable=true enumerable=true configurable=true get=undefined set=undefined
pre object 6 1,2,nonexistent,4,nonexistent,6
number 9
post object 9 foo,bar,quux,1,2,nonexistent,4,nonexistent,6
9 foo bar quux 1 2 undefined 4 undefined 6
length value=9 writable=true enumerable=true configurable=true get=undefined set=undefined
0 value=foo writable=true enumerable=false configurable=true get=undefined set=undefined
1 value=bar writable=true enumerable=false configurable=true get=undefined set=undefined
2 value=quux writable=true enumerable=true configurable=true get=undefined set=undefined
3 value=1 writable=true enumerable=false configurable=true get=undefined set=undefined
4 value=2 writable=true enumerable=true configurable=true get=undefined set=undefined
5 nonexistent
6 value=4 writable=true enumerable=true configurable=true get=undefined set=undefined
7 nonexistent
8 value=6 writable=true enumerable=true configurable=true get=undefined set=undefined
===*/

print('attributes');

function attributesTest() {
    var obj;

    // Step 6 makes room for unshifted value by copying values upwards,
    // but values in the empty space are not [[Delete]]'d, so the slots
    // will retain their attributes (and e.g. accessors remain accessors).
    // The elements can also be non-configurable because they're not deleted.

    obj = {}
    obj.my2 = 3;
    obj.my3 = 4;
    Object.defineProperties(obj, {
        '0': { value: 1, writable: true, enumerable: false, configurable: false },
        '1': { value: 2, writable: true, enumerable: true, configurable: true },
        '2': { get: function() { print('2 getter'); return this.my2; },
               set: function(v) { print('2 setter', v); this.my2 = v; },
               enumerable: false, configurable: false },
        '3': { get: function() { print('3 getter'); return this.my3; },
               set: function(v) { print('3 setter', v); this.my3 = v; },
               enumerable: true, configurable: true }
    });
    obj.length = 4;
    test(obj, ['foo','bar']);
    print(obj.length, obj[0], obj[1], obj[2], obj[3], obj[4], obj[5]);
    printDesc(obj, 'length');
    printDesc(obj, '0');
    printDesc(obj, '1');
    printDesc(obj, '2');
    printDesc(obj, '3');
    printDesc(obj, '4');
    printDesc(obj, '5');

    // Step 6 copies values upwards by the required amount, e.g. if 3 values
    // are unshifted, len-1 is copied to len+2, len-2 is copied to len+1,
    // and so on, '0' is copied to '3'.  If the source exists, a [[Get]]+[[Put]]
    // sequence is used; otherwise a [[Delete]] is used.  When both the source
    // and destination exist, the destination will retain its attributes.
    //
    // As above, the elements in the "room" for unshift are not [[Delete]]'d,
    // so they retain their attributes.

    obj = {}
    Object.defineProperties(obj, {
        // 0->3: both exist
        // 1->4: source exists, target does not
        // 2->5: source does not exist, target does
        // 3->6: source exists, target does not
        // 4->7: neither exist
        // 5->8: source exists, target does not

        '0': { value: 1, writable: true, enumerable: false, configurable: true },
        '1': { value: 2, writable: true, enumerable: false, configurable: true },
        // 2 does not exist
        '3': { value: 4, writable: true, enumerable: false, configurable: true },
        // 4 does not exist
        '5': { value: 6, writable: true, enumerable: false, configurable: true },
    });
    obj.length = 6;
    test(obj, ['foo','bar','quux']);
    print(obj.length, obj[0], obj[1], obj[2], obj[3], obj[4], obj[5], obj[6], obj[7], obj[8]);
    printDesc(obj, 'length');
    for (i = 0; i < 9; i++) {
        printDesc(obj, String(i));
    }
}

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

/*===
mutation
===*/

print('mutation');

function mutationTest() {
    // FIXME
}

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

/*===
protected
false undefined
pre object undefined 
TypeError
post object undefined 
false undefined
===*/

print('protected');

function protectedTest() {
    var obj;

    // non-extensible object without 'length'; a 'length' property
    // cannot be added

    obj = {};
    Object.preventExtensions(obj);
    print(obj.hasOwnProperty('length'), obj.length);
    test(obj);
    print(obj.hasOwnProperty('length'), obj.length);

    // FIXME: copy loop, side effects
    // FIXME: copy loop, protected

    // final length is not updated until step 10 (mostly matters for
    // non-arrays)
}

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

/*===
coercion
pre undefined
TypeError
pre null
TypeError
pre boolean undefined 
number 0
post boolean undefined 
pre boolean undefined 
number 0
post boolean undefined 
pre number undefined 
number 0
post number undefined 
pre string 3 f,o,o
TypeError
post string 3 f,o,o
pre object 2 1,2
number 2
post object 2 1,2
pre object undefined 
number 0
post object 0 
string 3.9 foo bar quux baz undefined undefined undefined
number 5
number 5 1 2 foo bar quux undefined undefined
number 4294967299.9 foo bar quux baz undefined undefined undefined
number 5
number 5 1 2 foo bar quux undefined undefined
number -4294967292.1 foo bar quux baz undefined undefined undefined
number 6
number 6 1 2 foo bar quux baz undefined
length toString
object 3 foo bar quux baz undefined undefined undefined
length valueOf
number 5
number 5 1 2 foo bar quux undefined undefined
===*/

print('coercion');

function coercionTest() {
    var obj;

    // this coercion

    test(undefined);
    test(null);
    test(true);
    test(false);
    test(123);
    test('foo');  // TypeError, because attempt to write non-writable length must fail
    test([1,2]);
    test({ foo: 1, bar: 2 });

    // length coercion

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

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

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

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

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

