// TODO: ensure no tests are started if test is in progress
(function($p) {

  var $E = pulp.unit._$E;
  
  var tableCounter = 0;
  var testCounter;
  var fnCache = {};
  var contextCache = {};
  var isRunning = false;
  var queue = [];

  var $ = function(id) {
    return pulp.Node && id instanceof pulp.Node ? id.raw : document.getElementById(id) || id;
  };
  var byId = function(id) {
    return document.getElementById(id);
  };

  pulp.unit.benchmark = function(parent, title, fns) {
    var html = '';
    html += '<div class="benchmark"><h2>' + title + '</h2>';
    html += '<p>Test duration: <input type="text" id="bench-seconds-' + tableCounter + '" size="5" value="0.5"';
    html += ' onchange="this.value = parseFloat(this.value).toFixed(1); this.value = this.value > 15 ? \'15.0\' : this.value <= 0 ? \'2.0\' : this.value" />';
    html += ' seconds each<p>';
    html += '<table class="bench-list">';
    html += '<tr><th class="list fn-name">Function Name</th>';
    html += '<th class="list fn-run"><input type="button" id="bench-run-all-' + tableCounter + '" value="Run All" onclick="pulp.unit.benchmark.runAll(' + tableCounter + ')" /></th>';
    html += '<th class="list fn-result">Results (lowest time is fastest)</th></tr>';
    testCounter = 0;
    $p.each(fns, function(fn, name) {
      if (name == 'SETUP' || name == 'TEARDOWN') {
        return;
      }
      html += '<tr><td class="list fn-name">' + name + '</td>';
      html += '<td class="list fn-run"><input type="button" id="bench-run-all-' + tableCounter + '-' + testCounter + '" value="Run" onclick="pulp.unit.benchmark.run(' + tableCounter + ', ' + testCounter + ')" /></td>';
      html += '<td class="list fn-result"id="bench-result-' + tableCounter + '-' + testCounter + '">&nbsp;</td></tr>';
      fnCache[tableCounter + '-' + (testCounter++)] = fn;
    });
    html += '</table></div>';
    $(parent).innerHTML += html;
    contextCache[tableCounter] = fns;
    tableCounter++;
  };
  
  $p.extend(pulp.unit.benchmark, {
    graphWidth: 350,
    run: function(table, test, runningAll) {
      if (!runningAll && isRunning) {
        queue.push([table, test]);
        return;
      }
      isRunning = true;
      if (contextCache[table].SETUP) {
        contextCache[table].SETUP();
      }
      var fn = fnCache[table + '-' + test];
      var seconds = parseFloat($('bench-seconds-' + table).value) || 2;
      var avgTime = pulp.Unit.prototype._runBenchmark(fn, seconds);
      $('bench-result-' + table + '-' + test).benchTime = avgTime;
      var i = 0, resultTd, time, totalTime, highestTime = 0, lowestTime = false;
      while (resultTd = byId('bench-result-' + table + '-' + (i++))) {
        time = resultTd.benchTime || 0;   
        totalTime += time;
        if (resultTd.benchTime !== undefined && (lowestTime === false || lowestTime > time)) {
          lowestTime = time;
        }
        if (highestTime < time) {
          highestTime = time;
        }
      }
      i = 0;
      while (resultTd = byId('bench-result-' + table + '-' + (i++))) {
        time = resultTd.benchTime || 0;
        pulp.unit.benchmark._writeBarGraph(resultTd, 100 * time / highestTime, time, lowestTime);
      }
      if (contextCache[table].TEARDOWN) {
        contextCache[table].TEARDOWN();
      }
      isRunning = false;
      if (!runningAll) {
        pulp.unit.benchmark._next();
      }
    },
    _next: function() {
      var next = queue.shift();
      if (next) {
        if (next[1]) {
          pulp.unit.benchmark.run(next[0], next[1]);
        } else {
          pulp.unit.benchmark.runAll(next[0]);
        }
      }
    },
    _writeBarGraph: function(td, percent, avgTime, lowestTime) {
      html = '';
      if (avgTime) {
        html += '<table class="bench-graph' + (lowestTime == avgTime ? ' fastest' : '') + '">';
        html += '<tr><td class="bench-graph-time" width="60">' + (avgTime).toFixed(3) + 'ms</td>';
        html += '<td class="bench-graph-filled" width="' + ((percent / 100) * pulp.unit.benchmark.graphWidth).toFixed(1) + '">&nbsp;</td>';
        html += '<td class="bench-graph-empty" width="' + (((1 - (percent / 100)) * pulp.unit.benchmark.graphWidth) + 1).toFixed(1) + '"></td></tr>';
        html += '</table>';
      } else {
        html += '&nbsp;';
      }
      td.innerHTML = html;
    },
    runAll: function(table, i, runningAll) {
      if (!runningAll && isRunning) {
        queue.push([table]);
        return;
      }
      isRunning = true;
      i = i || 0;
      var resultTd = byId('bench-result-' + table + '-' + i);
      if (resultTd) {
        resultTd.innerHTML = 'Running...';
        window.setTimeout(function() {
          pulp.unit.benchmark.run(table, i, true);
          pulp.unit.benchmark.runAll(table, i + 1, true);
        }, 10);
      } else {
        isRunning = false;
        pulp.unit.benchmark._next();
      }
    }
  });

})(pulp.base);

pulp.Modules['unit.benchmark'] = 'Benchmark';