import {dispatch} from "d3-dispatch";
import {map} from "d3-collection";
import {timer} from "d3-timer";

export function x(d) {
  return d.x;
}

export function y(d) {
  return d.y;
}

var initialRadius = 10,
    initialAngle = Math.PI * (3 - Math.sqrt(5));

export default function(nodes) {
  var simulation,
      alpha = 1,
      alphaMin = 0.001,
      alphaDecay = 1 - Math.pow(alphaMin, 1 / 300),
      alphaTarget = 0,
      drag = 0.6,
      forces = map(),
      fixes = {},
      stepper = timer(step),
      event = dispatch("tick", "end");

  if (nodes == null) nodes = [];

  function step() {
    tick();
    event.call("tick", simulation);
    if (alpha < alphaMin) {
      stepper.stop();
      event.call("end", simulation);
    }
  }

  function tick() {
    var i, n = nodes.length, node, fix;

    alpha += (alphaTarget - alpha) * alphaDecay;

    forces.each(function(force) {
      force(alpha);
    });

    for (i = 0; i < n; ++i) {
      node = nodes[i];
      node.x += node.vx *= drag;
      node.y += node.vy *= drag;
    }

    for (i in fixes) {
      fix = fixes[i], node = nodes[i];
      node.x = fix.x;
      node.y = fix.y;
      node.vx =
      node.vy = 0;
    }
  }

  function initializeNodes() {
    for (var i = 0, n = nodes.length, node; i < n; ++i) {
      node = nodes[i], node.index = i;
      if (isNaN(node.x) || isNaN(node.y)) {
        var radius = initialRadius * Math.sqrt(i), angle = i * initialAngle;
        node.x = radius * Math.cos(angle);
        node.y = radius * Math.sin(angle);
      }
      if (isNaN(node.vx) || isNaN(node.vy)) {
        node.vx = node.vy = 0;
      }
    }
  }

  function initializeForce(force) {
    if (force.initialize) force.initialize(nodes);
    return force;
  }

  initializeNodes();

  return simulation = {
    tick: tick,

    restart: function() {
      return stepper.restart(step), simulation;
    },

    stop: function() {
      return stepper.stop(), simulation;
    },

    nodes: function(_) {
      return arguments.length ? (nodes = _, initializeNodes(), forces.each(initializeForce), simulation) : nodes;
    },

    alpha: function(_) {
      return arguments.length ? (alpha = +_, simulation) : alpha;
    },

    alphaMin: function(_) {
      return arguments.length ? (alphaMin = +_, simulation) : alphaMin;
    },

    alphaDecay: function(_) {
      return arguments.length ? (alphaDecay = +_, simulation) : +alphaDecay;
    },

    alphaTarget: function(_) {
      return arguments.length ? (alphaTarget = +_, simulation) : alphaTarget;
    },

    drag: function(_) {
      return arguments.length ? (drag = 1 - _, simulation) : 1 - drag;
    },

    force: function(name, _) {
      return arguments.length > 1 ? ((_ == null ? forces.remove(name) : forces.set(name, initializeForce(_))), simulation) : forces.get(name);
    },

    fix: function(node, x, y) {
      return fixes[node.index] = {x: x == null ? node.x : +x, y: y == null ? node.y : +y}, simulation;
    },

    unfix: function(node) {
      return delete fixes[node.index], simulation;
    },

    on: function(name, _) {
      return arguments.length > 1 ? (event.on(name, _), simulation) : event.on(name);
    }
  };
}

@@ -3062,24 +3062,489 @@
 on;%0A    %7D,%0A%0A
+    find: function(x, y, radius) %7B%0A      var i = 0,%0A          n = nodes.length,%0A          dx,%0A          dy,%0A          d2,%0A          node,%0A          closest;%0A%0A      if (radius == null) radius = Infinity;%0A      else radius *= radius;%0A%0A      for (i = 0; i %3C n; ++i) %7B%0A        node = nodes%5Bi%5D;%0A        dx = x - node.x;%0A        dy = y - node.y;%0A        d2 = dx * dx + dy * dy;%0A        if (d2 %3C radius) closest = node, radius = d2;%0A      %7D%0A%0A      return closest;%0A    %7D,%0A%0A
     on: func

Add simulation.find. Fixes #31.