/*
 * Javascript Micro benchmark
 *
 * Copyright (c) 2017-2019 Fabrice Bellard
 * Copyright (c) 2017-2019 Charlie Gordon
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

if (typeof require !== 'undefined') {
    var fs = require('fs');
}

function pad(str, n) {
    str += "";
    while (str.length < n)
        str += " ";
    return str;
}

function pad_left(str, n) {
    str += "";
    while (str.length < n)
        str = " " + str;
    return str;
}

function pad_center(str, n) {
    str += "";
    while (str.length < n) {
        if ((n - str.length) & 1)
            str = str + " ";
        else
            str = " " + str;
    }
    return str;
}

var ref_data;
var log_data;

var heads  = [ "TEST", "N", "TIME (ns)", "REF (ns)", "SCORE (1000)" ];
var widths = [    22,   10,          9,     9,       9 ];
var precs  = [     0,   0,           2,     2,       0 ];
var total  = [     0,   0,           0,     0,       0 ];
var total_score = 0;
var total_scale = 0;

function log_line() {
    var i, n, s, a;
    s = "";
    for (i = 0, n = arguments.length; i < n; i++) {
        if (i > 0)
            s += " ";
        a = arguments[i];
        if (typeof a === "number") {
            total[i] += a;
            a = a.toFixed(precs[i]);
            s += pad_left(a, widths[i]);
        } else {
            s += pad_left(a, widths[i]);
        }
    }
    console.log(s);
}

var clocks_per_sec = 1000;
var max_iterations = 100;
var clock_threshold = 2;  /* favoring short measuring spans */
var min_n_argument = 1;
var get_clock;
if (typeof performance !== "undefined") {
    // use more precise clock on NodeJS
    // need a method call on performance object
    get_clock = () => performance.now();
} else
if (typeof os !== "undefined") {
    // use more precise clock on QuickJS
    get_clock = os.now;
} else {
    // use Date.now and round up to the next millisecond
    get_clock = () => {
        var t0 = Date.now();
        var t;
        while ((t = Date.now()) == t0)
            continue;
        return t;
    }
}

function log_one(text, n, ti) {
    var ref;

    if (ref_data)
        ref = ref_data[text];
    else
        ref = null;

    ti = Math.round(ti * 100) / 100;
    log_data[text] = ti;
    if (typeof ref === "number") {
        log_line(text, n, ti, ref, Math.round(ref * 1000 / ti));
        total_score += ti * 100 / ref;
        total_scale += 100;
    } else {
        log_line(text, n, ti);
        total_score += 100;
        total_scale += 100;
    }
}

function bench(f, text)
{
    var i, j, n, t, ti, nb_its, ref, ti_n, ti_n1;

    nb_its = n = 1;
    if (f.bench) {
        ti_n = f(text);
    } else {
        // measure ti_n: the shortest time for an individual operation
        ti_n = 1000000000;
        for(i = 0; i < 30; i++) {
            // measure ti: the shortest time for max_iterations iterations
            ti = 1000000000;
            for (j = 0; j < max_iterations; j++) {
                t = get_clock();
                nb_its = f(n);
                t = get_clock() - t;
                if (nb_its < 0)
                    return; // test failure
                if (ti > t)
                    ti = t;
            }
            if (ti >= clock_threshold / 10) {
                ti_n1 = ti / nb_its;
                if (ti_n > ti_n1)
                    ti_n = ti_n1;
            }
            if (ti >= clock_threshold && n >= min_n_argument)
                break;

            n = n * [ 2, 2.5, 2 ][i % 3];
        }
        // to use only the best timing from the last loop, uncomment below
        //ti_n = ti / nb_its;
    }
    /* nano seconds per iteration */
    log_one(text, n, ti_n * 1e9 / clocks_per_sec);
}

var global_res; /* to be sure the code is not optimized */

function empty_loop(n) {
    var j;
    for(j = 0; j < n; j++) {
    }
    return n;
}

function empty_down_loop(n) {
    var j;
    for(j = n; j > 0; j--) {
    }
    return n;
}

function empty_down_loop2(n) {
    var j;
    for(j = n; j --> 0;) {
    }
    return n;
}

function empty_do_loop(n) {
    var j = n;
    do { } while (--j > 0);
    return n;
}

function date_now(n) {
    var j;
    for(j = 0; j < n; j++) {
        Date.now();
    }
    return n;
}

