var Benchmarker = {};
Benchmarker.tests = ["*", "body", "body div", "div", "div div", "div div div", ".dialogue", "div.dialogue", "div .dialogue", "#speech5", "div#speech5", "div #speech5", "div.scene div.dialogue", "div#scene1.scene div.dialogue div", "#scene1 #speech1", "div[@class]", "div[@class~=dialogue]", "div[@class^=dia]", "div[@class$=logue]", "div[@class*=sce]", "div#speech27.character", "div.dialogue.scene.thirdClass", "div.dialogue > div#playwright", ".dialogue > #playwright", "div.character + div.dialogue", ".character + .dialogue", "div.character + div.dialogue + div.character", "div:empty", "div.buttons :first-child", "div.buttons :last-child", "button:first-child", "button:last-child", "div:not(.dialogue)", "tbody tr:nth-child(even)", "tbody > tr:nth-child(odd)", "tbody tr:nth-child(3n+1)", "tbody tr:nth-child(-n+7)", "div.character:first-of-type", "div.character:nth-last-of-type(3)", "div.dialogue:not(:last-of-type)", "div.character, div.dialogue", "div, h1, th, tbody"];

Benchmarker.libraries = ["$$", "Sorted$$", "jQuery", "DomQuery", "Dojo", "Base2"];

