// 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.

// Flags: --allow-natives-syntax

var typedArrayConstructors = [
  Uint8Array,
  Int8Array,
  Uint16Array,
  Int16Array,
  Uint32Array,
  Int32Array,
  Uint8ClampedArray,
  Float32Array,
  Float64Array
];

function defaultValue(constr) {
  if (constr == Float32Array || constr == Float64Array) return NaN;
  return 0;
}

function assertArrayLikeEquals(value, expected, type) {
  assertEquals(value.__proto__, type.prototype);
  assertEquals(expected.length, value.length);
  for (var i = 0; i < value.length; ++i) {
    assertEquals(expected[i], value[i]);
  }
}

(function() {
  var source = [-0, 0, 2**40, 2**41, 2**42];
  var arr = Float64Array.from(source);
  assertArrayLikeEquals(arr, source, Float64Array);

  arr = Float32Array.from(source);
  assertArrayLikeEquals(arr, source, Float32Array);
})();

(function() {
  var source = [-0, 0, , 2**42];
  var expected = [-0, 0, NaN, 2**42];
  var arr = Float64Array.from(source);
  assertArrayLikeEquals(arr, expected, Float64Array);

  arr = Float32Array.from(source);
  assertArrayLikeEquals(arr, expected, Float32Array);
})();

(function() {
  var source = {0: -0, 1: 0, 2: 2**40, 3: 2**41, 4: 2**42, length: 5};
  var expected = [-0, 0, 2**40, 2**41, 2**42];
  var arr = Float64Array.from(source);
  assertArrayLikeEquals(arr, expected, Float64Array);

  arr = Float32Array.from(source);
  assertArrayLikeEquals(arr, expected, Float32Array);
})();

(function() {
  var source = [-0, 0, , 2**42];
  Object.getPrototypeOf(source)[2] = 27;
  var expected = [-0, 0, 27, 2**42];
  var arr = Float64Array.from(source);
  assertArrayLikeEquals(arr, expected, Float64Array);

  arr = Float32Array.from(source);
  assertArrayLikeEquals(arr, expected, Float32Array);
})();

for (var constructor of typedArrayConstructors) {
  assertEquals(1, constructor.from.length);

  // TypedArray.from only callable on this subclassing %TypedArray%
  assertThrows(function () {constructor.from.call(Array, [])}, TypeError);


  // Assert that calling mapfn with / without thisArg in sloppy and strict modes
  // works as expected.
  var global = this;
  function non_strict() { assertEquals(global, this); }
  function strict() { 'use strict'; assertEquals(undefined, this); }
  function strict_null() { 'use strict'; assertEquals(null, this); }
  constructor.from([1], non_strict);
  constructor.from([1], non_strict, void 0);
  constructor.from([1], non_strict, null);
  constructor.from([1], strict);
  constructor.from([1], strict, void 0);
  constructor.from([1], strict_null, null);

  // TypedArray.from can only be called on TypedArray constructors
  assertThrows(function() {constructor.from.call({}, [])}, TypeError);
  assertThrows(function() {constructor.from.call([], [])}, TypeError);
  assertThrows(function() {constructor.from.call(1, [])}, TypeError);
  assertThrows(function() {constructor.from.call(undefined, [])}, TypeError);

  // Use a map function that returns non-numbers.
  function mapper(value, index) {
    return String.fromCharCode(value);
  }
  var d = defaultValue(constructor);
  assertArrayLikeEquals(
      constructor.from([72, 69, 89], mapper), [d, d, d], constructor);

  // Converting from various other types, demonstrating that it can
  // operate on array-like objects as well as iterables.
  // TODO(littledan): constructors should have similar flexibility.
  assertArrayLikeEquals(constructor.from(
      { length: 1, 0: 5 }), [5], constructor);

  assertArrayLikeEquals(constructor.from(
      { length: -1, 0: 5 }), [], constructor);

  assertArrayLikeEquals(constructor.from(
      [1, 2, 3]), [1, 2, 3], constructor);

  var set = new Set([1, 2, 3]);
  assertArrayLikeEquals(constructor.from(set), [1, 2, 3],
        constructor);

  function* generator() {
    yield 4;
    yield 5;
    yield 6;
  }

  assertArrayLikeEquals(constructor.from(generator()),
                        [4, 5, 6], constructor);

  // Check mapper is used for non-iterator case.
  function mapper2(value, index) {
    return value + 1;
  }
  var array_like = {
    0: 1,
    1: 2,
    2: 3,
    length: 3
  };
  assertArrayLikeEquals(constructor.from(array_like, mapper2),
                        [2, 3, 4], constructor);

  // With a smi source. Step 10 will set len = 0.
  var source = 1;
  assertArrayLikeEquals(constructor.from(source), [], constructor);

  assertThrows(function() { constructor.from(null); }, TypeError);
  assertThrows(function() { constructor.from(undefined); }, TypeError);
  assertThrows(function() { constructor.from([], null); }, TypeError);
  assertThrows(function() { constructor.from([], 'noncallable'); },
               TypeError);

  source = [1, 2, 3];
  var proxy = new Proxy(source, {});
  assertArrayLikeEquals(constructor.from(proxy), source, constructor);

  proxy = new Proxy(source, {
    get: function(target, name) {
      if (name === Symbol.iterator) return target[name];
      if (name === "length") return 3;
      return target[name] + 1;
    }
  });
  assertArrayLikeEquals(constructor.from(proxy), [2, 3, 4], constructor);
}