function date_parse(n) {
    var x0 = 0, dx = 0;
    var j;
    for(j = 0; j < n; j++) {
        var x1 = x0 - x0 % 1000;
        var x2 = -x0;
        var x3 = -x1;
        var d0 = new Date(x0);
        var d1 = new Date(x1);
        var d2 = new Date(x2);
        var d3 = new Date(x3);
        if (Date.parse(d0.toISOString()) != x0
        ||  Date.parse(d1.toGMTString()) != x1
        ||  Date.parse(d1.toString()) != x1
        ||  Date.parse(d2.toISOString()) != x2
        ||  Date.parse(d3.toGMTString()) != x3
        ||  Date.parse(d3.toString()) != x3) {
            console.log("Date.parse error for " + x0);
            return -1;
        }
        dx = (dx * 1.1 + 1) >> 0;
        x0 = (x0 + dx) % 8.64e15;
    }
    return n * 6;
}

function prop_read(n)
{
    var obj, sum, j;
    obj = {a: 1, b: 2, c:3, d:4 };
    sum = 0;
    for(j = 0; j < n; j++) {
        sum += obj.a;
        sum += obj.b;
        sum += obj.c;
        sum += obj.d;
    }
    global_res = sum;
    return n * 4;
}

function prop_write(n)
{
    var obj, j;
    obj = {a: 1, b: 2, c:3, d:4 };
    for(j = 0; j < n; j++) {
        obj.a = j;
        obj.b = j;
        obj.c = j;
        obj.d = j;
    }
    return n * 4;
}

function prop_update(n)
{
    var obj, j;
    obj = {a: 1, b: 2, c:3, d:4 };
    for(j = 0; j < n; j++) {
        obj.a += j;
        obj.b += j;
        obj.c += j;
        obj.d += j;
    }
    return n * 4;
}

function prop_create(n)
{
    var obj, i, j;
    for(j = 0; j < n; j++) {
        obj = {};
        obj.a = 1;
        obj.b = 2;
        obj.c = 3;
        obj.d = 4;
        obj.e = 5;
        obj.f = 6;
        obj.g = 7;
        obj.h = 8;
        obj.i = 9;
        obj.j = 10;
        for(i = 0; i < 10; i++) {
            obj[i] = i;
        }
    }
    return n * 20;
}

function prop_clone(n)
{
    var ref, obj, j, k;
    ref = { a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8, i:9, j:10 };
    for(k = 0; k < 10; k++) {
        ref[k] = k;
    }
    for (j = 0; j < n; j++) {
        global_res = { ...ref };
    }
    return n * 20;
}

function prop_delete(n)
{
    var ref, obj, j, k;
    ref = { a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8, i:9, j:10 };
    for(k = 0; k < 10; k++) {
        ref[k] = k;
    }
    for (j = 0; j < n; j++) {
        obj = { ...ref };
        delete obj.a;
        delete obj.b;
        delete obj.c;
        delete obj.d;
        delete obj.e;
        delete obj.f;
        delete obj.g;
        delete obj.h;
        delete obj.i;
        delete obj.j;
        for(k = 0; k < 10; k++) {
            delete obj[k];
        }
    }
    return n * 20;
}

function array_read(n)
{
    var tab, len, sum, i, j;
    tab = [];
    len = 10;
    for(i = 0; i < len; i++)
        tab[i] = i;
    sum = 0;
    for(j = 0; j < n; j++) {
        sum += tab[0];
        sum += tab[1];
        sum += tab[2];
        sum += tab[3];
        sum += tab[4];
        sum += tab[5];
        sum += tab[6];
        sum += tab[7];
        sum += tab[8];
        sum += tab[9];
    }
    global_res = sum;
    return len * n;
}

function array_write(n)
{
    var tab, len, i, j;
    tab = [];
    len = 10;
    for(i = 0; i < len; i++)
        tab[i] = i;
    for(j = 0; j < n; j++) {
        tab[0] = j;
        tab[1] = j;
        tab[2] = j;
        tab[3] = j;
        tab[4] = j;
        tab[5] = j;
        tab[6] = j;
        tab[7] = j;
        tab[8] = j;
        tab[9] = j;
    }
    return len * n;
}

function array_update(n)
{
    var tab, len, i, j;
    tab = [];
    len = 10;
    for(i = 0; i < len; i++)
        tab[i] = i;
    for(j = 0; j < n; j++) {
        tab[0] += j;
        tab[1] += j;
        tab[2] += j;
        tab[3] += j;
        tab[4] += j;
        tab[5] += j;
        tab[6] += j;
        tab[7] += j;
        tab[8] += j;
        tab[9] += j;
    }
    return len * n;
}

