var _ = require('lodash');
var utils = require('./utils');

/**
 * Abstract class providing common functionality to loggers
 * @param {[type]} log [description]
 * @param {[type]} config [description]
 */
function LoggerAbstract(log, config) {
  this.log = log;
  this.listeningLevels = [];

  utils.makeBoundMethods(this);

  // when the log closes, remove our event listeners
  this.log.once('closing', this.bound.cleanUpListeners);

  this.setupListeners(config.levels);
}

function padNumToTen(n) {
  return n < 10 ? '0' + n.toString(10) : n.toString(10);
}

/**
 * Create a timestamp string used in the format function. Defers to Log.timestamp if it is defined,
 * Also, feel free to override this at the logger level.
 * @return {String} - Timestamp in ISO 8601 UTC
 */
LoggerAbstract.prototype.timestamp = function() {
  var d = new Date();
  return (
    d.getUTCFullYear() +
    '-' +
    padNumToTen(d.getUTCMonth() + 1) +
    '-' +
    padNumToTen(d.getUTCDate()) +
    'T' +
    padNumToTen(d.getUTCHours()) +
    ':' +
    padNumToTen(d.getUTCMinutes()) +
    ':' +
    padNumToTen(d.getUTCSeconds()) +
    'Z'
  );
};

function indent(text, spaces) {
  var space = utils.repeat(' ', spaces || 2);
  return (text || '')
    .split(/\r?\n/)
    .map(function(line) {
      return space + line;
    })
    .join('\n');
}

LoggerAbstract.prototype.format = function(label, message) {
  return label + ': ' + this.timestamp() + '\n' + indent(message) + '\n\n';
};

LoggerAbstract.prototype.write = function() {
  throw new Error('This should be overwritten by the logger');
};

/**
 * Clear the current event listeners and then re-listen for events based on the level specified
 *
 * @method setupListeners
 * @private
 * @param  {Integer} level - The max log level that this logger should listen to
 * @return {undefined}
 */
LoggerAbstract.prototype.setupListeners = function(levels) {
  this.cleanUpListeners();

  this.listeningLevels = [];

  _.each(
    levels,
    _.bind(function(level) {
      var fnName = 'on' + utils.ucfirst(level);
      if (this.bound[fnName]) {
        this.listeningLevels.push(level);
        this.log.on(level, this.bound[fnName]);
      } else {
        throw new Error('Unable to listen for level "' + level + '"');
      }
    }, this)
  );
};

/**
 * Clear the current event listeners
 *
 * @method cleanUpListeners
 * @private
 * @return {undefined}
 */
LoggerAbstract.prototype.cleanUpListeners = utils.handler(function() {
  _.each(
    this.listeningLevels,
    _.bind(function(level) {
      this.log.removeListener(level, this.bound['on' + utils.ucfirst(level)]);
    }, this)
  );
});

/**
 * Handler for the logs "error" event
 *
 * @method onError
 * @private
 * @param  {Error} e - The Error object to log
 * @return {undefined}
 */
LoggerAbstract.prototype.onError = utils.handler(function(e) {
  this.write(e.name === 'Error' ? 'ERROR' : e.name, e.stack);
});

/**
 * Handler for the logs "warning" event
 *
 * @method onWarning
 * @private
 * @param  {String} msg - The message to be logged
 * @return {undefined}
 */
LoggerAbstract.prototype.onWarning = utils.handler(function(msg) {
  this.write('WARNING', msg);
});

/**
 * Handler for the logs "info" event
 *
 * @method onInfo
 * @private
 * @param  {String} msg - The message to be logged
 * @return {undefined}
 */
LoggerAbstract.prototype.onInfo = utils.handler(function(msg) {
  this.write('INFO', msg);
});

/**
 * Handler for the logs "debug" event
 *
 * @method onDebug
 * @private
 * @param  {String} msg - The message to be logged
 * @return {undefined}
 */
LoggerAbstract.prototype.onDebug = utils.handler(function(msg) {
  this.write('DEBUG', msg);
});

/**
 * Handler for the logs "trace" event
 *
 * @method onTrace
 * @private
 * @param  {String} msg - The message to be logged
 * @return {undefined}
 */
LoggerAbstract.prototype.onTrace = utils.handler(function(requestDetails) {
  this.write('TRACE', this._formatTraceMessage(requestDetails));
});

LoggerAbstract.prototype._formatTraceMessage = function(req) {
  return (
    '-> ' +
    req.method +
    ' ' +
    req.url +
    '\n' +
    this._prettyJson(req.body) +
    '\n' +
    '<- ' +
    req.status +
    '\n' +
    this._prettyJson(req.response)
  );
  /*
-> GET https://sldfkjsdlfksjdf:9200/slsdkfjlxckvxhclks?sdlkj=sdlfkje
{
  asdflksjdf
}

<- 502
{
  sldfksjdlf
}
*/
};

LoggerAbstract.prototype._prettyJson = function(body) {
  try {
    if (typeof body === 'string') {
      body = JSON.parse(body);
    }
    return JSON.stringify(body, null, '  ').replace(/'/g, '\\u0027');
  } catch (e) {
    return typeof body === 'string' ? body : '';
  }
};

module.exports = LoggerAbstract;
