Array.prototype.remove = function(from, to) {
  if (typeof from != "number") return this.remove(this.indexOf(from));
  var rest = this.slice((to || from) + 1 || this.length);
  this.length = from < 0 ? this.length + from : from;
  return this.push.apply(this, rest);
};

jQuery(function ($) {
  var r = new Raphael(document.getElementById("swarm"), 800, 600);
  var frameRate = 100;
  var speed = 1; // average # of comments per second
  var components = [];
  var authors = {};
  var dragObj = null;
  var dragStart, dragObjStart;
  
  function force(xDiff, yDiff, attenuation) {
    var d = Math.sqrt(Math.pow(xDiff, 2) + Math.pow(yDiff, 2));
    var m = Math.pow(d, -2);
    return {
      x: m * xDiff / d,
      y: m * yDiff / d
    };
  }
  
  function gravity(x, y) {
    return force((400 - x), (300 - y), 2);
  }
  function attract(x1, y1, x2, y2) {
    var xDiff = x2 - x1;
    var yDiff = y2 - y1;
    var d = Math.sqrt(Math.pow(xDiff, 2) + Math.pow(yDiff, 2));
    if (d < 100) return { x: 0, y: 0};
    var m = 1000 * Math.pow(d, -2);
    return {
      x: m * xDiff / d,
      y: m * yDiff / d
    };
  }
  function repulse(x1, y1, x2, y2) {
    var xDiff = x1 - x2;
    var yDiff = y1 - y2;
    var d = Math.sqrt(Math.pow(xDiff, 2) + Math.pow(yDiff, 2));
    if (d > 200) return { x: 0, y: 0 };
    var m = Math.pow(d, -2);
    return {
      x: m * xDiff / d,
      y: m * yDiff / d
    };
  }
  
  var Author = function Author(username) {
    var x = Math.random() * 700 + 50;
    var y = Math.random() * 500 + 50;
    var height = 48;
    var age = 0;
    var dx = 0, dy = 0;
    var outgoingLinks = [], incomingLinks = [];
    var img = r.image("profile-pics/" + username, x, y, height, height);
//    var dest = "http://extranet.atlassian.com/display/~" + username;
    img.attr("title", username);
    /* $(img[0]).click(function () {
      window.location = dest;
      return false;
    }); */
    var obj = {
      username: username,
      x: function () {
        return x;
      },
      y: function () {
        return y;
      },
      cx: function () {
        return x + height / 2;
      },
      cy: function () {
        return y + height / 2;
      },
      moveTo: function (nx, ny) {
        x = nx;
        y = ny;
        img[0].setAttribute("x", x);
        img[0].setAttribute("y", y);
        img[0].setAttribute("height", height);
        img[0].setAttribute("width", height);
        for (var l = 0; l<outgoingLinks.length; l++) {
          outgoingLinks[l].line[0].pathSegList.getItem(0).x = this.cx();
          outgoingLinks[l].line[0].pathSegList.getItem(0).y = this.cy();
        }
        for (var l = 0; l<incomingLinks.length; l++) {
          incomingLinks[l].line[0].pathSegList.getItem(1).x = this.cx();
          incomingLinks[l].line[0].pathSegList.getItem(1).y = this.cy();
        }
      },
      friction: function () {
        dx *= 0.99;
        dy *= 0.99;
      },
      linkFrom: function (link) {
        if (link.start === this) return;
        incomingLinks.push(link);
        img[0].parentNode.appendChild(img[0]);
        age = 0;
      },
      linkTo: function (other) {
        if (other === this) return;
        var line = r.path({stroke: "#aaa"}).moveTo(this.cx(), this.cy()).lineTo(other.cx(), other.cy());
        line[0].parentNode.insertBefore(line[0], line[0].parentNode.firstChild); // move to back
        var link = {
          start: this,
          end: other,
          line: line
        };
        outgoingLinks.push(link);
        other.linkFrom(link);
        img[0].parentNode.appendChild(img[0]);
        age = 0;
      },
      unlink: function (link) {
        if (this === link.start) {
          if (outgoingLinks.indexOf(link) >= 0) {
            outgoingLinks.remove(link);
          }
          link.end.unlink(link);
          $(link.line[0]).remove();
        } else {
          if (incomingLinks.indexOf(link) >= 0) {
            incomingLinks.remove(link);
          }
        }
      },
      remove: function () {
        while (outgoingLinks.length > 0) {
          outgoingLinks[0].start.unlink(outgoingLinks[0]);
        }
        while (incomingLinks.length > 0) {
          incomingLinks[0].start.unlink(incomingLinks[0]);
        }
        $(img[0]).remove();
        components.remove(this);
        delete authors[username];
      },
      age: function () {
        age++;
        height = Math.min(48, 48 / (age / 500));
        if (age > 2000) this.remove();
      },
      update: function () {
        var c = gravity(x, y);
        var ax = c.x, ay = c.y;
        for (var name in authors) {
          var other = authors[name];
          if (other === this) continue;
          var f = repulse(x, y, other.x(), other.y());
          ax += f.x * 10;
          ay += f.y * 10;
        }
        for (var l=0; l<outgoingLinks.length; l++) {
          var other = outgoingLinks[l].end;
          var f = attract(x, y, other.x(), other.y());
          ax += f.x;
          ay += f.y;
        }
        for (var l=0; l<incomingLinks.length; l++) {
          var other = incomingLinks[l].start;
          var f = attract(x, y, other.x(), other.y());
          ax += f.x;
          ay += f.y;
        }
        dx += ax;
        dy += ay;
        if (dx > 1) dx = 1;
        if (dx < -1) dx = -1;
        if (dy > 1) dy = 1;
        if (dy < -1) dy = -1;
        this.age();
        this.moveTo(x + dx, y + dy);
        this.friction();
      },
      draw: function () {}
    };
    $(img[0]).mousedown(function (e) {
      dragObj = obj;
      dragStart = { x: e.clientX, y: e.clientY };
      dragObjStart = { x: dragObj.x(), y: dragObj.y() };
      this.parentNode.appendChild(this); // bring to front
      return false;
    });
    return obj;
  };
  
  function getAuthor(username) {
    if (!(username in authors)) {
      authors[username] = new Author(username);
      components.push(authors[username]);
    }
    return authors[username];
  }

  function formatDate(date) {
    var d = date.getDate();
    d = "" + (d < 10 ? "0" : "") + d + " ";
    d += "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(/ /)[date.getMonth()] + " ";
    d += date.getFullYear();
    d = "Sun Mon Tue Wed Thu Fri Sat".split(/ /)[date.getDay()] + " " + d;
    return d;
  }
  
  function formatTime(date) {
    var h = date.getHours();
    var ampm = h >= 12 ? "PM" : "AM";
    var t = "" + (h == 0 ? 12 : (h > 12 ? h - 12 : h)) + ":";
    var m = date.getMinutes();
    t += "" + (m < 10 ? "0" : "") + m + " " + ampm;
    return t;
  }

  var Swarm = function Swarm(comments, pages) {
    var mindate = comments[0].date.getTime();
    var maxdate = comments[comments.length - 1].date.getTime();
    var frames = comments.length * frameRate / speed;
    var datedelta = (maxdate - mindate) / frames;
    var frame = 0;
    return {
      update: function () {
      },
      draw: function () {
        frame++;
        var d = mindate + frame * datedelta;
        while (comments.length > 0 && comments[0].date.getTime() <= d) {
          var c = comments.shift();
          getAuthor(c.author).linkTo(getAuthor(pages[c.page].author));
        }
        if (frame % 5 == 0)
          $("#date").html(formatDate(new Date(d)) + "<br/>" + formatTime(new Date(d)));
      }
    };
  };
  
  function animate(frameRate, pause, pauseMessage) {
    var interval;
    var runner = function () {
      for (var i=0; i<components.length; i++) {
        components[i].update();
      }
      for (var i=0; i<components.length; i++) {
        components[i].draw();
      }
    };
    interval = window.setInterval(runner, Math.floor(1000 / frameRate));
    $(pause).click(function (e) {
      if (e.target.constructor == window.SVGImageElement)
        return;
      if (interval != null) {
        window.clearInterval(interval);
        interval = null;
        $(pauseMessage).fadeIn();
      } else {
        interval = window.setInterval(runner, Math.floor(1000 / frameRate));
        $(pauseMessage).fadeOut();
      }
    });
  }

  function parseDate(isoDateString) {
    var d = isoDateString.split(/[: -]/);
    return new Date(Date.UTC(d[0], d[1] - 1, d[2], d[3], d[4], d[5]));
  }
  
  $("#swarm").mousemove(function (e) {
    if (dragObj != null) {
      var x = dragObjStart.x + e.clientX - dragStart.x;
      var y = dragObjStart.y + e.clientY - dragStart.y;
      dragObj.moveTo(x, y);
    }
  });
  $("#swarm").mouseup(function (e) {
    dragObj = null;
  });
  
  
  $.getJSON("data.js", function (data) {
    var comments = [];
    var pages = {};
    $.each(data, function (i, comment) { 
      comments.push({ 
        id: comment.commentid, 
        author: comment.commentauthor,
        date: parseDate(comment.commentdate), 
        page: comment.pageid });
      pages[comment.pageid] = {
        title: comment.pagetitle,
        date: parseDate(comment.pagedate),
        author: comment.pageauthor
      };
    });
    
    components.push(new Swarm(comments.reverse(), pages));
    animate(frameRate, $("#swarm"), $("#pause-message"));
  });
});
