// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

(function testSpreadCallsStrict() {
  "use strict"
  function countArgs() { return arguments.length; }

  // Test this argument
  function returnThis() { return this; }
  assertEquals(void 0, returnThis(..."test"));

  // Test argument counting with different iterables
  assertEquals(0, countArgs(...""));
  assertEquals(4, countArgs(..."test"));
  assertEquals(4, countArgs(..."tes", ..."t"));
  assertEquals(4, countArgs("t", ..."es", "t"));
  assertEquals(4, countArgs("tes", ..."t!!"));

  assertEquals(1, countArgs(...[1]));
  assertEquals(2, countArgs(...[1, 2]));
  assertEquals(3, countArgs(...[1, 2, 3]));
  assertEquals(4, countArgs(...[1, 2, 3, 4]));
  assertEquals(5, countArgs(...[1, 2, 3, 4, 5]));
  assertEquals(6, countArgs(...[1, 2, 3, 4, 5, 6]));

  assertEquals(1, countArgs(...[1.1]));
  assertEquals(2, countArgs(...[1.1, 2.2]));
  assertEquals(3, countArgs(...[1.1, 2.2, 3.3]));
  assertEquals(4, countArgs(...[1.1, 2.2, 3.3, 4.4]));
  assertEquals(5, countArgs(...[1.1, 2.2, 3.3, 4.4, 5.5]));
  assertEquals(6, countArgs(...[1.1, 2.2, 3.3, 4.4, 5.5, 6.6]));

  assertEquals(1, countArgs(...new Set([1])));
  assertEquals(2, countArgs(...new Set([1, 2])));
  assertEquals(3, countArgs(...new Set([1, 2, 3])));
  assertEquals(4, countArgs(...new Set([1, 2, 3, 4])));
  assertEquals(5, countArgs(...new Set([1, 2, 3, 4, 5])));
  assertEquals(6, countArgs(...new Set([1, 2, 3, 4, 5, 6])));

  assertEquals(3, countArgs(...(function*(){ yield 1; yield 2; yield 3; })()));

  // Test values
  function sum() {
    var sum = arguments[0];
    for (var i = 1; i < arguments.length; ++i) {
      sum += arguments[i];
    }
    return sum;
  }

  assertThrows(function() {
    sum(...0);
  }, TypeError);
  assertEquals(void 0, sum(...""));
  assertEquals(void 0, sum(...[]));
  assertEquals(void 0, sum(...new Set));
  assertEquals(void 0, sum(...(function*() { })()));

  assertEquals("test", sum(..."test"));
  assertEquals(10, sum(...[1, 2, 3, 4]));
  assertEquals(10, sum(...[1, 2, 3], 4));
  assertEquals(10, sum(1, ...[2, 3], 4));
  assertEquals(10, sum(1, ...[2, 3], ...[4]));
  assertEquals(10, sum(1, 2, ...[3, 4]));
  assertEquals(10, sum(...new Set([1, 2, 3, 4])));
  assertEquals(10, sum(...(function*() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
  })()));

  // nested spreads
  function makeArray() {
    var result = [];
    for (var i = 0; i < arguments.length; ++i) {
      result.push(arguments[i]);
    }
    return result;
  }
  assertEquals(10, sum(...makeArray(...[1, 2, 3, 4])));
  assertEquals("test!!!", sum(...makeArray(..."test!!!")));

  // Interleaved spread/unspread args
  assertEquals(36, sum(0, ...[1], 2, 3, ...[4, 5], 6, 7, 8));
  assertEquals(45, sum(0, ...[1], 2, 3, ...[4, 5], 6, 7, 8, ...[9]));

  // Methods
  var O = {
    returnThis: returnThis,
    countArgs: countArgs,
    sum: sum,
    makeArray: makeArray,

    nested: {
      returnThis: returnThis,
      countArgs: countArgs,
      sum: sum,
      makeArray: makeArray
    }
  };

  // Test this argument
  assertEquals(O, O.returnThis(..."test"));
  assertEquals(O, O["returnThis"](..."test"));
  assertEquals(O.nested, O.nested.returnThis(..."test"));
  assertEquals(O.nested, O.nested["returnThis"](..."test"));

  // Test argument counting with different iterables
  assertEquals(0, O.countArgs(...""));
  assertEquals(4, O.countArgs(..."test"));
  assertEquals(4, O.countArgs(..."tes", ..."t"));
  assertEquals(4, O.countArgs("t", ..."es", "t"));
  assertEquals(4, O.countArgs("tes", ..."t!!"));

  assertEquals(1, O.countArgs(...[1]));
  assertEquals(2, O.countArgs(...[1, 2]));
  assertEquals(3, O.countArgs(...[1, 2, 3]));
  assertEquals(4, O.countArgs(...[1, 2, 3, 4]));
  assertEquals(5, O.countArgs(...[1, 2, 3, 4, 5]));
  assertEquals(6, O.countArgs(...[1, 2, 3, 4, 5, 6]));

  assertEquals(1, O.countArgs(...new Set([1])));
  assertEquals(2, O.countArgs(...new Set([1, 2])));
  assertEquals(3, O.countArgs(...new Set([1, 2, 3])));
  assertEquals(4, O.countArgs(...new Set([1, 2, 3, 4])));
  assertEquals(5, O.countArgs(...new Set([1, 2, 3, 4, 5])));
  assertEquals(6, O.countArgs(...new Set([1, 2, 3, 4, 5, 6])));

  assertEquals(3, O.countArgs(
      ...(function*(){ yield 1; yield 2; yield 3; })()));

  // Test values
  assertEquals(void 0, O.sum(...""));
  assertEquals(void 0, O.sum(...[]));
  assertEquals(void 0, O.sum(...new Set));
  assertEquals(void 0, O.sum(...(function*() { })()));

  assertEquals("test", O.sum(..."test"));
  assertEquals(10, O.sum(...[1, 2, 3, 4]));
  assertEquals(10, O.sum(...[1, 2, 3], 4));
  assertEquals(10, O.sum(1, ...[2, 3], 4));
  assertEquals(10, O.sum(1, ...[2, 3], ...[4]));
  assertEquals(10, O.sum(1, 2, ...[3, 4]));
  assertEquals(10, O.sum(...new Set([1, 2, 3, 4])));
  assertEquals(10, O.sum(...(function*() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
  })()));

  // nested spreads
  assertEquals(10, O.sum(...O.makeArray(...[1, 2, 3, 4])));
  assertEquals("test!!!", O.sum(...O.makeArray(..."test!!!")));

  // Interleaved spread/unspread args
  assertEquals(36, O.sum(0, ...[1], 2, 3, ...[4, 5], 6, 7, 8));
  assertEquals(45, O.sum(0, ...[1], 2, 3, ...[4, 5], 6, 7, 8, ...[9]));
})();


