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

namespace array {
// Array.from( items [, mapfn [, thisArg ] ] )
// ES #sec-array.from
transitioning javascript builtin
ArrayFrom(js-implicit context: NativeContext, receiver: JSAny)(...arguments):
    JSReceiver {
  // Use fast path if:
  // * |items| is the only argument, and
  // * the receiver is the Array function.
  if (arguments.length == 1 && receiver == GetArrayFunction()) {
    try {
      return iterator::FastIterableToList(arguments[0]) otherwise Slow;
    } label Slow {
      // fall through
    }
  }

  const items = arguments[0];
  const mapfn = arguments[1];
  const thisArg = arguments[2];

  // 1. Let C be the this value.
  const c = receiver;

  let mapping: bool;
  // 2. If mapfn is undefined, let mapping be false.
  if (mapfn == Undefined) {
    mapping = false;
  } else {
    // a. If IsCallable(mapfn) is false, throw a TypeError exception.
    if (!Is<Callable>(mapfn)) deferred {
        ThrowTypeError(MessageTemplate::kCalledNonCallable, mapfn);
      }
    // b. Let mapping be true.
    mapping = true;
  }

  // 4. Let usingIterator be ? GetMethod(items, @@iterator).
  // 5. If usingIterator is not undefined, then
  try {
    const usingIterator = GetMethod(items, IteratorSymbolConstant())
        otherwise IteratorIsUndefined, IteratorNotCallable;

    let a: JSReceiver;
    // a. If IsConstructor(C) is true, then
    typeswitch (c) {
      case (c: Constructor): {
        // i. Let A be ? Construct(C).
        a = Construct(c);
      }
      case (JSAny): {
        // i. Let A be ? ArrayCreate(0).
        a = ArrayCreate(0);
      }
    }

    // c. Let iteratorRecord be ? GetIterator(items, sync, usingIterator).
    const iteratorRecord = iterator::GetIterator(items, usingIterator);

    const fastIteratorResultMap = GetIteratorResultMap();

    // d. Let k be 0.
    let k: Smi = 0;
    // e. Repeat,
    while (true) {
      // i. If k ≥ 2^53-1, then
      //   1. Let error be ThrowCompletion(a newly created TypeError object).
      //   2. Return ? IteratorClose(iteratorRecord, error).
      // The spec requires that we throw an exception if index reaches 2^53-1,
      // but an empty loop would take >100 days to do this many iterations. To
      // actually run for that long would require an iterator that never set
      // done to true and a target array which somehow never ran out of
      // memory, e.g. a proxy that discarded the values. Ignoring this case
      // just means we would repeatedly call CreateDataProperty with index =
      // 2^53
      assert(k < kMaxSafeInteger);

      // ii. Let Pk be ! ToString(k).

      // iii. Let next be ? IteratorStep(iteratorRecord).
      let next: JSReceiver;
      try {
        next = iterator::IteratorStep(iteratorRecord, fastIteratorResultMap)
            otherwise NextIsFalse;
      }
      // iv. If next is false, then
      label NextIsFalse {
        // 1. Perform ? Set(A, "length", k, true).
        array::SetPropertyLength(a, k);
        // 2. Return A.
        return a;
      }

      // v. Let nextValue be ? IteratorValue(next).
      const nextValue = iterator::IteratorValue(next, fastIteratorResultMap);

      let mappedValue: JSAny;
      // vi. If mapping is true, then
      if (mapping) {
        // 1. Let mappedValue be Call(mapfn, thisArg, « nextValue, k »).
        // 2. If mappedValue is an abrupt completion,
        //    return ? IteratorClose(iteratorRecord, mappedValue).
        // 3. Set mappedValue to mappedValue.[[Value]].
        try {
          mappedValue =
              Call(context, UnsafeCast<Callable>(mapfn), thisArg, nextValue, k);
        } catch (e) {
          iterator::IteratorCloseOnException(iteratorRecord);
          ReThrow(context, e);
        }
      } else {
        mappedValue = nextValue;
      }
      // viii. Let defineStatus be
      //       CreateDataPropertyOrThrow(A, Pk, mappedValue).
      // ix. If defineStatus is an abrupt completion,
      //     return ? IteratorClose(iteratorRecord, defineStatus).
      try {
        FastCreateDataProperty(a, k, mappedValue);
      } catch (e) deferred {
        iterator::IteratorCloseOnException(iteratorRecord);
        ReThrow(context, e);
      }
      // x. Set k to k + 1.
      k += 1;
    }
    unreachable;
  } label IteratorIsUndefined {
    // 6. NOTE: items is not an Iterable so assume it is an array-like object.
    // 7. Let arrayLike be ! ToObject(items).
    const arrayLike = ToObject_Inline(context, items);
    // 8. Let len be ? LengthOfArrayLike(arrayLike).
    const len = GetLengthProperty(arrayLike);

    let a: JSReceiver;
    // 9. If IsConstructor(C) is true, then
    typeswitch (c) {
      case (c: Constructor): {
        // a. Let A be ? Construct(C, « len »).
        a = Construct(c, len);
      }
      case (JSAny): {
        // a. Let A be ? ArrayCreate(len).
        a = ArrayCreate(len);
      }
    }

    // 11. Let k be 0.
    let k: Smi = 0;
    // 12. Repeat, while k < len
    while (k < len) {
      // a. Let Pk be ! ToString(k).
      // b. Let kValue be ? Get(arrayLike, Pk).
      const kValue = GetProperty(arrayLike, k);
      let mappedValue: JSAny;
      // c. If mapping is true, then
      if (mapping) {
        // i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, k »).
        mappedValue =
            Call(context, UnsafeCast<Callable>(mapfn), thisArg, kValue, k);
      } else {
        // d. Else, let mappedValue be kValue.
        mappedValue = kValue;
      }
      // e. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
      FastCreateDataProperty(a, k, mappedValue);
      // f. Set k to k + 1.
      k += 1;
    }

    // 13. Perform ? Set(A, "length", len, true).
    array::SetPropertyLength(a, len);
    // 14. Return A.
    return a;
  } label IteratorNotCallable(_value: JSAny) deferred {
    ThrowTypeError(MessageTemplate::kIteratorSymbolNonCallable);
  }
}
}