import { summary, run, bench } from 'mitata';

// Warmup (de-optimize `bench()` calls)
bench('noop', () => { });
bench('noop2', () => { });

// Example benchmark
/*summary(() => {
  const fn = () => null
  const fnAlt = function() {}
  function fnBasic() {}
  const fnAltWithReturn = function() { return null }
  function fnBasicWithReturn() { return null }

  bench('Reject with uncached error handler', async () => await Promise.reject().catch(() => null));
  bench('Reject with cached error handler', async () => await Promise.reject().catch(fn));

  bench('TT Reject with uncached error handler', () => Promise.reject().catch(() => null));
  bench('TT Reject with cached error handler', () => Promise.reject().catch(fnAlt));
  bench('TT Reject with cached basic error handler', () => Promise.reject().catch(fnBasic));
  bench('TT Reject with cached error handler with return', () => Promise.reject().catch(fnAltWithReturn));
  bench('TT Reject with cached basic error handler with return ', () => Promise.reject().catch(fnBasicWithReturn));
});*/

/*
summary(() => {
  function triple () {
    const bla = Math.round(Math.random()) ? true : null
    return bla === null
  }
  function doubleNull () {
    const bla = Math.round(Math.random()) ? true : null
    return bla == null
  }
  function doubleUndefined () {
    const bla = Math.round(Math.random()) ? true : undefined
    return bla == null
  }
  bench('null === null', triple);
  bench('null == null', doubleNull);
  bench('undefined == null', doubleUndefined);

  bench('2x null === null', triple);
  bench('2x null == null', doubleNull);
  bench('2x undefined == null', doubleUndefined);
})


summary(() => {
  function triple () {
    const bla = false ? true : null
    return bla === null
  }
  function doubleNull () {
    const bla = false ? true : null
    return bla == null
  }
  function doubleUndefined () {
    const bla = false ? true : undefined
    return bla == null
  }
  bench('const null === null', triple);
  bench('const null == null', doubleNull);
  bench('const undefined == null', doubleUndefined);

  bench('const 2x null === null', triple);
  bench('const 2x null == null', doubleNull);
  bench('const 2x undefined == null', doubleUndefined);
})*/

function printCurrentStatus(fn) {
  let opt = %GetOptimizationStatus(fn)
  console.log(`${fn.toString()}
${opt.toString(2).padStart(12, '0').split('').join(' ')}`)
}
function printTree() {
  console.log(`┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ │ │ │ │ │ │ └─╸ is function
│ │ │ │ │ │ │ │ │ │ └───╸ is never optimized
│ │ │ │ │ │ │ │ │ └─────╸ is always optimized
│ │ │ │ │ │ │ │ └───────╸ is maybe deoptimized
│ │ │ │ │ │ │ └─────────╸ is optimized
│ │ │ │ │ │ └───────────╸ is optimized by TurboFan
│ │ │ │ │ └─────────────╸ is interpreted
│ │ │ │ └───────────────╸ is marked for optimization
│ │ │ └─────────────────╸ is marked for concurrent optimization
│ │ └───────────────────╸ is optimizing concurrently
│ └─────────────────────╸ is executing
└───────────────────────╸ topmost frame is turbo fanned`)
}

let func1
let func2
let func3

