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

macro TryFastArrayShift(implicit context: Context)(receiver: JSAny): JSAny
    labels Slow, Runtime {
  const array: FastJSArray = Cast<FastJSArray>(receiver) otherwise Slow;
  let witness = NewFastJSArrayWitness(array);

  witness.EnsureArrayPushable() otherwise Slow;

  if (array.length == 0) {
    return Undefined;
  }

  const newLength = array.length - 1;

  // Check that we're not supposed to right-trim the backing store, as
  // implemented in elements.cc:ElementsAccessorBase::SetLengthImpl.
  if ((newLength + newLength + kMinAddedElementsCapacity) <
      array.elements.length) {
    goto Runtime;
  }

  // Check that we're not supposed to left-trim the backing store, as
  // implemented in elements.cc:FastElementsAccessor::MoveElements.
  if (newLength > kMaxCopyElements) goto Runtime;

  const result = witness.LoadElementOrUndefined(0);
  witness.ChangeLength(newLength);
  witness.MoveElements(0, 1, Convert<intptr>(newLength));
  witness.StoreHole(newLength);
  return result;
}

transitioning macro GenericArrayShift(implicit context: Context)(
    receiver: JSAny): JSAny {
  // 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. If len is zero, then
  if (length == 0) {
    // a. Perform ? Set(O, "length", 0, true).
    SetProperty(object, kLengthString, Convert<Smi>(0));
    // b. Return undefined.
    return Undefined;
  }

  // 4. Let first be ? Get(O, "0").
  const first = GetProperty(object, Convert<Smi>(0));
  // 5. Let k be 1.
  let k: Number = 1;
  // 6. Repeat, while k < len
  while (k < length) {
    // a. Let from be ! ToString(k).
    const from: Number = k;

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

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

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

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

    // f. Increase k by 1.
    k++;
  }

  // 7. Perform ? DeletePropertyOrThrow(O, ! ToString(len - 1)).
  DeleteProperty(object, length - 1, LanguageMode::kStrict);

  // 8. Perform ? Set(O, "length", len - 1, true).
  SetProperty(object, kLengthString, length - 1);

  // 9. Return first.
  return first;
}

// https://tc39.github.io/ecma262/#sec-array.prototype.shift
transitioning javascript builtin ArrayPrototypeShift(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  try {
    return TryFastArrayShift(receiver) otherwise Slow, Runtime;
  } label Slow {
    return GenericArrayShift(receiver);
  } label Runtime {
    tail ArrayShift(
        context, LoadTargetFromFrame(), Undefined,
        Convert<int32>(arguments.length));
  }
}
}