package scalaz

import Id._

sealed trait WriterT[F[+_], +W, +A] { self =>
  val run: F[(W, A)]

  import WriterT._

  def off: UnwriterT[F, W, A] =
    UnwriterT(run)

  /** alias for `off` */
  def unary_- : UnwriterT[F, W, A] =
    UnwriterT(run)

  def mapValue[X, B](f: ((W, A)) => (X, B))(implicit F: Functor[F]): WriterT[F, X, B] =
    writerT(F.map(run)(f))

  def mapWritten[X](f: W => X)(implicit F: Functor[F]): WriterT[F, X, A] =
    mapValue(wa => (f(wa._1), wa._2))

  def written(implicit F: Functor[F]): F[W] =
    F.map(run)(_._1)

  def value(implicit F: Functor[F]): F[A] =
    F.map(run)(_._2)

  def swap(implicit F: Functor[F]): WriterT[F, A, W] =
    mapValue(wa => (wa._2, wa._1))

  def :++>[WW >: W](w: => WW)(implicit F: Functor[F], W: Semigroup[WW]): WriterT[F, WW, A] =
    mapWritten(W.append(_, w))

  def :++>>[WW >: W](f: A => WW)(implicit F: Functor[F], W: Semigroup[WW]): WriterT[F, WW, A] =
    mapValue(wa => (W.append(wa._1, f(wa._2)), wa._2))

  def <++:[WW >: W](w: => WW)(implicit F: Functor[F], W: Semigroup[WW]): WriterT[F, WW, A] =
    mapWritten(W.append(w, _))

  def <<++:[WW >: W](f: A => WW)(implicit F: Functor[F], s: Semigroup[WW]): WriterT[F, WW, A] =
    mapValue(wa => (s.append(f(wa._2), wa._1), wa._2))

  def reset[WW >: W](implicit Z: Monoid[WW], F: Functor[F]): WriterT[F, WW, A] =
    mapWritten(_ => Z.zero)

  def map[B](f: A => B)(implicit F: Functor[F]): WriterT[F, W, B] =
    writerT(F.map(run)(wa => (wa._1, f(wa._2))))

  def foreach[B](f: A => Unit)(implicit E: Each[F]): Unit =
    E.each(run)(wa => f(wa._2))

  def ap[B, WW >: W](f: => WriterT[F, WW, A => B])(implicit F: Apply[F], W: Semigroup[WW]): WriterT[F, WW, B] = writerT {
    F.apply2(f.run, run) {
      case ((w1, fab), (w2, a)) => (W.append(w1, w2), fab(a))
    }
  }

  def flatMap[B, WW >: W](f: A => WriterT[F, WW, B])(implicit F: Bind[F], s: Semigroup[WW]): WriterT[F, WW, B] =
    writerT(F.bind(run){wa =>
      val z = f(wa._2).run
      F.map(z)(wb => (s.append(wa._1, wb._1), wb._2))
    })

  def traverse[G[+_] , B](f: A => G[B])(implicit G: Applicative[G], F: Traverse[F]): G[WriterT[F, W, B]] = {
    G.map(F.traverse(run){
      case (w, a) => G.map(f(a))(b => (w, b))
    })(WriterT(_))
  }

  def foldRight[B](z: => B)(f: (A, => B) => B)(implicit F: Foldable[F]) =
    F.foldr(run, z) { a => b =>
      f(a._2, b)
    }

  def bimap[C, D](f: W => C, g: A => D)(implicit F: Functor[F]) =
    writerT[F, C, D](F.map(run)({
      case (a, b) => (f(a), g(b))
    }))

  def leftMap[C](f: W => C)(implicit F: Functor[F]): WriterT[F, C, A] =
    bimap(f, identity)

  def bitraverse[G[_], C, D](f: W => G[C], g: A => G[D])(implicit G: Applicative[G], F: Traverse[F]) =
    G.map(F.traverse[G, (W, A), (C, D)](run) {
      case (a, b) => G.apply2(f(a), g(b))((_, _))
    })(writerT(_))

  def rwst[R, S](implicit F: Functor[F]): ReaderWriterStateT[F, R, W, S, A] = ReaderWriterStateT(
    (r, s) => F.map(self.run) {
      case (w, a) => (w, a, s)
    }
  )

  def wpoint[G[+_]](implicit F: Functor[F], P: Applicative[G]): WriterT[F, G[W], A] =
    writerT(F.map(self.run) {
      case (w, a) => (P.point(w), a)
    })

  def colocal[X](f: W => X)(implicit F: Functor[F]): WriterT[F, X, A] = mapWritten(f)
}

object WriterT extends WriterTFunctions with WriterTInstances {
  def apply[F[+_], W, A](v: F[(W, A)]): WriterT[F, W, A] =
    writerT(v)
}

trait WriterTInstances12 {
  implicit def writerFunctor[W]: WriterTFunctor[Id, W] = new WriterTFunctor[Id, W] {
    implicit def F = idInstance
  }
}
trait WriterTInstances11 extends WriterTInstances12 {
  implicit def writerTFunctor[F[+_], W](implicit F0: Functor[F]) = new WriterTFunctor[F, W] {
    implicit def F = F0
  }
}

