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

#include 'src/builtins/builtins-typed-array-gen.h'

namespace typed_array {
const kBuiltinNameFrom: constexpr string = '%TypedArray%.from';

type BuiltinsName extends int31 constexpr 'Builtins::Name';
const kTypedArrayPrototypeValues: constexpr BuiltinsName
    generates 'Builtins::kTypedArrayPrototypeValues';
const kArrayPrototypeValues: constexpr BuiltinsName
    generates 'Builtins::kArrayPrototypeValues';

extern builtin IterableToList(implicit context: Context)(JSAny, JSAny): JSArray;

// %TypedArray%.from ( source [ , mapfn [ , thisArg ] ] )
// https://tc39.github.io/ecma262/#sec-%typedarray%.from
transitioning javascript builtin
TypedArrayFrom(js-implicit context: NativeContext, receiver: JSAny)(
    ...arguments): JSTypedArray {
  try {
    const source: JSAny = arguments[0];
    const mapfnObj: JSAny = arguments[1];
    const thisArg = arguments[2];

    // 1. Let C be the this value.
    // 2. If IsConstructor(C) is false, throw a TypeError exception.
    const constructor = Cast<Constructor>(receiver) otherwise NotConstructor;

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

    // We split up this builtin differently to the way it is written in the
    // spec. We already have great code in the elements accessor for copying
    // from a JSArray into a TypedArray, so we use that when possible. We only
    // avoid calling into the elements accessor when we have a mapping
    // function, because we can't handle that. Here, presence of a mapping
    // function is the slow path. We also combine the two different loops in
    // the specification (starting at 7.e and 13) because they are essentially
    // identical. We also save on code-size this way.

    let finalLength: uintptr;
    let finalSource: JSAny;

    try {
      // 5. Let usingIterator be ? GetMethod(source, @@iterator).
      // TODO(v8:8906): Use iterator::GetIteratorMethod() once it supports
      // labels.
      const usingIterator = GetMethod(source, IteratorSymbolConstant())
          otherwise IteratorIsUndefined, IteratorNotCallable;

      try {
        // TypedArrays and JSArrays have iterators, so normally we would go
        // through the IterableToList case below, which would convert the
        // source to a JSArray (boxing the values if they won't fit in a Smi).
        //
        // However, if we can guarantee that the source object has the
        // built-in iterator and that the %ArrayIteratorPrototype%.next method
        // has not been overridden, then we know the behavior of the iterator:
        // returning the values in the TypedArray sequentially from index 0 to
        // length-1.
        //
        // In this case, we can avoid creating the intermediate array and the
        // associated HeapNumbers, and use the fast path in
        // TypedArrayCopyElements which uses the same ordering as the default
        // iterator.
        //
        // Drop through to the default check_iterator behavior if any of these
        // checks fail.

        // If there is a mapping, we need to gather the values from the
        // iterables before applying the mapping.
        if (mapping) goto UseUserProvidedIterator;

        const iteratorFn =
            Cast<JSFunction>(usingIterator) otherwise UseUserProvidedIterator;

        // Check that the ArrayIterator prototype's "next" method hasn't been
        // overridden.
        if (IsArrayIteratorProtectorCellInvalid()) goto UseUserProvidedIterator;

        typeswitch (source) {
          case (sourceArray: JSArray): {
            // Check that the iterator function is exactly
            // Builtins::kArrayPrototypeValues.
            if (!TaggedEqual(
                    iteratorFn.shared_function_info.function_data,
                    SmiConstant(kArrayPrototypeValues))) {
              goto UseUserProvidedIterator;
            }

            // Source is a JSArray with unmodified iterator behavior. Use the
            // source object directly, taking advantage of the special-case code
            // in TypedArrayCopyElements
            finalLength = Convert<uintptr>(sourceArray.length);
            finalSource = source;
          }
          case (sourceTypedArray: JSTypedArray): {
            const sourceBuffer = sourceTypedArray.buffer;
            if (IsDetachedBuffer(sourceBuffer)) goto UseUserProvidedIterator;

            // Check that the iterator function is exactly
            // Builtins::kTypedArrayPrototypeValues.
            if (!TaggedEqual(
                    iteratorFn.shared_function_info.function_data,
                    SmiConstant(kTypedArrayPrototypeValues)))
              goto UseUserProvidedIterator;

            // Source is a TypedArray with unmodified iterator behavior. Use the
            // source object directly, taking advantage of the special-case code
            // in TypedArrayCopyElements
            finalLength = sourceTypedArray.length;
            finalSource = source;
          }
          case (Object): {
            goto UseUserProvidedIterator;
          }
        }
      } label UseUserProvidedIterator {
        // 6. If usingIterator is not undefined, then
        //  a. Let values be ? IterableToList(source, usingIterator).
        //  b. Let len be the number of elements in values.
        const values: JSArray = IterableToList(source, usingIterator);

        finalLength = Convert<uintptr>(values.length);
        finalSource = values;
      }
    } label IteratorIsUndefined {
      // 7. NOTE: source is not an Iterable so assume it is already an
      // array-like object.

      // 8. Let arrayLike be ! ToObject(source).
      const arrayLike: JSReceiver = ToObject_Inline(context, source);

      // 9. Let len be ? LengthOfArrayLike(arrayLike).
      const length = GetLengthProperty(arrayLike);

      try {
        finalLength = ChangeSafeIntegerNumberToUintPtr(length)
            otherwise IfInvalidLength;
        finalSource = arrayLike;
      } label IfInvalidLength deferred {
        ThrowRangeError(MessageTemplate::kInvalidTypedArrayLength, length);
      }
    } label IteratorNotCallable(_value: JSAny) deferred {
      ThrowTypeError(MessageTemplate::kIteratorSymbolNonCallable);
    }

    const finalLengthNum = Convert<Number>(finalLength);

    // 6c/10. Let targetObj be ? TypedArrayCreate(C, «len»).
    const targetObj =
        TypedArrayCreateByLength(constructor, finalLengthNum, kBuiltinNameFrom);

    if (!mapping) {
      // Fast path.
      if (finalLength != 0) {
        // Call runtime.
        TypedArrayCopyElements(context, targetObj, finalSource, finalLengthNum);
      }
      return targetObj;
    }
    // Slow path.

    const mapfn: Callable = Cast<Callable>(mapfnObj) otherwise unreachable;
    const accessor: TypedArrayAccessor =
        GetTypedArrayAccessor(targetObj.elements_kind);

    // 6d-6e and 11-12.
    // 11. Let k be 0.
    // 12. Repeat, while k < len
    for (let k: uintptr = 0; k < finalLength; k++) {
      // 12a. Let Pk be ! ToString(k).
      const kNum = Convert<Number>(k);

      // 12b. Let kValue be ? Get(arrayLike, Pk).
      const kValue: JSAny = GetProperty(finalSource, kNum);

      let mappedValue: JSAny;
      // 12c. If mapping is true, then
      if (mapping) {
        // i. Let mappedValue be ? Call(mapfn, T, « kValue, k »).
        mappedValue = Call(context, mapfn, thisArg, kValue, kNum);
      } else {
        // 12d. Else, let mappedValue be kValue.
        mappedValue = kValue;
      }

      // 12e. Perform ? Set(targetObj, Pk, mappedValue, true).
      // Buffer may be detached during executing ToNumber/ToBigInt.
      accessor.StoreJSAny(context, targetObj, k, mappedValue)
          otherwise IfDetached;

      // 12f. Set k to k + 1. (done by the loop).
    }
    return targetObj;
  } label NotConstructor deferred {
    ThrowTypeError(MessageTemplate::kNotConstructor, receiver);
  } label IfDetached deferred {
    ThrowTypeError(MessageTemplate::kDetachedOperation, kBuiltinNameFrom);
  }
}
}