text: "use strict";
  
  /**
   * @module reporter-cli
   */
  
  var util = require("util");
  
  var Reporter = require("./reporter");
  
  var color = require("../color").codes;
  
  var ARROW = "►";
  var GOOD = "✓";
  var BAD = "✗";
  
  /**
   * @class FeedbackLineReporter
   * @constructor
   * @extends Reporter
   */
  function FeedbackLineReporter(options) {
      Reporter.call(this, options);
  
      this.batchDetails = {
          passed: 0,
          failed: 0,
          currentIndex: 0,
          coverage: [],
          calledLines: 0,
          coveredLines: 0,
          total: this.batch.tests.length
      };
  
      this.eta = "";
  
      this.beats = 0;
      this.spinIndex = 0;
  
      this.cli.rl.setPrompt("", 0);
  
      this.timeStart = new Date();
  
      this.feedbackLineDisplayed = false;
  }
  
  util.inherits(FeedbackLineReporter, Reporter);
  
  FeedbackLineReporter.prototype.bindEvents = function () {
      this.batch.on("agentResult", this.handleAgentResult.bind(this));
      this.batch.on("agentScriptError", this.handleAgentScriptError.bind(this));
      this.batch.on("agentError", this.handleAgentError.bind(this));
      this.batch.on("agentComplete", this.handleAgentComplete.bind(this));
      this.batch.on("agentProgress", this.updateFeedbackLine.bind(this));
      this.batch.on("agentBeat", this.handleAgentBeat.bind(this));
      this.batch.once("dispatch", this.handleDispatch.bind(this));
      this.batch.once("complete", this.handleComplete.bind(this));
      this.batch.once("end", this.handleEnd.bind(this));
  };
  
  /**
   * @method putsVerboseResult
   * @param {object} result Test result.
   * @private
   */
  FeedbackLineReporter.prototype.putsVerboseResult = function (result) {
      var self = this,
          lastSuite,
          k,
          k1,
          suite,
          test;
  
      function reportTestError(test) {
          var msg, m;
  
          if ("fail" === test.result) {
              if (!lastSuite || lastSuite !== suite.name) {
                  self.puts("   in", color.bold(suite.name));
                  lastSuite = suite.name;
              }
              msg = test.message.split("\n");
              self.puts("    ", color.bold(color.red(test.name)) + ":", msg[0]);
              for (m = 1; m < msg.length; m = m + 1) {
                  self.puts("       " + msg[m]);
              }
          }
      }
  
      function hasResults(o) {
          return (("passed" in test) && ("failed" in test) && ("type" in test));
      }
  
      function walk(o) {
          var i;
          for (i in o) {
              if (hasResults(o[i])) {
                  reportTestError(o[i]);
              } else {
                  walk(o[i]);
              }
          }
      }
  
      for (k in result) {
          suite = result[k];
          if (suite && "object" === typeof suite) {
              if (suite.failed) {
                  for (k1 in suite) {
                      test = suite[k1];
                      if ("object" === typeof test) {
                          if (hasResults(test)) {
                              walk(test);
                          } else {
                              reportTestError(test);
                          }
                      }
                  }
              }
          }
      }
  
      self.puts("");
  };
  
  FeedbackLineReporter.prototype.puts = function () {
      if (this.feedbackLineDisplayed) {
          this.clearLine();
          this.feedbackLineDisplayed = false;
      }
  
      this.cli.puts.apply(this.cli, Array.prototype.slice.call(arguments));
  };
  
  FeedbackLineReporter.prototype.clearLine = function () {
      // If the output of self.rl is NOT a TTY,
      // a bug in Node.js readline will cause
      // the unused first argument to be treated
      // as a Buffer.
      //
      // https://github.com/joyent/node/blob/6e2055889091a424fbb5c500bc3ab9c05d1c28b4/lib/readline.js#L291
      //
      // Wrap this call to determine if the rl
      // output is a terminal.
      if (this.cli.rl.terminal) {
          this.cli.rl.write(null, {
              ctrl: true,
              name: "u"
          });
      }
  };
  
  FeedbackLineReporter.prototype.formatTime = function (milliseconds) {
      var formatted = "",
          seconds = (milliseconds / 1000).toFixed(0),
          minutes = 0,
          minutesUnit,
          secondsUnit;
  
      if (seconds > 60) {
          minutes = (seconds / 60).toFixed(0);
          seconds %= 60;
      }
  
      minutesUnit = minutes === 1 ? "minute" : "minutes";
      secondsUnit = seconds === 1 ? "second" : "seconds";
  
      if (minutes > 0) {
          formatted += minutes + " " + minutesUnit + ", ";
      }
  
      if (!minutes && seconds <= 3) {
          // higher resolution
          seconds = (milliseconds / 1000).toFixed(2);
      }
  
      formatted += seconds + " " + secondsUnit;
  
      return formatted;
  
  };
  
  FeedbackLineReporter.prototype.updateETA = function () {
      var current = this.batchDetails.currentIndex,
          total = this.batchDetails.total,
          elapsed = (new Date().getTime()) - this.timeStart,
          eta = elapsed * (total / current - 1),
          formatted = this.formatTime(eta);
  
      if (formatted) {
          formatted = "ETA " + formatted + " ";
      }
  
      this.eta = formatted;
  };
  
  /**
   * @method updateFeedbackLine
   * @private
   */
  FeedbackLineReporter.prototype.updateFeedbackLine = function () {
      var current = this.batchDetails.currentIndex,
          total = this.batchDetails.total,
          percent = current / total * 100,
          spin = ["/", "|", "\\", "-"],
          spins = spin.length - 1,
          elapsed = (new Date().getTime()) - this.timeStart,
          bps = (this.beats * 1000) / elapsed,
          line;
  
      this.clearLine();
  
      line = ARROW + " Testing... " +
          spin[this.spinIndex] +
          " " + percent.toFixed(0) +
          "% complete (" + current + "/" +
          total + ") " +
          bps.toFixed(2) + " beats/sec " +
          this.eta;
  
      if (this.cli.rl.terminal) {
          this.cli.rl.write(line);
      } else {
          this.cli.puts(line);
      }
  
      this.feedbackLineDisplayed = true;
  
      this.spinIndex += 1;
      if (this.spinIndex > spins) {
          this.spinIndex = 0;
      }
  };
  
  /**
   * @method handleAgentResult
   */
  FeedbackLineReporter.prototype.handleAgentResult = function (agent, details) {
      var passed = details.passed,
          failed = details.failed,
          icon = failed ? BAD : GOOD,
          iconColor = failed ? color.red : color.green;
  
      this.batchDetails.currentIndex += 1;
  
      this.batchDetails.passed += passed;
      this.batchDetails.failed += failed;
  
      this.updateETA();
  
      if (details.coverage) {
          this.batchDetails.coverage.push(details.coverage);
      }
  
      if (failed) {
          if (!details.name) {
              details.name = details.url;
          }
          this.clearLine();
          this.puts(iconColor(icon), color.bold(details.name), "on", agent);
          this.putsVerboseResult(details);
      }
  };
  
  /**
   * @method handleAgentScriptError
   */
  FeedbackLineReporter.prototype.handleAgentScriptError = function (agent, details) {
      this.puts(color.red(BAD + " Script error") + ": " + details.message);
      this.puts("  URL: " + details.url);
      this.puts("  Line: " + details.line);
      this.puts("  User-Agent: " + agent);
  };
  
  /**
   * @method handleAgentError
   */
  FeedbackLineReporter.prototype.handleAgentError = function (agent, details) {
      this.puts(color.red(BAD + " Error") + ": " + details.message);
      this.puts("  User-Agent: " + agent);
  };
  
  /**
   * @method handleAgentComplete
   */
  FeedbackLineReporter.prototype.handleAgentComplete =  function (agent) {
      this.puts(GOOD, "Agent completed:", agent);
  };
  
  /**
   * @method handleAgentBeat
   */
  FeedbackLineReporter.prototype.handleAgentBeat = function (agent) {
      this.beats += 1;
      if (this.cli.rl.terminal) {
          this.updateFeedbackLine();
      }
  };
  
  /**
   * @method handleDispatch
   */
  FeedbackLineReporter.prototype.handleDispatch = function (agents) {
      if (!agents.length) {
          this.cli.error(BAD, "No browsers connected, exiting.");
          this.emit("error");
          return;
      }
      this.puts(GOOD, "Testing started on", agents.join(", "));
      this.batchDetails.total *= agents.length;
  };
  
  /**
   * @method handleComplete
   */
  FeedbackLineReporter.prototype.handleComplete = function () {
      this.updateFeedbackLine();
  
      var duration = Number(new Date()) - this.timeStart,
          total = this.batchDetails.passed + this.batchDetails.failed,
          durationString = "(" + this.formatTime(duration) + ")";
  
      if (this.batchDetails.failed) {
          this.puts(color.red(BAD + " Failures") + ":", this.batchDetails.failed,
              "of", total, "tests failed.", durationString);
      } else {
          this.puts(color.green(GOOD + " " + total + " tests passed!"), durationString);
      }
  };
  
  /**
   * @method handleEnd
   */
  FeedbackLineReporter.prototype.handleEnd = function () {
      this.emit("end", this.batchDetails.failed ? 3 : 0);
  };
  
  module.exports = FeedbackLineReporter;
  