// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/** Information about a particular build. */
function BuildInfo(json) {
  // Parse out the status message for the build.
  var statusText;
  if (json.currentStep) {
    statusText = 'running ' + json.currentStep.name;
  } else {
    statusText = json.text.join(' ');
  }

  // Determine what state the build is in.
  var state;
  if (statusText.indexOf('exception') != -1) {
    state = 'exception';
  } else if (statusText.indexOf('build successful') == 0) {
    state = 'success';
  } else if (statusText.indexOf('failed') == 0) {
    state = 'failed';
  } else if (statusText.indexOf('offline') != -1) {
    state = 'offline';
  } else if (statusText.indexOf('warnings') != -1) {
    state = 'warnings';
  } else if (statusText.indexOf('running') != -1) {
    state = 'running';
  } else {
    state = 'unknown';
  }

  var failures = (state == 'failed') ? this.parseFailures(json) : null;

  this.number = json.number;
  this.state = state;
  this.failures = failures;
  this.statusText = statusText;
  this.truncatedStatusText = truncateStatusText(statusText);
}

/** Save data about failed tests to perform blamelist intersections. */
BuildInfo.prototype.parseFailures = function(json) {
  var revisionRange = this.getRevisionRange(json);
  if (revisionRange == null) return null;

  var failures = [];
  var botName = json.builderName;
  for (var i = 0; i < json.steps.length; ++i) {
    var step = json.steps[i];
    var binaryName = step.name;
    if (step.results[0] != 0) {  // Failed.
      for (var j = 0; j < step.logs.length; ++j) {
        var log = step.logs[j];
        if (log[0] == 'stdio')
          continue;
        var testName = log[0];
        failures.push([botName, binaryName, testName, revisionRange]);
      }
    }
  }

  return failures;
};

/**
 * Get the revisions involved in a build.  Sadly, this only works on Chromium's
 * main builders because downstream trees provide git revision SHA1s through
 * JSON instead of SVN numbers.
 */
BuildInfo.prototype.getRevisionRange = function(json) {
  if (json.sourceStamp.changes.length == 0) {
    return null;
  }

  var lowest = parseInt(json.sourceStamp.changes[0].revision, 10);
  var highest = parseInt(json.sourceStamp.changes[0].revision, 10);
  for (var i = 1; i < json.sourceStamp.changes.length; ++i) {
    var rev = parseInt(json.sourceStamp.changes[i].revision, 10);
    if (rev < lowest)
      lowest = rev;
    if (rev > highest)
      highest = rev;
  }
  return [lowest, highest];
};

/** Creates HTML to display info about this build. */
BuildInfo.prototype.createHtml = function(buildNumberCell,
                                          botUrl,
                                          showFullInfo) {
  var fullStatusText = 'Build ' + this.number + ':\n' + this.statusText;
  createBuildHtml(buildNumberCell,
                  botUrl + '/builds/' + this.number,
                  showFullInfo ? this.number : null,
                  fullStatusText,
                  showFullInfo ? this.truncatedStatusText : null,
                  this.state);
};

/** Creates a table cell for a particular build number. */
function createBuildHtml(cellElement,
                         url,
                         buildNumber,
                         fullStatusText,
                         truncatedStatusText,
                         buildState) {
  // Create a link to the build results.
  var linkElement = document.createElement('a');
  linkElement.href = url;

  // Display either the build number (for the last completed build), or show the
  // status of the step.
  var buildIdentifierElement = document.createElement('span');
  if (buildNumber) {
    buildIdentifierElement.className = 'build-identifier';
    buildIdentifierElement.innerHTML = buildNumber;
  } else {
    buildIdentifierElement.className = 'build-letter';
    buildIdentifierElement.innerHTML = buildState.toUpperCase()[0];
  }
  linkElement.appendChild(buildIdentifierElement);

  // Show the status of the build in truncated form so it doesn't take up the
  // whole screen.
  if (truncatedStatusText) {
    var statusElement = document.createElement('span');
    statusElement.className = 'build-status';
    statusElement.innerHTML = truncatedStatusText;
    linkElement.appendChild(statusElement);
  }

  // Tack the cell onto the end of the row.
  cellElement.className = buildState;
  cellElement.title = fullStatusText;
  cellElement.appendChild(linkElement);
}
