package scalaz

import Scalaz._

trait Logger[L, A] extends NewType[Writer[IndSeq[L], A]] {
  import Logger._

  def log: LOG[L]
  def over: A

  val value = writer(log, over)

  def map[B](f: A => B): Logger[L, B] = new Logger[L, B] {
    val log = Logger.this.log
    val over = f(Logger.this.over)
  }

  def flatMap[B](f: A => Logger[L, B]): Logger[L, B] = {
    val l = f(over)
    new Logger[L, B] {
      val log = Logger.this.log |+| l.log
      val over = l.over
    }
  }

  def foreach(f: A => Unit) =
    f(over)

  /**
   * Transform the log by the given function.
   */
  def withLog(k: LOG[L] => LOG[L]): Logger[L, A] = new Logger[L, A] {
    val log = k(Logger.this.log)
    val over = Logger.this.over
  }

  /**
   * Transform each log value by the given function.
   */
  def withEachLog(k: L => L): Logger[L, A] =
    withLog(_  k)

  /**
   * Set the log to the given value, losing any previous value.
   */
  def setLog(l: LOG[L]): Logger[L, A] =
    withLog(_ => l)

  /**
   * Append the given value to the current log.
   */
  def :+->(e: L): Logger[L, A] =
    withLog(_ |+| e.η[LOG])

  /**
   * Append the given value to the current log by applying to the underlying value.
   */
  def :->>(e: A => L): Logger[L, A] =
    :+->(e(over))

  /**
   * Prepend the given value to the current log.
   */
  def <-+:(e: L): Logger[L, A] =
    withLog(e.η[LOG] |+| _)

  /**
   * Prepend the given value to the current log by applying to the underlying value.
   */
  def <<-:(e: A => L): Logger[L, A] =
    <-+:(e(over))

  /**
   * Append the given value to the current log.
   */
  def :++->(e: LOG[L]): Logger[L, A] =
    withLog(_ |+| e)

  /**
   * Append the given value to the current log by applying to the underlying value.
   */
  def :+->>(e: A => LOG[L]): Logger[L, A] =
    withLog(_ |+| e(over))

  /**
   * Prepend the given value to the current log.
   */
  def <-++:(e: LOG[L]): Logger[L, A] =
    withLog(e |+| _)

  /**
   * Prepend the given value to the current log by applying to the underlying value.
   */
  def <<-+:(e: A => LOG[L]): Logger[L, A] =
    <-++:(e(over))

  /**
   * Set the log to be empty.
   */
  def resetLog: Logger[L, A] =
    withLog(_ => [LOG[L]])

  /**
   * Runs the given side-effect on the log, then returns this underlying value. '''CAUTION: side-effect'''
   */
  def effectLog(k: LOG[L] => Unit): Logger[L, A] = {
    k(log)
    this
  }

  /**
   * Runs the given side-effect on each element of the log, then returns this underlying value. '''CAUTION: side-effect'''
   */
  def effectEachLog(k: L => Unit): Logger[L, A] =
    effectLog(_ foreach k)

  /**
   * Runs the given side-effect on the log, then returns this underlying value with an empty log. '''CAUTION: side-effect'''
   */
  def flushLog(k: LOG[L] => Unit): Logger[L, A] = {
    effectLog(k)
    resetLog
  }

  /**
   * Runs the given side-effect on each element of the log, then returns this underlying value with an empty log. '''CAUTION: side-effect'''
   */
  def flushEachLog(k: L => Unit): Logger[L, A] = {
    effectLog(_ foreach k)
    resetLog
  }

  /**
   * Prints the log, then returns this underlying value. '''CAUTION: side-effect'''
   */
  def printLog(implicit s: Show[L]): Logger[L, A] =
    effectLog(_.println)

  /**
   * Prints each element of the log, then returns this underlying value. '''CAUTION: side-effect'''
   */
  def printEachLog(implicit s: Show[L]): Logger[L, A] =
    effectEachLog(_.println)

  /**
   * Prints the log, then returns this underlying value with an empty log. '''CAUTION: side-effect'''
   */
  def printFlushLog(implicit s: Show[L]): Logger[L, A] =
    flushLog(_.println)

