/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2006-2013, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */


package scala.collection
package generic

import scala.language.higherKinds

/** A template for companion objects of `Traversable` and subclasses thereof.
 *  This class provides a set of operations to create `$Coll` objects.
 *  It is typically inherited by companion objects of subclasses of `Traversable`.
 *
 *  @since 2.8
 *
 *  @define coll collection
 *  @define Coll `Traversable`
 *  @define factoryInfo
 *    This object provides a set of operations to create `$Coll` values.
 *    @author Martin Odersky
 *    @version 2.8
 *  @define canBuildFromInfo
 *    The standard `CanBuildFrom` instance for $Coll objects.
 *    @see CanBuildFrom
 *  @define genericCanBuildFromInfo
 *    The standard `CanBuildFrom` instance for $Coll objects.
 *    The created value is an instance of class `GenericCanBuildFrom`,
 *    which forwards calls to create a new builder to the
 *    `genericBuilder` method of the requesting collection.
 *    @see CanBuildFrom
 *    @see GenericCanBuildFrom
 */
abstract class GenTraversableFactory[CC[X] <: GenTraversable[X] with GenericTraversableTemplate[X, CC]]
  extends GenericCompanion[CC] {

  // A default implementation of GenericCanBuildFrom which can be cast
  // to whatever is desired.
  private class ReusableCBF extends GenericCanBuildFrom[Nothing] {
    override def apply() = newBuilder[Nothing]
  }
  // Working around SI-4789 by using a lazy val instead of an object.
  lazy val ReusableCBF: GenericCanBuildFrom[Nothing] = new ReusableCBF

  /** A generic implementation of the `CanBuildFrom` trait, which forwards
   *  all calls to `apply(from)` to the `genericBuilder` method of
   *  $coll `from`, and which forwards all calls of `apply()` to the
   *  `newBuilder` method of this factory.
   */
  class GenericCanBuildFrom[A] extends CanBuildFrom[CC[_], A, CC[A]] {
    /** Creates a new builder on request of a collection.
     *  @param from  the collection requesting the builder to be created.
     *  @return the result of invoking the `genericBuilder` method on `from`.
     */
    def apply(from: Coll) = from.genericBuilder[A]

    /** Creates a new builder from scratch
     *  @return the result of invoking the `newBuilder` method of this factory.
     */
    def apply() = newBuilder[A]
  }

  /** Concatenates all argument collections into a single $coll.
   *
   *  @param xss the collections that are to be concatenated.
   *  @return the concatenation of all the collections.
   */
  def concat[A](xss: Traversable[A]*): CC[A] = {
    val b = newBuilder[A]
    // At present we're using IndexedSeq as a proxy for "has a cheap size method".
    if (xss forall (_.isInstanceOf[IndexedSeq[_]]))
      b.sizeHint(xss.map(_.size).sum)

    for (xs <- xss.seq) b ++= xs
    b.result
  }

  /** Produces a $coll containing the results of some element computation a number of times.
   *  @param   n  the number of elements contained in the $coll.
   *  @param   elem the element computation
   *  @return  A $coll that contains the results of `n` evaluations of `elem`.
   */
  def fill[A](n: Int)(elem: => A): CC[A] = {
    val b = newBuilder[A]
    b.sizeHint(n)
    var i = 0
    while (i < n) {
      b += elem
      i += 1
    }
    b.result
  }

  /** Produces a two-dimensional $coll containing the results of some element computation a number of times.
   *  @param   n1  the number of elements in the 1st dimension
   *  @param   n2  the number of elements in the 2nd dimension
   *  @param   elem the element computation
   *  @return  A $coll that contains the results of `n1 x n2` evaluations of `elem`.
   */
  def fill[A](n1: Int, n2: Int)(elem: => A): CC[CC[A]] =
    tabulate(n1)(_ => fill(n2)(elem))

  /** Produces a three-dimensional $coll containing the results of some element computation a number of times.
   *  @param   n1  the number of elements in the 1st dimension
   *  @param   n2  the number of elements in the 2nd dimension
   *  @param   n3  the number of elements in the 3nd dimension
   *  @param   elem the element computation
   *  @return  A $coll that contains the results of `n1 x n2 x n3` evaluations of `elem`.
   */
  def fill[A](n1: Int, n2: Int, n3: Int)(elem: => A): CC[CC[CC[A]]] =
    tabulate(n1)(_ => fill(n2, n3)(elem))

  /** Produces a four-dimensional $coll containing the results of some element computation a number of times.
   *  @param   n1  the number of elements in the 1st dimension
   *  @param   n2  the number of elements in the 2nd dimension
   *  @param   n3  the number of elements in the 3nd dimension
   *  @param   n4  the number of elements in the 4th dimension
   *  @param   elem the element computation
   *  @return  A $coll that contains the results of `n1 x n2 x n3 x n4` evaluations of `elem`.
   */
  def fill[A](n1: Int, n2: Int, n3: Int, n4: Int)(elem: => A): CC[CC[CC[CC[A]]]] =
    tabulate(n1)(_ => fill(n2, n3, n4)(elem))

  /** Produces a five-dimensional $coll containing the results of some element computation a number of times.
   *  @param   n1  the number of elements in the 1st dimension
   *  @param   n2  the number of elements in the 2nd dimension
   *  @param   n3  the number of elements in the 3nd dimension
   *  @param   n4  the number of elements in the 4th dimension
   *  @param   n5  the number of elements in the 5th dimension
   *  @param   elem the element computation
   *  @return  A $coll that contains the results of `n1 x n2 x n3 x n4 x n5` evaluations of `elem`.
   */
  def fill[A](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(elem: => A): CC[CC[CC[CC[CC[A]]]]] =
    tabulate(n1)(_ => fill(n2, n3, n4, n5)(elem))

  /** Produces a $coll containing values of a given function over a range of integer values starting from 0.
   *  @param  n   The number of elements in the $coll
   *  @param  f   The function computing element values
   *  @return A $coll consisting of elements `f(0), ..., f(n -1)`
   */
  def tabulate[A](n: Int)(f: Int => A): CC[A] = {
    val b = newBuilder[A]
    b.sizeHint(n)
    var i = 0
    while (i < n) {
      b += f(i)
      i += 1
    }
    b.result
  }

  /** Produces a two-dimensional $coll containing values of a given function over ranges of integer values starting from 0.
   *  @param   n1  the number of elements in the 1st dimension
   *  @param   n2  the number of elements in the 2nd dimension
   *  @param   f   The function computing element values
   *  @return A $coll consisting of elements `f(i1, i2)`
   *          for `0 <= i1 < n1` and `0 <= i2 < n2`.
   */
  def tabulate[A](n1: Int, n2: Int)(f: (Int, Int) => A): CC[CC[A]] =
    tabulate(n1)(i1 => tabulate(n2)(f(i1, _)))

  /** Produces a three-dimensional $coll containing values of a given function over ranges of integer values starting from 0.
   *  @param   n1  the number of elements in the 1st dimension
   *  @param   n2  the number of elements in the 2nd dimension
   *  @param   n3  the number of elements in the 3nd dimension
   *  @param   f   The function computing element values
   *  @return A $coll consisting of elements `f(i1, i2, i3)`
   *          for `0 <= i1 < n1`, `0 <= i2 < n2`, and `0 <= i3 < n3`.
   */
  def tabulate[A](n1: Int, n2: Int, n3: Int)(f: (Int, Int, Int) => A): CC[CC[CC[A]]] =
    tabulate(n1)(i1 => tabulate(n2, n3)(f(i1, _, _)))

  /** Produces a four-dimensional $coll containing values of a given function over ranges of integer values starting from 0.
   *  @param   n1  the number of elements in the 1st dimension
   *  @param   n2  the number of elements in the 2nd dimension
   *  @param   n3  the number of elements in the 3nd dimension
   *  @param   n4  the number of elements in the 4th dimension
   *  @param   f   The function computing element values
   *  @return A $coll consisting of elements `f(i1, i2, i3, i4)`
   *          for `0 <= i1 < n1`, `0 <= i2 < n2`, `0 <= i3 < n3`, and `0 <= i4 < n4`.
   */
  def tabulate[A](n1: Int, n2: Int, n3: Int, n4: Int)(f: (Int, Int, Int, Int) => A): CC[CC[CC[CC[A]]]] =
    tabulate(n1)(i1 => tabulate(n2, n3, n4)(f(i1, _, _, _)))

  /** Produces a five-dimensional $coll containing values of a given function over ranges of integer values starting from 0.
   *  @param   n1  the number of elements in the 1st dimension
   *  @param   n2  the number of elements in the 2nd dimension
   *  @param   n3  the number of elements in the 3nd dimension
   *  @param   n4  the number of elements in the 4th dimension
   *  @param   n5  the number of elements in the 5th dimension
   *  @param   f   The function computing element values
   *  @return A $coll consisting of elements `f(i1, i2, i3, i4, i5)`
   *          for `0 <= i1 < n1`, `0 <= i2 < n2`, `0 <= i3 < n3`, `0 <= i4 < n4`, and `0 <= i5 < n5`.
   */
  def tabulate[A](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(f: (Int, Int, Int, Int, Int) => A): CC[CC[CC[CC[CC[A]]]]] =
    tabulate(n1)(i1 => tabulate(n2, n3, n4, n5)(f(i1, _, _, _, _)))

  /** Produces a $coll containing a sequence of increasing of integers.
   *
   *  @param start the first element of the $coll
   *  @param end   the end value of the $coll (the first value NOT contained)
   *  @return  a $coll with values `start, start + 1, ..., end - 1`
   */
  def range[T: Integral](start: T, end: T): CC[T] = range(start, end, implicitly[Integral[T]].one)

  /** Produces a $coll containing equally spaced values in some integer interval.
   *  @param start the start value of the $coll
   *  @param end   the end value of the $coll (the first value NOT contained)
   *  @param step  the difference between successive elements of the $coll (must be positive or negative)
   *  @return      a $coll with values `start, start + step, ...` up to, but excluding `end`
   */
  def range[T: Integral](start: T, end: T, step: T): CC[T] = {
    val num = implicitly[Integral[T]]
    import num._

    if (step == zero) throw new IllegalArgumentException("zero step")
    val b = newBuilder[T]
    b sizeHint immutable.NumericRange.count(start, end, step, false)
    var i = start
    while (if (step < zero) end < i else i < end) {
      b += i
      i += step
    }
    b.result
  }

  /** Produces a $coll containing repeated applications of a function to a start value.
   *
   *  @param start the start value of the $coll
   *  @param len   the number of elements contained inthe $coll
   *  @param f     the function that's repeatedly applied
   *  @return      a $coll with `len` values in the sequence `start, f(start), f(f(start)), ...`
   */
  def iterate[A](start: A, len: Int)(f: A => A): CC[A] = {
    val b = newBuilder[A]
    if (len > 0) {
      b.sizeHint(len)
      var acc = start
      var i = 1
      b += acc

      while (i < len) {
        acc = f(acc)
        i += 1
        b += acc
      }
    }
    b.result
  }
}