// Extracted from Tweet Nodes and The Lightweight Visual Thesaurus
// by Dave Hoover in April 2008
// version 0.0.1
//
// Updated to use prototype.js version 1.6
// by Dave Hoover in May 2008
// version 0.0.2

var World = Class.create({
	initialize: function(element) {
		this.element = element
		this.things = new Array()
	},

	add: function(thing) {
		this.things.push(thing)
	},

	isInBounds: function(position) {
		return position.x < this.element.width && position.y < this.element.height
	},

	userClicked: function(position) {
		if (this.isInBounds(position)) {
			for (var i = 0; i < this.things.length; i++) {
				var thing = this.things[i]
				if (thing.intersects(position.x, position.y)) {
					thing.onDown(position)
				}
			}
		}
	},

	userReleased: function(position) {
		for (var i = 0; i < this.things.length; i++) {
			this.things[i].onUp(position)
		}
	},

	userMovedTo: function(position, callback) {
// Global variable
		ctx.clearRect(0, 0, this.element.width, this.element.height)
		for (var i = 0; i < this.things.length; i++) {
			var thing = this.things[i]
			thing.onOver(position, callback)
			thing.show()
		}
	},

	doubleClick: function(position, callback) {
		if (this.isInBounds(position)) {
			for (var i = 0; i < this.things.length; i++) {
				var thing = this.things[i]
				if (thing.intersects(position.x, position.y)) {
					callback(thing)
				}
			}
		}
	},

	draw: function() {
// Global variable
		ctx.clearRect(0, 0, this.element.width, this.element.height)
		for (var i = 0; i < this.things.length; i++) {
			this.things[i].situate()
			this.things[i].show()
		}
	},

	reset: function() {
// Global variable
		ctx.clearRect(0, 0, this.element.width, this.element.height)
		this.things = new Array()
	}
})

