function hash_string_int(s) {
  var hash = 1;
  for (var i = 0; i < s.length; ++i) {
    var ch = s.charCodeAt(i);
    hash = (hash * 163 + ch) % 2147483647;
  }
  return hash;
}

function hash_string_float(s) {
  return hash_string_int(s) / 2147483647;
}

function HSVtoRGB(h, s, v) {
  var r, g, b, i, f, p, q, t;
  i = Math.floor(h * 6);
  f = h * 6 - i;
  p = v * (1 - s);
  q = v * (1 - f * s);
  t = v * (1 - (1 - f) * s);
  switch (i % 6) {
    case 0: r = v, g = t, b = p; break;
    case 1: r = q, g = v, b = p; break;
    case 2: r = p, g = v, b = t; break;
    case 3: r = p, g = q, b = v; break;
    case 4: r = t, g = p, b = v; break;
    case 5: r = v, g = p, b = q; break;
  }
  return {r: Math.round(r * 255),
          g: Math.round(g * 255),
          b: Math.round(b * 255)};
}

// Generate unique looking color for a given bar index.
function generateBarColor(index, label) {
  var u1 = hash_string_float(label);
  var u2 = hash_string_float(label.split("").reverse().join(""));
  var h = 0, s = 0.8, v = 0.8;

  if (label.startsWith("AMD")) {
    h = 0;
  } else if (label.startsWith("Intel")) {
    h = 0.66;
  } else if (label.startsWith("Nvidia")) {
    h = 0.33;
  }

  h += ((u1 + u2) * 0.5 - 0.5) * 0.2;
  if (h < 0) h = 1.0 - h;
  s += (u1 - 0.5) * 0.5;
  v += (u2 - 0.5) * 0.25;
  var color = HSVtoRGB(h, s, v);
  return "rgb(" + Math.round(color.r).toString() + ", " +
                  Math.round(color.g).toString() + ", " +
                  Math.round(color.b).toString() + ")";
}

// Correct device name, ensuring it has clear vendor indication,
// and has no trademarks or other things.
function correctDeviceName(name) {
  if (name.startsWith("TITAN") ||
      name.startsWith("Quadro") ||
      name.startsWith("GeForce")) {
    return "Nvidia " + name;
  }
  if (name.startsWith("Radeon")) {
    return "AMD " + name;
  }
  return name.replace(/( @.*)/, "");
}

function strcmp(a, b) {
  return ( ( a == b ) ? 0 : ( ( a > b ) ? 1 : -1 ) );
}

function clone(object) {
  return jQuery.extend(true, {}, object);
}

function padValue(value, size) {
  var s = String(value);
  while (s.length < (size || 2)) {
    s = "0" + s;
  }
  return s;
}

function secondsToHumanReadable(seconds) {
  var h = Math.floor((seconds %= 86400) / 3600);
  var m = Math.floor((seconds %= 3600) / 60);
  var s = Math.floor(seconds % 60);
  var msec = Math.floor((seconds % 60) % 1 * 100);
  var result = "";
  if  (h != 0) {
    result += h + ":";
  }
  result += padValue(m, 2) + ":";
  result += padValue(s, 2) + ".";
  result += padValue(msec, 2);
  return result;
}

function setDatasetColor(data, is_bar) {
  var index = 0;
  for (dataset of data.datasets) {
    var color = Chart.helpers.color(generateBarColor(index, dataset.label));
    dataset.backgroundColor = color.alpha(0.5).rgbString();
    dataset.borderColor = color.alpha(1.0).rgbString();
    if (is_bar) {
      dataset.borderWidth = 1;
    } else {
      dataset.fill = false;
    }
    index++;
  }
}

function setCorrectDatasetLabels(data) {
  for (dataset of data.datasets) {
    dataset.label = correctDeviceName(dataset.label);
  }
}

function compareDatasets(dataset_a, dataset_b) {
  return strcmp(dataset_a.label, dataset_b.label);
}

function sortDataset(data) {
  data.datasets.sort(compareDatasets);
}

function prepareDataset(data, is_bar) {
  setCorrectDatasetLabels(data);
  sortDataset(data);
  setDatasetColor(data, is_bar);
}

function findDatasetMaxValue(data) {
  var max_value = 0;
  for (dataset of data.datasets) {
    for (value of dataset.data) {
      if (value === null) {
        continue;
      }
      if (typeof value !== "number") {
          value = value.y;
      }
      max_value = Math.max(max_value, value);
    }
  }
  return max_value;
}

