// 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_filter {
  transitioning javascript builtin
  ArrayFilterLoopEagerDeoptContinuation(implicit context: Context)(
      receiver: Object, callback: Object, thisArg: Object, array: Object,
      initialK: Object, length: Object, initialTo: Object): Object {
    // All continuation points in the optimized filter implementation are
    // after the ToObject(O) call that ensures we are dealing with a
    // JSReceiver.
    //
    // Also, this great mass of casts is necessary because the signature
    // of Torque javascript builtins requires Object type for all parameters
    // other than {context}.
    const jsreceiver = Cast<JSReceiver>(receiver) otherwise unreachable;
    const callbackfn = Cast<Callable>(callback) otherwise unreachable;
    const outputArray = Cast<JSReceiver>(array) otherwise unreachable;
    const numberK = Cast<Number>(initialK) otherwise unreachable;
    const numberTo = Cast<Number>(initialTo) otherwise unreachable;
    const numberLength = Cast<Number>(length) otherwise unreachable;

    return ArrayFilterLoopContinuation(
        jsreceiver, callbackfn, thisArg, outputArray, jsreceiver, numberK,
        numberLength, numberTo);
  }

  transitioning javascript builtin
  ArrayFilterLoopLazyDeoptContinuation(implicit context: Context)(
      receiver: Object, callback: Object, thisArg: Object, array: Object,
      initialK: Object, length: Object, valueK: Object, initialTo: Object,
      result: Object): Object {
    // All continuation points in the optimized filter implementation are
    // after the ToObject(O) call that ensures we are dealing with a
    // JSReceiver.
    const jsreceiver = Cast<JSReceiver>(receiver) otherwise unreachable;
    const callbackfn = Cast<Callable>(callback) otherwise unreachable;
    const outputArray = Cast<JSReceiver>(array) otherwise unreachable;
    let numberK = Cast<Number>(initialK) otherwise unreachable;
    let numberTo = Cast<Number>(initialTo) otherwise unreachable;
    const numberLength = Cast<Number>(length) otherwise unreachable;

    // This custom lazy deopt point is right after the callback. filter() needs
    // to pick up at the next step, which is setting the callback result in
    // the output array. After incrementing k and to, we can glide into the loop
    // continuation builtin.
    if (ToBoolean(result)) {
      FastCreateDataProperty(outputArray, numberTo, valueK);
      numberTo = numberTo + 1;
    }

    numberK = numberK + 1;

    return ArrayFilterLoopContinuation(
        jsreceiver, callbackfn, thisArg, outputArray, jsreceiver, numberK,
        numberLength, numberTo);
  }

  transitioning builtin ArrayFilterLoopContinuation(implicit context: Context)(
      receiver: JSReceiver, callbackfn: Callable, thisArg: Object,
      array: JSReceiver, o: JSReceiver, initialK: Number, length: Number,
      initialTo: Number): Object {
    let to: Number = initialTo;
    // 5. Let k be 0.
    // 6. Repeat, while k < len
    for (let k: Number = initialK; k < length; k++) {
      // 6a. Let Pk be ! ToString(k).
      // k is guaranteed to be a positive integer, hence ToString is
      // side-effect free and HasProperty/GetProperty do the conversion inline.

      // 6b. Let kPresent be ? HasProperty(O, Pk).
      const kPresent: Boolean = HasProperty_Inline(o, k);

      // 6c. If kPresent is true, then
      if (kPresent == True) {
        // 6c. i. Let kValue be ? Get(O, Pk).
        const kValue: Object = GetProperty(o, k);

        // 6c. ii. Perform ? Call(callbackfn, T, <kValue, k, O>).
        const result: Object = Call(context, callbackfn, thisArg, kValue, k, o);

        // iii. If selected is true, then...
        if (ToBoolean(result)) {
          // 1. Perform ? CreateDataPropertyOrThrow(A, ToString(to), kValue).
          FastCreateDataProperty(array, to, kValue);
          // 2. Increase to by 1.
          to = to + 1;
        }
      }

      // 6d. Increase k by 1. (done by the loop).
    }
    return array;
  }

  transitioning macro FastArrayFilter(implicit context: Context)(
      fastO: FastJSArray, len: Smi, callbackfn: Callable, thisArg: Object,
      output: FastJSArray) labels Bailout(Number, Number) {
    let k: Smi = 0;
    let to: Smi = 0;
    let fastOW = NewFastJSArrayWitness(fastO);
    let fastOutputW = NewFastJSArrayWitness(output);

    fastOutputW.EnsureArrayPushable() otherwise goto Bailout(k, to);

    // Build a fast loop over the array.
    for (; k < len; k++) {
      fastOW.Recheck() otherwise goto Bailout(k, to);

      // Ensure that we haven't walked beyond a possibly updated length.
      if (k >= fastOW.Get().length) goto Bailout(k, to);
      const value: Object = fastOW.LoadElementNoHole(k) otherwise continue;
      const result: Object =
          Call(context, callbackfn, thisArg, value, k, fastOW.Get());
      if (ToBoolean(result)) {
        try {
          // Since the call to {callbackfn} is observable, we can't
          // use the Bailout label until we've successfully stored.
          // Hence the {SlowStore} label.
          fastOutputW.Recheck() otherwise SlowStore;
          if (fastOutputW.Get().length != to) goto SlowStore;
          fastOutputW.Push(value) otherwise SlowStore;
        }
        label SlowStore {
          FastCreateDataProperty(fastOutputW.stable, to, value);
        }
        to = to + 1;
      }
    }
  }

  // This method creates a 0-length array with the ElementsKind of the
  // receiver if possible, otherwise, bails out. It makes sense for the
  // caller to know that the slow case needs to be invoked.
  macro FastFilterSpeciesCreate(implicit context: Context)(
      receiver: JSReceiver): JSReceiver labels Slow {
    const len: Smi = 0;
    if (IsArraySpeciesProtectorCellInvalid()) goto Slow;
    const o = Cast<FastJSArray>(receiver) otherwise Slow;
    const newMap: Map =
        LoadJSArrayElementsMap(o.map.elements_kind, LoadNativeContext(context));
    return AllocateJSArray(PACKED_SMI_ELEMENTS, newMap, len, len);
  }

  // https://tc39.github.io/ecma262/#sec-array.prototype.filter
  transitioning javascript builtin
  ArrayFilter(implicit context: Context)(receiver: Object, ...arguments):
      Object {
    try {
      if (IsNullOrUndefined(receiver)) {
        goto NullOrUndefinedError;
      }

      // 1. Let O be ? ToObject(this value).
      const o: JSReceiver = ToObject_Inline(context, receiver);

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

      // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
      if (arguments.length == 0) {
        goto TypeError;
      }
      const callbackfn = Cast<Callable>(arguments[0]) otherwise TypeError;

      // 4. If thisArg is present, let T be thisArg; else let T be undefined.
      const thisArg: Object = arguments.length > 1 ? arguments[1] : Undefined;
      let output: JSReceiver;

      // Special cases.
      let k: Number = 0;
      let to: Number = 0;
      try {
        output = FastFilterSpeciesCreate(o) otherwise SlowSpeciesCreate;

        try {
          const smiLen: Smi = Cast<Smi>(len) otherwise goto Bailout(k, to);
          const fastOutput =
              Cast<FastJSArray>(output) otherwise goto Bailout(k, to);
          const fastO = Cast<FastJSArray>(o) otherwise goto Bailout(k, to);

          FastArrayFilter(fastO, smiLen, callbackfn, thisArg, fastOutput)
              otherwise Bailout;
          return output;
        }
        label Bailout(kValue: Number, toValue: Number) deferred {
          k = kValue;
          to = toValue;
        }
      }
      label SlowSpeciesCreate {
        output = ArraySpeciesCreate(context, receiver, 0);
      }

      return ArrayFilterLoopContinuation(
          o, callbackfn, thisArg, output, o, k, len, to);
    }
    label TypeError deferred {
      ThrowTypeError(kCalledNonCallable, arguments[0]);
    }
    label NullOrUndefinedError deferred {
      ThrowTypeError(kCalledOnNullOrUndefined, 'Array.prototype.filter');
    }
  }
}