/*@cc_on
	if(!Array.indexOf) {
		Array.prototype.indexOf = function(obj) {
			for(var i = 0; i < this.length; i++) if(this[i] == obj) return i;
			return -1;
		}
	}
@*/

function Clone() {}
function clone(obj) {
	Clone.prototype = obj;
	return new Clone();
}

var Agent = Base.extend({
	constructor: function() {
		this.value = {};
	},
	begin: function() {
		this.workingset = clone(this.value);
		return this;
	},
	cancel: function() {
		delete this.workingset;
		return this;
	},
	commit: function() {
		var workingset = this.workingset, value = this.value, i;
		for(i in workingset) value[i] = workingset[i];
		return this;
	}
});

var Particle = Agent.extend({
	constructor: function() {
		this.value = {
			px:	0,
			py: 0,
			vx: 0,
			vy: 0,
			fx: 0,
			fy: 0,
		};
	},
	begin: function() {
		this.base();
		this.workingset.fx = this.workingset.fy = 0;
		return this;
	}
});

var RaphaelParticle = Particle.extend({
	freeze: false,
	constructor: function(el) {
		this.base();
		this.el = el;
		el.particle = this;
		this.sync();
	},
	sync: function() {
		return this;
	}
});

var RaphaelBoxParticle = RaphaelParticle.extend({
	sync: function() {
		var bb = this.el.getBBox();
		this.value.px = this.el.attr('x')+bb.width/2;
		this.value.py = this.el.attr('y')+bb.height/2;
		return this;
	},
	commit: function() {
		if(this.freeze) return this.cancel();
		var bb = this.el.getBBox();
		this.el.attr({x:this.workingset.px-bb.width/2,y:this.workingset.py-bb.height/2});
		return this.base();
	}
});

var RaphaelPointParticle = RaphaelParticle.extend({
	sync: function() {
		this.value.px = this.el.attr('x');
		this.value.py = this.el.attr('y');
		return this;
	},
	commit: function() {
		if(this.freeze) return this.cancel();
		this.el.attr({x:this.workingset.px,y:this.workingset.py});
		return this.base();
	}
});

var RaphaelRoundParticle = RaphaelParticle.extend({
	sync: function() {
		this.value.px = this.el.attr('cx');
		this.value.py = this.el.attr('cy');
		return this;
	},
	commit: function() {
		if(this.freeze) return this.cancel();
		this.el.attr({cx:this.workingset.px,cy:this.workingset.py});
		return this.base();
	}
});

var Interaction = Base.extend({
	constructor: function() {
		this.particle = [];
	},
	add: function(particle) {
		if(this.particle.indexOf(particle) < 0) this.particle.push(particle);
		return this;
	},
	remove: function(particle) {
		var i = this.particle.indexOf(particle);
		if(i >= 0) this.particle.splice(i,1);
		return this;
	},
	indexOf: function(particle) {
		return this.particle.indexOf(particle);
	},
	process: function(dt) {
		return this;
	}
});

var RaphaelInteraction = Interaction.extend({
	setPath: function(path) {
		this.path = path;
		return this;
	},
	show: function() {
		return this;
	}
});

var Spring = RaphaelInteraction.extend({
	constructor: function(factor,len) {
		this.base();
		this.factor = factor;
		this.len = len;
	},
	add: function(particle) {
		this.base(particle);
		while(this.particle.length > 2) this.particle.shift();
		return this;
	},
	process: function() {
		var factor = this.factor, ni = this.particle[0].workingset, nj = this.particle[1].workingset;
		var dx = ni.px-nj.px, dy = ni.py-nj.py;
		if(this.len != 0) {
			var dlen = (Math.sqrt(dx*dx+dy*dy)-this.len)/this.len;
			dx *= dlen;
			dy *= dlen;
		}
		f = factor*dx;
		ni.fx -= f;
		nj.fx += f;
		f = factor*dy;
		ni.fy -= f;
		nj.fy += f;
		return this;
	},
	show: function() {
		var ni = this.particle[0].value, nj = this.particle[1].value;
		this.path.attr('path','M'+ni.px+' '+ni.py+'L'+nj.px+' '+nj.py).toBack();
		return this;
	}
});

