/*
  Title:	Thirteenth Parallel Toolkit - Drawing Library - ALPHA Version
  Description:	Provides functionality for animating elements

  Creator:      Dan Pupius <http://pupius.co.uk>
  Publisher:	Thirteenth Parallel <http://13thparallel.org>
  Date:         2005-02-02
  Rights:       Copyright (c)2002-2005 Thirteenth Parallel
                You are free to use the scripts for non-commercial projects,
		as long as you leave these copyright statements intact.
*/


Toolkit.Drawing = {}
Toolkit.Math = {
	degToRad: function(x) { return (x*Math.PI) / 180; },
	radToDeg: function(x) { return (angle*180) / Math.PI; },

	factorial: function(n) {
		if(n<1) { return 0; }
		var retVal = 1;
		for(var i=1;i<=n;i++) retVal *= i;
		return retVal;
	},

	//The number of ways of obtaining an ordered subset of k elements from a set of n elements
	permutations: function(n,k) {
		if(n==0 || k==0) return 1;
		return (Toolkit.Math.factorial(n) / Toolkit.Math.factorial(n-k));
	},

	//The number of ways of picking n unordered outcomes from r possibilities
	combinations: function(n,r) {
		if(n==0 || r==0) return 1;
		return (Toolkit.Math.factorial(n) / (Toolkit.Math.factorial(n-r) * Toolkit.Math.factorial(r)));
	},

	bernstein: function (t,n,i) {
		return ( Toolkit.Math.combinations(n,i) * Math.pow(t,i) * Math.pow(1-t,n-i) );
	}
};



//A collection of objects that define curves, use getValue(n)
//to get a point on the curve where 0 <= n <= 1
//Unless otherwise stated all curves are n-dimensional, using arrays to represent coordinates.	All arrays must be of
//same length otherwise errors will occur!
Toolkit.Drawing.Curves = {

	//Creates a straight line object
	Line: function(start, end) {
		this.start = start;
		this.end = end;
		this.dimensions = start.length;

		//simple function to find point on an n-dimensional, straight line
		this.getValue = function(n) {
			var retVal = new Array(this.dimensions);
			for(var i=0;i<this.dimensions;i++)
				retVal[i] = ((this.end[i] - this.start[i]) * n) + this.start[i];
			return retVal;
		}

		return this;
	},


	//Takes an array of points, the first is the start point, the last is end point and the ones in
	//between are the Bezier control points.	REQUIRES THIRTEEN.MATH
	Bezier: function(pnts) {
		this.getValue = function(step) {
			var retVal = new Array(this.p[0].length);
			for(var k=0;j<this.p[0].length;k++) retVal[k]=0;
			for(var j=0;j<this.p[0].length;j++) {
				var C=0; var D=0;
				for(var i=0;i<this.p.length;i++) C += this.p[i][j] * this.p[this.p.length-1][0] * Toolkit.Math.bernstein(step,this.p.length,i);
				for(var l=0;l<this.p.length;l++) D += this.p[this.p.length-1][0] * Toolkit.Math.bernstein(step,this.p.length,l);
				retVal[j] = C/D;
			}
			return retVal;
		}
		this.p = pnts;
		return this;
	},


	//Catmull-Rom Spline - allows you to interpolate a smooth curve through a set of points in n-dimensional space
	CatmullRom : function(pnts,c) {
		this.getValue = function(step) {
			var percent = step * (this.p.length-1);
			var node = Math.floor(percent);
			var progress = percent - node;

			var i0 = node-1; if(i0 < 0) i0 = 0;
			var i = node;
			var i1 = node+1; if(i1 >= this.p.length) i1 = this.p.length-1;
			var i2 = node+2; if(i2 >= this.p.length) i2 = this.p.length-1;

			var u = progress;
			var u2 = progress*progress;
			var u3 = progress*progress*progress;

			var retVal = new Array(this.p[0].length);
			for(var k=0;k<this.p[0].length;k++) {
				var x1 = ( -this.c * this.p[i0][k] ) + ( (2 - this.c) * this.p[i][k] ) + ( (this.c-2) * this.p[i1][k] ) + ( this.c * this.p[i2][k] );
				var x2 = ( 2 * this.c * this.p[i0][k] ) + ( (this.c-3) * this.p[i][k] ) + ( (3 - 2 * this.c) * this.p[i1][k] ) + ( -this.c * this.p[i2][k] );
				var x3 = ( -this.c * this.p[i0][k] ) + ( this.c * this.p[i1][k] );
				var x4 = this.p[i][k];

				retVal[k] = x1*u3 + x2*u2 + x3*u + x4;
			}
			return retVal;

		}


		if(!c) this.c = 0.7;
		else this.c = c;
		this.p = pnts;

		return this;
	},


	//Creates a circle object, where start and end are points on the circle, and angle is
	//angle between them.	Function works out radius and centre of circle for you.
	//!!!!! Only works with 2D points !!!!!
	Circle: function(start, end, angle) {
		this.start = start;
		this.end = end;
		this.angle = angle;

		//Use Cosine rule to find radius of the circle
		this.radius = Math.sqrt( (end[0]-start[0])*(end[0]-start[0]) + (end[1]-start[1])*(end[1]-start[1]) ) / (2 * Math.sin(Toolkit.Math.degToRad(angle)));

		//Use Sine rule to find centre of circle
		this.centre = [start[0] + this.radius*Math.sin(Toolkit.Math.degToRad(angle/2)), start[1] - this.radius*Math.cos(Toolkit.Math.degToRad(angle/2))];

		//document.getElementById("dot3").style.left = this.centre[0];
		//document.getElementById("dot3").style.top = this.centre[1];

		this.getValue = function(n) {
			var retVal = new Array(2);
			var theta = Toolkit.Math.degToRad(angle*n);

			//This bit basically translates the circle to (0,0), rotates the point and then translates it back
			retVal[0] = (this.start[0] - this.centre[0])*Math.cos(theta) - (this.start[1] - this.centre[1])*Math.sin(theta) + this.centre[0];
			retVal[1] = (this.start[0] - this.centre[0])*Math.sin(theta) + (this.start[1] - this.centre[1])*Math.cos(theta) + this.centre[1];

			return retVal;
		}
	}
}



