// Copyright 2018 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 {
extern builtin ArrayUnshift(Context, JSFunction, JSAny, int32): JSAny;

transitioning macro GenericArrayUnshift(
    context: Context, receiver: JSAny, arguments: Arguments): Number {
  // 1. Let O be ? ToObject(this value).
  const object: JSReceiver = ToObject_Inline(context, receiver);

  // 2. Let len be ? ToLength(? Get(O, "length")).
  const length: Number = GetLengthProperty(object);

  // 3. Let argCount be the number of actual arguments.
  const argCount: Smi = Convert<Smi>(arguments.length);

  // 4. If argCount > 0, then.
  if (argCount > 0) {
    // a. If len + argCount > 2**53 - 1, throw a TypeError exception.
    if (length + argCount > kMaxSafeInteger) {
      ThrowTypeError(MessageTemplate::kInvalidArrayLength);
    }

    // b. Let k be len.
    let k: Number = length;

    // c. Repeat, while k > 0.
    while (k > 0) {
      // i. Let from be ! ToString(k - 1).
      const from: Number = k - 1;

      // ii. Let to be ! ToString(k + argCount - 1).
      const to: Number = k + argCount - 1;

      // iii. Let fromPresent be ? HasProperty(O, from).
      const fromPresent: Boolean = HasProperty(object, from);

      // iv. If fromPresent is true, then
      if (fromPresent == True) {
        // 1. Let fromValue be ? Get(O, from).
        const fromValue: JSAny = GetProperty(object, from);

        // 2. Perform ? Set(O, to, fromValue, true).
        SetProperty(object, to, fromValue);
      } else {
        // 1. Perform ? DeletePropertyOrThrow(O, to).
        DeleteProperty(object, to, LanguageMode::kStrict);
      }

      // vi. Decrease k by 1.
      --k;
    }

    // d. Let j be 0.
    let j: Smi = 0;

    // e. Let items be a List whose elements are, in left to right order,
    //    the arguments that were passed to this function invocation.
    // f. Repeat, while items is not empty
    while (j < argCount) {
      // ii .Perform ? Set(O, ! ToString(j), E, true).
      SetProperty(object, j, arguments[Convert<intptr>(j)]);

      // iii. Increase j by 1.
      ++j;
    }
  }

  // 5. Perform ? Set(O, "length", len + argCount, true).
  const newLength: Number = length + argCount;
  SetProperty(object, kLengthString, newLength);

  // 6. Return length + argCount.
  return newLength;
}

// https://tc39.github.io/ecma262/#sec-array.prototype.unshift
transitioning javascript builtin ArrayPrototypeUnshift(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  try {
    const array: FastJSArray = Cast<FastJSArray>(receiver) otherwise Slow;
    array::EnsureWriteableFastElements(array);

    const map: Map = array.map;
    if (!IsExtensibleMap(map)) goto Slow;
    EnsureArrayLengthWritable(map) otherwise Slow;

    tail ArrayUnshift(
        context, LoadTargetFromFrame(), Undefined,
        Convert<int32>(arguments.length));
  } label Slow {
    return GenericArrayUnshift(context, receiver, arguments);
  }
}
}