var Unlocker = new Class({
	Implements: [Events,Options],
	options: {
		events: {
		}
	},
	ctx: undefined,
	canvas: undefined,
	canvasSize: undefined,
	drawables: [],
	dotgrid: [],
	line: undefined,
	lastDot: undefined,
	disabled: false,
	ajax: undefined,
	initialize: function(canvas, options) {
		this.setOptions(options);
		this.addEvents(options.events);
		this.canvas = canvas;
		this.ctx = canvas.getContext('2d');
		this.canvasSize = canvas.getSize();
		
		this.ajax = new Request.JSON({url: 'checker.php', link: 'cancel' });
		this.ajax.addEvent('success', function(response) {
			if ($type(response) == 'object' && typeof response.page != 'undefined' && typeof response.token != 'undefined') {
				this.fireEvent('update', { msg: 'Unlocked! Forwarding To Page...', url: response.page+'?'+response.token });
			} else {
				this.fireEvent('update', { msg: 'Incorrect.', error: true });
				this.setError();
				this.redraw();
			}
		}.bindWithEvent(this));
		
		var line = this.line = new UnlockerLine();
		
		var dotgrid = [];
		var dotgridPoses = [];
		for(var i=0; i < 3; i++) {
			for(var j=0; j < 3; j++) {
				var pos = {x: 75 + (i*100), y: 75 + (j*100) };
				dotgridPoses.push(pos);
				dotgrid.push(new UnlockerDot({pos: pos}));
			}
		}
		this.dotgrid = dotgrid;

		this.add(dotgrid);
		this.add(line);
		
		this.setupMouseHandler();
		
		this.redraw();
		this.fireEvent('update', { msg: 'Connect Some Dots.' });
	},
	getHash: function() {
		var str = '';
		this.dotgrid.each(function(dot) {
			str += dot.toString();
		});
		return MD5(SHA1(str));
	},
	addEvents: function(events) {
		for (var name in events) {
			this.addEvent(name, events[name]);
		}
	},
	setupMouseHandler: function() {
		var canvas = this.canvas;
		var epos;
		epos = canvas.getPosition();
		window.addEvent('resize', function() {
			epos = canvas.getPosition();
		});
		
		var lastOver = null;
		var currentOver = null;
		var UP = 1;
		var DOWN = 2;
		var mouse = UP;
		canvas.addEvent('mousedown', function(event) {
			mouse = DOWN;
			if (this.disabled)
				return;
			if (currentOver != null && !currentOver.isActive()) {
				this.addActiveDot(currentOver);
				this.redraw();
			}
		}.bindWithEvent(this));
		$(document.body).addEvent('mouseup', function(event) {
			mouse = UP;
			if (this.disabled)
				return;
			this.line.setCurrentPos(null);
			
			var length = this.line.getLength();
			if (length > 0) {
				if (length < 4) {
					this.fireEvent('update', { msg: 'Not Enough Dots Connected.', error: true })
					this.setError();
				} else {
					this.fireEvent('update', { msg: 'Checking...' });
					this.disabled = true;
					//console.log(this.getHash());

					this.ajax.send({data:{h:this.getHash()}})
				}				
			}
			
			this.redraw();
		}.bindWithEvent(this));
		$(document.body).addEvent('mousemove', function(event){
			var mpos = event.client;
			var pos = { x: mpos.x - epos.x, y: mpos.y - epos.y };
			var e = this.getElementAt(pos);
			currentOver = e;
			if (this.disabled)
				return;

			if (e != lastOver) {
				if (mouse == UP) {
					lastOver && !lastOver.isActive() && lastOver.setState(UnlockerDot.State.Normal);
					e && !e.isActive() && e.setState(UnlockerDot.State.Circle);
				}
				if (mouse == DOWN) {
					if (lastOver == null && e != null && !e.isActive()) {
						this.addActiveDot(e);
					}
				}
				lastOver = e;
			}
			
			if (mouse == DOWN)
				this.line.setCurrentPos(pos);
			
			this.redraw();
		}.bindWithEvent(this));
	},
	setError: function() {
		this.dotgrid.each(function(dot) {
			dot.setError(true);
		});
		this.disabled = true;
		(function() {
			this.reset();
		}.bind(this)).delay(3000);
	},
	reset: function() {
		this.dotgrid.each(function(dot) {
			dot.setError(false);
			dot.setState(UnlockerDot.State.Normal);
			dot.setDirection(null);
		});	
		this.line.clear();
		this.disabled = false;
		this.lastDot = undefined;
		this.redraw();
		this.fireEvent('update', { msg: 'Connect Some Dots.' });
	},
	getAngle: function(v) {
		var result = Math.atan(v.y/v.x);
		if (v.x < 0)
			result += Math.PI;
		return result;
	},
	addActiveDot: function(dot) {
		dot.setState(UnlockerDot.State.Active)
		this.line.addPos(dot.options.pos);
		
		if (this.lastDot) {
			var thisPos = dot.options.pos;
			var lastPos = this.lastDot.options.pos;
			var v = { x: thisPos.x - lastPos.x, y: thisPos.y - lastPos.y };
			
			this.lastDot.setDirection(this.getAngle(v));
		}
		
		this.lastDot = dot;
		
		var length = this.line.getLength();
		this.fireEvent('update', { msg: length + ' Dot' + (length == 1 ? '' : 's') + ' connected.' });
	},
	getElementAt: function(pos) {
		var a = this.drawables;
		for (var i = a.length - 1; i >= 0; i--) {
			var e = a[i];
			if (e.clip(pos))
				return e;
		}
		return null;
	},
	add: function(drawable) {
		if ($type(drawable) == 'array')
			drawable.each(function(e) {
				this.drawables.push(e);
			}.bind(this));
		else
			this.drawables.push(drawable);
	},
	redraw: function() {
		var ctx = this.ctx;
		
		ctx.globalCompositeOperation = 'destination-over';
		ctx.clearRect(0,0,this.canvasSize.x,this.canvasSize.y);
		ctx.save();
		
		this.drawables.each(function(e) {
			if (e.draw)
				e.draw(ctx);
		});
		
		ctx.restore();
	}
});

