/**
 * User Interface for the Scenario Runner.
 *
 * @param {Object} The jQuery UI object for the UI.
 */
angular.scenario.ui.Html = function(context) {
  this.context = context;
  context.append(
    '
' +
    ''
  );
};
/**
 * The severity order of an error.
 */
angular.scenario.ui.Html.SEVERITY = ['pending', 'success', 'failure', 'error'];
/**
 * Adds a new spec to the UI.
 *
 * @param {Object} The spec object created by the Describe object.
 */
angular.scenario.ui.Html.prototype.addSpec = function(spec) {
  var self = this;
  var specContext = this.findContext(spec.definition);
  specContext.find('> .tests').append(
    ''
  );
  specContext = specContext.find('> .tests li:last');
  return new angular.scenario.ui.Html.Spec(specContext, spec.name, 
    function(status) {
      status = self.context.find('#status-legend .status-' + status);
      var parts = status.text().split(' ');
      var value = (parts[0] * 1) + 1;
      status.text(value + ' ' + parts[1]);
    }
  );
};
/**
 * Finds the context of a spec block defined by the passed definition.
 *
 * @param {Object} The definition created by the Describe object.
 */
angular.scenario.ui.Html.prototype.findContext = function(definition) {
  var self = this;
  var path = [];
  var currentContext = this.context.find('#specs');
  var currentDefinition = definition;
  while (currentDefinition && currentDefinition.name) {
    path.unshift(currentDefinition);
    currentDefinition = currentDefinition.parent;
  }
  angular.foreach(path, function(defn) {
    var id = 'describe-' + defn.id;
    if (!self.context.find('#' + id).length) {
      currentContext.find('> .test-children').append(
        ''
      );
      self.context.find('#' + id).find('> h2').text('describe: ' + defn.name);
    }
    currentContext = self.context.find('#' + id);
  });
  return this.context.find('#describe-' + definition.id);
};
/**
 * A spec block in the UI.
 *
 * @param {Object} The jQuery object for the context of the spec.
 * @param {String} The name of the spec.
 * @param {Function} Callback function(status) to call when complete.
 */
angular.scenario.ui.Html.Spec = function(context, name, doneFn) {
  this.status = 'pending';
  this.context = context;
  this.startTime = new Date().getTime();
  this.doneFn = doneFn;
  context.append(
    '' +
    '  
' +
    '    ' +
    '    ' +
    '  
' +
    '
 ' +
    ''
  );
  context.find('> .test-info').click(function() {
    var scrollpane = context.find('> .scrollpane');
    var actions = scrollpane.find('> .test-actions');
    var name = context.find('> .test-info .test-name');
    if (actions.find(':visible').length) {
      actions.hide();
      name.removeClass('open').addClass('closed');
    } else {
      actions.show();
      scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
      name.removeClass('closed').addClass('open');
    }
  });
  context.find('> .test-info .test-name').text('it ' + name);
};
/**
 * Adds a new Step to this spec and returns it.
 *
 * @param {String} The name of the step.
 * @param {Function} function() that returns a string with the file/line number
 *  where the step was added from.
 */
angular.scenario.ui.Html.Spec.prototype.addStep = function(name, location) {
  this.context.find('> .scrollpane .test-actions').append('');
  var stepContext = this.context.find('> .scrollpane .test-actions li:last');
  var self = this;
  return new angular.scenario.ui.Html.Step(stepContext, name, location, function(status) {
    if (indexOf(angular.scenario.ui.Html.SEVERITY, status) >
      indexOf(angular.scenario.ui.Html.SEVERITY, self.status)) {
      self.status = status;
    }
    var scrollpane = self.context.find('> .scrollpane');
    scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
  });
};
/**
 * Completes the spec and sets the timer value.
 */
angular.scenario.ui.Html.Spec.prototype.complete = function() {
  this.context.removeClass('status-pending');
  var endTime = new Date().getTime();
  this.context.find("> .test-info .timer-result").
    text((endTime - this.startTime) + "ms");
  if (this.status === 'success') {
    this.context.find('> .test-info .test-name').addClass('closed');
    this.context.find('> .scrollpane .test-actions').hide();
  }
};
/**
 * Finishes the spec, possibly with an error.
 *
 * @param {Object} An optional error
 */
angular.scenario.ui.Html.Spec.prototype.finish = function() {
  this.complete();
  this.context.addClass('status-' + this.status);
  this.doneFn(this.status);
};
/**
 * Finishes the spec, but with a Fatal Error.
 *
 * @param {Object} Required error
 */
angular.scenario.ui.Html.Spec.prototype.error = function(error) {
  this.status = 'error';
  this.context.append('');
  this.context.find('> pre').text(formatException(error));
  this.finish();
};
/**
 * A single step inside an it block (or a before/after function).
 *
 * @param {Object} The jQuery object for the context of the step.
 * @param {String} The name of the step.
 * @param {Function} function() that returns file/line number of step.
 * @param {Function} Callback function(status) to call when complete.
 */
angular.scenario.ui.Html.Step = function(context, name, location, doneFn) {
  this.context = context;
  this.name = name;
  this.location = location;
  this.startTime = new Date().getTime();
  this.doneFn = doneFn;
  context.append(
    '' +
    ''
  );
  context.find('> .test-title').text(name);
  var scrollpane = context.parents('.scrollpane');
  scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
};
/**
 * Completes the step and sets the timer value.
 */
angular.scenario.ui.Html.Step.prototype.complete = function(error) {
  this.context.removeClass('status-pending');
  var endTime = new Date().getTime();
  this.context.find(".timer-result").
    text((endTime - this.startTime) + "ms");
  if (error) {
    if (!this.context.find('.test-title pre').length) {
      this.context.find('.test-title').append('');
    }
    var message = _jQuery.trim(this.location() + '\n\n' + formatException(error));
    this.context.find('.test-title pre').text(message);
  }
};
/**
 * Finishes the step, possibly with an error.
 *
 * @param {Object} An optional error
 */
angular.scenario.ui.Html.Step.prototype.finish = function(error) {
  this.complete(error);
  if (error) {
    this.context.addClass('status-failure');
    this.doneFn('failure');
  } else {
    this.context.addClass('status-success');
    this.doneFn('success');
  }
};
/**
 * Finishes the step, but with a Fatal Error.
 *
 * @param {Object} Required error
 */
angular.scenario.ui.Html.Step.prototype.error = function(error) {
  this.complete(error);
  this.context.addClass('status-error');
  this.doneFn('error');
};