var DELTAT     = 0.03; // fixed time step, no relation to real time
var SPRINGK    = 5;    // spring constant, stiffness of springs
var MASS       = 1;    // all the physics is bogus
var GRAVITY    = 20;
var RESISTANCE = 10;
var REJECTF    = 350;  // reject from each other
var STOPVEL    = 0.1;  // prevent endless jittering
var STOPACC    = 0.1;
var BOUNCE     = 0.75; // percent of velocity when bouncing off a wall

var started = false;
var Xpos = -999, Ypos = -999;
var node = new Array(), edge = new Array();
var head;

function Node(x, y, sx, sy) {
  this.x  = x;
  this.y  = y;
  this.sx = 0;
  this.sy = 0;
  this.dx = 0;
  this.dy = 0;
  this.visited = false;
  this.edge = new Array();
  this.moveTo = function(x,y) { this.x = x; this.y = y; }
  this.moveToD = function() { this.moveTo(this.x+this.dx,this.y+this.dy); }
  this.moveTo(x,y);
}

function getDiv(divname) {
  return document.getElementById(divname);
}

function DivNode(id, x, y, sx, sy) {
  this.div   = document.getElementById(id);
  this.style = this.div.style;
  this.style.display = "block";
  this.x  = -999;
  this.y  = -999;
  this.sx = sx;
  this.sy = sy;
  this.dx = 0;
  this.dy = 0;
  this.visited = false;
  this.edge = new Array();
  this.moveTo = function(x,y) {
    if ( x != this.x ) {
      this.x = x;
      this.style.left = (x - this.sx/2) + "px";
    }
    if ( y != this.y ) {
      this.y = y;
      this.style.top  = (y - this.sy/2) + "px";
    }
  }
  this.moveToD = function() {
    this.moveTo(this.x+this.dx,this.y+this.dy);
  }
  this.moveTo(x,y);
}

function Edge(n1, n2, len) {
  this.n1 = n1;
  this.n2 = n2;
  this.len = len;
  this.length = function() {
    var dx = this.n1.x - this.n2.x, dy = this.n1.y - this.n2.y;
    return Math.sqrt(dx*dx+dy*dy);
  }
}

function addNode(ix, iy) {
  return node[node.length] = new Node(Xpos+ix, Ypos+iy);
}

function addDivNode(id, sx, sy, ix, iy) {
  return node[node.length] = new DivNode(id, Xpos+ix, Ypos+iy, sx, sy);
}

function addEdge(n1, n2, len) {
  var e = edge[edge.length] = new Edge(n1,n2,len);
  n1.edge[n1.edge.length] = e;
  n2.edge[n2.edge.length] = e;
  return e;
}

function init() {
  started = true;
  makeGraph();
  setInterval(animate, 20);
}

var width, height;
var someMoved = true, oldXpos, oldYpos;

function visit(n) {
  var i;
  var num = n.edge.length;
  var fx = 0, fy = 0;
  for ( i = 0; i < num; i++ ) {
    var e = n.edge[i];
    var dx = e.n1.x - e.n2.x, dy = e.n1.y - e.n2.y;
    var len = Math.sqrt(dx*dx + dy*dy);
    if ( len > 0.01 ) {
      var s = ( n == e.n1 ? -1 : +1 );
      fx += s * SPRINGK * (len - e.len) * dx / len;
      fy += s * SPRINGK * (len - e.len) * dy / len;
    }
  }
  for ( i = 0; i < node.length; i++ ) {
    var m = node[i];
    if ( m != n ) {
      var dx = m.x - n.x, dy = m.y - n.y;
      var len = dx*dx + dy*dy;
      if ( len > 0.001 ) {
        fx -= REJECTF * dx / len;
        fy -= REJECTF * dy / len;
      }
    }
  }
  // compute new accel, including gravity & resistance
  var accelx = (fx - n.dx * RESISTANCE) / MASS;
  var accely = (fy - n.dy * RESISTANCE) / MASS + GRAVITY;
  // compute new velocity
  n.dx += DELTAT * accelx;
  n.dy += DELTAT * accely;
  // stop so it doesn't jitter when nearly still
  if (Math.abs(n.dx) < STOPVEL && Math.abs(n.dy) < STOPVEL &&
      Math.abs(accelx) < STOPACC && Math.abs(accely) < STOPACC) {
    n.dx = 0;
    n.dy = 0;
  } else {
    someMoved = true;
    // bounce off 3 walls (leave ceiling open)
    if (n.y+n.dy >= height-n.sy/2-1) {
      n.dy = -BOUNCE*Math.abs(n.dy);
      n.y = height-n.sy/2-1;
    }
    if (n.x+n.dx >= width-n.sx/2-1) {
      n.dx = -BOUNCE*Math.abs(n.dx);
      n.x = width-n.sx/2-1;
    }
    if (n.x+n.dx <= n.sx/2+1) {
      n.dx = BOUNCE*Math.abs(n.dx);
      n.x = n.sx/2+1;
    }
    // move to new position
    n.moveToD();
  }
}

function animate() {
  if ( ! someMoved && oldXpos == Xpos && oldYpos == Ypos ) return;
  var i;
  var toVisit = new Array();
  var toVisit1 = 0, toVisit2 = 0;
  var head = node[0];
  if (window.innerHeight) {
    height = window.innerHeight;
    width = window.innerWidth;
  } else if (document.documentElement.clientHeight == 0) {
    height = document.body.clientHeight;
    width = document.body.clientWidth;
  } else {
    height = document.documentElement.clientHeight;
    width = document.documentElement.clientWidth;
  }
  for ( i = 0; i < node.length; i++ ) node[i].visited = false;
  toVisit1 = 0; toVisit2 = 1; toVisit[0] = head;
  someMoved = false;
  while ( toVisit1 < toVisit2 ) {
    var n = toVisit[toVisit1++];
    if ( ! n.visited ) {
      n.visited = true;
      if ( n == head )
        n.moveTo(Xpos, Ypos);
      else
        visit(n);
      var num = n.edge.length;
      for ( i = 0; i < num; i++ ) {
        e = n.edge[i];
        toVisit[toVisit2++] = n == e.n1 ? e.n2 : e.n1;
      }
    }
  }
  oldXpos = Xpos; oldYpos = Ypos;
}

document.body.style.overflow = "hidden";
document.body.onmousemove =
  function(e) {
    if (!e) e = window.event;
    if (e) {
      if (e.pageX) {
        Xpos = e.pageX;
        Ypos = e.pageY;
      } else if (e.clientX) {
        Xpos = e.clientX + document.body.scrollLeft
                         + document.documentElement.scrollLeft;
        Ypos = e.clientY + document.body.scrollTop
                         + document.documentElement.scrollTop;
      }
      if (Xpos >= 0) {
        if (!started) init();
        return true;
      }
    }
  }
