/* sbt -- Simple Build Tool
 * Copyright 2008, 2009, 2010 Mark Harrah
 */
package sbt

import scala.collection.mutable.ListBuffer

/**
 * A logger that can buffer the logging done on it and then can flush the buffer
 * to the delegate logger provided in the constructor.  Use 'startRecording' to
 * start buffering and then 'play' from to flush the buffer to the backing logger.
 *  The logging level set at the time a message is originally logged is used, not
 * the level at the time 'play' is called.
 *
 * This class assumes that it is the only client of the delegate logger.
 */
class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
  private[this] val buffer = new ListBuffer[LogEvent]
  private[this] var recording = false

  /** Enables buffering. */
  def record() = synchronized { recording = true }
  def buffer[T](f: => T): T = {
    record()
    try { f }
    finally { stopQuietly() }
  }
  def bufferQuietly[T](f: => T): T = {
    record()
    try {
      val result = f
      clear()
      result
    } catch { case e: Throwable => stopQuietly(); throw e }
  }
  def stopQuietly() = synchronized { try { stop() } catch { case e: Exception => () } }

  /**
   * Flushes the buffer to the delegate logger.  This method calls logAll on the delegate
   * so that the messages are written consecutively. The buffer is cleared in the process.
   */
  def play(): Unit = synchronized { delegate.logAll(buffer.readOnly); buffer.clear() }
  /** Clears buffered events and disables buffering. */
  def clear(): Unit = synchronized { buffer.clear(); recording = false }
  /** Plays buffered events and disables buffering. */
  def stop(): Unit = synchronized { play(); clear() }

  override def ansiCodesSupported = delegate.ansiCodesSupported
  override def setLevel(newLevel: Level.Value): Unit = synchronized {
    super.setLevel(newLevel)
    if (recording)
      buffer += new SetLevel(newLevel)
    else
      delegate.setLevel(newLevel)
  }
  override def setSuccessEnabled(flag: Boolean): Unit = synchronized {
    super.setSuccessEnabled(flag)
    if (recording)
      buffer += new SetSuccess(flag)
    else
      delegate.setSuccessEnabled(flag)
  }
  override def setTrace(level: Int): Unit = synchronized {
    super.setTrace(level)
    if (recording)
      buffer += new SetTrace(level)
    else
      delegate.setTrace(level)
  }

  def trace(t: => Throwable): Unit =
    doBufferableIf(traceEnabled, new Trace(t), _.trace(t))
  def success(message: => String): Unit =
    doBufferable(Level.Info, new Success(message), _.success(message))
  def log(level: Level.Value, message: => String): Unit =
    doBufferable(level, new Log(level, message), _.log(level, message))
  def logAll(events: Seq[LogEvent]): Unit = synchronized {
    if (recording)
      buffer ++= events
    else
      delegate.logAll(events)
  }
  def control(event: ControlEvent.Value, message: => String): Unit =
    doBufferable(Level.Info, new ControlEvent(event, message), _.control(event, message))
  private def doBufferable(level: Level.Value, appendIfBuffered: => LogEvent, doUnbuffered: AbstractLogger => Unit): Unit =
    doBufferableIf(atLevel(level), appendIfBuffered, doUnbuffered)
  private def doBufferableIf(condition: => Boolean, appendIfBuffered: => LogEvent, doUnbuffered: AbstractLogger => Unit): Unit = synchronized {
    if (condition) {
      if (recording)
        buffer += appendIfBuffered
      else
        doUnbuffered(delegate)
    }
  }
}