await (async function () {
  return
  const fn1 = async () => await Promise.reject().catch(() => null) === null ? 1 : 0;
  const fn2 = async () => await Promise.reject().catch(() => {}) == null ? 1 : 0;
  const fn3 = async () => await Promise.reject().catch(() => null) == null ? 1 : 0;
  const noop = () => null;
  const fn4 = async () => await Promise.reject().catch(noop) == null ? 1 : 0;
  const fn5 = async () => await Promise.reject().catch(noop) === null ? 1 : 0;
  
  
  const fn6 = () => Promise.reject().catch(() => null).then(x => x === null ? 1 : 0);
  const fn7 = () => Promise.reject().catch(() => {}).then(x => x == null ? 1 : 0);
  const fn8 = () => Promise.reject().catch(() => null).then(x => x == null ? 1 : 0);
  const fn9 = () => Promise.reject().catch(noop).then(x => x == null ? 1 : 0);
  const fn10 = () => Promise.reject().catch(noop).then(x => x === null ? 1 : 0);

  func1 = [fn1, fn2, fn3, fn4, fn5, fn6, fn7, fn8, fn9, fn10];
  for (let fun of func1) {
    %OptimizeFunctionOnNextCall(fun);
  }
  for (var i = 0; i < 100000; i++) {
    for (let fun of func1) {
      await fun()
    }
  }
  for (let fun of func1) {
    printCurrentStatus(fun);
  }
  printTree()

  summary(() => {
    bench('Null with ===', fn1);
    bench('Undefined with ==', fn2);
    bench('Null with ==', fn3);
    bench('Cached Null with ==', fn4);
    bench('Cached Null with ===', fn5);
  
  
    bench('[sync] Null with ===', fn6);
    bench('[sync] Undefined with ==', fn7);
    bench('[sync] Null with ==', fn8);
    bench('[sync] Cached Null with ==', fn9);
    bench('[sync] Cached Null with ===', fn10);
  });
})


await (async function () {
  const alt1 = function () { (Math.round(Math.random()) ? null : 1) ?? 0 };
  const alt2 = function () { (Math.round(Math.random()) ? null : 1) || 0 };
  const alt3 = function () { (Math.round(Math.random()) ? undefined : 1) ?? 0 };
  const alt4 = function () { (Math.round(Math.random()) ? undefined : 1) || 0 };

  let check = new Array(100).fill(0).map(() => Math.round(Math.random()) ? null : 1)
  let check2 = new Array(100).fill(0).map(() => Math.round(Math.random()) ? undefined : 1)

  const alt5 = function () { let out = 0; for (let x of check) { out += x ?? 0 } return out };
  const alt6 = function () { let out = 0; for (let x of check) { out += x || 0 } return out };
  const alt7 = function () { let out = 0; for (let x of check2) { out += x ?? 0 } return out };
  const alt8 = function () { let out = 0; for (let x of check2) { out += x || 0 } return out };

  func2 = [alt1, alt2, alt3, alt4, alt5, alt6, alt7, alt8];
  for (let fun of func2) {
    %OptimizeFunctionOnNextCall(fun);
  }
  for (var i = 0; i < 100000; i++) {
    for (let fun of func2) {
      fun()
    }
  }
  for (let fun of func2) {
    printCurrentStatus(fun);
  }
  printTree()

  summary(() => {
    bench('null/1 ?? 0', alt1);
    bench('null/1 || 0', alt2);
    bench('undefined/1 ?? 0', alt3);
    bench('undefined/1 || 0', alt4);
  });

  summary(() => {
    bench('arr null/1 ?? null', alt5);
    bench('arr null/1 || null', alt6);
    bench('arr und/1 ?? null', alt7);
    bench('arr und/1 || null', alt8);
  });
})()


await (async function () {
  const alt1 = function () { let out = 0; for (let x = 0; x < 100; x++) { out += (Math.round(Math.random()) ? null : 1) ?? 0 } return out };
  const alt2 = function () { let out = 0; for (let x = 0; x < 100; x++) { out += (Math.round(Math.random()) ? null : 1) || 0 } return out };
  const alt3 = function () { let out = 0; for (let x = 0; x < 100; x++) { out += (Math.round(Math.random()) ? undefined : 1) ?? 0 } return out };
  const alt4 = function () { let out = 0; for (let x = 0; x < 100; x++) { out += (Math.round(Math.random()) ? undefined : 1) || 0 } return out };

  func3 = [alt1, alt2, alt3, alt4];
  for (let fun of func3) {
    %OptimizeFunctionOnNextCall(fun);
  }
  for (var i = 0; i < 100000; i++) {
    for (let fun of func3) {
      fun()
    }
  }
  for (let fun of func3) {
    printCurrentStatus(fun);
  }
  printTree()

  summary(() => {
    bench('loop null/1 ?? 0', alt1);
    bench('loop null/1 || 0', alt2);
    bench('loop und/1 ?? 0', alt3);
    bench('loop und/1 || 0', alt4);
  });
})()

// Start the benchmark
run().then(() => {
  for (let fun of func3) {
    printCurrentStatus(fun);
  }
  printTree()
});