var _ = require('underscore');
var isopackets = require("./isopackets.js");
var Console = require('./console.js').Console;

// runLog is primarily used by the parts of the tool which run apps locally. It
// writes to standard output (and standard error, if rawLogs is set), and allows
// special output forms like "write this line, but let the next line overwrite
// it". It also makes its output available to the proxy, to be displayed to web
// browsers if the app fails to run.
//
// It's not the only mechanism used for gathering messages! buildmessage is a
// more structured way of gathering messages, but unlike log, it does not print
// messages immediately.
//
// Some other parts of the code (eg commands and warehouse) write directly to
// process.std{out,err} or to console.log; we should be careful to not do that
// anywhere that may overlap with use of runLog.


var getLoggingPackage = function () {
  var Log = isopackets.load('logging').logging.Log;

  // Since no other process will be listening to stdout and parsing it,
  // print directly in the same format as log messages from other apps
  Log.outputFormat = 'colored-text';

  return Log;
};

var RunLog = function () {
  var self = this;

  self.rawLogs = false;

  self.messages = []; // list of log objects
  self.maxLength = 100;

  // If non-null, the last thing logged was "server restarted"
  // message, and the value will be the number of consecutive such
  // messages that have been logged with no other intervening messages
  self.consecutiveRestartMessages = null;
  self.consecutiveClientRestartMessages = null;

  // If non-null, the last thing that was logged was a temporary
  // message (with a carriage return but no newline), and this is its
  // length.
  self.temporaryMessageLength = null;
};

_.extend(RunLog.prototype, {
  _record: function (msg) {
    var self = this;

    self.messages.push(msg);
    if (self.messages.length > self.maxLength) {
      self.messages.shift();
    }
  },

  _clearSpecial: function () {
    var self = this;

    if (self.consecutiveRestartMessages) {
      self.consecutiveRestartMessages = null;
      Console.info();
    }

    if (self.consecutiveClientRestartMessages) {
      self.consecutiveClientRestartMessages = null;
      Console.info();
    }

    if (self.temporaryMessageLength) {
      var spaces = new Array(self.temporaryMessageLength + 1).join(' ');
      process.stdout.write(spaces + Console.CARRIAGE_RETURN);
      self.temporaryMessageLength = null;
    }
  },

  setRawLogs: function (rawLogs) {
    this.rawLogs = !!rawLogs;
  },

  logAppOutput: function (line, isStderr) {
    var self = this;

    var Log = getLoggingPackage();

    var obj = (isStderr ?
               Log.objFromText(line, { level: 'warn', stderr: true }) :
               Log.parse(line) || Log.objFromText(line));
    self._record(obj);

    self._clearSpecial();
    if (self.rawLogs)
      Console[isStderr ? "rawError" : "rawInfo"](line + "\n");
    else
      Console.rawInfo(Log.format(obj, { color: true }) + "\n");

    // XXX deal with test server logging differently?!
  },

  // Log the message.
  //  msg: message
  //  options:
  //    - arrow: if true, preface with => and wrap accordingly.
  log: function (msg, options) {
    var self = this;
    options = options || {};
    var obj = {
      time: new Date,
      message: msg
      // in the future, might want to add something else to
      // distinguish messages from runner from message from the app,
      // but for now, nothing would use it, so we'll keep it simple
    };
    self._record(obj);

    self._clearSpecial();

    // Process the options. By default, we want to wordwrap the message with
    // Console.info. If we ask for raw output, then we don't want to do that. If
    // we ask for an arrow, we want to wrap around with => as the bulletPoint.
    Console[options.arrow ? 'arrowInfo' : 'info'](msg);
  },

  // Write a message to the terminal that will get overwritten by the
  // next message logged. (Don't put it in the log that getLog
  // returns.)
  // XXX Maybe this should return an object that you have to pass to the
  //     subsequent log call, and only such a log call will overwrite it (and an
  //     intervening log call will cause this to stay on the screen)?
  //     eg, a log call from the updater can interweave with the logTemporary
  //     calls in run-all.js
  logTemporary: function (msg) {
    var self = this;

    self._clearSpecial();
    process.stdout.write(msg + Console.CARRIAGE_RETURN);
    self.temporaryMessageLength = msg.length;
  },

  logRestart: function () {
    var self = this;

    if (self.consecutiveRestartMessages) {
      // replace old message in place. this assumes that the new restart message
      // is not shorter than the old one.
      process.stdout.write(Console.CARRIAGE_RETURN);
      self.messages.pop();
      self.consecutiveRestartMessages ++;
    } else {
      self._clearSpecial();
      self.consecutiveRestartMessages = 1;
    }

    var message = "=> Meteor server restarted";
    if (self.consecutiveRestartMessages > 1)
      message += " (x" + self.consecutiveRestartMessages + ")";
    // no newline, so that we can overwrite it if we get another
    // restart message right after this one
    process.stdout.write(message);

    self._record({
      time: new Date,
      message: message
    });
  },

  logClientRestart: function () {
    var self = this;

    if (self.consecutiveClientRestartMessages) {
      // replace old message in place. this assumes that the new restart message
      // is not shorter than the old one.
      process.stdout.write(Console.CARRIAGE_RETURN);
      self.messages.pop();
      self.consecutiveClientRestartMessages ++;
    } else {
      self._clearSpecial();
      self.consecutiveClientRestartMessages = 1;
    }

    var message = "=> Client modified -- refreshing";
    if (self.consecutiveClientRestartMessages > 1)
      message += " (x" + self.consecutiveClientRestartMessages + ")";
    // no newline, so that we can overwrite it if we get another
    // restart message right after this one
    process.stdout.write(message);

    self._record({
      time: new Date,
      message: message
    });
  },

  finish: function () {
    var self = this;

    self._clearSpecial();
  },

  clearLog: function () {
    var self = this;
    self.messages = [];
  },

  getLog: function () {
    var self = this;
    return self.messages;
  }
});

// Create a singleton instance of RunLog. Expose its public methods on the
// object you get with require('./run-log.js').
var runLogInstance = new RunLog;
_.each(
  ['log', 'logTemporary', 'logRestart', 'logClientRestart', 'logAppOutput',
   'setRawLogs', 'finish', 'clearLog', 'getLog'],
  function (method) {
    exports[method] = _.bind(runLogInstance[method], runLogInstance);
  });