function array_prop_create(n)
{
    var tab, i, j, len;
    len = 1000;
    for(j = 0; j < n; j++) {
        tab = [];
        for(i = 0; i < len; i++)
            tab[i] = i;
    }
    return len * n;
}

function array_slice(n)
{
    var ref, a, i, j, len;
    len = 1000;
    ref = [];
    for(i = 0; i < len; i++)
        ref[i] = i;
    for(j = 0; j < n; j++) {
        ref[0] = j;
        a = ref.slice();
        a[0] = 0;
        global_res = a;
    }
    return len * n;
}

function array_length_read(n)
{
    var tab, sum, j;
    tab = [1, 2, 3];
    sum = 0;
    for(j = 0; j < n; j++) {
        sum += tab.length;
        sum += tab.length;
        sum += tab.length;
        sum += tab.length;
    }
    global_res = sum;
    return n * 4;
}

function array_length_decr(n)
{
    var tab, ref, i, j, len;
    len = 1000;
    ref = [];
    for(i = 0; i < len; i++)
        ref[i] = i;
    for(j = 0; j < n; j++) {
        tab = ref.slice();
        for(i = len; i --> 0;)
            tab.length = i;
    }
    return len * n;
}

function array_hole_length_decr(n)
{
    var tab, ref, i, j, len;
    len = 1000;
    ref = [];
    for(i = 0; i < len; i++) {
        if (i % 10 == 9)
            ref[i] = i;
    }
    for(j = 0; j < n; j++) {
        tab = ref.slice();
        for(i = len; i --> 0;)
            tab.length = i;
    }
    return len * n;
}

function array_push(n)
{
    var tab, i, j, len;
    len = 500;
    for(j = 0; j < n; j++) {
        tab = [];
        for(i = 0; i < len; i++)
            tab.push(i);
    }
    return len * n;
}

function array_pop(n)
{
    var tab, ref, i, j, len, sum;
    len = 500;
    ref = [];
    for(i = 0; i < len; i++)
        ref[i] = i;
    for(j = 0; j < n; j++) {
        tab = ref.slice();
        sum = 0;
        for(i = 0; i < len; i++)
            sum += tab.pop();
        global_res = sum;
    }
    return len * n;
}

function typed_array_read(n)
{
    var tab, len, sum, i, j;
    len = 10;
    tab = new Int32Array(len);
    for(i = 0; i < len; i++)
        tab[i] = i;
    sum = 0;
    for(j = 0; j < n; j++) {
        sum += tab[0];
        sum += tab[1];
        sum += tab[2];
        sum += tab[3];
        sum += tab[4];
        sum += tab[5];
        sum += tab[6];
        sum += tab[7];
        sum += tab[8];
        sum += tab[9];
    }
    global_res = sum;
    return len * n;
}

function typed_array_write(n)
{
    var tab, len, i, j;
    len = 10;
    tab = new Int32Array(len);
    for(i = 0; i < len; i++)
        tab[i] = i;
    for(j = 0; j < n; j++) {
        tab[0] = j;
        tab[1] = j;
        tab[2] = j;
        tab[3] = j;
        tab[4] = j;
        tab[5] = j;
        tab[6] = j;
        tab[7] = j;
        tab[8] = j;
        tab[9] = j;
    }
    return len * n;
}

function arguments_test()
{
    return arguments[0] + arguments[1] + arguments[2];
}

function arguments_read(n)
{
    sum = 0;
    for(j = 0; j < n; j++) {
        sum += arguments_test(j, j, j);
        sum += arguments_test(j, j, j);
        sum += arguments_test(j, j, j);
        sum += arguments_test(j, j, j);
    }
    global_res = sum;
    return n * 4;
}

function arguments_strict_test()
{
    "use strict";
    return arguments[0] + arguments[1] + arguments[2];
}

function arguments_strict_read(n)
{
    sum = 0;
    for(j = 0; j < n; j++) {
        sum += arguments_strict_test(j, j, j);
        sum += arguments_strict_test(j, j, j);
        sum += arguments_strict_test(j, j, j);
        sum += arguments_strict_test(j, j, j);
    }
    global_res = sum;
    return n * 4;
}

var global_var0;

function global_read(n)
{
    var sum, j;
    global_var0 = 0;
    sum = 0;
    for(j = 0; j < n; j++) {
        sum += global_var0;
        sum += global_var0;
        sum += global_var0;
        sum += global_var0;
    }
    global_res = sum;
    return n * 4;
}