var Magnet = RaphaelInteraction.extend({
	constructor: function(factor) {
		this.base();
		this.factor = factor;
	},
	process: function() {
		var factor = this.factor, particle = this.particle, ni, nj, fx, fy;
		for(var i = particle.length-1; i > 0; i--) {	// go from end to 1
			ni = particle[i].workingset;
			for(var j = 0; j < i; j++) {			// go from 0 to i-1
				nj = particle[j].workingset;
				r3 = Math.pow(Math.pow(ni.px-nj.px,2)+Math.pow(ni.py-nj.py,2),3/2);
				if(r3 > 0.001) {
					fx = factor*(ni.px-nj.px)/r3
					fy = factor*(ni.py-nj.py)/r3
				} else {
					var angle = 2*Math.PI*Math.random();
					fx = factor*Math.cos(angle);
					fy = factor*Math.sin(angle);
				}
				ni.fx += fx;
				ni.fy += fy;
				nj.fx -= fx;
				nj.fy -= fy;
			}
		}
		return this;
	}
});

var Nucleus = RaphaelInteraction.extend({
	constructor: function(factor,cx,cy) {
		this.base();
		this.factor = factor;
		this.setCenter(cx,cy);
	},
	setCenter: function(cx,cy) {
		this.cx = cx;
		this.cy = cy;
	},
	process: function() {
		var factor = this.factor, ni;
		for(var i in this.particle) {
			ni = this.particle[i].workingset;
			ni.fx -= factor*(ni.px-this.cx);
			ni.fy -= factor*(ni.py-this.cy);
		}
		return this;
	},
	show: function() {
		var path = [], ni;
		for(var i in this.particle) {
			ni = this.particle[i].value;
			path.push('M'+this.cx+' '+this.cy+'L'+ni.px+' '+ni.py)
		}
		this.path.attr('path',path.join(''))
		this.path.toBack();
		return this;
	}
});

var Wall = RaphaelInteraction.extend({
	constructor: function(factor,xmin,ymin,xmax,ymax) {
		this.base();
		this.factor = factor;
		this.setWall(xmin,ymin,xmax,ymax);
	},
	setWall: function(xmin,ymin,xmax,ymax) {
		this.xmin = xmin;
		this.ymin = ymin;
		this.xmax = xmax;
		this.ymax = ymax;
	},
	process: function() {
		var factor = this.factor, particle = this.particle, ni, r;
		var xmin = this.xmin, ymin = this.ymin, xmax = this.xmax, ymax = this.ymax;
		for(var i = 0; i < particle.length; i++) {
			ni = particle[i].workingset;
			r = ni.px-xmin;
			if(r < 0) ni.fx -= factor*r;
			r = ni.py-ymin;
			if(r < 0) ni.fy -= factor*r;
			r = ni.px-xmax;
			if(r > 0) ni.fx -= factor*r;
			r = ni.py-ymax;
			if(r > 0) ni.fy -= factor*r;
		}
		return this;
	}
});

var ForceLayout = Base.extend({
	energy: 0,
	denergy: 0,
	constructor: function(paper) {
		this.paper = paper;
		this.node = {};
		this.globalinteraction = [];
		this.interaction = [];
		this.kick();
	},
	kick: function() {
		this.denergy = Infinity;
	},
	manage: function(node) {
		if(node.uid == undefined) node.uid = uid();
		this.node[node.uid] = node;
		for(var i in this.globalinteraction) this.globalinteraction[i].add(node);
		return this;
	},
	interact: function(interaction,global) {
		this.interaction.push(interaction);
		if(global) {
			this.globalinteraction.push(interaction);
			for(var k in this.node) interaction.add(this.node[k]);
		}
	},
	begin: function() {
		var node = this.node;
		for(var k in node) node[k].begin();
		return this;
	},
	cancel: function() {
		var node = this.node;
		for(var k in node) node[k].cancel();
		return this;
	},
	commit: function() {
		var node = this.node;
		for(var k in node) node[k].commit();
		return this;
	},
	process: function(timestep,damping) {
		var energy = 0, nodes = 0, particle;
		for(var i in this.interaction) this.interaction[i].process();
		for(var k in this.node) {
			particle = this.node[k].workingset;
			particle.vx = (particle.vx+timestep*particle.fx)*damping;
			particle.vy = (particle.vy+timestep*particle.fy)*damping;
			particle.px += timestep*particle.vx;
			particle.py += timestep*particle.vy;
			energy += particle.vx*particle.vx+particle.vy*particle.vy;
			nodes++
		}
		this.denergy = Math.abs(energy-this.energy)/nodes;
		this.energy = energy;
		return this;
	},
	show: function() {
		for(var i in this.interaction) this.interaction[i].show();
		return this;
	}
});