Object.extend(Benchmarker, {
  initialize: function(options) {
    this.options = Object.extend({ times: 5 }, options || {});
    
    this.loggerWindow = new Window({
      id: 'logger_window',
      resizable: false,
      draggable: true,
      minimizable: false, 
      maximizable: false, 
      width: 600,
      height: 200,
      maxWidth: 600,
      maxHeight: 200,
      minWidth: 600,
      minHeight: 200,
      top: 50,
      left: 50,
      title: 'Results Log'
    });
    this.loggerWindow.setContent('logger', false, false);
    

    this.resultsWindow = new Window({ 
      top: 20,
      left: 500,
      id: 'time_window', 
      resizable: true, 
      draggable: true, 
      minimizable: false, 
      maximizable: false, 
      title: 'Selector Speed Test',
      width: 500,
      height: 400
      });
    this.resultsWindow.setContent('time-test', false, false);
    this.resultsWindow.show();
    window.setTimeout( Benchmarker.load.bind(Benchmarker), 300 );
    
    if (Prototype.Browser.IE) {
      $('results').setStyle({ height: '300px', top: '120px', width: '516px' });
      $('results').down('table').setStyle({ width: '499px' });
      $('time_window').setStyle({ width: '532px' });
    }
  },
  
  cache: {},
  
  stopped: false,
  
  buildCache: function() {
    for (var i = 0, test; i < Benchmarker.tests.length; i++) {
      test = Benchmarker.tests[i];
      this.cache[test] = $$(test.replace('@', ''));
    }
  },
  
  load: function() {
    for (var i = 0; i < Benchmarker.tests.length; i++) {
      var str = "<tr>";
      str += "<td><input type='checkbox' checked='checked' /></td>";
      str += "<td class='test'>" + Benchmarker.tests[i] + "</td>";
      Benchmarker.libraries.each( function(fn) { str += "<td class='empty'></td>"; });
      str += "</tr>";
      new Insertion.Bottom($$('#results tbody')[0], str);
    }
    $$('tbody tr:nth-child(even)').each( function(tr) { 
      Element.addClassName(tr, 'alternate');
    });
    $$('tbody tr:nth-child(even)').each( function(tr) { Element.addClassName(tr, 'alternate'); });
    $$('button.runTests').each( function(button) {
      Event.observe(button, 'click', function() {
        Benchmarker.stopped = false;
        var checkedRows = $$('#results tbody > tr').select( function(row) { 
          return row.down('td').down('input').checked;
        });
        checkedRows.each( function(tr) {
          var tds = tr.getElementsBySelector('td.full');
          tds.each( function(td) {
            td.removeClassName('full').removeClassName('best').removeClassName('worst');
            td.update('').addClassName('empty');          
          });
        });
        Benchmarker.testElements = checkedRows.invoke('down', 'td.test');
        Benchmarker.run(Benchmarker.testElements, Benchmarker.libraries.clone());
      }.bind(this));
    });
    
    $$('button.stopTests').each( function(button) {
      Event.observe(button, 'click', function() { Benchmarker.stopped = true; });
    });
    $$('button.selectAll').each( function(button) {
      Event.observe(button, 'click', function() {
        $$('#results input[type=checkbox]').each( function(input) { input.checked = true; } );
      });
    });
    $$('button.deselectAll').each( function(button) {
      Event.observe(button, 'click', function() {
        $$('#results input[type=checkbox]').each( function(input) { input.checked = false; });
      });
    });
    $$('button.showLogger').each( function(button) {
      Event.observe(button, 'click', function() { Benchmarker.loggerWindow.show(); });
    });
    
    var headers = Benchmarker.libraries.inject("", function(memo, lib) {
      return memo + "<th class='lib'>" + lib + "</th>";
    });
    var headerRow = $$('#results thead tr')[0];
    headers = headerRow.innerHTML + headers;
    Element.update(headerRow, headers);
    Benchmarker.buildCache();
  },
  
  run: function(tests, libraries) {
    if (Benchmarker.stopped) return;
    var times = this.options.times;
    var element = tests[0], library = libraries[0], timeResults = [];
    if (!library) { window.setTimeout(runNextTest(tests, libraries), 100); return; }
    if (!element) { this.winners(); this.log(); return; }
    var code = element.innerHTML.unescapeHTML(), officialResults = this.cache[code];
    var td = element.next('td.empty').removeClassName('empty').addClassName('full');
    if (!library.startsWith("jQ")) code = code.replace('@', '');
    for (var i = 0; i < times + 2; i++) {
      var time = new Date();
      try { window[library](code); }
      catch(e) { }
      timeResults.push(new Date() - time);
    }
    var diff = Math.sum(timeResults) - Math.max.apply(Math, timeResults) - Math.min.apply(Math, timeResults);
    try {
      var results = window[library](code);
      var ms = (Math.round(diff / times * 100) / 100) + "ms";
      if ((officialResults.length == 0 && results != 0) ||
       (results.length > 0 && officialResults.length == results.length))
        td.update(ms);
      else td.addClassName('fail').update(ms);
    } catch(e) {
      td.addClassName('fail').update('FAIL');
    }
    window.setTimeout(runNextLibrary(tests, libraries), 100);
  },
  
  winners: function() {
    function sorter(node) { return parseInt(node.innerHTML, 10); }
    $$('#results tbody tr').each( function(tr) {
      var tds = tr.getElementsBySelector('td.full:not(.fail)');
      if (tds.length < 2) return;
      tds = tds.sortBy(sorter);
      tds[0].addClassName('best');
      tds.last().addClassName('worst');
    });
  },
  
  log: function() {
    function serialize(td) {
      var value = td.innerHTML.unescapeHTML().replace(/ms$/, '');
      if (td.hasClassName('fail')) value += "*";
      return value;
    }
    var header = $$('#results thead tr')[0];
    str = header.getElementsBySelector('th').slice(1).map(serialize).join(',');
    $('logger').value += str + "\n";
    $$('#results tbody tr').each( function(tr) {
      var tds = tr.getElementsBySelector('td.full');
      if (tds.length == 0) return;
      var str = "";
      str += tr.down('td.test').innerHTML.unescapeHTML() + ',';      
      str += tds.map(serialize).join(',');
      $('logger').value += str + "\n";
    });
  }
});

function runNextTest(tests, libraries) {
  return function() { Benchmarker.run(tests.slice(1), Benchmarker.libraries.clone()); };
}

function runNextLibrary(tests, libraries) {
  return function() { Benchmarker.run(tests, libraries.slice(1)); }
}

Math.sum = function(arr) {
  var sum = 0;
  for(i = 0; i < arr.length; i++) sum += arr[i];
  return sum;
};


Event.observe(window, 'load', function() { Benchmarker.initialize({ times: 1 }); });