// Copyright (c) 2011, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

typedef num NumericValueSelector<T>(T value);

/**
 * General purpose collection utilities.
 * TODO(jmesserly): make these top level functions?
 */
class CollectionUtils {

  static void insertAt(List arr, int pos, value) {
    assert (pos >= 0);
    assert (pos <= arr.length);

    if (pos == arr.length) {
      arr.add(value);
    } else {
      // TODO(sigmund): replace this push with a call that ensures capacity
      // (currently not supported in the JS implementation of list). E.g.
      // [: arr.length = arr.length + 1; :]
      arr.add(null);

      // shift elements from [pos] (note: arr already has null @ length - 1)
      for (int i = arr.length - 2; i >= pos; i--) {
        arr[i + 1] = arr[i];
      }
      arr[pos] = value;

      // TODO(jmesserly): we won't need to do this once List
      // implements insertAt
      if (arr is ObservableList) {
        // TODO(jmesserly): shouldn't need to cast after testing instanceof
        ObservableList obs = arr;
        obs.recordListInsert(pos, value);
      }
    }
  }

  // Collection<T> supports most of the ES 5 list methods, but it's missing
  // map.

  // TODO(jmesserly): we might want a version of this that return an iterable,
  // however JS, Python and Ruby versions are all eager.
  static List map(Iterable source, var mapper) {
    // TODO(jmesserly): I was trying to set the capacity here, but instead it
    // seems to create a fixed list. Hence assigning by index below.
    List result = new List(source is List ? source.dynamic.length : null);
    int i = 0;
    for (final item in source) {
      result[i++] = mapper(item);
    }
    return result;
  }

  /**
   * Finds the item in [source] that matches [test].  Returns null if
   * no item matches.  The typing should be:
   * T find(Iterable<T> source, bool test(T item)), but we don't have generic
   * functions.
   */
  static find(Iterable source, bool test(item)) {
    for (final item in source) {
      if (test(item)) return item;
    }

    return null;
  }

  /** Compute the minimum of an iterable. Returns null if empty. */
  static num min(Iterable source) {
    final iter = source.iterator();
    if (!iter.hasNext()) {
      return null;
    }
    num best = iter.next();
    while (iter.hasNext()) {
      best = Math.min(best, iter.next());
    }
    return best;
  }

  /** Compute the maximum of an iterable. Returns null if empty. */
  static num max(Iterable source) {
    final iter = source.iterator();
    if (!iter.hasNext()) {
      return null;
    }
    num best = iter.next();
    while (iter.hasNext()) {
      best = Math.max(best, iter.next());
    }
    return best;
  }

  /** Orders an iterable by its values, or by a key selector. */
  static List orderBy(Iterable source, [NumericValueSelector selector = null]) {
    final result = new List.from(source);
    sortBy(result, selector);
    return result;
  }

  /** Sorts a list by its values, or by a key selector. */
  // TODO(jmesserly): we probably don't want to call the key selector more than
  // once for a given element. This would improve performance and the API
  // contract could be stronger.
  static void sortBy(List list, [NumericValueSelector selector = null]) {
    if (selector != null) {
      list.sort((x, y) => selector(x) - selector(y));
    } else {
      list.sort((x, y) => x - y);
    }
  }

  /** Compute the sum of an iterable. An empty iterable is an error. */
  static num sum(Iterable source, [NumericValueSelector selector = null]) {
    final iter = source.iterator();
    num total = 0;
    if (selector != null) {
      do {
        total += selector(iter.next());
      } while (iter.hasNext());
    } else {
      do {
        total += iter.next();
      } while (iter.hasNext());
    }
    return total;
  }

  // TODO(jmesserly): something like should exist on Map, either a method or a
  // constructor, see bug #5340679
  static void copyMap(Map dest, Map source) {
    for (final k in source.getKeys()) {
      dest[k] = source[k];
    }
  }
}