trait WriterTInstances10 extends WriterTInstances11 {
  implicit def writerApply[W](implicit W0: Semigroup[W]) = new WriterTApply[Id, W] {
    implicit def F = idInstance
    implicit def W = W0
  }
}

trait WriterTInstances9 extends WriterTInstances10 {
  implicit def writerTApply[F[+_], W](implicit W0: Semigroup[W], F0: Apply[F]) = new WriterTApply[F, W] {
    implicit def F = F0
    implicit def W = W0
  }
}

trait WriterTInstances8 extends WriterTInstances9 {
  implicit def writerApplicative[W](implicit W0: Monoid[W]) = new WriterTApplicative[Id, W] {
    implicit def F = idInstance
    implicit def W = W0
  }
}

trait WriterTInstances7 extends WriterTInstances8 {
  implicit def writerTApplicative[F[+_], W](implicit W0: Monoid[W], F0: Applicative[F]) = new WriterTApplicative[F, W] {
    implicit def F = F0
    implicit def W = W0
  }
}

trait WriterTInstances6 extends WriterTInstances7 {
  implicit def writerMonad[W](implicit W0: Monoid[W]) = new WriterTMonad[Id, W] {
    implicit def F = idInstance
    implicit def W = W0
  }
}

trait WriterTInstance5 extends WriterTInstances6 {
  implicit def writerTMonad[F[+_], W](implicit W0: Monoid[W], F0: Monad[F]) = new WriterTMonad[F, W] {
    implicit def F = F0
    implicit def W = W0
  }
}

trait WriterTInstances4 extends WriterTInstance5 {
  implicit def writerBifunctor = new WriterTBifunctor[Id] {
    implicit def F = idInstance
  }
  implicit def writerFoldable[W] = new WriterTFoldable[Id, W] {
    implicit def F = idInstance
  }
  implicit def writerEqual[W, A](implicit E: Equal[(W, A)]) = E.contramap((_: Writer[W, A]).run)
}

trait WriterTInstances3 extends WriterTInstances4 {
  implicit def writerTBifunctor[F[+_]](implicit F0: Functor[F]) = new WriterTBifunctor[F] {
    implicit def F = F0
  }
  implicit def writerTFoldable[F[+_], W](implicit F0: Foldable[F]) = new WriterTFoldable[F, W] {
    implicit def F = F0
  }
  implicit def writerTEqual[F[+_], W, A](implicit E: Equal[F[(W, A)]]) = E.contramap((_: WriterT[F, W, A]).run)
}

trait WriterTInstances2 extends WriterTInstances3 {
  implicit def writerComonad[W] = new WriterComonad[W] {
    implicit def F = implicitly
  }
}

trait WriterTInstances1 extends WriterTInstances2 {
  implicit def writerBitraverse: WriterTBitraverse[Id] = new WriterTBitraverse[Id] {
    implicit def F = idInstance
  }
  implicit def writerTraverse[W]: WriterTTraverse[Id, W] = new WriterTTraverse[Id, W] {
    implicit def F = idInstance
  }
  implicit def writerEach[W]: WriterTEach[Id, W] = new WriterTEach[Id, W] {
    implicit def F = idInstance
  }
}

trait WriterTInstances0 extends WriterTInstances1 {
  implicit def writerTBitraverse[F[+_]](implicit F0: Traverse[F]) = new WriterTBitraverse[F] {
    implicit def F = F0
  }
  implicit def writerTTraverse[F[+_], W](implicit F0: Traverse[F]) = new WriterTTraverse[F, W] {
    implicit def F = F0
  }
  implicit def writerTIndex[W] = new WriterTIndex[W] {
  }
  implicit def writerEach[F[+_], W](implicit F0: Each[F]) = new WriterTEach[F, W] {
    implicit def F = F0
  }
}

trait WriterTInstances extends WriterTInstances0 {
  implicit def writerTMonadListen[F[+_], W](implicit F0: Monad[F], W0: Monoid[W]) = new WriterMonadListen[F, W] {
    implicit def F = F0
    implicit def W = W0
  }

  implicit def writerTMonadTrans[W](implicit W0: Monoid[W]): MonadTrans[({type λ[α[+_], β] = WriterT[α, W, β]})] = new WriterTMonadTrans[W] {
    implicit def W = W0
  }
}

trait WriterTFunctions {
  def writerT[F[+_], W, A](v: F[(W, A)]): WriterT[F, W, A] = new WriterT[F, W, A] {
    val run = v
  }

  import StoreT._

  def writer[W, A](v: (W, A)): Writer[W, A] =
    writerT[Id, W, A](v)

  def tell[W](w: W): Writer[W, Unit] = writer(w -> ())

  def put[F[+_], W, A](value: F[A])(w: W)(implicit F: Functor[F]): WriterT[F, W, A] =
    WriterT(F.map(value)(a => (w, a)))