var Node = Class.create({
	initialize: function(word, url, context, position, color, radius, font) {
		this.word = word
		this.url = url 
		this.context = context
		this.position = position
		this.radius = radius
		this.fillStyle = this.originalFillStyle = color
		this.repellants = new Array()
		this.edges = new Array()
		this.move = 3
		this.font = font
	},

	onDown: function(position) {
		this.dragging = position
		this.fillStyle = 'yellow'
		this.show()
	},

	onUp: function(position) {
		this.dragging = undefined
		this.fillStyle = this.originalFillStyle
		this.situate()
		this.show()
	},

	onOver: function(position, callback) {
		if (this.intersects(position.x, position.y)) {
			this.rolloverContent(callback)
			this.fillStyle = "orange"
			this.showingWordAsLabel = true
		} else {
			this.fillStyle = this.originalFillStyle
			this.showingWordAsLabel = false
		}

		if (this.dragging != undefined) {
			deltaX = position.x - this.dragging.x
			deltaY = position.y - this.dragging.y
			this.position.x += deltaX
			this.position.y += deltaY

			this.dragging = position
		} else {
			this.situate()
		}
	},

	rolloverContent: function(callback) {
		// do nothing
	},
	
	loadImage: function(src) {
		if (!this.image) {
			this.image = new Image()
			this.image.src = src
			this.image.width = 48
			this.image.height = 48
		}
	},

	distanceFrom: function(other) {
		var x = other.position.x - this.position.x
		var y = other.position.y - this.position.y
		return Math.sqrt(x * x + y * y)
	},

	hasCollisionWith: function(other) {
		return this.intersectsWith(other) || this.distanceFrom(other) <= (this.radius + other.radius)
	},

	intersectsWith: function(other) {
                if (this.image) {
			var widthFromMiddle = (this.image.width / 2) / 2
                        var rightX = this.position.x + widthFromMiddle
                        var leftX = this.position.x - widthFromMiddle

			var heightFromMiddle = (this.image.height/ 2) / 2
                        var bottomY = this.position.y + heightFromMiddle
                        var topY = this.position.y - heightFromMiddle

			if (other.position.x > leftX && other.position.x < rightX && other.position.y > topY && other.position.y < bottomY) {
				return true;
			}
		}
		return this.distanceFrom(other) <= this.radius
	},

	intersects: function(x, y) {
		return this.intersectsWith({position: {x: x, y: y}})
	},

	moveAwayFrom: function(r, multiplier) {
		var move = this.move * (multiplier || 1)
		if (r.position.x < this.position.x) {
			this.position.x += move
		} else if (r.position.x > this.position.x) {
			this.position.x -= move
		} else {
			var chance = Math.random()
			if (chance > .5) {
				this.position.x += move
			} else {
				this.position.x -= move
			}
		}
		if (r.position.y < this.position.y) {
			this.position.y += move
		} else if (r.position.y > this.position.y) {
			this.position.y -= move
		} else {
			var chance = Math.random()
			if (chance > .5) {
				this.position.y += move
			} else {
				this.position.y -= move
			}
		}
	},

	situate: function() {
		if (this.dragging != undefined) {
			return
		}

		for (var i = 0; i < this.repellants.length; i++) {
			var r = this.repellants[i]
// Global: repelLimit
			if (this.distanceFrom(r) < repelLimit) {
				this.moveAwayFrom(r)
			}
		}

		for (var i = 0; i < this.edges.length; i++) {
			var r = this.edges[i].r
			this.moveAwayFrom(r)
		}

		for (var i = 0; i < this.edges.length; i++) {
			var maxDistance = this.edges[i].d
			var r = this.edges[i].r
			while (this.distanceFrom(r) > maxDistance) {
				if (r.position.x < this.position.x) {
					this.position.x--
				} else if (r.position.x > this.position.x) {
					this.position.x++
				}

				if (r.position.y < this.position.y) {
					this.position.y--
				} else if (r.position.y > this.position.y) {
					this.position.y++
				}
			}
		}

		for (var i = 0; i < this.repellants.length; i++) {
			var r = this.repellants[i]
			while (this.hasCollisionWith(r)) {
				this.moveAwayFrom(r)
			}
		}
	},

	show: function() {
		// To ensure that lines are always behind
		this.context.globalCompositeOperation = 'destination-over'
		for (var i = 0; i < this.edges.length; i++) {
			var other = this.edges[i].r
			this.context.strokeStyle = 'grey'
			this.context.lineWidth = 1.0
			this.context.beginPath()
			this.context.moveTo(this.position.x, this.position.y)
			this.context.lineTo(other.position.x, other.position.y)
			this.context.closePath()
			this.context.stroke()
		}
		this.context.globalCompositeOperation = 'source-over'
		this.draw()
	},

	draw: function() {
		if (this.image) {
			var width = this.image.width / 2
			var height = this.image.height / 2
			this.context.drawImage(this.image, this.position.x - (width / 2), this.position.y - (height / 2), width, height)
			if (this.showingWordAsLabel) {
				drawString(this.context, this.word, this.font, this.position.x - (width / 2), this.position.y - height)
			}
		} else {
			drawString(this.context, this.word, this.font, this.position.x + 2, this.position.y)
			this.context.fillStyle = this.fillStyle
			this.context.arc(this.position.x, this.position.y, 3, 0, Math.PI*2, true)
			this.context.fill()
		}
	},

	addRepellant: function(r) {
		if (r == this) {
			return
		}
		for (var i = 0; i < this.repellants.length; i++) {
			if (r == this.repellants[i]) {
				return
			}
		}
		this.repellants[this.repellants.length] = r
	},

	addEdge: function(r, maxDistance) {
		this.edges[this.edges.length] =  { r: r, d: maxDistance }
	}
})

var Sense = Class.create(Node, {
	draw: function() {
		this.context.fillStyle = this.fillStyle
		this.context.arc(this.position.x, this.position.y, this.radius, 0, Math.PI*2, true)
		this.context.fill()
	},
	rolloverContent: function(callback) {
		callback(this.word)
    	}
})


// From Benjamin Joffe, http://www.random.abrahamjoffe.com.au/public/JavaScripts/canvas/fonts.htm
var tahoma8=new Image();
tahoma8.src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAikAAAANAgMAAAARXxaEAAAABlBMVEX///8AAABVwtN+AAACCklEQVR4Xu3UwaokKxgD4ATiPkL7PhHsvQf0/V/lgl1F02fuYZjF7CabsqQWX4Vf8UP+RQCMAgIi/kZyPxUAawV5bdKEcn3zthiEBz+Idx5llq9V1s62VnPFfEw3Ls6W9lyptCtdNxrSOFlWKatxuixU5CEIU8O+RJdFuiwl/maBfrBIJoUQlul+LOmU7CC3xUR6MlQEQxmyicrVhUpxflp4WzrworQyW5mrla9VR22PltWya3kOZXgoz9mlEAPRsVRijFFRab4tHoqQBFEVAmfIffY90o10aUTHYgPEy0KgYF4WDMXXj43ufsrmw5Iy3J24lxniOfdAW+bjw9KQeGjEipGEmL3MDTfJwxnKw4RnEmGvXywTBUEh0I5lH8uTt8UPSVUjqci7l2YdS1evqMtsQW4LcSyCdpQkfR0LJ3pABdECIuNY9prsZ4UOuAM5FnzrxRy3hdzjttAy622B6bdlEK8XiDiWyMPPjYFhkMDdC9694PRCwI/bwpel3hZjSCJpiRj37PKa3SgzHZKNJDZPgCMQpXQkRt80BmWQwT0vJr/NyyvteWbX5Ysexd1eLduNfYmUVL7wfJ3pYFtrua4osxGc1kZaLstG49x9krMRjbFBl0kGWIZ9LDKptwUdxGcM5F7xY/vnGL9JmQCCK28LhOt++b+UgPhTS/mjexfXvasIKhugPyz/AfyHcZZwWdfAAAAAAElFTkSuQmCC';
tahoma8.c='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789!@#$%^&*()-=[]\\;\',./_+{}|:"<>?`~';
tahoma8.w=[6,6,5,6,6,4,6,6,2,3,5,2,8,6,6,6,6,4,5,4,6,6,8,6,6,5,7,6,7,7,6,6,7,7,4,5,6,5,8,7,8,6,8,7,6,6,7,6,10,6,6,6,3,6,6,6,6,6,6,6,6,6,6,4,10,8,6,11,8,7,6,4,4,4,8,4,4,4,4,2,4,4,4,6,8,5,5,4,4,4,8,8,5,6,8];
tahoma8.h=13;

