load(libdir + "asserts.js");
load(libdir + "iteration.js");

var k1 = {};
var k2 = {};
var k3 = {};
var k4 = {};

function test_patched() {
  let orig = Set.prototype.add;

  // If adder is modified, constructor should call it.
  var called = false;

  Set.prototype.add = function(k, v) {
    assertEq(k, k1);
    orig.call(this, k2);
    called = true;
  };

  var arr = [k1];

  var m = new Set(arr);

  assertEq(called, true);
  assertEq(m.size, 1);
  assertEq(m.has(k1), false);
  assertEq(m.has(k2), true);

  Set.prototype.add = orig;
}

function test_proxy1() {
  let orig = Set.prototype.add;

  // If adder is modified, constructor should call it.
  var called = false;

  Set.prototype.add = new Proxy(function(k, v) {
    assertEq(k, k1);
    orig.call(this, k2);
    called = true;
  }, {});

  var arr = [k1];

  var m = new Set(arr);

  assertEq(called, true);
  assertEq(m.size, 1);
  assertEq(m.has(k1), false);
  assertEq(m.has(k2), true);

  Set.prototype.add = orig;
}

function test_proxy2() {
  let orig = Set.prototype.add;

  // If adder is modified, constructor should call it.
  var called = false;

  Set.prototype.add = new Proxy(function() {
  }, {
    apply: function(target, that, args) {
      var [k, v] = args;
      assertEq(k, k1);
      orig.call(that, k2);
      called = true;
    }
  });

  var arr = [k1];

  var m = new Set(arr);

  assertEq(called, true);
  assertEq(m.size, 1);
  assertEq(m.has(k1), false);
  assertEq(m.has(k2), true);

  Set.prototype.add = orig;
}

function test_change1() {
  let orig = Set.prototype.add;

  // Change to adder in GetIterator(..) call should be ignored.
  var called = false;
  var modified = false;

  var arr = [k1];

  var proxy_arr = new Proxy(arr, {
    get: function(target, name) {
      if (name == Symbol.iterator) {
        modified = true;
        Set.prototype.add = function() {
          called = true;
        };
      }
      return target[name];
    }
  });

  var m = new Set(proxy_arr);

  assertEq(modified, true);
  assertEq(called, false);
  assertEq(m.size, 1);
  assertEq(m.has(k1), true);
  assertEq(m.has(k2), false);

  Set.prototype.add = orig;
}

function test_change2() {
  let orig = Set.prototype.add;

  // Change to adder in adder(...) call should be ignored.
  var called = false;
  var count = 0;

  Set.prototype.add = function(k, v) {
    if (count == 0) {
      assertEq(k, k1);
      orig.call(this, k3);
      Set.prototype.add = function() {
        called = true;
      };
      count = 1;
    } else {
      assertEq(k, k2);
      orig.call(this, k4);
      count = 2;
    }
  };

  var arr = [k1, k2];

  var m = new Set(arr);

  assertEq(called, false);
  assertEq(count, 2);
  assertEq(m.size, 2);
  assertEq(m.has(k1), false);
  assertEq(m.has(k2), false);
  assertEq(m.has(k3), true);
  assertEq(m.has(k4), true);

  Set.prototype.add = orig;
}

function test_error() {
  let orig = Set.prototype.add;

  var arr = [k1];

  // Set should throw TypeError if adder is not callable.
  Set.prototype.add = null;
  assertThrowsInstanceOf(() => new Set(arr), TypeError);
  Set.prototype.add = {};
  assertThrowsInstanceOf(() => new Set(arr), TypeError);

  // Set should propagate error thrown by adder.
  Set.prototype.add = function() {
    throw SyntaxError();
  };
  assertThrowsInstanceOf(() => new Set(arr), SyntaxError);

  Set.prototype.add = orig;
}

function test() {
 test_patched();
 test_proxy1();
 test_proxy2();
 test_change1();
 test_change2();
 test_error();
}

test();