  /**
   * Prints each element of the log, then returns this underlying value with an empty log. '''CAUTION: side-effect'''
   */
  def printFlushEachLog(implicit s: Show[L]): Logger[L, A] =
    flushEachLog(_.println)
}

object Logger {
  type LOG[C] = IndSeq[C]

  implicit def LoggerInjective[L] = Injective[({type λ[α]= Logger[L, α]})#λ]

  implicit val LoggerBifunctor: Bifunctor[Logger] = new Bifunctor[Logger] {
    def bimap[A, B, C, D](k: Logger[A, B], f: A => C, g: B => D): Logger[C, D] =
      mkLogger(g(k.over)) setLog (k.log map f)
  }

  implicit def LoggerPure[L]: Pure[({type λ[α]= Logger[L, α]})] = new Pure[({type λ[α]=Logger[L, α]})] {
    def pure[A](a: => A) = new Logger[L, A] {
      val log = [LOG[L]]
      val over = a
    }
  }

  implicit def LoggerFunctor[L]: Functor[({type λ[α]=Logger[L, α]})] = new Functor[({type λ[α]= Logger[L, α]})] {
    def fmap[A, B](x: Logger[L, A], f: A => B) =
      x map f
  }

  implicit def LoggerApply[L]: Apply[({type λ[α]=Logger[L, α]})] = new Apply[({type λ[α]=Logger[L, α]})] {
    def apply[A, B](f: Logger[L, A => B], a: Logger[L, A]): Logger[L, B] = {
      val w1 = f.value
      val w2 = a.value

      new Logger[L, B] {
        val log = w1.written |+| w2.written
        val over = w1.over(w2.over)
      }
    }
  }

  implicit def LoggerBind[L]: Bind[({type λ[α]=Logger[L, α]})] = new Bind[({type λ[α]=Logger[L, α]})] {
    def bind[A, B](a: Logger[L, A], f: A => Logger[L, B]) =
      a flatMap f
  }

  implicit def LoggerEach[L]: Each[({type λ[α]=Logger[L, α]})] = new Each[({type λ[α]= Logger[L, α]})] {
    def each[A](x: Logger[L, A], f: A => Unit) =
      x foreach f
  }

  implicit def LoggerIndex[L]: Index[({type λ[α]=Logger[L, α]})] = new Index[({type λ[α]=Logger[L, α]})] {
    def index[A](a: Logger[L, A], n: Int) =
      if(n == 0) Some(a.over) else None
  }

  implicit def LoggerFoldable[L]: Foldable[({type λ[α]=Logger[L, α]})] = new Foldable[({type λ[α]=Logger[L, α]})] {
    override def foldRight[A, B](t: Logger[L, A], b: => B, f: (A, => B) => B) =
      f(t.over, b)
  }

  implicit def LoggerTraverse[L]: Traverse[({type λ[α]=Logger[L, α]})] = new Traverse[({type λ[α]=Logger[L, α]})] {
    def traverse[F[_] : Applicative, A, B](f: A => F[B], t: Logger[L, A]) =
      f(t.over)  (b => new Logger[L, B] {
        val log = t.log
        val over = b
      })
  }

  implicit def LoggerShow[L : Show, A : Show]: Show[Logger[L, A]] = new Show[Logger[L, A]] {
    def show(a: Logger[L, A]) =
      ("Logger(" + a.log.shows + "," + a.over.shows + ")").toList
  }

  implicit def LoggerEqual[L, A: Equal]: Equal[Logger[L, A]] = new Equal[Logger[L, A]] {
    def equal(a1: Logger[L, A], a2: Logger[L, A]) =
      a1.over === a2.over
  }

  implicit def LoggerOrder[L, A: Order]: Order[Logger[L, A]] = new Order[Logger[L, A]] {
    def order(a1: Logger[L, A], a2: Logger[L, A]) =
      a1.over ?|? a2.over
  }

  implicit def LoggerZero[L, A: Zero]: Zero[Logger[L, A]] = new Zero[Logger[L, A]] {
    val zero = new Logger[L, A] {
      val log = [LOG[L]]
      val over = [A]
    }
  }
}

trait Loggers {
  import Logger._

  def mkLogger[L] = new (Id ~> (({type λ[α]= Logger[L, α]}))) {
    def apply[A](a: A) = new Logger[L, A] {
      def log = [LOG[L]]
      def over = a
    }
  }
}