// Tests that modify global state in a way that affects fast paths e.g. by
// invalidating protectors or changing prototypes.
for (var constructor of typedArrayConstructors) {
  source = [1, 2, 3];
  source[Symbol.iterator] = undefined;
  assertArrayLikeEquals(constructor.from(source), source, constructor);

  source = [{ valueOf: function(){ return 42; }}];
  source[Symbol.iterator] = undefined;
  assertArrayLikeEquals(constructor.from(source), [42], constructor);

  Number.prototype[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
  }
  assertArrayLikeEquals(constructor.from(1), [1, 2, 3], constructor);
  assertArrayLikeEquals(constructor.from(1.1), [1, 2, 3], constructor);

  var nullIterator = {};
  nullIterator[Symbol.iterator] = null;
  assertArrayLikeEquals(constructor.from(nullIterator), [],
                        constructor);

  var nonObjIterator = {};
  nonObjIterator[Symbol.iterator] = function() { return 'nonObject'; };
  assertThrows(function() { constructor.from(nonObjIterator); },
               TypeError);

  assertThrows(function() { constructor.from([], null); }, TypeError);

  d = defaultValue(constructor);
  let ta1 = new constructor(3).fill(1);
  Object.defineProperty(ta1, "length", {get: function() {
    return 6;
  }});
  delete ta1[Symbol.iterator];
  delete ta1.__proto__[Symbol.iterator];
  delete ta1.__proto__.__proto__[Symbol.iterator];
  assertArrayLikeEquals(constructor.from(ta1), [1, 1, 1, d, d, d], constructor);

  let ta2 = new constructor(3).fill(1);
  Object.defineProperty(ta2, "length", {get: function() {
    %ArrayBufferDetach(ta2.buffer);
    return 6;
  }});
  assertArrayLikeEquals(constructor.from(ta2), [d, d, d, d, d, d], constructor);

  var o1 = {0: 0, 1: 1, 2: 2, length: 6};
  assertArrayLikeEquals(constructor.from(o1), [0, 1, 2, d, d, d], constructor);

  // Ensure iterator is only accessed once, and only invoked once
  var called = 0;
  var arr = [1, 2, 3];
  var obj = {};
  var counter = 0;

  // Test order --- only get iterator method once
  function testIterator() {
    called++;
    assertEquals(obj, this);
    return arr[Symbol.iterator]();
  }
  var getCalled = 0;
  Object.defineProperty(obj, Symbol.iterator, {
    get: function() {
      getCalled++;
      return testIterator;
    },
    set: function() {
      assertUnreachable('@@iterator should not be set');
    }
  });
  assertArrayLikeEquals(constructor.from(obj), [1, 2, 3], constructor);
  assertEquals(getCalled, 1);
  assertEquals(called, 1);

  assertEquals(constructor, Uint8Array.from.call(constructor, [1]).constructor);
  assertEquals(Uint8Array, constructor.from.call(Uint8Array, [1]).constructor);
}