  /** Puts the written value that is produced by applying the given function into a writer transformer and associates with `value` */
  def putWith[F[+_], W, A](value: F[A])(w: A => W)(implicit F: Functor[F]): WriterT[F, W, A] =
    WriterT(F.map(value)(a => (w(a), a)))

}

//
// Type class implementation traits
//
import WriterT.writerT

private[scalaz] trait WriterTFunctor[F[+_], W] extends Functor[({type λ[]=WriterT[F, W, α]})] {
  implicit def F: Functor[F]

  override def map[A, B](fa: WriterT[F, W, A])(f: A => B) = fa map f
}

private[scalaz] trait WriterTApply[F[+_], W] extends Apply[({type λ[]=WriterT[F, W, α]})] with WriterTFunctor[F, W] {
  implicit def F: Apply[F]
  implicit def W: Semigroup[W]

  override def ap[A, B](fa: => WriterT[F, W, A])(f: => WriterT[F, W, A => B]) = fa ap f
}

private[scalaz] trait WriterTApplicative[F[+_], W] extends Applicative[({type λ[]=WriterT[F, W, α]})] with WriterTApply[F, W] {
  implicit def F: Applicative[F]
  implicit def W: Monoid[W]
  def point[A](a: => A) = writerT(F.point((W.zero, a)))
}

private[scalaz] trait WriterTEach[F[+_], W] extends Each[({type λ[]=WriterT[F, W, α]})] {
  implicit def F: Each[F]

  def each[A](fa: WriterT[F, W, A])(f: A => Unit) = fa foreach f
}

// TODO does Index it make sense for F other than Id?
private[scalaz] trait WriterTIndex[W] extends Index[({type λ[]=WriterT[Id, W, α]})] {
  def index[A](fa: WriterT[Id, W, A], i: Int) = if(i == 0) Some(fa.value) else None
}

private[scalaz] trait WriterTMonad[F[+_], W] extends Monad[({type λ[]=WriterT[F, W, α]})] with WriterTApplicative[F, W] {
  implicit def F: Monad[F]

  def bind[A, B](fa: WriterT[F, W, A])(f: A => WriterT[F, W, B]) = fa flatMap f
}

private[scalaz] trait WriterTFoldable[F[+_], W] extends Foldable.FromFoldr[({type λ[]=WriterT[F, W, α]})] {
  implicit def F: Foldable[F]

  override def foldRight[A, B](fa: WriterT[F, W, A], z: => B)(f: (A, => B) => B) = fa.foldRight(z)(f)
}

private[scalaz] trait WriterTTraverse[F[+_], W] extends Traverse[({type λ[]=WriterT[F, W, α]})] with WriterTFoldable[F, W] {
  implicit def F: Traverse[F]

  def traverseImpl[G[+_]: Applicative, A, B](fa: WriterT[F, W, A])(f: A => G[B]) = fa traverse f
}

private[scalaz] trait WriterTBifunctor[F[+_]] extends Bifunctor[({type λ[+α, +β]=WriterT[F, α, β]})] {
  implicit def F: Functor[F]

  override def bimap[A, B, C, D](fab: WriterT[F, A, B])(f: A => C, g: B => D) =
    fab.bimap(f, g)
}

private[scalaz] trait WriterTBitraverse[F[+_]] extends Bitraverse[({type λ[+α, +β]=WriterT[F, α, β]})] with WriterTBifunctor[F] {
  implicit def F: Traverse[F]

  def bitraverseImpl[G[_]: Applicative, A, B, C, D](fab: WriterT[F, A, B])(f: A => G[C], g: B => G[D]) =
    fab.bitraverse(f, g)
}

private[scalaz] trait WriterComonad[W] extends Comonad[({type λ[] = Writer[W, α]})] with WriterTFunctor[Id, W] {
  def copoint[A](p: Writer[W, A]): A = p.value

  override def cojoin[A](fa: Writer[W, A]): Writer[W, Writer[W, A]] =
    Writer(fa.written, fa)

  override def cobind[A, B](fa: Writer[W, A])(f: (Writer[W, A]) => B): Writer[W, B] =
    Writer(fa.written, f(fa))
}

private[scalaz] trait WriterTMonadTrans[W] extends MonadTrans[({type λ[α[+_], β] = WriterT[α, W, β]})] {
  def liftM[M[+_], B](mb: M[B])(implicit M: Monad[M]): WriterT[M, W, B] =
    WriterT(M.map(mb)((W.zero, _)))

  implicit def W: Monoid[W]

  implicit def apply[M[+_]: Monad]: Monad[({type λ[α]=WriterT[M, W, α]})] = WriterT.writerTMonad
}

private[scalaz] trait WriterMonadListen[F[+_], W] extends MonadListen[({type λ[+α, +β] = WriterT[F, α, β]})#λ, W] with WriterTMonad[F, W] {
  implicit def F: Monad[F]
  implicit def W: Monoid[W]

  def writer[A](w: W, v: A): WriterT[F, W, A] = WriterT.writerT(F.point((w, v)))

  def listen[A](fa: WriterT[F, W, A]): WriterT[F, W, (A, W)] =
    WriterT(F.bind(fa.run){ case (w, a) => F.point((w, (a, w))) })
}