var util = require('util');
var events = require('events');
var _ = require('lodash');
var table = require('text-table');
var chalk = require('chalk');

// padding step
var step    = '  ';
var padding = ' ';

// color -> status mappings
var colors = {
  skip      : 'yellow',
  force     : 'yellow',
  create    : 'green',
  invoke    : 'bold',
  conflict  : 'red',
  identical : 'cyan',
  info      : 'gray'
};

function pad(status) {
  var max = 'identical'.length;
  var delta = max - status.length;
  return delta ? new Array(delta + 1).join(' ') + status : status;
}

// borrowed from https://github.com/mikeal/logref/blob/master/main.js#L6-15
function formatter(msg, ctx) {
  while (msg.indexOf('%') !== -1) {
    var start = msg.indexOf('%');
    var end = msg.indexOf(' ', start);

    if (end === -1) {
      end = msg.length;
    }

    msg = msg.slice(0, start) + ctx[msg.slice(start + 1, end)] + msg.slice(end);
  }
  return msg;
}

module.exports = function logger() {
  // `this.log` is a [logref](https://github.com/mikeal/logref)
  // compatible logger, with an enhanced API.
  //
  // It also has EventEmitter like capabilities, so you can call on / emit
  // on it, namely used to increase or decrease the padding.
  //
  // All logs are done against STDERR, letting you stdout for meaningfull
  // value and redirection, should you need to generate output this way.
  //
  // Log functions take two arguments, a message and a context. For any
  // other kind of paramters, `console.error` is used, so all of the
  // console format string goodies you're used to work fine.
  //
  // - msg      - The message to show up
  // - context  - The optional context to escape the message against
  //
  // Retunrns the logger
  function log(msg, ctx) {
    msg = msg || '';
    if (!ctx) {
      ctx = {};
    }

    if (typeof ctx === 'object' && !Array.isArray(ctx)) {
      console.error(formatter(msg, ctx));
    } else {
      console.error.apply(console, arguments);
    }

    return log;
  }

  _.extend(log, events.EventEmitter.prototype);

  // A simple write method, with formatted message. If `msg` is
  // ommitted, then a single `\n` is written.
  //
  // Returns the logger
  log.write = function (msg) {
    if (!msg) {
      return this.write('\n');
    }

    process.stderr.write(util.format.apply(util, arguments));
    return this;
  };

  // Same as `log.write()` but automatically appends a `\n` at the end
  // of the message.
  log.writeln = function () {
    return this.write.apply(this, arguments).write();
  };

  // Convenience helper to write sucess status, this simply prepends the
  // message with a gren `✔`.
  log.ok = function (msg) {
    this.write(chalk.green('✔ ') + util.format.apply(util, arguments) + '\n');
    return this;
  };

  log.error = function (msg) {
    this.write(chalk.red('✗ ') + util.format.apply(util, arguments) + '\n');
    return this;
  };

  log.on('up', function () {
    padding = padding + step;
  });

  log.on('down', function () {
    padding = padding.replace(step, '');
  });

  Object.keys(colors).forEach(function (status) {
    // Each predefined status has its logging method utility, handling
    // status color and padding before the usual `.write()`
    //
    // Example
    //
    //    this.log
    //      .write()
    //      .info('Doing something')
    //      .force('Forcing filepath %s, 'some path')
    //      .conflict('on %s' 'model.js')
    //      .write()
    //      .ok('This is ok');
    //
    // The list of status and mapping colors
    //
    //    skip       yellow
    //    force      yellow
    //    create     green
    //    invoke     bold
    //    conflict   red
    //    identical  cyan
    //    info       grey
    //
    // Returns the logger
    log[status] = function () {
      var color = colors[status];
      this.write(chalk[color](pad(status))).write(padding);
      this.write(util.format.apply(util, arguments) + '\n');
      return this;
    };
  });

  // A basic wrapper around `cli-table` package, resetting any single
  // char to empty strings, this is used for aligning options and
  // arguments without too much Math on our side.
  //
  // - opts - A list of rows or an Hash of options to pass through cli
  //          table.
  //
  // Returns the table reprensetation
  log.table = function (opts) {
    var tableData = [];

    opts = Array.isArray(opts) ? { rows: opts } : opts;
    opts.rows = opts.rows || [];

    opts.rows.forEach(function (row) {
      tableData.push(row);
    });

    return table(tableData);
  };

  return log;
};
