'use strict';

const { mustCall } = require('../common');

const TIMEOUT = 10;
const SPIN_DUR = 50;

const assert = require('assert');
const { performance, eventLoopUtilization } = require('perf_hooks');
const { Worker, parentPort } = require('worker_threads');

// Verifies that `performance.eventLoopUtilization` is an alias of
// `perf_hooks.eventLoopUtilization`.
assert.strictEqual(performance.eventLoopUtilization, eventLoopUtilization);
const { nodeTiming } = performance;
const elu = eventLoopUtilization();

// Take into account whether this test was started as a Worker.
if (nodeTiming.loopStart === -1) {
  assert.strictEqual(nodeTiming.idleTime, 0);
  assert.deepStrictEqual(elu, { idle: 0, active: 0, utilization: 0 });
  assert.deepStrictEqual(eventLoopUtilization(elu),
                         { idle: 0, active: 0, utilization: 0 });
  assert.deepStrictEqual(eventLoopUtilization(elu, eventLoopUtilization()),
                         { idle: 0, active: 0, utilization: 0 });
}

const nodeTimingProps = ['name', 'entryType', 'startTime', 'duration',
                         'nodeStart', 'v8Start', 'environment', 'loopStart',
                         'loopExit', 'bootstrapComplete', 'idleTime'];
for (const p of nodeTimingProps)
  assert.ok(typeof JSON.parse(JSON.stringify(nodeTiming))[p] ===
    typeof nodeTiming[p]);

setTimeout(mustCall(function r() {
  const elu1 = eventLoopUtilization();

  // Force idle time to accumulate before allowing test to continue.
  if (elu1.idle <= 0)
    return setTimeout(mustCall(r), 5);

  const t = Date.now();
  while (Date.now() - t < SPIN_DUR);

  const elu2 = eventLoopUtilization(elu1);
  const elu3 = eventLoopUtilization();
  const elu4 = eventLoopUtilization(elu3, elu1);

  assert.strictEqual(elu2.idle, 0);
  assert.strictEqual(elu4.idle, 0);
  assert.strictEqual(elu2.utilization, 1);
  assert.strictEqual(elu4.utilization, 1);
  assert.strictEqual(elu3.active - elu1.active, elu4.active);
  assert.ok(elu2.active > SPIN_DUR - 10, `${elu2.active} <= ${SPIN_DUR - 10}`);
  assert.ok(elu2.active < elu4.active, `${elu2.active} >= ${elu4.active}`);
  assert.ok(elu3.active > elu2.active, `${elu3.active} <= ${elu2.active}`);
  assert.ok(elu3.active > elu4.active, `${elu3.active} <= ${elu4.active}`);

  setTimeout(mustCall(runIdleTimeTest), TIMEOUT);
}), 5);

function runIdleTimeTest() {
  const idleTime = nodeTiming.idleTime;
  const elu1 = eventLoopUtilization();
  const sum = elu1.idle + elu1.active;

  assert.ok(sum >= elu1.idle && sum >= elu1.active,
            `idle: ${elu1.idle}  active: ${elu1.active}  sum: ${sum}`);
  assert.strictEqual(elu1.idle, idleTime);
  assert.strictEqual(elu1.utilization, elu1.active / sum);

  setTimeout(mustCall(runCalcTest), TIMEOUT, elu1);
}

function runCalcTest(elu1) {
  const now = performance.now();
  const elu2 = eventLoopUtilization();
  const elu3 = eventLoopUtilization(elu2, elu1);
  const active_delta = elu2.active - elu1.active;
  const idle_delta = elu2.idle - elu1.idle;

  assert.ok(elu2.idle >= 0, `${elu2.idle} < 0`);
  assert.ok(elu2.active >= 0, `${elu2.active} < 0`);
  assert.ok(elu3.idle >= 0, `${elu3.idle} < 0`);
  assert.ok(elu3.active >= 0, `${elu3.active} < 0`);
  assert.ok(elu2.idle + elu2.active > elu1.idle + elu1.active,
            `${elu2.idle + elu2.active} <= ${elu1.idle + elu1.active}`);
  assert.ok(elu2.idle + elu2.active >= now - nodeTiming.loopStart,
            `${elu2.idle + elu2.active} < ${now - nodeTiming.loopStart}`);
  assert.strictEqual(elu3.active, elu2.active - elu1.active);
  assert.strictEqual(elu3.idle, elu2.idle - elu1.idle);
  assert.strictEqual(elu3.utilization,
                     active_delta / (idle_delta + active_delta));

  setImmediate(mustCall(runWorkerTest));
}

function runWorkerTest() {
  // Use argv to detect whether we're running as a Worker called by this test
  // vs. this test also being called as a Worker.
  if (process.argv[2] === 'iamalive') {
    parentPort.postMessage(JSON.stringify(eventLoopUtilization()));
    return;
  }

  const elu1 = eventLoopUtilization();
  const worker = new Worker(__filename, { argv: [ 'iamalive' ] });

  worker.on('message', mustCall((msg) => {
    const elu2 = eventLoopUtilization(elu1);
    const data = JSON.parse(msg);

    assert.ok(elu2.active + elu2.idle > data.active + data.idle,
              `${elu2.active + elu2.idle} <= ${data.active + data.idle}`);
  }));
}
