text: var _ = require('lodash'),
      colors = require('colors/safe'),
      Table = require('cli-table2'),
      format = require('util').format,
  
      util = require('../../util'),
      cliUtils = require('./cli-utils'),
      print = require('../../print'),
      pad = cliUtils.padLeft,
  
      LF = '\n',
      SPC = ' ',
      DOT = '.',
      E = '',
  
      PostmanCLIReporter,
      extractSNR;
  
  // sets theme for colors for console logging
  colors.setTheme({
      log: 'grey',
      info: 'cyan',
      warn: 'yellow',
      debug: 'blue',
      error: 'red'
  });
  
  extractSNR = function (executions) {
      var snr;
  
      // eslint-disable-next-line lodash/collection-method-value
      _.isArray(executions) && _.forEachRight(executions, function (execution) {
          var nextReq = _.get(execution, 'result.return.nextRequest');
          if (nextReq) {
              snr = nextReq;
              return false;
          }
      });
  
      return snr;
  };
  
  /**
   * CLI reporter
   *
   * @param {EventEmitter} emitter - An EventEmitter instance with event handler attachers to trigger reporting.
   * @param {Object} reporterOptions - CLI reporter options object.
   * @param {Boolean=} reporterOptions.silent - Boolean flag to turn off CLI reporting altogether, if set to true.
   * @param {Boolean=} reporterOptions.noAssertions - Boolean flag to turn off assertion reporting, if set to true.
   * @param {Boolean=} reporterOptions.noSummary - Boolean flag to turn off summary reporting altogether, if set to true.
   * @param {Boolean=} reporterOptions.noFailures - Boolean flag to turn off failure reporting altogether, if set to true.
   * @param {Boolean=} reporterOptions.noConsole - Boolean flag to turn off console logging, if set to true.
   * @param {Object} options - A set of generic collection run options.
   * @returns {*}
   */
  PostmanCLIReporter = function (emitter, reporterOptions, options) {
      var currentGroup = options.collection,
          inspect = cliUtils.inspector(options),
          wrap = cliUtils.wrapper(),
          symbols = cliUtils.symbols(options.disableUnicode);
  
      // respect silent option to not report anything
      if (reporterOptions.silent || options.silent) {
          return; // we simply do not register anything!
      }
  
      // we register the `done` listener first so that in case user does not want to show results of collection run, we
      // simply do not register the other events
      emitter.on('done', function () {
          // for some reason, if there is no run summary, it is unexpected and hence don't validate this
          var run = this.summary.run;
  
          // show the summary table (provided option does not say it is not to be shown)
          if (!reporterOptions.noSummary) {
              print(LF + PostmanCLIReporter.parseStatistics(run.stats, run.timings, run.transfers, options) + LF);
          }
  
          // show the failures table (provided option does not say it is not to be shown)
          if (!reporterOptions.noFailures && run.failures && run.failures.length) {
              print(LF + PostmanCLIReporter.parseFailures(run.failures) + LF);
          }
      });
  
      emitter.on('start', function () {
          var collectionIdentifier = currentGroup && (currentGroup.name || currentGroup.id);
  
          // print the newman banner
          print('%s\n\n', colors.reset('newman'));
  
          // print the collection name and newman info line
          collectionIdentifier && print.lf('%s', colors.reset(collectionIdentifier));
      });
  
      emitter.on('beforeIteration', function (err, o) {
          if (err || o.cursor.cycles <= 1) {
              return; // do not print iteration banner if it is a single iteration run
          }
  
          // print the iteration info line
          print.lf(LF + colors.gray.underline('Iteration %d/%d'), o.cursor.iteration + 1, o.cursor.cycles);
      });
  
      emitter.on('test', function (err, o) {
          if (err) {
              return;
          }
          var snr = extractSNR(o.executions);
          if (snr) {
              print.lf(LF + colors.gray('Attempting to set next request to', snr));
          }
      });
  
      emitter.on('beforeItem', function (err, o) {
          if (err) { return; }
  
          var itemGroup = o.item.parent(),
              root = !itemGroup || (itemGroup === options.collection);
  
          // in case this item belongs to a separate folder, print that folder name
          if (itemGroup && (currentGroup !== itemGroup)) {
              !root && print('\n%s %s', symbols.folder, colors.reset(util.getFullName(itemGroup)));
  
              // set the flag that keeps track of the currently running group
              currentGroup = itemGroup;
          }
  
          // we print the item name. the symbol prefix denotes if the item is in root or under folder.
          // @todo - when we do indentation, we would not need symbolic representation
          o.item && print.lf('\n%s %s', (root ?
              symbols.root : symbols.sub), colors.reset(o.item.name || E));
      });
  
      // print out the request name to be executed and start a spinner
      emitter.on('beforeRequest', function (err, o) {
          if (err) { return; }
  
          o.request && print('  %s %s ', colors.gray(o.request.method), colors.gray(o.request.url)).wait(colors.gray);
      });
  
      // output the response code, reason and time
      emitter.on('request', function (err, o) {
          if (err) { return; }
  
          var size = o.response && o.response.size();
          size = size && (size.header || 0) + (size.body || 0) || 0;
  
          err ? print.lf(colors.red('[errored]')) :
              print.lf(colors.gray('[%d %s, %s, %s]'), o.response.code, o.response.reason(),
                  util.filesize(size), util.prettyms(o.response.responseTime));
      });
  
      // Print script errors in real time
      emitter.on('script', function (err, o) {
          err && print.lf(colors.red.bold('%s⠄ %s in %s-script'), pad(this.summary.run.failures.length, 3, SPC), err.name,
              o.event && o.event.listen || 'unknown');
      });
  
      !reporterOptions.noAssertions && emitter.on('assertion', function (err, o) {
          var passed = !err;
  
          // handle skipped test display
          if (o.skipped) {
              print.lf('%s %s', colors.cyan('  - '), colors.cyan('[skipped] ' + o.assertion));
              return;
          }
  
          // print each test assertions
          print.lf('%s %s', passed ? colors.green(`  ${symbols.ok} `) :
              colors.red.bold(pad(this.summary.run.failures.length, 3, SPC) + symbols.dot), passed ?
              colors.gray(o.assertion) : colors.red.bold(o.assertion));
      });
  
      // show user console logs in a neatly formatted way (provided user has not disabled the same)
      !reporterOptions.noConsole && emitter.on('console', function (err, o) {
          if (err) { return; }
  
          var color = colors[o.level] || colors.gray,
              message;
  
          // we first merge all messages to a string. while merging we run the values to util.inspect to colour code the
          // messages based on data type
          message = wrap(_.reduce(o.messages, function (log, message) { // wrap the whole message to the window size
              return (log += (log ? colors.white(', ') : '') + inspect(message));
          }, E), `  ${color(symbols.console.middle)} `); // add an indentation line at the beginning
  
          print.buffer(color(`  ${symbols.console.top}\n`), color(`  ${symbols.console.bottom}\n`))
              // tweak the message to ensure that its surrounding is not brightly coloured.
              // also ensure to remove any blank lines generated due to util.inspect
              .nobuffer(colors.gray(message.replace(/\n\s*\n/g, LF) + LF));
      });
  };
  
  _.assignIn(PostmanCLIReporter, {
  
      // @todo: change function signature to accept run object and options, thereby reducing parameters
      /**
       * A CLI reporter method to parse collection run statistics into a CLI table.
       *
       * @param {Object} stats - The cumulative collection run status object.
       * @param {Object} stats.iterations - A set of values for total, pending, and failed iterations.
       * @param {Number} stats.iterations.total - Total iterations in the current collection run.
       * @param {Number} stats.iterations.pending - Pending iterations in the current collection run.
       * @param {Number} stats.iterations.failed - Failed iterations in the current collection run.
       * @param {Object} stats.requests - A set of values for total, pending, and failed requests.
       * @param {Number} stats.requests.total - Total requests in the current collection run.
       * @param {Number} stats.requests.pending - Pending requests in the current collection run.
       * @param {Number} stats.requests.failed - Failed requests in the current collection run.
       * @param {Object} stats.testScripts - A set of values for total, pending, and failed testScripts.
       * @param {Number} stats.testScripts.total - Total testScripts in the current collection run.
       * @param {Number} stats.testScripts.pending - Pending testScripts in the current collection run.
       * @param {Number} stats.testScripts.failed - Failed testScripts in the current collection run.
       * @param {Object} stats.prerequestScripts - A set of values for total, pending, and failed prerequestScripts.
       * @param {Number} stats.prerequestScripts.total - Total prerequestScripts in the current collection run.
       * @param {Number} stats.prerequestScripts.pending - Pending prerequestScripts in the current collection run.
       * @param {Number} stats.prerequestScripts.failed - Failed prerequestScripts in the current collection run.
       * @param {Object} stats.assertions - A set of values for total, pending, and failed assertions.
       * @param {Number} stats.assertions.total - Total assertions in the current collection run.
       * @param {Number} stats.assertions.pending - Pending assertions in the current collection run.
       * @param {Number} stats.assertions.failed - Failed assertions in the current collection run.
       * @param {Object} timings - A set of values for the timings of the current collection run.
       * @param {Number} timings.completed - The end timestamp for the current collection run.
       * @param {Number} timings.started - The start timestamp for the current collection run
       * @param {String} timings.responseAverage - The average response time across all requests
       * @param {Object} transfers - A set of details on the network usage for the current collection run.
       * @param {String} transfers.responseTotal - The net extent of the data transfer achieved during the collection run.
       * @param {Object} options - The set of generic collection run options.
       * @returns {Table} - The constructed collection run statistics table.
       */
      parseStatistics: function (stats, timings, transfers, options) {
          var summaryTable;
  
          // create the summary table
          summaryTable = new Table({
              chars: options.disableUnicode && cliUtils.cliTableTemplateFallback,
              style: { head: [] },
              head: [E, 'executed', '  failed'],
              colAligns: ['right', 'right', 'right'],
              colWidths: [25]
          });
  
          // add specific rows to show in summary
          stats && _.forEach([{
              source: 'iterations',
              label: 'iterations'
          }, {
              source: 'requests',
              label: 'requests'
          }, {
              source: 'testScripts',
              label: 'test-scripts'
          }, {
              source: 'prerequestScripts',
              label: 'prerequest-scripts'
          }, {
              source: 'assertions',
              label: 'assertions'
          }], function (row) {
              var metric = stats[row.source],
                  label = row.label;
  
              // colour the label based on the failure or pending count of the metric
              label = metric.failed ? colors.red(label) : (metric.pending ? label : colors.green(label));
  
              // push the statistics
              summaryTable.push([
                  label,
                  metric.total,
                  (metric.failed ? colors.red(metric.failed) : metric.failed)
                  // @todo - add information of pending scripts
                  // (metric.failed ? colors.red(metric.failed) : metric.failed) +
                  //     (metric.pending ? format(' (%d pending)', metric.pending) : E)
              ]);
          });
  
          // add the total execution time to summary
          timings && summaryTable.push([{
              colSpan: 3,
              content: format('total run duration: %s', util.prettyms(timings.completed - timings.started)),
              hAlign: 'left' // since main style was set to right
          }]);
  
          // add row to show total data received
          transfers && summaryTable.push([{
              colSpan: 3,
              content: format('total data received: %s (approx)', util.filesize(transfers.responseTotal)),
              hAlign: 'left'
          }]);
  
          // add row to show average response time
          summaryTable.push([{
              colSpan: 3,
              content: format('average response time: %s', util.prettyms(timings.responseAverage)),
              hAlign: 'left'
          }]);
  
          return summaryTable;
      },
  
      /**
       * A CLI reporter method to parse collection run failure statistics into a CLI table.
       *
       * @param  {Array} failures - An array of failure objects.
       * @returns {Table} - The constructed CLI failure Table object.
       */
      parseFailures: function (failures) {
          var failureTable = new Table({
              head: [{
                  hAlign: 'right',
                  content: colors.red.underline('#')
              }, colors.red.underline('failure'),
              colors.red.underline('detail')],
              chars: cliUtils.cliTableTemplate_Blank,
              wordWrap: true,
              colAligns: ['right'],
              colWidths: cliUtils.noTTY() ? [] : (function (size, indexOrder) {
                  var colWidths;
  
                  if (size.exists && size.width && (size.width > 20)) {
                      colWidths = [];
                      colWidths[0] = indexOrder + 3;
                      colWidths[1] = parseInt((size.width - colWidths[0]) * 0.2, 10);
                      colWidths[2] = parseInt(size.width - (colWidths[0] + colWidths[1] + 5), 10);
                  }
  
                  return colWidths;
              }(cliUtils.dimension(), Number(failures.length.toString().length)))
          });
  
          _.forEach(failures, function (failure, index) {
              var name = failure.error && failure.error.name || E,
                  message = failure.error && failure.error.test || E;
  
              // augment name with iteration information
              failure.cursor && (failure.cursor.cycles > 1) &&
                  (name += LF + colors.gray('iteration: ' + (failure.cursor.iteration + 1)));
  
              // include the assertion error message in the failure details
              failure.error && (message += LF + colors.gray(failure.error.message || E));
  
              // augment the message with stack information
              failure.at && (message += LF + colors.gray('at ' + failure.at));
  
              // augment message with item information
              failure.source &&
                  (message += format(colors.gray('\ninside "%s"'), util.getFullName(failure.source)));
  
              failureTable.push([pad(Number(index + 1), Number(failures.length.toString().length)).toString() +
                  DOT, name, message]);
          });
  
          return failureTable;
      }
  });
  
  // Mark the CLI reporter as dominant, so that no two dominant reporters are together
  PostmanCLIReporter.prototype.dominant = true;
  
  module.exports = PostmanCLIReporter;
  