var OUTLIER_THRESHOLD = 0.05;
var MARGIN_SIZE = 0.10;

Heatmap.prototype.calculate = function(data) {
  this.cleanUpData(data);
  this.calculateBounds();
  this.calculateHeatmap();

  this.drawTraces = [];
  for (var i = 0; i < data.length; ++i)
    this.drawTraces.push(false);
};

Heatmap.prototype.cleanUpData = function(data) {
  // Data, indexed by revision and trace.
  this.traces = {};

  for (var bot in data) {
    var bot_data = data[bot];
    for (var run_index = 0; run_index < bot_data.length; ++run_index) {
      var run_data = bot_data[run_index];
      if (run_data == null)
        continue
      var stories_data = run_data['user_story_runs'];
      for (var story_index = 0; story_index < stories_data.length; ++story_index) {
        var story_data = stories_data[story_index];
        var story_name = story_data['user_story'];
        if (story_name == 'summary')
          continue

        values = story_data['values'];

        var index = bot_data.length - run_index - 1;
        if (!this.traces[index])
          this.traces[index] = {};
        this.traces[index][story_index] = values;
      }
    }
  }

  this.revisions = Object.keys(this.traces).sort();
};

Heatmap.prototype.calculateBounds = function() {
  var values = [];
  for (var revision in this.traces)
    for (var trace in this.traces[revision])
      for (var value of this.traces[revision][trace])
        values.push(value);

  // Exclude OUTLIER_THRESHOLD% of the points.
  values.sort(function(a, b) {return a - b});
  this.min = percentile(values, OUTLIER_THRESHOLD / 2);
  this.max = percentile(values, -OUTLIER_THRESHOLD / 2);

  // Ease bounds by adding margins.
  var margin = (this.max - this.min) * MARGIN_SIZE;
  this.min -= margin;
  this.max += margin;
};

Heatmap.prototype.calculateHeatmap = function() {
  this.data = {};
  for (var revision in this.traces) {
    for (var trace in this.traces[revision]) {
      for (var value of this.traces[revision][trace]) {
        var bucket = this.findBucket(value);

        if (this.data[revision] == null)
          this.data[revision] = {};
        if (this.data[revision][bucket] == null)
          this.data[revision][bucket] = [];
        this.data[revision][bucket].push(trace);
      }
    }
  }
};

Heatmap.prototype.findBucket = function(value) {
  var bucket = Math.floor(mapRange(value, this.min, this.max, 0, this.resolution));
  return constrain(bucket, 0, this.resolution - 1);
};