var ITERATIONS = 10;
var INNER_ITERATIONS = 100;

let instance = wasmEvalText(`(module
    (func (export "add") (param i32) (param i32) (result i32)
     local.get 0
     local.get 1
     i32.add
    )

    (func (export "no_arg") (result i32)
     i32.const 42
     i32.const 58
     i32.add
    )

    (global $g (mut i32) (i32.const 0))

    (func (export "set_global_one") (param i32)
     local.get 0
     global.set $g
    )

    (func (export "set_global_two") (param i32) (param i32)
     local.get 0
     local.get 1
     i32.add
     global.set $g
    )

    (func (export "glob") (result i32)
     global.get $g
    )
)`).exports;

function run(name, func) {
    for (let i = ITERATIONS; i --> 0;) {
        func();
    }
}

function testCallKnown() {
    for (let i = 0; i < INNER_ITERATIONS; i++) {
        assertEq(instance.add(i, i + 1), 2*i + 1);
    }
}

function testCallKnownRectifying() {
    for (let i = 0; i < INNER_ITERATIONS; i++) {
        assertEq(instance.add(i + 1), i+1);
    }
}

function jsAdd(x, y) {
    return (x|0) + (y|0) | 0;
}

function testCallGeneric() {
    var arr = [instance.add, jsAdd];
    for (let i = 0; i < INNER_ITERATIONS; i++) {
        assertEq(arr[i%2](i, i+1), 2*i + 1);
    }
}

function testCallGenericRectifying() {
    var arr = [instance.add, jsAdd];
    for (let i = 0; i < INNER_ITERATIONS; i++) {
        assertEq(arr[i%2](i+1), i + 1);
    }
}

function testCallScriptedGetter() {
    var obj = {};
    Object.defineProperty(obj, 'x', {
        get: instance.no_arg
    });
    for (let i = 0; i < INNER_ITERATIONS; i++) {
        assertEq(obj.x, 100);
    }
}

function testCallScriptedGetterRectifying() {
    var obj = {};
    Object.defineProperty(obj, 'x', {
        // Missing two arguments.
        get: instance.add
    });
    for (let i = 0; i < INNER_ITERATIONS; i++) {
        assertEq(obj.x, 0);
    }
}

function testCallScriptedSetter() {
    var obj = {};
    Object.defineProperty(obj, 'x', {
        set: instance.set_global_one
    });
    for (let i = 0; i < INNER_ITERATIONS; i++) {
        obj.x = i;
    }
    assertEq(instance.glob(), INNER_ITERATIONS-1);
}

function testCallScriptedSetterRectifying() {
    var obj = {};
    Object.defineProperty(obj, 'x', {
        set: instance.set_global_two
    });
    for (let i = 0; i < INNER_ITERATIONS; i++) {
        obj.x = i;
    }
    assertEq(instance.glob(), INNER_ITERATIONS-1);
}

function testFunctionApplyArray() {
    for (let i = 0; i < INNER_ITERATIONS; i++) {
        assertEq(instance.add.apply(null, [i, i + 1]), 2*i+1);
    }
}

function testFunctionApplyArrayRectifying() {
    for (let i = 0; i < INNER_ITERATIONS; i++) {
        assertEq(instance.add.apply(null, [i + 1]), i+1);
    }
}

function testFunctionApplyArgs() {
    function wrapper() {
        assertEq(instance.add.apply(null, arguments), 2*arguments[0]+1);
    }
    for (let i = 0; i < INNER_ITERATIONS; i++) {
        wrapper(i, i + 1);
    }
}

function testFunctionApplyArgsRectifying() {
    function wrapper() {
        assertEq(instance.add.apply(null, arguments), arguments[0]);
    }
    for (let i = 0; i < INNER_ITERATIONS; i++) {
        wrapper(i + 1);
    }
}

function testFunctionCall() {
    for (let i = 0; i < INNER_ITERATIONS; i++) {
        assertEq(instance.add.call(null, i, i + 1), 2*i+1);
    }
}

function testFunctionCallRectifying() {
    for (let i = 0; i < INNER_ITERATIONS; i++) {
        assertEq(instance.add.call(null, i + 1), i+1);
    }
}

run('call known', testCallKnown);
run('call known rectifying', testCallKnownRectifying);

run('call generic', testCallGeneric);
run('call generic rectifying', testCallGenericRectifying);

run('scripted getter', testCallScriptedGetter);
run('scripted getter rectifiying', testCallScriptedGetterRectifying);
run('scripted setter', testCallScriptedSetter);
run('scripted setter rectifiying', testCallScriptedSetterRectifying);

run('function.apply array', testFunctionApplyArray);
run('function.apply array rectifying', testFunctionApplyArrayRectifying);
run('function.apply args', testFunctionApplyArgs);
run('function.apply args rectifying', testFunctionApplyArgsRectifying);

run('function.call', testFunctionCall);
run('function.call rectifying', testFunctionCallRectifying);