function global_write(n)
{
    var j;
    for(j = 0; j < n; j++) {
        global_var0 = j;
        global_var0 = j;
        global_var0 = j;
        global_var0 = j;
    }
    return n * 4;
}

function global_write_strict(n)
{
    "use strict";
    var j;
    for(j = 0; j < n; j++) {
        global_var0 = j;
        global_var0 = j;
        global_var0 = j;
        global_var0 = j;
    }
    return n * 4;
}

function local_destruct(n)
{
    var j, v1, v2, v3, v4;
    var array = [ 1, 2, 3, 4, 5];
    var o = { a:1, b:2, c:3, d:4 };
    var a, b, c, d;
    for(j = 0; j < n; j++) {
        [ v1, v2,, v3, ...v4] = array;
        ({ a, b, c, d } = o);
        ({ a: a, b: b, c: c, d: d } = o);
    }
    return n * 12;
}

var global_v1, global_v2, global_v3, global_v4;
var global_a, global_b, global_c, global_d;

function global_destruct(n)
{
    var j, v1, v2, v3, v4;
    var array = [ 1, 2, 3, 4, 5 ];
    var o = { a:1, b:2, c:3, d:4 };
    var a, b, c, d;
    for(j = 0; j < n; j++) {
        [ global_v1, global_v2,, global_v3, ...global_v4] = array;
        ({ a: global_a, b: global_b, c: global_c, d: global_d } = o);
    }
    return n * 8;
}

function global_destruct_strict(n)
{
    "use strict";
    var j, v1, v2, v3, v4;
    var array = [ 1, 2, 3, 4, 5 ];
    var o = { a:1, b:2, c:3, d:4 };
    var a, b, c, d;
    for(j = 0; j < n; j++) {
        [ global_v1, global_v2,, global_v3, ...global_v4] = array;
        ({ a: global_a, b: global_b, c: global_c, d: global_d } = o);
    }
    return n * 8;
}

function g(a)
{
    return 1;
}

function global_func_call(n)
{
    var j, sum;
    sum = 0;
    for(j = 0; j < n; j++) {
        sum += g(j);
        sum += g(j);
        sum += g(j);
        sum += g(j);
    }
    global_res = sum;
    return n * 4;
}

function func_call(n)
{
    function f(a)
    {
        return 1;
    }

    var j, sum;
    sum = 0;
    for(j = 0; j < n; j++) {
        sum += f(j);
        sum += f(j);
        sum += f(j);
        sum += f(j);
    }
    global_res = sum;
    return n * 4;
}

function func_closure_call(n)
{
    function f(a)
    {
        sum++;
    }

    var j, sum;
    sum = 0;
    for(j = 0; j < n; j++) {
        f(j);
        f(j);
        f(j);
        f(j);
    }
    global_res = sum;
    return n * 4;
}

function int_arith(n)
{
    var i, j, sum;
    global_res = 0;
    for(j = 0; j < n; j++) {
        sum = 0;
        for(i = 0; i < 1000; i++) {
            sum += i * i;
        }
        global_res += sum;
    }
    return n * 1000;
}

function float_arith(n)
{
    var i, j, sum, a, incr, a0;
    global_res = 0;
    a0 = 0.1;
    incr = 1.1;
    for(j = 0; j < n; j++) {
        sum = 0;
        a = a0;
        for(i = 0; i < 1000; i++) {
            sum += a * a;
            a += incr;
        }
        global_res += sum;
    }
    return n * 1000;
}

function bigint_arith(n, bits)
{
    var i, j, sum, a, incr, a0, sum0;
    sum0 = global_res = BigInt(0);
    a0 = BigInt(1) << BigInt(Math.floor((bits - 10) * 0.5));
    incr = BigInt(1);
    for(j = 0; j < n; j++) {
        sum = sum0;
        a = a0;
        for(i = 0; i < 1000; i++) {
            sum += a * a;
            a += incr;
        }
        global_res += sum;
    }
    return n * 1000;
}

function bigint32_arith(n)
{
    return bigint_arith(n, 32);
}

function bigint64_arith(n)
{
    return bigint_arith(n, 64);
}

function bigint256_arith(n)
{
    return bigint_arith(n, 256);
}

function map_set_string(n)
{
    var s, i, j, len = 1000;
    for(j = 0; j < n; j++) {
        s = new Map();
        for(i = 0; i < len; i++) {
            s.set(String(i), i);
        }
        for(i = 0; i < len; i++) {
            if (!s.has(String(i)))
                throw Error("bug in Map");
        }
    }
    return n * len;
}