(function testSpreadCallsSloppy() {
  // Test this argument
  function returnThis() { return this; }
  assertEquals(this, returnThis(..."test"));

  function countArgs() { return arguments.length; }

  // Test argument counting with different iterables
  assertEquals(0, countArgs(...""));
  assertEquals(4, countArgs(..."test"));
  assertEquals(4, countArgs(..."tes", ..."t"));
  assertEquals(4, countArgs("t", ..."es", "t"));
  assertEquals(4, countArgs("tes", ..."t!!"));

  assertEquals(1, countArgs(...[1]));
  assertEquals(2, countArgs(...[1, 2]));
  assertEquals(3, countArgs(...[1, 2, 3]));
  assertEquals(4, countArgs(...[1, 2, 3, 4]));
  assertEquals(5, countArgs(...[1, 2, 3, 4, 5]));
  assertEquals(6, countArgs(...[1, 2, 3, 4, 5, 6]));

  assertEquals(1, countArgs(...new Set([1])));
  assertEquals(2, countArgs(...new Set([1, 2])));
  assertEquals(3, countArgs(...new Set([1, 2, 3])));
  assertEquals(4, countArgs(...new Set([1, 2, 3, 4])));
  assertEquals(5, countArgs(...new Set([1, 2, 3, 4, 5])));
  assertEquals(6, countArgs(...new Set([1, 2, 3, 4, 5, 6])));

  assertEquals(3, countArgs(...(function*(){
    yield 1;
    yield 2;
    yield 3;
  })()));

  // Test values
  function sum() {
    var sum = arguments[0];
    for (var i = 1; i < arguments.length; ++i) {
      sum += arguments[i];
    }
    return sum;
  }

  assertThrows(function() {
    sum(...0);
  }, TypeError);
  assertEquals(void 0, sum(...""));
  assertEquals(void 0, sum(...[]));
  assertEquals(void 0, sum(...new Set));
  assertEquals(void 0, sum(...(function*() { })()));

  assertEquals("test", sum(..."test"));
  assertEquals(10, sum(...[1, 2, 3, 4]));
  assertEquals(10, sum(...[1, 2, 3], 4));
  assertEquals(10, sum(1, ...[2, 3], 4));
  assertEquals(10, sum(1, ...[2, 3], ...[4]));
  assertEquals(10, sum(1, 2, ...[3, 4]));
  assertEquals(10, sum(...new Set([1, 2, 3, 4])));
  assertEquals(10, sum(...(function*() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
  })()));

  // nested spreads
  function makeArray() {
    var result = [];
    for (var i = 0; i < arguments.length; ++i) {
      result.push(arguments[i]);
    }
    return result;
  }
  assertEquals(10, sum(...makeArray(...[1, 2, 3, 4])));
  assertEquals("test!!!", sum(...makeArray(..."test!!!")));

  // Interleaved spread/unspread args
  assertEquals(36, sum(0, ...[1], 2, 3, ...[4, 5], 6, 7, 8));
  assertEquals(45, sum(0, ...[1], 2, 3, ...[4, 5], 6, 7, 8, ...[9]));

  // Methods
  var O = {
    returnThis: returnThis,
    countArgs: countArgs,
    sum: sum,
    makeArray: makeArray,

    nested: {
      returnThis: returnThis,
      countArgs: countArgs,
      sum: sum,
      makeArray: makeArray
    }
  };

  // Test this argument
  assertEquals(O, O.returnThis(..."test"));
  assertEquals(O, O["returnThis"](..."test"));
  assertEquals(O.nested, O.nested.returnThis(..."test"));
  assertEquals(O.nested, O.nested["returnThis"](..."test"));

  // Test argument counting with different iterables
  assertEquals(0, O.countArgs(...""));
  assertEquals(4, O.countArgs(..."test"));
  assertEquals(4, O.countArgs(..."tes", ..."t"));
  assertEquals(4, O.countArgs("t", ..."es", "t"));
  assertEquals(4, O.countArgs("tes", ..."t!!"));

  assertEquals(1, O.countArgs(...[1]));
  assertEquals(2, O.countArgs(...[1, 2]));
  assertEquals(3, O.countArgs(...[1, 2, 3]));
  assertEquals(4, O.countArgs(...[1, 2, 3, 4]));
  assertEquals(5, O.countArgs(...[1, 2, 3, 4, 5]));
  assertEquals(6, O.countArgs(...[1, 2, 3, 4, 5, 6]));

  assertEquals(1, O.countArgs(...new Set([1])));
  assertEquals(2, O.countArgs(...new Set([1, 2])));
  assertEquals(3, O.countArgs(...new Set([1, 2, 3])));
  assertEquals(4, O.countArgs(...new Set([1, 2, 3, 4])));
  assertEquals(5, O.countArgs(...new Set([1, 2, 3, 4, 5])));
  assertEquals(6, O.countArgs(...new Set([1, 2, 3, 4, 5, 6])));

  assertEquals(3, O.countArgs(...(function*(){
    yield 1;
    yield 2;
    yield 3;
  })()));

  // Test values
  assertEquals(void 0, O.sum(...""));
  assertEquals(void 0, O.sum(...[]));
  assertEquals(void 0, O.sum(...new Set));
  assertEquals(void 0, O.sum(...(function*() { })()));

  assertEquals("test", O.sum(..."test"));
  assertEquals(10, O.sum(...[1, 2, 3, 4]));
  assertEquals(10, O.sum(...[1, 2, 3], 4));
  assertEquals(10, O.sum(1, ...[2, 3], 4));
  assertEquals(10, O.sum(1, ...[2, 3], ...[4]));
  assertEquals(10, O.sum(1, 2, ...[3, 4]));
  assertEquals(10, O.sum(...new Set([1, 2, 3, 4])));
  assertEquals(10, O.sum(...(function*() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
  })()));

  // nested spreads
  assertEquals(10, O.sum(...O.makeArray(...[1, 2, 3, 4])));
  assertEquals("test!!!", O.sum(...O.makeArray(..."test!!!")));

  // Interleaved spread/unspread args
  assertEquals(36, O.sum(0, ...[1], 2, 3, ...[4, 5], 6, 7, 8));
  assertEquals(45, O.sum(0, ...[1], 2, 3, ...[4, 5], 6, 7, 8, ...[9]));
})();


