
// centralized stdout writer.

exports.doColor = doColor
exports.write = write

var npm = require("../npm.js")
  , tty = require("tty")
  , streams = {}
  , ttys = {}
  , net = require("net")
  , util = require("util")
  , deadStreams = {}

function doColor (stream) {
  var conf = npm.config.get("color")
  return (!conf) ? false
       : (conf === "always") ? true
       : isatty(stream)
}
function isatty (stream) {
  // console.error("isatty?", stream)
  if (!tty.isatty) return true
  if (!stream) return false
  if (stream.isTTY) return true
  if (stream && (typeof stream.fd === "number")) {
    stream.isTTY = tty.isatty(stream.fd)
  }
  return stream.isTTY
}

function write (args, stream, lf, cb) {
  // console.error("write", [args, stream, lf, cb])
  if (typeof cb !== "function" && typeof lf === "function") {
    cb = lf
    lf = null
  }
  if (typeof cb !== "function" && typeof stream === "function") {
    cb = stream
    stream = npm.config.get("outfd")
  }

  stream = getStream(stream)
  // console.error("gotStream", stream)
  if (lf == null) lf = isatty(stream)
  if (!stream) return cb && cb(), false
  if (!Array.isArray(args)) args = [args]

  // console.error("write", args)

  var msg = ""
    , colored = doColor(stream)
  msg = args.map(function (arg) {
    if (typeof arg !== "string") {
      return util.inspect(arg, false, 5, colored) + "\n"
    }
    if (!colored) arg = arg.replace(/\033\[[0-9;]*m/g, '')
    if (!npm.config.get("unicode")) {
      arg = arg.replace(/└/g, "`")
               .replace(/─/g, "-")
               .replace(/│/g, "|")
               .replace(/├/g, "+")
               .replace(/┬/g, "-")
    }
    return arg
  }).join(" ")

  // listen to the "output" event to cancel/modify/redirect
  npm.output = {stream:stream, message:msg}
  npm.emit("output", npm.output)
  if (!npm.output) return cb && cb(), false // cancelled
  stream = npm.output.stream
  msg = npm.output.message

  // EPIPE errors just mean that the stream is not listening
  // any more.  Mark the stream as dead, and return.
  if (deadStreams[stream.fd]) {
    return cb && cb(), false
  }
  if (!deadStreams.hasOwnProperty(stream.fd)) {
    deadStreams[stream.fd] = false
    stream.on("error", function (er) {
      if (er.code === "EPIPE") {
        deadStreams[stream.fd] = true
        return cb && cb()
      }
      if (stream.listeners("error").length === 1) {
        throw er
      }
    })
  }

  // use the \r\n in case we're in raw mode.
  msg = msg.split(/\r?\n/).concat("").join(lf ? "\r\n" : "\n")
  // output to stderr should be synchronous
  if (stream === process.stderr || stream.fd === 2) {
    process.stderr.write(msg)
    if (cb) cb()
    return true
  }
  // console.error("writing ", msg)
  var flushed = stream.write(msg)
  if (flushed && cb) {
    process.nextTick(cb)
  } else if (cb) {
    stream.once("drain", cb)
  }
  return flushed
}

var hadError = false
function getStream (fd) {
  if (hadError) return

  var stream
  if (!fd && fd !== 0) return
  if (typeof fd === "string") fd = +fd

  // console.error("getStream", fd, hadError)

  if (fd && typeof fd === "object") {
    stream = fd
    fd = fd.fd
  } else if (streams[fd]) {
    stream = streams[fd]
  } else {
    switch (fd) {
      case 1:
        stream = process.stdout
        stream.fd = fd
        stream.writable = true
        break
      case 2:
        stream = process.stderr
        stream.fd = fd
        stream.writable = true
        break
      default:
        try {
          stream = new net.Stream(fd)
          if (!stream || !stream.writable) {
            throw new Error("Stream not writable")
          }
        } catch (ex) {
          // if this fails, then regular logging is most likely broken.
          var er = new Error("cannot output to fd "+fd + ": "+
                             (ex.stack || ex.message).substr(7) + "\n")
          console.error(er.stack)
          hadError = true
          process.exit(1)
        }
    }
  }

  if (!stream || !stream.writable) return
  return streams[fd] = stream
}