var tahoma8bold=new Image();
tahoma8bold.src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoEAAAANAgMAAACr0FyhAAAABlBMVEX///8AAABVwtN+AAACSUlEQVR4Xu3VQYrjQAwF0C+w9m6Q7qOC1P4LSve/yiA7cUK6p2mGmd38hVMKmHpIdhl/If8jAYAQwrpQ/OsE7jFkrzVXwIDt0CRBZFy0p1ABAzP0XX/FpaScnqVl6mMxozcZpUuXU1dl5JiqZswKpaJkYXeWltJHVMiGcAFCKDaCLYRdwi4uoYOfhJBvhWPCzCQMpgrLES7JKVNNzQwql5ASZn2HgiaBQz2wy6b9K7saSh7CHQbsgDyFJEag46NkOUtHOTPH9GTF0ZbUXMwxrbd15mihPoUl4TPXZ6HSQ8NMIaFyCK2FN7952S1v2DY1sQmD1oTtilehNVuv/uSY4DWc2VsdpVmZxCUEc5TC1LMM3ssl8bHSH0KnSrBkmoSHo4WKtdFHwZTMW7Yw8rZ2EdKag0ZtLYxXIQEq4AL4Kay7cL0I6SPIUmPJknjpoZuewp2pU+Yydeo6hbUkFOsQGnQncoSZeQu3XekfE1CL5tQpxCmstaDBpgKbAWMAPIWhXwg1D2G1UKXyEupjyjtSphx/E3YKuZQqPISE+mohzdDCWmI4hAroOeVLeD2HZw8NiPVJOF+FaKHJGL2DxCG0pzBHC3EX2ik0kNYrHFdVkHchN2OtD8NooQSuKYsiB96fw3s8zzdlSp20FvJ4U+ix/CH0jLyfNqFl6pPMauEohZ5zRsVDWKH0UWOBHgV2NYA9lEdnZBES/J0QJAxvaTRfCns7dr4N8ZNU7CG4cgkxwQ3P8/DryDogfyh0/DQvwuubghtiQ7Ug+Sb8BYH3sjwcbInrAAAAAElFTkSuQmCC';
tahoma8bold.c='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789!@#$%^&*()-=[]\\;\',./_+{}|:"<>?`~';
tahoma8bold.w=[7,7,6,7,7,4,7,7,3,4,7,3,11,7,7,7,7,5,6,5,7,7,9,7,7,6,8,7,7,8,6,6,8,8,5,6,7,6,10,7,8,7,8,8,7,7,8,7,11,7,7,7,3,7,7,7,7,7,7,7,7,7,7,3,10,9,7,13,9,9,7,5,5,5,9,5,5,6,3,3,3,3,6,7,9,7,7,7,3,6,9,9,6,6,10];
tahoma8bold.h=15;

function drawString(context, s, f, x, y){
	y=Math.round(y);
	var z=x=Math.round(x),t,i,j;
	if(!f.f){
		f.f=[t=0],i=0,j=f.w.length;
		while(++i<j)f.f[i]=t+=f.w[i-1];
	}
	s=s.split(''),i=0,j=s.length;
	while(i<j)if((t=f.c.indexOf(s[i++]))>=0)
		context.drawImage(f,f.f[t],0,f.w[t],f.height,x,y,f.w[t],f.height),x+=f.w[t];
		else if(s[i-1]=='\n')x=z,y+=f.h;
}
// End Joffe: Thanks Benjamin!

