text: 'use strict'
  var Progress = require('are-we-there-yet')
  var Gauge = require('gauge')
  var EE = require('events').EventEmitter
  var log = exports = module.exports = new EE()
  var util = require('util')
  
  var setBlocking = require('set-blocking')
  var consoleControl = require('console-control-strings')
  
  setBlocking(true)
  var stream = process.stderr
  Object.defineProperty(log, 'stream', {
    set: function (newStream) {
      stream = newStream
      if (this.gauge) this.gauge.setWriteTo(stream, stream)
    },
    get: function () {
      return stream
    }
  })
  
  // by default, decide based on tty-ness.
  var colorEnabled
  log.useColor = function () {
    return colorEnabled != null ? colorEnabled : stream.isTTY
  }
  
  log.enableColor = function () {
    colorEnabled = true
    this.gauge.setTheme({hasColor: colorEnabled, hasUnicode: unicodeEnabled})
  }
  log.disableColor = function () {
    colorEnabled = false
    this.gauge.setTheme({hasColor: colorEnabled, hasUnicode: unicodeEnabled})
  }
  
  // default level
  log.level = 'info'
  
  log.gauge = new Gauge(stream, {
    enabled: false, // no progress bars unless asked
    theme: {hasColor: log.useColor()},
    template: [
      {type: 'progressbar', length: 20},
      {type: 'activityIndicator', kerning: 1, length: 1},
      {type: 'section', default: ''},
      ':',
      {type: 'logline', kerning: 1, default: ''}
    ]
  })
  
  log.tracker = new Progress.TrackerGroup()
  
  // we track this separately as we may need to temporarily disable the
  // display of the status bar for our own loggy purposes.
  log.progressEnabled = log.gauge.isEnabled()
  
  var unicodeEnabled
  
  log.enableUnicode = function () {
    unicodeEnabled = true
    this.gauge.setTheme({hasColor: this.useColor(), hasUnicode: unicodeEnabled})
  }
  
  log.disableUnicode = function () {
    unicodeEnabled = false
    this.gauge.setTheme({hasColor: this.useColor(), hasUnicode: unicodeEnabled})
  }
  
  log.setGaugeThemeset = function (themes) {
    this.gauge.setThemeset(themes)
  }
  
  log.setGaugeTemplate = function (template) {
    this.gauge.setTemplate(template)
  }
  
  log.enableProgress = function () {
    if (this.progressEnabled) return
    this.progressEnabled = true
    this.tracker.on('change', this.showProgress)
    if (this._pause) return
    this.gauge.enable()
  }
  
  log.disableProgress = function () {
    if (!this.progressEnabled) return
    this.progressEnabled = false
    this.tracker.removeListener('change', this.showProgress)
    this.gauge.disable()
  }
  
  var trackerConstructors = ['newGroup', 'newItem', 'newStream']
  
  var mixinLog = function (tracker) {
    // mixin the public methods from log into the tracker
    // (except: conflicts and one's we handle specially)
    Object.keys(log).forEach(function (P) {
      if (P[0] === '_') return
      if (trackerConstructors.filter(function (C) { return C === P }).length) return
      if (tracker[P]) return
      if (typeof log[P] !== 'function') return
      var func = log[P]
      tracker[P] = function () {
        return func.apply(log, arguments)
      }
    })
    // if the new tracker is a group, make sure any subtrackers get
    // mixed in too
    if (tracker instanceof Progress.TrackerGroup) {
      trackerConstructors.forEach(function (C) {
        var func = tracker[C]
        tracker[C] = function () { return mixinLog(func.apply(tracker, arguments)) }
      })
    }
    return tracker
  }
  
  // Add tracker constructors to the top level log object
  trackerConstructors.forEach(function (C) {
    log[C] = function () { return mixinLog(this.tracker[C].apply(this.tracker, arguments)) }
  })
  
  log.clearProgress = function (cb) {
    if (!this.progressEnabled) return cb && process.nextTick(cb)
    this.gauge.hide(cb)
  }
  
  log.showProgress = function (name, completed) {
    if (!this.progressEnabled) return
    var values = {}
    if (name) values.section = name
    var last = log.record[log.record.length - 1]
    if (last) {
      values.subsection = last.prefix
      var disp = log.disp[last.level] || last.level
      var logline = this._format(disp, log.style[last.level])
      if (last.prefix) logline += ' ' + this._format(last.prefix, this.prefixStyle)
      logline += ' ' + last.message.split(/\r?\n/)[0]
      values.logline = logline
    }
    values.completed = completed || this.tracker.completed()
    this.gauge.show(values)
  }.bind(log) // bind for use in tracker's on-change listener
  
  // temporarily stop emitting, but don't drop
  log.pause = function () {
    this._paused = true
    if (this.progressEnabled) this.gauge.disable()
  }
  
  log.resume = function () {
    if (!this._paused) return
    this._paused = false
  
    var b = this._buffer
    this._buffer = []
    b.forEach(function (m) {
      this.emitLog(m)
    }, this)
    if (this.progressEnabled) this.gauge.enable()
  }
  
  log._buffer = []
  
  var id = 0
  log.record = []
  log.maxRecordSize = 10000
  log.log = function (lvl, prefix, message) {
    var l = this.levels[lvl]
    if (l === undefined) {
      return this.emit('error', new Error(util.format(
        'Undefined log level: %j', lvl)))
    }
  
    var a = new Array(arguments.length - 2)
    var stack = null
    for (var i = 2; i < arguments.length; i++) {
      var arg = a[i - 2] = arguments[i]
  
      // resolve stack traces to a plain string.
      if (typeof arg === 'object' && arg &&
          (arg instanceof Error) && arg.stack) {
        arg.stack = stack = arg.stack + ''
      }
    }
    if (stack) a.unshift(stack + '\n')
    message = util.format.apply(util, a)
  
    var m = { id: id++,
              level: lvl,
              prefix: String(prefix || ''),
              message: message,
              messageRaw: a }
  
    this.emit('log', m)
    this.emit('log.' + lvl, m)
    if (m.prefix) this.emit(m.prefix, m)
  
    this.record.push(m)
    var mrs = this.maxRecordSize
    var n = this.record.length - mrs
    if (n > mrs / 10) {
      var newSize = Math.floor(mrs * 0.9)
      this.record = this.record.slice(-1 * newSize)
    }
  
    this.emitLog(m)
  }.bind(log)
  
  log.emitLog = function (m) {
    if (this._paused) {
      this._buffer.push(m)
      return
    }
    if (this.progressEnabled) this.gauge.pulse(m.prefix)
    var l = this.levels[m.level]
    if (l === undefined) return
    if (l < this.levels[this.level]) return
    if (l > 0 && !isFinite(l)) return
  
    // If 'disp' is null or undefined, use the lvl as a default
    // Allows: '', 0 as valid disp
    var disp = log.disp[m.level] != null ? log.disp[m.level] : m.level
    this.clearProgress()
    m.message.split(/\r?\n/).forEach(function (line) {
      if (this.heading) {
        this.write(this.heading, this.headingStyle)
        this.write(' ')
      }
      this.write(disp, log.style[m.level])
      var p = m.prefix || ''
      if (p) this.write(' ')
      this.write(p, this.prefixStyle)
      this.write(' ' + line + '\n')
    }, this)
    this.showProgress()
  }
  
  log._format = function (msg, style) {
    if (!stream) return
  
    var output = ''
    if (this.useColor()) {
      style = style || {}
      var settings = []
      if (style.fg) settings.push(style.fg)
      if (style.bg) settings.push('bg' + style.bg[0].toUpperCase() + style.bg.slice(1))
      if (style.bold) settings.push('bold')
      if (style.underline) settings.push('underline')
      if (style.inverse) settings.push('inverse')
      if (settings.length) output += consoleControl.color(settings)
      if (style.beep) output += consoleControl.beep()
    }
    output += msg
    if (this.useColor()) {
      output += consoleControl.color('reset')
    }
    return output
  }
  
  log.write = function (msg, style) {
    if (!stream) return
  
    stream.write(this._format(msg, style))
  }
  
  log.addLevel = function (lvl, n, style, disp) {
    // If 'disp' is null or undefined, use the lvl as a default
    if (disp == null) disp = lvl
    this.levels[lvl] = n
    this.style[lvl] = style
    if (!this[lvl]) {
      this[lvl] = function () {
        var a = new Array(arguments.length + 1)
        a[0] = lvl
        for (var i = 0; i < arguments.length; i++) {
          a[i + 1] = arguments[i]
        }
        return this.log.apply(this, a)
      }.bind(this)
    }
    this.disp[lvl] = disp
  }
  
  log.prefixStyle = { fg: 'magenta' }
  log.headingStyle = { fg: 'white', bg: 'black' }
  
  log.style = {}
  log.levels = {}
  log.disp = {}
  log.addLevel('silly', -Infinity, { inverse: true }, 'sill')
  log.addLevel('verbose', 1000, { fg: 'blue', bg: 'black' }, 'verb')
  log.addLevel('info', 2000, { fg: 'green' })
  log.addLevel('http', 3000, { fg: 'green', bg: 'black' })
  log.addLevel('warn', 4000, { fg: 'black', bg: 'yellow' }, 'WARN')
  log.addLevel('error', 5000, { fg: 'red', bg: 'black' }, 'ERR!')
  log.addLevel('silent', Infinity)
  
  // allow 'error' prefix
  log.on('error', function () {})
  