package scalaz

import Scalaz._
/**
 * A Reducer[C,M] is a Monoid[M] that maps
 * values of type C through `unit` to values of type M. A C-Reducer may also
 * supply operations which tack on another C to an existing 'Monoid' M on the left
 * or right. These specialized reductions may be more efficient in some scenarios
 * and are used when appropriate by a 'Generator'. The names 'cons' and 'snoc' work
 * by analogy to the synonymous operations in the list monoid.
 * 
 * Minimal definition: 'unit' or 'snoc'
 * 
 * Based on a Haskell library by Edward Kmett
 */
abstract class Reducer[C, M](implicit mm: Monoid[M]) {
  val monoid = mm
  def unit(c: C): M = snoc(mzero, c)
  def snoc(m: M, c: C): M = m |+| unit(c)
  def cons(c: C, m: M): M = unit(c) |+| m

  final def &&&[N](r: Reducer[C, N]): Reducer[C, (M, N)] = {
    implicit val n = r.monoid
    new Reducer[C, (M, N)] {
      override def unit(x: C) = (Reducer.this.unit(x), r.unit(x))
      override def snoc(p: (M, N), x: C) = (Reducer.this.snoc(p._1, x), r.snoc(p._2, x))
      override def cons(x: C, p: (M, N)) = (Reducer.this.cons(x, p._1), r.cons(x, p._2))
    }
  }
}

trait Reducers {
  import Scalaz._
  implicit def ReducerMonoid[C, M](r: Reducer[C, M]) = r.monoid

  /** Construct a Reducer with the given unit function and monoid */
  def Reducer[C, M: Monoid](unit: C => M) = {
    val u = unit
    new Reducer[C, M] {
      override def unit(c: C) = u(c)
    }
  }

  def Reducer[M: Monoid]: Reducer[M, M] = Reducer(x => x)

  def ListReducer[C] = new Reducer[C, List[C]] {
    override def unit(c: C) = List(c)
    override def cons(c: C, cs: List[C]) = c :: cs
  }

  def StreamReducer[C] = new Reducer[C, Stream[C]] {
    override def unit(c: C) = Stream(c)
    override def cons(c: C, cs: Stream[C]) = c #:: cs
  }

  def UnitReducer[C]: Reducer[C, Unit] = Reducer((c: C) => ())

  def AnyReducer: Reducer[Boolean, Boolean] = Reducer(x => x)

  def AllReducer: Reducer[Boolean, BooleanConjunction] = Reducer(x => conjunction(x))

  def EndoReducer[A]: Reducer[A => A, Endo[A]] = Reducer(EndoTo(_))

  def DualReducer[A: Monoid]: Reducer[A, Dual[A]] = Reducer(_.dual)

  def IntProductReducer: Reducer[Int, IntMultiplication] = Reducer(x => multiplication(x))

  def CharProductReducer: Reducer[Char, CharMultiplication] = Reducer(x => multiplication(x))

  def ByteProductReducer: Reducer[Byte, ByteMultiplication] = Reducer(x => multiplication(x))

  def LongProductReducer: Reducer[Long, LongMultiplication] = Reducer(x => multiplication(x))

  def ShortProductReducer: Reducer[Short, ShortMultiplication] = Reducer(x => multiplication(x))

  def BigIntProductReducer: Reducer[BigInt, BigIntMultiplication] = Reducer((x: BigInt) => multiplication(x))
 
  import java.math.BigInteger
  def BigIntegerProductReducer: Reducer[BigInteger, BigIntegerMultiplication] = Reducer((x: BigInteger) => multiplication(x))

  def FirstReducer[A]: Reducer[A, FirstOption[A]] = Reducer(x => Some(x))
  def FirstOptionReducer[A]: Reducer[Option[A], FirstOption[A]] = Reducer(x => x)

  def LastReducer[A]: Reducer[A, LastOption[A]] = Reducer(x => Some(x))
  def LastOptionReducer[A]: Reducer[Option[A], LastOption[A]] = Reducer(x => x)


}