function map_set_int(n)
{
    var s, i, j, len = 1000;
    for(j = 0; j < n; j++) {
        s = new Map();
        for(i = 0; i < len; i++) {
            s.set(i, i);
        }
        for(i = 0; i < len; i++) {
            if (!s.has(i))
                throw Error("bug in Map");
        }
    }
    return n * len;
}

function map_set_bigint(n)
{
    var s, i, j, len = 1000;
    for(j = 0; j < n; j++) {
        s = new Map();
        for(i = 0; i < len; i++) {
            s.set(BigInt(i), i);
        }
        for(i = 0; i < len; i++) {
            if (!s.has(BigInt(i)))
                throw Error("bug in Map");
        }
    }
    return n * len;
}

function map_delete(n)
{
    var a, i, j;

    len = 1000;
    for(j = 0; j < n; j++) {
        a = new Map();
        for(i = 0; i < len; i++) {
            a.set(String(i), i);
        }
        for(i = 0; i < len; i++) {
            a.delete(String(i));
        }
    }
    return len * n;
}

function weak_map_set(n)
{
    var a, i, j, tab;

    len = 1000;
    tab = [];
    for(i = 0; i < len; i++) {
        tab.push({ key: i });
    }
    for(j = 0; j < n; j++) {
        a = new WeakMap();
        for(i = 0; i < len; i++) {
            a.set(tab[i], i);
        }
    }
    return len * n;
}

function weak_map_delete(n)
{
    var a, i, j, tab;

    len = 1000;
    for(j = 0; j < n; j++) {
        tab = [];
        for(i = 0; i < len; i++) {
            tab.push({ key: i });
        }
        a = new WeakMap();
        for(i = 0; i < len; i++) {
            a.set(tab[i], i);
        }
        for(i = 0; i < len; i++) {
            tab[i] = null;
        }
    }
    return len * n;
}


function array_for(n)
{
    var r, i, j, sum, len = 100;
    r = [];
    for(i = 0; i < len; i++)
        r[i] = i;
    for(j = 0; j < n; j++) {
        sum = 0;
        for(i = 0; i < len; i++) {
            sum += r[i];
        }
        global_res = sum;
    }
    return n * len;
}

function array_for_in(n)
{
    var r, i, j, sum, len = 100;
    r = [];
    for(i = 0; i < len; i++)
        r[i] = i;
    for(j = 0; j < n; j++) {
        sum = 0;
        for(i in r) {
            sum += r[i];
        }
        global_res = sum;
    }
    return n * len;
}

function array_for_of(n)
{
    var r, i, j, sum, len = 100;
    r = [];
    for(i = 0; i < len; i++)
        r[i] = i;
    for(j = 0; j < n; j++) {
        sum = 0;
        for(i of r) {
            sum += i;
        }
        global_res = sum;
    }
    return n * len;
}

function math_min(n)
{
    var i, j, r;
    r = 0;
    for(j = 0; j < n; j++) {
        for(i = 0; i < 1000; i++)
            r = Math.min(i, 500);
        global_res = r;
    }
    return n * 1000;
}

function regexp_ascii(n)
{
    var i, j, r, s;
    s = "the quick brown fox jumped over the lazy dog"
    for(j = 0; j < n; j++) {
        for(i = 0; i < 1000; i++)
            r = /the quick brown fox/.exec(s)
        global_res = r;
    }
    return n * 1000;
}

function regexp_utf16(n)
{
    var i, j, r, s;
    s = "the quick brown ᶠᵒˣ jumped over the lazy ᵈᵒᵍ"
    for(j = 0; j < n; j++) {
        for(i = 0; i < 1000; i++)
            r = /the quick brown ᶠᵒˣ/.exec(s)
        global_res = r;
    }
    return n * 1000;
}

function regexp_replace(n)
{
    var i, j, r, s;
    s = "the quick abc brown fox jumped abc over the lazy dog"
    for(j = 0; j < n; j++) {
        for(i = 0; i < 1000; i++)
            r = s.replace(/abc /g, "-");
        global_res = r;
    }
    return n * 1000;
}

function string_length(n)
{
    var str, sum, j;
    str = "abcde";
    sum = 0;
    for(j = 0; j < n; j++) {
        sum += str.length;
        sum += str.length;
        sum += str.length;
        sum += str.length;
    }
    global_res = sum;
    return n * 4;
}

/* incremental string contruction as local var */
function string_build1(n)
{
    var i, j, r;
    for(j = 0; j < n; j++) {
        r = "";
        for(i = 0; i < 1000; i++)
            r += "x";
        global_res = r;
    }
    return n * 1000;
}

