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

module array {
  macro LoadElement<ElementsAccessor : type, T : type>(
      elements: FixedArrayBase, index: Smi): T;

  LoadElement<FastPackedSmiElements, Smi>(
      elements: FixedArrayBase, index: Smi): Smi {
    const elems: FixedArray = unsafe_cast<FixedArray>(elements);
    return unsafe_cast<Smi>(elems[index]);
  }

  LoadElement<FastPackedObjectElements, Object>(
      elements: FixedArrayBase, index: Smi): Object {
    const elems: FixedArray = unsafe_cast<FixedArray>(elements);
    return elems[index];
  }

  LoadElement<FastPackedDoubleElements, float64>(
      elements: FixedArrayBase, index: Smi): float64 {
    try {
      const elems: FixedDoubleArray = unsafe_cast<FixedDoubleArray>(elements);
      return LoadDoubleWithHoleCheck(elems, index) otherwise Hole;
    }
    label Hole {
      // This macro is only used for PACKED_DOUBLE, loading the hole should
      // be impossible.
      unreachable;
    }
  }

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

  StoreElement<FastPackedSmiElements, Smi>(
      elements: FixedArrayBase, index: Smi, value: Smi) {
    const elems: FixedArray = unsafe_cast<FixedArray>(elements);
    StoreFixedArrayElementSmi(elems, index, value, SKIP_WRITE_BARRIER);
  }

  StoreElement<FastPackedObjectElements, Object>(
      elements: FixedArrayBase, index: Smi, value: Object) {
    const elems: FixedArray = unsafe_cast<FixedArray>(elements);
    elems[index] = value;
  }

  StoreElement<FastPackedDoubleElements, float64>(
      elements: FixedArrayBase, index: Smi, value: float64) {
    const elems: FixedDoubleArray = unsafe_cast<FixedDoubleArray>(elements);

    assert(value == Float64SilenceNaN(value));
    StoreFixedDoubleArrayElementWithSmiIndex(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>(
      elements: FixedArrayBase, length: Smi) {
    let lower: Smi = 0;
    let upper: Smi = length - 1;

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

  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(context, 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 lower_value: Object = Undefined;
      let upper_value: Object = Undefined;

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

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

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

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

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

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

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

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

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

    // 6. Return O.
    return object;
  }

  macro EnsureWriteableFastElements(array: JSArray) {
    const elements: FixedArrayBase = array.elements;
    if (elements.map != kCOWMap) return;

    // There are no COW *_DOUBLE_ELEMENTS arrays, so we are allowed to always
    // extract FixedArrays and don't have to worry about FixedDoubleArrays.
    assert(IsFastSmiOrTaggedElementsKind(array.map.elements_kind));

    const length: Smi = array.length_fast;
    array.elements = ExtractFixedArray(
        unsafe_cast<FixedArray>(elements), 0, length, length, kFixedArrays);
  }

  macro TryFastPackedArrayReverse(receiver: Object) labels Slow {
    const array: JSArray = cast<JSArray>(receiver) otherwise Slow;
    EnsureWriteableFastElements(array);
    assert(array.elements.map != kCOWMap);

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

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