// Copyright 2006 Kushal Dave (kushaldave.com)

/**
 * Takes a set of values with an "active" subset and dislays
 * a graph of these values overlayed on the complete set. Also
 * provides methods for accessing the color of a given value
 * entry for use by other displays. The color is computing by
 * dividing the active set into thirds.
 */
 
/**
 * Create a grapher.
 * 
 * @param {String} id of element to place graph in
 * @param {Array} set of objects, where .index should be graphed
 * @param {String} index, property name for array above
 */ 
function Grapher(id, vals, index) {
    this.canvas = document.getElementById(id);
    
    // Copy values into relevantVals array, tracking min and max
    var relevantVals = [];
    var maxVal = 0;
    var minVal = 1000;
    for (var i = 0; i < vals.length; i++) {
        if (!vals[i][index]) {
            relevantVals.push(-1);
        } else {
            var thisVal = Math.round(vals[i][index] / 200);
          	relevantVals.push(thisVal);
            if (thisVal > maxVal) {
  		        maxVal = thisVal;
            }
            if (thisVal < minVal) {
                minVal = thisVal;
            }
        }
    }
    this.minVal = minVal;
    this.relevantVals = relevantVals;
    this.span = maxVal - minVal;
    
    // Group relevantVals into buckets
    var buckets = [];
    this.clearArray_(buckets, this.span+1);
    for (i = 0; i < relevantVals.length; i++) {
        if (relevantVals[i] > -1){
            buckets[relevantVals[i] - minVal]++;
        }
    }
    this.buckets = buckets;
    var biggestBucket = 0;
    for (i = 0; i < buckets.length; i++) {
        if (buckets[i] > biggestBucket) {
            biggestBucket = buckets[i];
        }
    }
    this.biggestBucket = biggestBucket;
}

/**
 * Returns an absolutely positioned div with specified location
 * and height.
 */
Grapher.prototype.getPositionedDiv_ = function(x, y, w, h) {
  var elt = document.createElement("div");
  elt.style.position = "absolute";
  elt.style.overflow = "hidden";
  elt.style.left = x+"px";
  elt.style.top = y+"px";
  if (w && h) {
    elt.style.width = w+"px";
    elt.style.height = h+"px"
  }
  return elt;
}

var WIDTH_MARGIN = 5;
var TOP_MARGIN = 20;
var BOTTOM_MARGIN = 30;

/**
 * Draws a rectangle for the specified bucket.
 * 
 * @param {Node} ctx to append to
 * @param {Number} pxPerBucket number of pixels of width per bucket before margin is subtracted
 * @param {Number} pxPerEntry number of pixels of height per bucket before margin is subtracted
 * @param {Number} column of this bucket, for calculating offset
 * @param {Number} size, height of this bucket
 * @param {Number} availHeight for entire graph
 * @param {String} color for this rectangle.
 */
Grapher.prototype.drawRect = function (ctx, pxPerBucket, pxPerEntry, col,
    size, availHeight, color) {
  var x = Math.round(pxPerBucket * col) + WIDTH_MARGIN;
  var h = Math.round(pxPerEntry * size);
  var y = Math.round(availHeight - h - BOTTOM_MARGIN);
  var w = Math.round(pxPerBucket - WIDTH_MARGIN);
  var elt = this.getPositionedDiv_(x, y, w, h);
  elt.style.backgroundColor = color;
  ctx.appendChild(elt);
}

/**
 * Writes a label for specified bucket.
 * 
 * @param {Node} ctx to append to
 * @param {Number} pxPerBucket number of pixels of width per bucket before margin is subtracted
 * @param {Number} col of this bucket, for calculating offset
 * @param {Number} availHeight for entire graph
 * @param {String} contents of label
 */
Grapher.prototype.addLabel = function (ctx, pxPerBucket, col,  availHeight,
    contents) {
  var x = Math.round(pxPerBucket * col) + WIDTH_MARGIN;
  var y = availHeight - BOTTOM_MARGIN + 2;
  var elt = this.getPositionedDiv_(x, y);
  elt.style.fontSize = "75%";
  elt.style.fontFamily = "Georgia";
  var priceStr = String(contents);
  elt.innerHTML = [priceStr.substring(0, priceStr.length - 3), ",", priceStr.substring(priceStr.length - 3)].join("");
  ctx.appendChild(elt);
}

/**
 * Draws the graph inside of the specified node. Called by setActive.
 */
Grapher.prototype.render = function() {
  var ctx = this.canvas;
  ctx.innerHTML = "";
  
  var availWidth = ctx.offsetWidth - WIDTH_MARGIN; // fix for IE
  var availHeight = ctx.offsetHeight;
  var buckets = this.buckets;
  var activeBuckets = this.activeBuckets;
  var pxPerBucket = availWidth / buckets.length;
  var pxPerEntry = (availHeight - TOP_MARGIN - BOTTOM_MARGIN) / this.biggestBucket;
  
  for (var i = 0; i < buckets.length; i++) {
    this.drawRect(ctx, pxPerBucket, pxPerEntry,i, buckets[i], availHeight, 'rgb(225,225,225)');
  }
  
  for (var i = 0; i < activeBuckets.length; i++) {
    this.drawRect(ctx, pxPerBucket, pxPerEntry, i, activeBuckets[i], availHeight, this.getGradientColor(i));
  }
  
  
  this.drawRect(ctx, availWidth + WIDTH_MARGIN, 1, 0, 1, availHeight, 'rgb(180,180,180)');
  
  var minPxPerLabel = 75;
  var barsPerLabel = Math.round(minPxPerLabel / pxPerBucket);
  for (var i = 0; i < buckets.length; i++) {
    if (i % barsPerLabel == 0) {
      this.addLabel(ctx, pxPerBucket, i,  availHeight, (i + this.minVal) * 200);
    }  
  }
  
  
}

var MAX_GREEN = 222; //95;
var MAX_RED = 222;
var CONST_BLUE = 25;

Grapher.prototype.getGradientColorFromIndex = function(index) {
  return this.getGradientColor(this.relevantVals[index] - this.minVal);
}

Grapher.prototype.getGradientColor = function(bucket) {
    //var frac = (this.activeSpan == 0) ? 0 : (bucket + this.minVal - this.activeMin) / this.activeSpan;
    var frac = bucket / this.span;
    var red = Math.round(MAX_RED * frac);
    var green = Math.round(MAX_GREEN * (1 - frac));
    return "rgb(" + [red, green, CONST_BLUE].join(",") + ")";
}

Grapher.prototype.setActive = function(activeIndices) {
    var activeBuckets = [];
    var minVal = this.minVal;
    this.clearArray_(activeBuckets, this.buckets.length);
    var numReallyActive = 0;
    var activeMin = 100000;
    var activeMax = 0;
    for (var i = 0; i < activeIndices.length; i++) {
        var val = this.relevantVals[activeIndices[i]];
        if (val > -1){
          activeBuckets[val - minVal]++;
          numReallyActive++;
        }
        if (val < activeMin) {
          activeMin = val;
        }
        if (val > activeMax) {
          activeMax = val;
        }
    }
    this.activeMin = activeMin;
    this.activeMax = activeMax;
    this.activeSpan = activeMax - activeMin;
    this.activeBuckets = activeBuckets;
        
    // Compute cutoffs
    // Divide activeIndices by 3 and then loop through
    this.render();
}

/**
 * Zeroes out array of specified size.
 */
Grapher.prototype.clearArray_ = function(array, size) {
    for (var i = 0; i < size; i++) {
        array[i] = 0;
    }
}