function buildChart(ctx, bare_data) {
  var data = clone(bare_data);
  prepareDataset(data, true);
  var my_chart = new Chart(
      ctx,
      {
        type: 'bar',
        data: data,
        options: { responsive: true,
                   legend: {position: 'top'},
                   title: {display: true,
                           text: 'Benchmark Results'},
                   scales: {xAxes: [{display: true,
                                     scaleLabel: {display: true,
                                                  labelString: 'Scene'}}],
                            yAxes: [{display: true,
                                     scaleLabel: {display: true,
                                                  labelString: 'Render time (sec)'},
                                     ticks: {min: 0}}]},
                   tooltips: {// mode: 'index',
                              callbacks: {
                                footer: function(tooltipItems, data) {
                                  var human_time = "";
                                  tooltipItems.forEach(
                                      function(tooltipItem) {
                                        var render_time = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
                                        human_time = secondsToHumanReadable(render_time);
                                      }
                                  );
                                  if (human_time != "") {
                                    return "Render Time: " + human_time;
                                  }
                                  return "";
                                },
                              },
                              filter: function(item, data) {
                                var data = data.datasets[item.datasetIndex].data[item.index];
                                return !isNaN(data) && data !== null;
                              },
                              footerFontStyle: 'normal'},
                }
      });
  // TODO(sergey): How can we avoid storing chart in some obscure global variable?
  window.my_chart = my_chart;
}

function updateEnabledScenesCallback(bare_data) {
  /* Collect list of enabled scenes. */
  var enabled_scenes = [];
  var disabled_scenes = [];
  var scene_index = 0;
  for (scene_name of bare_data.labels) {
    var id = "scene_" + scene_name + "_id";
    var checkbox = document.getElementById(id);
    if (checkbox.checked) {
      enabled_scenes.push({"index": scene_index, "name": scene_name});
    } else {
      disabled_scenes.push({"index": scene_index, "name": scene_name});
    }
    ++scene_index;
  }
  /* Construct new data by making a copy of original one and removing
   * disabeld scenes.
   *
   * Note: we reverse te list so indices for removal are alweays staying
   * the same.
   */
  disabled_scenes.reverse();
  var updated_data = clone(bare_data);
  prepareDataset(updated_data, true);
  for (scene of disabled_scenes) {
    // Remove label first.
    updated_data.labels.splice(scene.index, 1);
    updated_data.datasets.forEach(function(dataset, dataset_index) {
      dataset.data.splice(scene.index, 1);
    });
  }
  /* Update the actual chart. */
  var my_chart = window.my_chart;
  my_chart.data = updated_data;
  my_chart.update();
}

function buildScenesSelector(ctx, bare_data) {
  var div = document.createElement("div");
  div.className = "scene_selector_container";
  for (scene_name of data.labels) {
    var span = document.createElement("span");

    var checkbox = document.createElement('input');
    checkbox.type = "checkbox";
    checkbox.name = "scene_" + scene_name;
    checkbox.checked = true;
    checkbox.id = "scene_" + scene_name + "_id";
    checkbox.onclick = function() { updateEnabledScenesCallback(bare_data); };
    span.appendChild(checkbox);

    var label = document.createElement('label');
    label.htmlFor = "scene_" + scene_name + "_id";
    label.appendChild(document.createTextNode(scene_name));
    span.appendChild(label);

    div.appendChild(span);
  }
  ctx.appendChild(div);
}

function historyChartGetSceneStats(bare_data, scene_name) {
  var datasets = [];
  for (dataset of bare_data.datasets) {
    if (dataset.scene_name != scene_name) {
      continue;
    }
    datasets.push({"data": dataset.data,
                   "label": dataset.device_name});
  }
  return {"datasets": datasets};
}

function buildHistoryChart(ctx, bare_data, scene_name) {
  var data = historyChartGetSceneStats(bare_data, scene_name);
  prepareDataset(data, false);
  new Chart(
      ctx,
      {
        type: 'line',
        data: data,
        options: { responsive: true,
                   legend: {position: 'top'},
                   title: {display: true,
                           text: scene_name + ' benchmark results'},
                   scales: {xAxes:  [{type: "time",
                                      time: {format: 'DD/MM/YYYY HH:mm',
                                             tooltipFormat: 'll HH:mm'},
                                      scaleLabel: {display: true,
                                                   labelString: 'Date'}},],
                            yAxes: [{display: true,
                                     scaleLabel: {display: true,
                                                  labelString: 'Render time (sec)'},
                                     ticks: {min: 0}}]},

                            elements: { line: { tension: 0.000001 } },
                }
      });
}

function comapreTableRows(row_a, row_b) {
  return strcmp(row_a[0], row_b[0]);
}

function sortTableRows(rows) {
  rows.sort(comapreTableRows);
}

function buildSpreadsheet(ctx, data) {
  // Generate columns for header.
  var columns = [{label: "", name: "", width: 300}];
  for (label of data.labels) {
    columns.push({label: label, name: label});
  }
  // Generate actual data.
  var rows = [];
  for (dataset of data.datasets) {
    var row = [correctDeviceName(dataset.label)];
    for (value of dataset.data) {
      if (value == null) {
        row.push("");
      } else {
        row.push(secondsToHumanReadable(value));
      }
    }
    rows.push(row);
  }
  sortTableRows(rows);
  // Create actual table.
  $(ctx).jqGrid({
    data: rows,
    localReader: {repeatitems: true},
    datatype: "local",
    styleUI : 'Bootstrap',
    colModel: columns,
    caption: "Spreadsheet",
    height: "100%",
    rowNum: 999999,
  });
}