/* incremental string contruction using + */
function string_build1x(n)
{
    var i, j, r;
    for(j = 0; j < n; j++) {
        r = "";
        for(i = 0; i < 1000; i++)
            r = r + "x";
        global_res = r;
    }
    return n * 1000;
}

/* incremental string contruction using +2c */
function string_build2c(n)
{
    var i, j;
    for(j = 0; j < n; j++) {
        var r = "";
        for(i = 0; i < 1000; i++)
            r += "xy";
        global_res = r;
    }
    return n * 1000;
}

/* incremental string contruction as arg */
function string_build2(n, r)
{
    var i, j;
    for(j = 0; j < n; j++) {
        r = "";
        for(i = 0; i < 1000; i++)
            r += "x";
        global_res = r;
    }
    return n * 1000;
}

/* incremental string contruction by prepending */
function string_build3(n)
{
    var i, j, r;
    for(j = 0; j < n; j++) {
        r = "";
        for(i = 0; i < 1000; i++)
            r = "x" + r;
        global_res = r;
    }
    return n * 1000;
}

/* incremental string contruction with multiple reference */
function string_build4(n)
{
    var i, j, r, s;
    for(j = 0; j < n; j++) {
        r = "";
        for(i = 0; i < 1000; i++) {
            s = r;
            r += "x";
        }
        global_res = r;
    }
    return n * 1000;
}

/* append */
function string_build_large1(n)
{
    var i, j, r, len = 20000;
    for(j = 0; j < n; j++) {
        r = "";
        for(i = 0; i < len; i++)
            r += "abcdef";
        global_res = r;
    }
    return n * len;
}

/* prepend */
function string_build_large2(n)
{
    var i, j, r, len = 20000;
    for(j = 0; j < n; j++) {
        r = "";
        for(i = 0; i < len; i++)
            r = "abcdef" + r;
        global_res = r;
    }
    return n * len;
}

/* sort bench */

function sort_bench(text) {
    function random(arr, n, def) {
        for (var i = 0; i < n; i++)
            arr[i] = def[(Math.random() * n) >> 0];
    }
    function random8(arr, n, def) {
        for (var i = 0; i < n; i++)
            arr[i] = def[(Math.random() * 256) >> 0];
    }
    function random1(arr, n, def) {
        for (var i = 0; i < n; i++)
            arr[i] = def[(Math.random() * 2) >> 0];
    }
    function hill(arr, n, def) {
        var mid = n >> 1;
        for (var i = 0; i < mid; i++)
            arr[i] = def[i];
        for (var i = mid; i < n; i++)
            arr[i] = def[n - i];
    }
    function comb(arr, n, def) {
        for (var i = 0; i < n; i++)
            arr[i] = def[(i & 1) * i];
    }
    function crisscross(arr, n, def) {
        for (var i = 0; i < n; i++)
            arr[i] = def[(i & 1) ? n - i : i];
    }
    function zero(arr, n, def) {
        for (var i = 0; i < n; i++)
            arr[i] = def[0];
    }
    function increasing(arr, n, def) {
        for (var i = 0; i < n; i++)
            arr[i] = def[i];
    }
    function decreasing(arr, n, def) {
        for (var i = 0; i < n; i++)
            arr[i] = def[n - 1 - i];
    }
    function alternate(arr, n, def) {
        for (var i = 0; i < n; i++)
            arr[i] = def[i ^ 1];
    }
    function jigsaw(arr, n, def) {
        for (var i = 0; i < n; i++)
            arr[i] = def[i % (n >> 4)];
    }
    function incbutone(arr, n, def) {
        for (var i = 0; i < n; i++)
            arr[i] = def[i];
        if (n > 0)
            arr[n >> 2] = def[n];
    }
    function incbutfirst(arr, n, def) {
        if (n > 0)
            arr[0] = def[n];
        for (var i = 1; i < n; i++)
            arr[i] = def[i];
    }
    function incbutlast(arr, n, def) {
        for (var i = 0; i < n - 1; i++)
            arr[i] = def[i + 1];
        if (n > 0)
            arr[n - 1] = def[0];
    }

    var sort_cases = [ random, random8, random1, jigsaw, hill, comb,
                      crisscross, zero, increasing, decreasing, alternate,
                      incbutone, incbutlast, incbutfirst ];

    var n = sort_bench.array_size || 10000;
    var array_type = sort_bench.array_type || Array;
    var def, arr;
    var i, j, x, y;
    var total = 0;

    var save_total_score = total_score;
    var save_total_scale = total_scale;

    // initialize default sorted array (n + 1 elements)
    def = new array_type(n + 1);
    if (array_type == Array) {
        for (i = 0; i <= n; i++) {
            def[i] = i + "";
        }
    } else {
        for (i = 0; i <= n; i++) {
            def[i] = i;
        }
    }
    def.sort();
    for (var f of sort_cases) {
        var ti = 0, tx = 0;
        for (j = 0; j < 100; j++) {
            arr = new array_type(n);
            f(arr, n, def);
            var t1 = get_clock();
            arr.sort();
            t1 = get_clock() - t1;
            tx += t1;
            if (!ti || ti > t1)
                ti = t1;
            if (tx >= clocks_per_sec)
                break;
        }
        total += ti;

        i = 0;
        x = arr[0];
        if (x !== void 0) {
            for (i = 1; i < n; i++) {
                y = arr[i];
                if (y === void 0)
                    break;
                if (x > y)
                    break;
                x = y;
            }
        }
        while (i < n && arr[i] === void 0)
            i++;
        if (i < n) {
            console.log("sort_bench: out of order error for " + f.name +
                        " at offset " + (i - 1) +
                        ": " + arr[i - 1] + " > " + arr[i]);
        }
        if (sort_bench.verbose)
            log_one("sort_" + f.name, 1, ti / 100);
    }
    total_score = save_total_score;
    total_scale = save_total_scale;
    return total / n / 100;
}
sort_bench.bench = true;
sort_bench.verbose = false;

