// 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_reverse {
  macro LoadElement<ElementsAccessor: type, T: type>(
      elements: FixedArrayBase, index: Smi): T;

  LoadElement<array::FastPackedSmiElements, Smi>(implicit context: Context)(
      elements: FixedArrayBase, index: Smi): Smi {
    const elements: FixedArray = UnsafeCast<FixedArray>(elements);
    return UnsafeCast<Smi>(elements.objects[index]);
  }

  LoadElement<array::FastPackedObjectElements, Object>(
      implicit context: Context)(elements: FixedArrayBase, index: Smi): Object {
    const elements: FixedArray = UnsafeCast<FixedArray>(elements);
    return elements.objects[index];
  }

  LoadElement<array::FastPackedDoubleElements, float64>(
      implicit context: Context)(elements: FixedArrayBase, index: Smi):
      float64 {
    const elements: FixedDoubleArray = UnsafeCast<FixedDoubleArray>(elements);
    // This macro is only used for PACKED_DOUBLE, loading the hole should
    // be impossible.
    return LoadDoubleWithHoleCheck(elements, index)
        otherwise unreachable;
  }

  macro StoreElement<ElementsAccessor: type, T: type>(
      implicit context:
          Context)(elements: FixedArrayBase, index: Smi, value: T);

  StoreElement<array::FastPackedSmiElements, Smi>(implicit context: Context)(
      elements: FixedArrayBase, index: Smi, value: Smi) {
    const elems: FixedArray = UnsafeCast<FixedArray>(elements);
    StoreFixedArrayElement(elems, index, value, SKIP_WRITE_BARRIER);
  }

  StoreElement<array::FastPackedObjectElements, Object>(
      implicit context:
          Context)(elements: FixedArrayBase, index: Smi, value: Object) {
    const elements: FixedArray = UnsafeCast<FixedArray>(elements);
    elements.objects[index] = value;
  }

  StoreElement<array::FastPackedDoubleElements, float64>(
      implicit context:
          Context)(elements: FixedArrayBase, index: Smi, value: float64) {
    const elems: FixedDoubleArray = UnsafeCast<FixedDoubleArray>(elements);
    StoreFixedDoubleArrayElementSmi(elems, index, value);
  }

  // Fast-path for all PACKED_* elements kinds. These do not need to check
  // whether a property is present, so we can simply swap them using fast
  // FixedArray loads/stores.
  macro FastPackedArrayReverse<Accessor: type, T: type>(
      implicit context: Context)(elements: FixedArrayBase, length: Smi) {
    let lower: Smi = 0;
    let upper: Smi = length - 1;

    while (lower < upper) {
      const lowerValue: T = LoadElement<Accessor, T>(elements, lower);
      const upperValue: T = LoadElement<Accessor, T>(elements, upper);
      StoreElement<Accessor>(elements, lower, upperValue);
      StoreElement<Accessor>(elements, upper, lowerValue);
      ++lower;
      --upper;
    }
  }

  transitioning macro GenericArrayReverse(context: Context, receiver: Object):
      Object {
    // 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 middle be floor(len / 2).
    // 4. Let lower be 0.
    // 5. Repeat, while lower != middle.
    //   a. Let upper be len - lower - 1.

    // Instead of calculating the middle value, we simply initialize upper
    // with len - 1 and decrement it after each iteration.
    let lower: Number = 0;
    let upper: Number = length - 1;

    while (lower < upper) {
      let lowerValue: Object = Undefined;
      let upperValue: Object = Undefined;

      // b. Let upperP be ! ToString(upper).
      // c. Let lowerP be ! ToString(lower).
      // d. Let lowerExists be ? HasProperty(O, lowerP).
      const lowerExists: Boolean = HasProperty(object, lower);

      // e. If lowerExists is true, then.
      if (lowerExists == True) {
        // i. Let lowerValue be ? Get(O, lowerP).
        lowerValue = GetProperty(object, lower);
      }

      // f. Let upperExists be ? HasProperty(O, upperP).
      const upperExists: Boolean = HasProperty(object, upper);

      // g. If upperExists is true, then.
      if (upperExists == True) {
        // i. Let upperValue be ? Get(O, upperP).
        upperValue = GetProperty(object, upper);
      }

      // h. If lowerExists is true and upperExists is true, then
      if (lowerExists == True && upperExists == True) {
        // i. Perform ? Set(O, lowerP, upperValue, true).
        SetProperty(object, lower, upperValue);

        // ii. Perform ? Set(O, upperP, lowerValue, true).
        SetProperty(object, upper, lowerValue);
      } else if (lowerExists == False && upperExists == True) {
        // i. Perform ? Set(O, lowerP, upperValue, true).
        SetProperty(object, lower, upperValue);

        // ii. Perform ? DeletePropertyOrThrow(O, upperP).
        DeleteProperty(object, upper, kStrict);
      } else if (lowerExists == True && upperExists == False) {
        // i. Perform ? DeletePropertyOrThrow(O, lowerP).
        DeleteProperty(object, lower, kStrict);

        // ii. Perform ? Set(O, upperP, lowerValue, true).
        SetProperty(object, upper, lowerValue);
      }

      // l. Increase lower by 1.
      ++lower;
      --upper;
    }

    // 6. Return O.
    return object;
  }

  macro TryFastPackedArrayReverse(implicit context: Context)(receiver: Object)
      labels Slow {
    const array: FastJSArray = Cast<FastJSArray>(receiver) otherwise Slow;

    const kind: ElementsKind = array.map.elements_kind;
    if (kind == PACKED_SMI_ELEMENTS) {
      array::EnsureWriteableFastElements(array);
      FastPackedArrayReverse<array::FastPackedSmiElements, Smi>(
          array.elements, array.length);
    } else if (kind == PACKED_ELEMENTS) {
      array::EnsureWriteableFastElements(array);
      FastPackedArrayReverse<array::FastPackedObjectElements, Object>(
          array.elements, array.length);
    } else if (kind == PACKED_DOUBLE_ELEMENTS) {
      FastPackedArrayReverse<array::FastPackedDoubleElements, float64>(
          array.elements, array.length);
    } else {
      goto Slow;
    }
  }

  // https://tc39.github.io/ecma262/#sec-array.prototype.reverse
  transitioning javascript builtin ArrayPrototypeReverse(
      context: Context, receiver: Object, ...arguments): Object {
    try {
      TryFastPackedArrayReverse(receiver) otherwise Baseline;
      return receiver;
    }
    label Baseline {
      return GenericArrayReverse(context, receiver);
    }
  }
}