function tokenize(input, callback) {
  while(input.length > 0) {
    callback(input.match(/^[\/\.\?]|[^\/\.\?]+/)[0]);
    input = input.replace(/^[\/\.\?]|[^\/\.\?]+/, '');
  }
}

var graph = d3.select("#chart-2 svg");
var svg_edges = {};
var svg_nodes = {};

graph.selectAll("g.edge").each(function() {
  var node  = d3.select(this);
  var index = node.select("title").text().split("->");
  var left  = parseInt(index[0]);
  var right = parseInt(index[1]);

  if(!svg_edges[left]) { svg_edges[left] = {} }
  svg_edges[left][right] = node;
});

graph.selectAll("g.node").each(function() {
  var node  = d3.select(this);
  var index = parseInt(node.select("title").text());
  svg_nodes[index] = node;
});

function reset_graph() {
  for(var key in svg_edges) {
    for(var mkey in svg_edges[key]) {
      var node = svg_edges[key][mkey];
      var path = node.select("path");
      var arrow = node.select("polygon");
      path.style("stroke", "black");
      arrow.style("stroke", "black").style("fill", "black");
    }
  }

  for(var key in svg_nodes) {
    var node = svg_nodes[key];
    node.select('ellipse').style("fill", "white");
    node.select('polygon').style("fill", "white");
  }
  return false;
}

function highlight_edge(from, to) {
  var node = svg_edges[from][to];
  var path = node.select("path");
  var arrow = node.select("polygon");

  path
    .transition().duration(500)
    .style("stroke", "green");

  arrow
    .transition().duration(500)
    .style("stroke", "green").style("fill", "green");
}

function highlight_state(index, color) {
  if(!color) { color = "green"; }

  svg_nodes[index].select('ellipse')
    .style("fill", "white")
    .transition().duration(500)
    .style("fill", color);
}

function highlight_finish(index) {
  svg_nodes[index].select('ellipse')
    .style("fill", "while")
    .transition().duration(500)
    .style("fill", "blue");
}

function match(input) {
  reset_graph();
  var table           = tt();
  var states          = [[0, null]];
  var regexp_states   = table['regexp_states'];
  var string_states   = table['string_states'];
  var stdparam_states = table['stdparam_states'];
  var accepting       = table['accepting'];
  var default_re      = new RegExp("^[^.\/?]+$");
  var start_index     = 0;

  highlight_state(0);

  tokenize(input, function(token) {
    var end_index = start_index + token.length;

    var new_states = [];
    for(var key in states) {
      var state_parts = states[key];
      var state = state_parts[0];
      var previous_start = state_parts[1];

      if(previous_start == null) {
        if(string_states[state] && string_states[state][token]) {
          var new_state = string_states[state][token];
          highlight_edge(state, new_state);
          highlight_state(new_state);
          new_states.push([new_state, null]);
        }

        if(stdparam_states[state] && default_re.test(token)) {
          for(var key in stdparam_states[state]) {
            var new_state = stdparam_states[state][key];
            highlight_edge(state, new_state);
            highlight_state(new_state);
            new_states.push([new_state, null]);
          }
        }
      }

      if(regexp_states[state]) {
        var slice_start = previous_start != null ? previous_start : start_index;

        for(var key in regexp_states[state]) {
          var re = new RegExp("^" + key + "$");

          var accumulation = input.slice(slice_start, end_index);

          if(re.test(accumulation)) {
            var new_state = regexp_states[state][key];
            highlight_edge(state, new_state);
            highlight_state(new_state);
            new_states.push([new_state, null]);
          }

          // retry the same regexp with the accumulated data either way
          new_states.push([state, slice_start]);
        }
      }
    }

    states = new_states;
    start_index = end_index;
  });

  for(var key in states) {
    var state_parts = states[key];
    var state = state_parts[0];
    var slice_start = state_parts[1];

    // we must ignore ones that are still accepting more data
    if (slice_start != null) continue;

    if(accepting[state]) {
      highlight_finish(state);
    } else {
      highlight_state(state, "red");
    }
  }

  return false;
}