function int_to_string(n)
{
    var s, r, j;
    r = 0;
    for(j = 0; j < n; j++) {
        s = (j % 10) + '';
        s = (j % 100) + '';
        s = (j) + '';
    }
    return n * 3;
}

function int_toString(n)
{
    var s, r, j;
    r = 0;
    for(j = 0; j < n; j++) {
        s = (j % 10).toString();
        s = (j % 100).toString();
        s = (j).toString();
    }
    return n * 3;
}

function float_to_string(n)
{
    var s, r, j;
    r = 0;
    for(j = 0; j < n; j++) {
        s = (j % 10 + 0.1) + '';
        s = (j + 0.1) + '';
        s = (j * 12345678 + 0.1) + '';
    }
    return n * 3;
}

function float_toString(n)
{
    var s, r, j;
    r = 0;
    for(j = 0; j < n; j++) {
        s = (j % 10 + 0.1).toString();
        s = (j + 0.1).toString();
        s = (j * 12345678 + 0.1).toString();
    }
    return n * 3;
}

function float_toFixed(n)
{
    var s, r, j;
    r = 0;
    for(j = 0; j < n; j++) {
        s = (j % 10 + 0.1).toFixed(j % 16);
        s = (j + 0.1).toFixed(j % 16);
        s = (j * 12345678 + 0.1).toFixed(j % 16);
    }
    return n * 3;
}

function float_toPrecision(n)
{
    var s, r, j;
    r = 0;
    for(j = 0; j < n; j++) {
        s = (j % 10 + 0.1).toPrecision(j % 16 + 1);
        s = (j + 0.1).toPrecision(j % 16 + 1);
        s = (j * 12345678 + 0.1).toPrecision(j % 16 + 1);
    }
    return n * 3;
}

function float_toExponential(n)
{
    var s, r, j;
    r = 0;
    for(j = 0; j < n; j++) {
        s = (j % 10 + 0.1).toExponential(j % 16);
        s = (j + 0.1).toExponential(j % 16);
        s = (j * 12345678 + 0.1).toExponential(j % 16);
    }
    return n * 3;
}

function string_to_int(n)
{
    var s, r, j;
    r = 0;
    s = "12345";
    for(j = 0; j < n; j++) {
        r += (s | 0);
    }
    global_res = r;
    return n;
}

function string_to_float(n)
{
    var s, r, j;
    r = 0;
    s = "12345.6";
    for(j = 0; j < n; j++) {
        r -= s;
    }
    global_res = r;
    return n;
}

function load_result(filename)
{
    var has_filename = filename;
    var has_error = false;
    var str, res;

    if (!filename)
        filename = "microbench.txt";

    if (typeof fs !== "undefined") {
        // read the file in Node.js
        try {
            str = fs.readFileSync(filename, { encoding: "utf8" });
        } catch {
            has_error = true;
        }
    } else
    if (typeof std !== "undefined") {
        // read the file in QuickJS
        var f = std.open(filename, "r");
        if (f) {
            str = f.readAsString();
            f.close();
        } else {
            has_error = true;
        }
    } else {
        return null;
    }
    if (has_error) {
        if (has_filename) {
            // Should throw exception?
            console.log("cannot load " + filename);
        }
        return null;
    }
    res = JSON.parse(str);
    return res;
}