(function testSpreadEvaluationOrder() {
  "use strict";
  var log = "";
  function* gen() { log += "X"; yield 0; log += "Y"; }
  function a() { log += "A"; }
  function b() { log += "B"; return gen(); }
  function* c() { log += 'C1'; yield 1; log += 'C2'; }
  function d() { log += "D"; }
  function e() { log += "E"; }
  function fn() {
    var args = [];
    for (var i = 0; i < arguments.length; ++i) args.push(arguments[i]);
    return args;
  }

  var result = fn(a(), ...b(), d());
  assertEquals([undefined, 0, undefined], result);
  assertEquals("ABXYD", log);

  log = "";
  result = fn(...b(), d());
  assertEquals([0, undefined], result);
  assertEquals("BXYD", log);

  log = "";
  result = fn(a(), ...b());
  assertEquals([undefined, 0], result);
  assertEquals("ABXY", log);

  log = "";
  result = fn(a(), ...b(), ...c(), d(), e());
  assertEquals([undefined, 0, 1, undefined, undefined], result);
  assertEquals("ABXYC1C2DE", log);

  log = "";
  result = fn(a(), ...b(), ...c(), d(), e(), ...b(), ...c());
  assertEquals([undefined, 0, 1, undefined, undefined, 0, 1], result);
  assertEquals("ABXYC1C2DEBXYC1C2", log);
})();