//draws a curve using DIVs, you can either specify the classname or use the default class "pixel"
//steps is the number of Divs it draws.	The function returns an object reference which can be used
//to edit the curve once it has been drawn, or delete it.
Toolkit.Drawing.drawCurve = function(curve,steps,className) {
	if(!className) className = "pixel";
	if(!steps) steps = 100;
	this.pixels = new Array(steps)
	for(var i=0;i<steps;i++) {
		var pt = curve.getValue(i/steps);
		this.pixels[i] = document.createElement("div");
		this.pixels[i].className = className;
		this.pixels[i].style.left = pt[0];
		this.pixels[i].style.top = pt[1];
		document.body.appendChild(this.pixels[i]);
	}

	return this;
}





Toolkit.Drawing.animations = new Array();

Toolkit.Drawing.Animation = function(curve,duration,acc) {

	//a 1D-Bezier curve to be used for accelleration
	if(typeof acc.getValue == "function") this.speedCurve = acc;
	else if(acc) {
		this.acc = acc;
		this.speedCurve = new Toolkit.Drawing.Curves.Bezier([[0],[Math.abs((1+this.acc)/3)],[(2+this.acc)/3],[1]]);
	} else this.acc = 0;

	this.duration = duration;

	//other state variables
	this.startTime = 0;
	this.endTime = 0;
	this.percent = 0;
	this.active = false;
	this.lastFrame = new Date().valueOf();
	this.fps = 0;

	//save to array so we can access later through timeouts.... maybe a bit messy.
	this.id = Toolkit.Drawing.animations.length;
	Toolkit.Drawing.animations[this.id] = this;

	this.curve = curve;
	if(!curve.getValue) { alert("[Toolkit.Drawing.Animation] Invalid Curve object"); return; }

	//events
	this.onstart = function() {};
	this.onend = function() {};
	this.onanimate = function() {};
	this.onpause = function() {};
	this.onunpause = function() {};

	this.start = function() {
		var coordinates = this.curve.getValue(0);
		var evt = { coordinates: coordinates,
		            startTime: new Date().valueOf(),
		            duration: this.duration,
		            percent: 0
		          };

		this.onstart(evt);

		this.startTime = new Date().valueOf(),
		this.endTime = new Date().valueOf() + this.duration;
		this.percent = 0;
		this.active = true;

		this.cycle();
	}

	this.stop = function(gotoEnd) {
		var curTime = new Date().valueOf();
		var step = (curTime - this.startTime) / (this.endTime - this.startTime);
		if(step>1) step=1;

		this.percent = step*100;
		this.fps = 1000 / ((new Date().valueOf()) - this.lastFrame);
		this.lastFrame = new Date().valueOf();
		this.active = false;

		var substep = step;
		if(this.acc != 0) substep = this.speedCurve.getValue(step)[0];

		var e = null;
		if(gotoEnd) e = { coordinates: this.curve.getValue(1), startTime: this.startTime, duration: this.duration, percent: this.percent, fps: Math.round(this.fps) };
		else e = { coordinates: this.curve.getValue(substep), startTime: this.startTime, duration: this.duration, percent: this.percent, fps: Math.round(this.fps) };

		this.onanimate(e);
		this.onend(e);
	}

	this.cycle = function() {
		var curTime = new Date().valueOf();
		var step = (curTime - this.startTime) / (this.endTime - this.startTime);
		this.percent = step*100;
		this.fps = 1000 / ((new Date().valueOf()) - this.lastFrame);
		this.lastFrame = new Date().valueOf();

		if(step <= 1 && this.active == true) {
			var substep = step;
			if(this.acc != 0) substep = this.speedCurve.getValue(step)[0];

			this.onanimate({ coordinates: this.curve.getValue(substep), startTime: this.startTime, duration: this.duration, percent: this.percent, fps: Math.round(this.fps) });
			setTimeout("Toolkit.Drawing.animations[" + this.id + "].cycle()",5);

		} else if(this.active==true) {
			this.active = false;
			var e = { coordinates: this.curve.getValue(1), startTime: this.startTime, duration: this.duration, percent: this.percent, fps: Math.round(this.fps) };
			this.onanimate(e);
			this.onend(e);

		}
	}

	//return this;
}