function save_result(filename, obj)
{
    var str = JSON.stringify(obj, null, 2) + "\n";
    var has_error = false;

    if (typeof fs !== "undefined") {
        // save the file in Node.js
        try {
            str = fs.writeFileSync(filename, str, { encoding: "utf8" });
        } catch {
            has_error = true;
        }
    } else
    if (typeof std !== "undefined") {
        // save the file in QuickJS
        var f = std.open(filename, "w");
        if (f) {
            f.puts(str);
            f.close();
        } else {
            has_error = 'true';
        }
    } else {
        return;
    }
    if (has_error)
        console.log("cannot save " + filename);
}

function main(argc, argv, g)
{
    var test_list = [
        empty_loop,
        empty_down_loop,
        empty_down_loop2,
        empty_do_loop,
        date_now,
        date_parse,
        prop_read,
        prop_write,
        prop_update,
        prop_create,
        prop_clone,
        prop_delete,
        array_read,
        array_write,
        array_update,
        array_prop_create,
        array_slice,
        array_length_read,
        array_length_decr,
        array_hole_length_decr,
        array_push,
        array_pop,
        typed_array_read,
        typed_array_write,
        arguments_read,
        arguments_strict_read,
        global_read,
        global_write,
        global_write_strict,
        local_destruct,
        global_destruct,
        global_destruct_strict,
        global_func_call,
        func_call,
        func_closure_call,
        int_arith,
        float_arith,
        map_set_string,
        map_set_int,
        map_set_bigint,
        map_delete,
        weak_map_set,
        weak_map_delete,
        array_for,
        array_for_in,
        array_for_of,
        math_min,
        regexp_ascii,
        regexp_utf16,
        regexp_replace,
        string_length,
        string_build1,
        string_build1x,
        string_build2c,
        string_build2,
        string_build3,
        string_build4,
        string_build_large1,
        string_build_large2,
        int_to_string,
        int_toString,
        float_to_string,
        float_toString,
        float_toFixed,
        float_toPrecision,
        float_toExponential,
        string_to_int,
        string_to_float,
    ];
    var tests = [];
    var i, j, n, f, name, found;
    var ref_file, new_ref_file = "microbench-new.txt";

    if (typeof BigInt === "function") {
        /* BigInt test */
        test_list.push(bigint32_arith);
        test_list.push(bigint64_arith);
        test_list.push(bigint256_arith);
    }
    test_list.push(sort_bench);

    for (i = 1; i < argc;) {
        name = argv[i++];
        if (name == "-a") {
            sort_bench.verbose = true;
            continue;
        }
        if (name == "-t") {
            name = argv[i++];
            sort_bench.array_type = g[name];
            if (typeof sort_bench.array_type !== "function") {
                console.log("unknown array type: " + name);
                return 1;
            }
            continue;
        }
        if (name == "-n") {
            sort_bench.array_size = +argv[i++];
            continue;
        }
        if (name == "-r") {
            ref_file = argv[i++];
            continue;
        }
        if (name == "-s") {
            new_ref_file = argv[i++];
            continue;
        }
        for (j = 0, found = false; j < test_list.length; j++) {
            f = test_list[j];
            if (f.name.startsWith(name)) {
                tests.push(f);
                found = true;
            }
        }
        if (!found) {
            console.log("unknown benchmark: " + name);
            return 1;
        }
    }
    if (tests.length == 0)
        tests = test_list;

    ref_data = load_result(ref_file);
    log_data = {};
    log_line.apply(null, heads);
    n = 0;

    for(i = 0; i < tests.length; i++) {
        f = tests[i];
        bench(f, f.name, ref_data, log_data);
        if (ref_data && ref_data[f.name])
            n++;
    }
    if (ref_data)
        log_line("total", "", total[2], total[3], Math.round(total_scale * 1000 / total_score));
    else
        log_line("total", "", total[2]);

    if (tests == test_list && new_ref_file)
        save_result(new_ref_file, log_data);
}

if (typeof scriptArgs === "undefined") {
    scriptArgs = [];
    if (typeof process.argv === "object")
        scriptArgs = process.argv.slice(1);
}
main(scriptArgs.length, scriptArgs, this);
