/**
 * Calculate the forces the nodes apply on each other based on a repulsion field.
 * This field is linearly approximated.
 *
 * @private
 */
exports._calculateNodeForces = function () {
  var dx, dy, angle, distance, fx, fy, combinedClusterSize,
    repulsingForce, node1, node2, i, j;

  var nodes = this.calculationNodes;
  var nodeIndices = this.calculationNodeIndices;

  // approximation constants
  var a_base = -2 / 3;
  var b = 4 / 3;

  // repulsing forces between nodes
  var nodeDistance = this.constants.physics.repulsion.nodeDistance;
  var minimumDistance = nodeDistance;

  // we loop from i over all but the last entree in the array
  // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
  for (i = 0; i < nodeIndices.length - 1; i++) {
    node1 = nodes[nodeIndices[i]];
    for (j = i + 1; j < nodeIndices.length; j++) {
      node2 = nodes[nodeIndices[j]];
      combinedClusterSize = node1.clusterSize + node2.clusterSize - 2;

      dx = node2.x - node1.x;
      dy = node2.y - node1.y;
      distance = Math.sqrt(dx * dx + dy * dy);

      minimumDistance = (combinedClusterSize == 0) ? nodeDistance : (nodeDistance * (1 + combinedClusterSize * this.constants.clustering.distanceAmplification));
      var a = a_base / minimumDistance;
      if (distance < 2 * minimumDistance) {
        if (distance < 0.5 * minimumDistance) {
          repulsingForce = 1.0;
        }
        else {
          repulsingForce = a * distance + b; // linear approx of  1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness))
        }

        // amplify the repulsion for clusters.
        repulsingForce *= (combinedClusterSize == 0) ? 1 : 1 + combinedClusterSize * this.constants.clustering.forceAmplification;
        repulsingForce = repulsingForce / distance;

        fx = dx * repulsingForce;
        fy = dy * repulsingForce;

        node1.fx -= fx;
        node1.fy -= fy;
        node2.fx += fx;
        node2.fy += fy;
      }
    }
  }
};
