var table_size = 0;
var version_names = "XABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");

function adjust_table_size () {
    
    var versions = parseInt($("#version-count").val(),10);
    var err = "";
  
    if (isNaN(versions)) {
        err = "Unknown number";
        $("#version-count-err").html(err);
        if ($("#version-count-err").is(":hidden")) $("#version-count-err").show("fast");
        return;
    }
    
    if (versions < 2) {
        err = "Must have at least 2 versions!";
        $("#version-count-err").html(err);
        if ($("#version-count-err").is(":hidden")) $("#version-count-err").show("fast");
        return;
    }

    if (versions > version_names.length - 1) {
        err = "Must have less than "+(version_names.length - 1)+" versions !";
        $("#version-count-err").html(err);
        $("#version-count").val(table_size)
        if ($("#version-count-err").is(":hidden")) $("#version-count-err").show("fast");
        return;
    }
  
    if (!$("#version-count-err").is(":hidden")) $("#version-count-err").hide("fast");
      
    for (var i = table_size + 1; i <= versions; i++) {      
        var new_row = '<tr id="row_'+i+'">';    
        new_row += '<td><input id="name_'+i+'" value="'+version_names[i]+'" /></td>';
        new_row += '<td><input id="trials_'+i+'" class="number" /></td>';
        new_row += '<td><input id="success_'+i+'" class="number" /></td>';
        new_row += '</tr>';
        $("#data tbody").append(new_row);    
    }
    
    for (var i = table_size; i > versions; i--) {    
        $("#row_"+i).remove();
    }
    
    table_size = versions;
}


function calculate_and_display_answer () {
    var version_information;
    
    try {
        var version_information = extract_version_information();
    }
    catch (err) {
      
        if (err.message) {
            $("#data-errs").html(err.message);
        }
        else {
            $("#data-errs").html(err);
        }
        
        if ($("#data-errs").is(":hidden")) $("#data-errs").show("fast");
        return false;
    }
    
    if (!$("#data-errs").is(":hidden")) $("#data-errs").hide("fast");


    var min = version_information[0];
    var max = version_information[0];
    var data_all = [];
    var versions = version_information.length;
    var min_value = max[1];
    
    for (var i = 0; i < versions; i++) {
        var this_version = version_information[i];
        data_all.push( [this_version[1], this_version[2]] );
      
        if (this_version[3] > max[3]) {
            max = this_version;
        }
        else if (this_version[3] < min[3]) {
            min = this_version;
        }
      
        if (this_version[1] < min_value)
            min_value = this_version[1];
      
        if (this_version[2] < min_value)
            min_value = this_version[2];
    }

    var g_test_all = calculate_g_test(data_all);
        g_test_all = calculate_g_test_with_yates(data_all);
  

    var p_all = chisqrprob(versions - 1, g_test_all);
    var certainty_all = round_to_precision( 100 * (1-p_all), 2);

    if (min_value < 10) {
        $("#results").html('<strong>Warning:</strong> Some measurements were below 10, so confidence values may be inaccurate.<br><br>');
    }
    else {
        $("#results").html('');
    }

    if (2 == versions) {
    
        $("#results").html($("#results").html() +
          "The G-test statistic is " + round_to_precision(g_test_all, 4) +
          " so version '" + max[0] + "' wins with " + certainty_all +
          "% confidence.");
    }
    else {
        
        var data = [[min[1], min[2]], [max[1], max[2]]];
        var g_test;
        g_test = calculate_g_test_with_yates(data);
        
        var p = chisqrprob(1, g_test);
        
        // Fudge factor time
        if (3 == versions)
            p *= 1.65;
        else if (4 == versions)
            p *= 2.8;
        else if (5 == versions)
            p *= 5;
        else if (6 == versions)
            p *= 8;
        else 
            p *= versions * (versions - 1) / 2;

    if (p >= 1) {
        $("#results").html($("#results").html() + "No version is currently cleary winning.");
    }
    else {
        var certainty = round_to_precision( 100 * (1-p), 2);
        $("#results").html($("#results").html() +
          "Comparing versions '" + min[0] + "' and '" + max[0] +
          "' gives a G-test statistic of " +
          round_to_precision(g_test, 4) + " so version '" +
          min[0] + "' can be dropped with at least " +
          certainty + "% confidence.");
    }
    $("#results").html($("#results").html() +
        "<br><br>The G-test statistic across all versions is " +
        round_to_precision(g_test_all, 4) +
        " so we conclude there is some difference with " +
        certainty_all + "% confidence.");
    }
}

