package scalaz

import Scalaz._

sealed trait WriterT[M[_], W, A] extends NewType[M[(W, A)]] {
  val value: M[(W, A)]
}

object WriterT {
  implicit def WriterTInjective[M[_], W] = Injective[({type λ[α]= WriterT[M, W, α]})#λ]

  implicit def WriterTPure[M[_]: Pure, W: Zero]: Pure[({type λ[α]= WriterT[M, W, α]})] = new Pure[({type λ[α]=WriterT[M, W, α]})] {
    def pure[A](a: => A) = new WriterT[M, W, A] {
      val value = ([W], a).η[M]
    }
  }

  implicit def WriterTFunctor[M[_]: Functor, W]: Functor[({type λ[α]=WriterT[M, W, α]})] = new Functor[({type λ[α]= WriterT[M, W, α]})] {
    def fmap[A, B](x: WriterT[M, W, A], f: A => B) = new WriterT[M, W, B] {
      val value = x.value  (_  f)
    }
  }

  implicit def WriterTApply[M[_], W](implicit ma: Apply[M], ftr: Functor[M], ss: Semigroup[W]): Apply[({type λ[α]=WriterT[M, W, α]})] = new Apply[({type λ[α]=WriterT[M, W, α]})] {
    def apply[A, B](f: WriterT[M, W, A => B], a: WriterT[M, W, A]): WriterT[M, W, B] = new WriterT[M, W, B] {
      val value = f.value.<**>(a.value) { case ((w2, fff), (w1, aaa)) => (w1 |+| w2, fff(aaa)) }
    }
  }

  implicit def WriterTBind[M[_], W](implicit mnd: Monad[M], sg: Semigroup[W]): Bind[({type λ[α]=WriterT[M, W, α]})] = new Bind[({type λ[α]=WriterT[M, W, α]})] {
    def bind[A, B](a: WriterT[M, W, A], f: A => WriterT[M, W, B]) = new WriterT[M, W, B] {
      val value =
        a.value >>= {
          case (w1, aaa) => {
            val hh = f(aaa).value
            hh  { case (w2, b) => (w1 |+| w2, b) }
          }
        }
    }
  }

  implicit def WriterTEach[M[_]: Each, W]: Each[({type λ[α]=WriterT[M, W, α]})] = new Each[({type λ[α]= WriterT[M, W, α]})] {
    def each[A](x: WriterT[M, W, A], f: A => Unit) = x.value foreach { case (_, a) => f(a) }
  }
}

trait WriterTs {
  def writerT[M[_], W, A](v: M[(W, A)]): WriterT[M, W, A] = new WriterT[M, W, A] {
    val value = v
  }
}