'use strict';
// Flags: --expose-gc

const common = require('../../common');
const assert = require('assert');

const test_reference = require(`./build/${common.buildType}/test_reference`);

// This test script uses external values with finalizer callbacks
// in order to track when values get garbage-collected. Each invocation
// of a finalizer callback increments the finalizeCount property.
assert.strictEqual(test_reference.finalizeCount, 0);

{
  // External value without a finalizer
  let value = test_reference.createExternal();
  assert.strictEqual(test_reference.finalizeCount, 0);
  assert.strictEqual(typeof value, 'object');
  test_reference.checkExternal(value);
  value = null;
  global.gc();
  assert.strictEqual(test_reference.finalizeCount, 0);
}

{
  // External value with a finalizer
  let value = test_reference.createExternalWithFinalize();
  assert.strictEqual(test_reference.finalizeCount, 0);
  assert.strictEqual(typeof value, 'object');
  test_reference.checkExternal(value);
  value = null;
  global.gc();
  assert.strictEqual(test_reference.finalizeCount, 1);
}

{
  // Strong reference
  let value = test_reference.createExternalWithFinalize();
  assert.strictEqual(test_reference.finalizeCount, 0);
  test_reference.createReference(value, 1);
  assert.strictEqual(test_reference.referenceValue, value);
  value = null;
  global.gc(); // Value should NOT be GC'd because there is a strong ref
  assert.strictEqual(test_reference.finalizeCount, 0);
  test_reference.deleteReference();
  global.gc(); // Value should be GC'd because the strong ref was deleted
  assert.strictEqual(test_reference.finalizeCount, 1);
}

{
  // Strong reference, increment then decrement to weak reference
  let value = test_reference.createExternalWithFinalize();
  assert.strictEqual(test_reference.finalizeCount, 0);
  test_reference.createReference(value, 1);
  value = null;
  global.gc(); // Value should NOT be GC'd because there is a strong ref
  assert.strictEqual(test_reference.finalizeCount, 0);

  assert.strictEqual(test_reference.incrementRefcount(), 2);
  global.gc(); // Value should NOT be GC'd because there is a strong ref
  assert.strictEqual(test_reference.finalizeCount, 0);

  assert.strictEqual(test_reference.decrementRefcount(), 1);
  global.gc(); // Value should NOT be GC'd because there is a strong ref
  assert.strictEqual(test_reference.finalizeCount, 0);

  assert.strictEqual(test_reference.decrementRefcount(), 0);
  global.gc(); // Value should be GC'd because the ref is now weak!
  assert.strictEqual(test_reference.finalizeCount, 1);

  test_reference.deleteReference();
  global.gc(); // Value was already GC'd
  assert.strictEqual(test_reference.finalizeCount, 1);
}

{
  // Weak reference
  let value = test_reference.createExternalWithFinalize();
  assert.strictEqual(test_reference.finalizeCount, 0);
  test_reference.createReference(value, 0);
  assert.strictEqual(test_reference.referenceValue, value);
  value = null;
  setImmediate(common.mustCall(() => {
    // This test only works if gc() is called from an immediate callback.
    global.gc(); // Value should be GC'd because there is only a weak ref
    assert.strictEqual(test_reference.referenceValue, undefined);
    assert.strictEqual(test_reference.finalizeCount, 1);
    test_reference.deleteReference();
  }));
}
