/*! @license
 * Shaka Player
 * Copyright 2016 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

goog.provide('shaka.util.ArrayUtils');


/**
 * @namespace shaka.util.ArrayUtils
 * @summary Array utility functions.
 */


shaka.util.ArrayUtils = class {
  /**
   * Returns whether the two values contain the same value.  This correctly
   * handles comparisons involving NaN.
   * @param {T} a
   * @param {T} b
   * @return {boolean}
   * @template T
   */
  static defaultEquals(a, b) {
    // NaN !== NaN, so we need to special case it.
    if (typeof a === 'number' &&
        typeof b === 'number' && isNaN(a) && isNaN(b)) {
      return true;
    }
    return a === b;
  }


  /**
   * Remove given element from array (assumes no duplicates).
   * @param {!Array<T>} array
   * @param {T} element
   * @template T
   */
  static remove(array, element) {
    const index = array.indexOf(element);
    if (index > -1) {
      array.splice(index, 1);
    }
  }


  /**
   * Count the number of items in the list that pass the check function.
   * @param {!Array<T>} array
   * @param {function(T):boolean} check
   * @return {number}
   * @template T
   */
  static count(array, check) {
    let count = 0;

    for (const element of array) {
      count += check(element) ? 1 : 0;
    }

    return count;
  }


  /**
   * Determines if the given arrays contain equal elements in any order.
   *
   * @param {!Array<T>} a
   * @param {!Array<T>} b
   * @param {function(T, T):boolean=} compareFn
   * @return {boolean}
   * @template T
   */
  static hasSameElements(a, b, compareFn) {
    if (!compareFn) {
      compareFn = shaka.util.ArrayUtils.defaultEquals;
    }
    if (a.length != b.length) {
      return false;
    }

    const copy = b.slice();
    for (const item of a) {
      const idx = copy.findIndex((other) => compareFn(item, other));
      if (idx == -1) {
        return false;
      }
      // Since order doesn't matter, just swap the last element with
      // this one and then drop the last element.
      copy[idx] = copy[copy.length - 1];
      copy.pop();
    }

    return copy.length == 0;
  }


  /**
   * Partitions an array into two arrays based on a predicate.
   * Returns [matching, notMatching].
   *
   * @param {!Array<T>} array
   * @param {function(T):boolean} predicate
   * @return {!Array<!Array<T>>}
   * @template T
   */
  static partition(array, predicate) {
    const yes = [];
    const no = [];
    for (const element of array) {
      if (predicate(element)) {
        yes.push(element);
      } else {
        no.push(element);
      }
    }
    return [yes, no];
  }


  /**
   * Returns the index of the first element for which `predicate` returns
   * false.  Assumes the array is partitioned so that predicate returns true
   * for a contiguous prefix followed by all-false (i.e. a standard
   * lower-bound / partition-point binary search).
   *
   * @param {!Array<T>} array
   * @param {function(T):boolean} predicate
   * @return {number}
   * @template T
   */
  static binarySearch(array, predicate) {
    let lo = 0;
    let hi = array.length;
    while (lo < hi) {
      const mid = (lo + hi) >>> 1;
      if (predicate(array[mid])) {
        lo = mid + 1;
      } else {
        hi = mid;
      }
    }
    return lo;
  }


  /**
   * Determines if the given arrays contain equal elements in the same order.
   *
   * @param {Array<T>} a
   * @param {Array<T>} b
   * @param {function(T, T):boolean=} compareFn
   * @return {boolean}
   * @template T
   */
  static equal(a, b, compareFn) {
    if (a === b) {
      return true;
    }
    if (!a || !b) {
      return a == b;
    }
    if (!compareFn) {
      compareFn = shaka.util.ArrayUtils.defaultEquals;
    }
    if (a.length != b.length) {
      return false;
    }

    for (let i = 0; i < a.length; i++) {
      if (!compareFn(a[i], b[i])) {
        return false;
      }
    }
    return true;
  }
};
