var query = document.location.search.replace(/(^\?)/, '').split(
    '&').map(function (n) {
    n = n.split('=');
    if (n[0]) {
      this[decodeURIComponent(n[0])] = decodeURIComponent(n[1]);
    }
    return this;
  }.bind({}))[0];
  $.each(query, function (key, value) {
    var ctlvalue, ctlkey = key;
    switch (key) {
      case 'binned':
        ctlvalue = value ? value : 'auto';
        break;
      case 'dataset':
        ctlvalue = value ? value : 'adderall';
        break;
      case 'gaussian':
      case 'scaleWithZoom':
        ctlvalue = value === 'true';
        heatmapOptions.style[key] = value;
        break;
      case 'gradient':
        var parts = value.split(',').map(parseFloat);
        if (parts.length >= 5) {
          var gradient = {0: {r: 0, g: 0, b: 0, a: 0}};
          for (var i = 0; i < parts.length - 4; i += 5) {
            gradient[parts[i]] = {
              r: parts[i + 1] / 255,
              g: parts[i + 2] / 255,
              b: parts[i + 3] / 255,
              a: parts[i + 4]
            };
          }
          heatmapOptions.style.color = gradient;
        }
        break;
      case 'minIntensity': case 'maxIntensity':
        value = value.length ? parseFloat(value) : null;
        if (!isNaN(value)) {
          heatmapOptions[key] = ctlvalue = value;
        }
        break;
      case 'opacity':
        value = value.length ? parseFloat(value) : 0.75;
        if (!isNaN(value)) {
          layerOptions[key] = ctlvalue = value;
        }
        break;
      case 'points':
        if (value.length) {
          points = ctlvalue = parseInt(value, 10);
        }
        break;
      case 'radius': case 'blurRadius':
        if (value.length) {
          value = parseFloat(value);
          if (!isNaN(value)) {
            heatmapOptions.style[key] = ctlvalue = value;
          }
        }
        break;
      case 'updateDelay':
        if (value.length) {
          heatmapOptions[key] = ctlvalue = parseInt(value, 10);
        }
        break;
    }
    if (ctlvalue !== undefined) {
      $('#' + ctlkey).val(ctlvalue);
    }
  });
  
  var gradkeys = Object.keys(heatmapOptions.style.color).sort();
  $.each(gradkeys, function (idx, key) {
    var entry = heatmapOptions.style.color[key];
    $('#gradI' + (idx + 1)).val(key);
    $('#gradR' + (idx + 1)).val(Math.round(entry.r * 255));
    $('#gradG' + (idx + 1)).val(Math.round(entry.g * 255));
    $('#gradB' + (idx + 1)).val(Math.round(entry.b * 255));
    $('#gradA' + (idx + 1)).val(entry.a);
  });
  
  function fetch_data() {
    var dataset = $('#dataset').val(),
        url = '../../data/' + $('#dataset option:selected').attr('url');
    $.ajax(url, {
      success: function (resp) {
        window.heatmap.datapoints = null;
        var rows;
        switch (dataset) {
          case 'adderall':
            rows = resp.split(/\r\n|\n|\r/);
            rows.splice(0, 1);
            rows = rows.map(function (r) {
              var fields = r.split(',');
              return [fields[12], fields[24], fields[25]].map(parseFloat);
            });
            break;
          case 'cities':
            rows = resp.split(/\r\n|\n|\r/);
            rows.splice(rows.length - 1, 1);
            rows = rows.map(function (r) {
              var fields = r.split('","');
              return ['' + fields[0].replace(/(^\s+|\s+$|^"|"$)/g, '').length, fields[2].replace(/(^\s+|\s+$|^"|"$)/g, ''), fields[3].replace(/(^\s+|\s+$|^"|"$)/g, '')].map(parseFloat);
            });
            break;
          case 'earthquakes':
            rows = resp;
            break;
        }
        datapoints = rows;
        window.heatmap.datapoints = datapoints;
        var text = 'Loaded: ' + datapoints.length;
        $('#points-loaded').text(text).attr('title', text);
        show_points(datapoints);
      }
    });
  }
  
  function show_points(datapoints) {
    window.heatmap.rows = null;
    var rows = datapoints;
    var maxrows = parseInt(points, 10) || rows.length;
    if (rows.length > maxrows) {
      rows = rows.slice(0, maxrows);
    } else if (rows.length < maxrows) {
      rows = rows.slice();
      while (rows.length < maxrows) {
        for (var i = rows.length - 1; i >= 0 && rows.length < maxrows; i -= 1) {
          rows.push([
            rows[i][0] + Math.random() * 0.1 - 0.05,
            rows[i][1] + Math.random() * 0.1 - 0.05,
            rows[i][2] + Math.random() * 0.1 - 0.05]);
        }
      }
    }
    heatmap.data(rows);
    window.heatmap.rows = rows;
    heatmap.draw();
    var text = 'Shown: ' + rows.length;
    $('#points-shown').text(text).attr('title', text);
  }
  
  function change_controls(evt) {
    var ctl = $(evt.target),
        param = ctl.attr('id'),
        value = ctl.val();
    if (ctl.is('[type="checkbox"]')) {
      value = ctl.is(':checked') ? 'true' : 'false';
    }
    if (value === '' && ctl.attr('placeholder')) {
      value = ctl.attr('placeholder');
    }
    if (!param || value === query[param]) {
      return;
    }
    var processedValue = (
      ctl.is('[type="checkbox"]') ? (value === 'true') : value);
    if (ctl.closest('table.gradient').length) {
      param = 'gradient';
    }
    switch (param) {
      case 'binned':
        heatmapOptions[param] = value;
        heatmap[param](value);
        heatmap.draw();
        break;
      case 'blurRadius': case 'radius':
        processedValue = value.length ? parseFloat(value) : undefined;
        if (isNaN(processedValue) || processedValue === undefined ||
            processedValue < 0) {
          return;
        }
        heatmapOptions.style[param] = processedValue;
        heatmap.style(param, processedValue);
        heatmap.draw();
        break;
      case 'dataset':
        fetch_data();
        break;
      case 'gaussian':
      case 'scaleWithZoom':
        heatmapOptions.style[param] = processedValue;
        heatmap.style(param, processedValue);
        heatmap.draw();
        break;
      case 'gradient':
        var gradient = {};
        for (var idx = 1; idx <= 6; idx += 1) {
          var gradkey = parseFloat($('#gradI' + idx).val());
          if (isNaN(gradkey)) {
            continue;
          }
          gradient[gradkey] = {
            r: parseInt($('#gradR' + idx).val() || 0) / 255,
            g: parseInt($('#gradG' + idx).val() || 0) / 255,
            b: parseInt($('#gradB' + idx).val() || 0) / 255,
            a: parseFloat($('#gradA' + idx).val() || 0)
          };
        }
        if (!(0 in gradient && 1 in gradient)) {
          value = '';
          break;
        }
        heatmapOptions.style.color = gradient;
        heatmap.style('color', gradient);
        heatmap.draw();
        var gradkeys = Object.keys(heatmapOptions.style.color).sort();
        value = gradkeys.map(function (key) {
          return [key, Math.round(gradient[key].r * 255), Math.round(gradient[key].g * 255), Math.round(gradient[key].b * 255), gradient[key].a].join(',');
        }).join(',');
        break;
      case 'minIntensity': case 'maxIntensity':
        processedValue = value.length ? parseFloat(value) : null;
        if (isNaN(processedValue)) {
          return;
        }
        heatmapOptions[param] = processedValue;
        heatmap[param](processedValue);
        heatmap.draw();
        break;
      case 'opacity':
        processedValue = value.length ? parseFloat(value) : undefined;
        if (isNaN(processedValue) || processedValue === undefined) {
          return;
        }
        layerOptions[param] = processedValue;
        layer[param](processedValue);
        break;
      case 'points':
        points = parseInt(value);
        show_points(datapoints);
        break;
      case 'updateDelay':
        processedValue = value.length ? parseInt(value) : 50;
        heatmapOptions[param] = processedValue;
        heatmap[param](processedValue);
        break;
    }