(function testArrayPrototypeHoleGetterModifiesIteratorPrototypeNext() {
  function sum() {
    var sum = arguments[0];
    for (var i = 1; i < arguments.length; ++i) {
      sum += arguments[i];
    }
    return sum;
  }
  var a = [1, 2];
  a[3] = 4;
  var called = 0;

  Object.defineProperty(Array.prototype, 2, {
    get: function() {
      var ai = a[Symbol.iterator]();
      var original_next = ai.__proto__["next"];
      Object.defineProperty(ai.__proto__, "next", {
        get: function() {
          called++;
          return original_next;
        }
      });
      return 3;
    },
    configurable: true
  });

  assertEquals(10, sum(...a));
  assertEquals(2, called);

  Object.defineProperty(Array.prototype, 2, {});
})();

(function testArrayHasOtherPrototype() {
  function countArgs() { return arguments.length; }
  var a = [1, 2, 3];
  var b = {};
  Object.defineProperty(b, Symbol.iterator, {
    value: function*() {
      yield 4;
    },
    configurable: true
  });

  Object.setPrototypeOf(a, b);

  assertEquals(1, countArgs(...a));
})();

(function testArrayIteratorPrototypeGetter() {
  function countArgs() { return arguments.length; }
  var a = [1, 2, 3];
  var ai = a[Symbol.iterator]();
  var called = 0;

  var original_next = ai.__proto__["next"];

  Object.defineProperty(ai.__proto__, "next", {
    get: function() {
      called++;
      return original_next;
    }
  });

  countArgs(...a);

  // should be called 4 times; 3 for the values, 1 for the final
  // {value: undefined, done: true} pair
  assertEquals(4, called);
})();

(function testArrayIteratorPrototypeModified() {
  function countArgs() { return arguments.length; }
  var a = [1,2,3];
  var ai = a[Symbol.iterator]();
  Object.defineProperty(ai.__proto__, "next", {
    value: function() {
      return {value: undefined, done: true};
     },
     configurable: true
  });

  assertEquals(0, countArgs(...a));

})();

(function testCustomArrayPrototypeIterator() {
  var origIterator =
      Object.getOwnPropertyDescriptor(Array.prototype, Symbol.iterator);
  Object.defineProperty(Array.prototype, Symbol.iterator, {
    value: function*() {
      yield 1;
      yield 2;
      yield 3;
    },
    configurable: true
  });
  function returnCountStrict() { 'use strict'; return arguments.length; }
  function returnCountSloppy() { return arguments.length; }

  assertEquals(3, returnCountStrict(...[1]));
  assertEquals(4, returnCountStrict(1, ...[2]));
  assertEquals(5, returnCountStrict(1, ...[2], 3));
  assertEquals(3, returnCountSloppy(...[1]));
  assertEquals(4, returnCountSloppy(1, ...[2]));
  assertEquals(5, returnCountSloppy(1, ...[2], 3));

  Object.defineProperty(Array.prototype, Symbol.iterator, origIterator);
})();

(function testGetPropertyIteratorCalledExactlyOnce() {
  function countArgs() { return arguments.length; }
  var a = [1, 2, 3];
  var called = 0;

  Object.defineProperty(Array.prototype, Symbol.iterator, {
    value: function*() {
      yield 1;
      yield 2;
    },
    configurable: true
  });

  var it = a[Symbol.iterator];
  Object.defineProperty(a, Symbol.iterator, {
    get: function() {
      called++;
      return it;
    }
  });

  countArgs(...a);

  assertEquals(1, called);
})();