var Drawable = new Class({
	Implements: [Options],
	options: {},
	initialize: function(options) {
		this.setOptions(options);
	},
	draw: function(ctx) {
	},
	clip: function(pos) {
		return false;
	}
});

var UnlockerLine = new Class({
	Extends: Drawable,
	poses: [],
	currentPos: undefined,
	initialize: function(options) {
		this.parent(options);
	},
	draw: function(ctx) {
		ctx.save();
		
		ctx.lineWidth = 20;
		ctx.strokeStyle = '#555';
		ctx.lineCap = 'round';
		
		ctx.beginPath();
		var poses = this.poses;
		
		var l = poses.length
		if (l >= 1) {
			
			ctx.moveTo(poses[0].x, poses[0].y);
			
			for (var i = 1; i < l; i++) {
				ctx.lineTo(poses[i].x, poses[i].y);
				ctx.stroke();
				ctx.beginPath();
				ctx.moveTo(poses[i].x, poses[i].y);
			}
			
			if (this.currentPos && l < 9) {
				ctx.lineTo(this.currentPos.x, this.currentPos.y);
				ctx.stroke();
			}
		}
		
		ctx.restore();
	},
	addPos: function(pos) {
		this.poses.push(pos);
	},
	setCurrentPos: function(pos) {
		this.currentPos = pos;
	},
	getLength: function() {
		return this.poses.length;
	},
	clear: function() {
		this.poses = [];
	}
});

var UnlockerDot = new Class({
	Extends: Drawable,
	defaultSize: 40,
	circlePercent: 1.8,
	state: 0,
	error: false,
	dir: null,
	initialize: function(options) {
		this.parent(options);
	},
	draw: function(ctx) {
		ctx.save();		

		ctx.translate(this.options.pos.x , this.options.pos.y);

		if (this.dir != null && this.state == 2)
			this.drawDir(ctx);
		if (this.state == 1 || this.state == 2)
			this.drawCircle(ctx);
		
		this.drawDot(ctx);
		
		ctx.restore();
	},
	drawDot: function(ctx) {
		var size = this.getSize();
		if (this.state == 2) {
			size *= 0.75;
		}
		var halfsize = size/2;
		
		var radgrad = ctx.createRadialGradient(size/3,size/3,size/8,
				size/2,size/2,size/2);
		radgrad.addColorStop(0, '#ddd');
		radgrad.addColorStop(0.9, '#555');
		radgrad.addColorStop(1, 'rgba(1,0,0,0)');
		
		ctx.save();
		
		ctx.fillStyle = radgrad;
		
		ctx.translate(-halfsize,-halfsize);		
		ctx.fillRect(0,0,size,size);
		ctx.restore();
		
	},
	drawCircle: function(ctx) {
		var halfsize = this.getSize()/2;
		var rad = halfsize*this.circlePercent;
		ctx.beginPath();
		ctx.lineWidth = 5;
		ctx.strokeStyle = this.error ? '#f33' : '#af0';
		ctx.arc(0,0,rad, 0, 2*Math.PI, true);
		
		if (this.state == 2) {
			var radgrad = ctx.createRadialGradient(0,0,0,
					0,0,rad);
			radgrad.addColorStop(0, 'rgba(0,0,0,0)');
			radgrad.addColorStop(1, 'rgba(0,0,0,0.9)');
			ctx.fillStyle = radgrad;
			ctx.fill();
		}
		
		ctx.stroke();	
	},
	drawDir: function(ctx) {
		
		var size = this.getSize()*this.circlePercent/2;
		
		ctx.save();
		
		ctx.rotate(this.dir);
		
		ctx.fillStyle = this.error ? '#f33' : '#af0';
		
		ctx.beginPath();
		ctx.moveTo(size-5, 0);
		ctx.lineTo(size-12,7);
		ctx.lineTo(size-12,-7);
		ctx.fill();
		
		ctx.restore();		
	},
	clip: function(pos) {
		var dist = Math.sqrt(
				Math.pow(pos.x - this.options.pos.x,2) + 
				Math.pow(pos.y - this.options.pos.y,2)
			);
		if (this.state == 1 || this.state == 2) {
			return dist < this.getSize()*this.circlePercent/2
		}
		return dist < this.getSize()/2;
	},
	getSize: function() {
		var size = this.options.size;
		return size ? size : this.defaultSize;
	},
	setDirection: function(dir) {
		this.dir = dir;
	},
	setState: function(state) {
		this.state = state;
	},
	isActive: function() {
		return this.state == 2;
	},
	setError: function(bool) {
		this.error = bool;
	},
	toString: function() {
		return (this.state == 2 ? '1' : '0')+','+(this.dir ? this.dir : 'null');
	}
});
UnlockerDot.State = { Normal: 0, Circle: 1, Active: 2 };

window.addEvent('domready', function() {
	var canvas = $('unlock');
	var status = $('status');
	var ctx;
	if (canvas.getContext) {
		var unlock = new Unlocker(canvas, { events: {
			update: function(arg) {
				status.set('text', arg.msg);
				
				if (arg.error) {
					status.addClass('error');
				} else {
					status.removeClass('error');
				}
				
				if (arg.url) {
					location.href = arg.url;
				}
			}
		}});
		
	}
});