function extract_version_information () {
    var get_counts_from_values;
    
    if ($("#success-count").is(":checked")) {
      get_counts_from_values = function (name, trials, success) {
        if (trials < success) {
          throw(
            "Version '" + name + "' cannot have more successes than trials"
          );
        }
    
        return [success, trials - success];
      };
    }
    else if ($("#success-percentage").is(":checked")) {
      get_counts_from_values = function (name, trials, success) {
        if (100 < success) {
          throw(
            "Version '" + name + "' cannot succeed with probability > 100%"
          );
        }
    
        return [trials*success/100, trials*(1 - success/100)];
      };
    } else {
      throw("Unknown success type");
    }

    var saw_name = {}; var to_return = [];
    
    for (var i = 1; i <= table_size; i++) {
        var name = $("#name_" + i).val();
        
        if ("" == name) {
          throw("Version " + i + " has no name?");
        } else if (saw_name[name] > 0) {
          throw(
            "Versions " + i + " and " + saw_name[name] + " are named " + name
          );
        } else {
          saw_name[name] = i;
        }
        
        var trials = $("#trials_" + i).val();
        if (! (0 < trials)) {
          throw("Version '" + name + "' needs trials!");
        }
        
        var success = $("#success_" + i).val();
        if (! (0 < success)) {
          throw("Version '" + name + "' needs successes!");
        }
        
        var counts = get_counts_from_values(name, trials - 0, success - 0);
        to_return.push(
          [name, counts[0], counts[1], counts[0]/(counts[0] + counts[1])]
        );
    }

  return to_return;
}

function calculate_g_test (data) {
  var rows = data.length;
  var columns = data[0].length;

  // Initialize our subtotals
  var row_totals = [];
  for (var i = 0; i < rows; i++) {
    row_totals[i] = 0;
  }

  var column_totals = [];
  for (var j = 0; j < columns; j++) {
    column_totals[j] = 0;
  }

  var total = 0;

  // First we calculate the totals for the row and the column
  for (var i = 0; i < rows; i++) {
    for (var j = 0; j < columns; j++) {
      var entry = data[i][j] - 0;  // - 0 ensures numeric
      row_totals[i]    += entry;
      column_totals[j] += entry;
      total            += entry;
    }
  }

  // Now we calculate the g-test contribution from each entry.
  var g_test = 0;;
  for (var i = 0; i < rows; i++) {
    for (var j = 0; j < columns; j++) {
      var expected = row_totals[i] * column_totals[j] / total;
      var seen     = data[i][j];

      g_test      += 2 * seen * Math.log( seen / expected );
    }
  }

  return g_test;
}

function calculate_g_test_with_yates (data) {
  var rows = data.length;
  var columns = data[0].length;

  // Initialize our subtotals
  var row_totals = [];
  for (var i = 0; i < rows; i++) {
    row_totals[i] = 0;
  }

  var column_totals = [];
  for (var j = 0; j < columns; j++) {
    column_totals[j] = 0;
  }

  var total = 0;

  // First we calculate the totals for the row and the column
  for (var i = 0; i < rows; i++) {
    for (var j = 0; j < columns; j++) {
      var entry = data[i][j] - 0;  // - 0 ensures numeric
      row_totals[i]    += entry;
      column_totals[j] += entry;
      total            += entry;
    }
  }

  // Now we calculate the g-test contribution from each entry.
  var g_test = 0;;
  for (var i = 0; i < rows; i++) {
    for (var j = 0; j < columns; j++) {
      var expected = row_totals[i] * column_totals[j] / total;
      var seen     = data[i][j];

      // The Yates continuity correction, adjust the seen value 0.5
      // towards the expected value.
      if (expected + 0.5 < seen)
        seen -= 0.5;
      else if (expected - 0.5 > seen)
        seen += 0.5;
      else
        seen = expected;

      g_test      += 2 * seen * Math.log( seen / expected );
    }
  }

  return g_test;
}


$(document).ready(function() {
    adjust_table_size();
    
    $("#version-count").keyup(function() {
        adjust_table_size();
    })
    
    $("#calculate").click(function() {
        calculate_and_display_answer();
    })
});
