// anim.js - written by Brandon Moon for Pixelture
// copyright 2011

// Globals (for managing animations)
var delays = new Array();
var anims = new Array();
var running = false;
var pihalf = 1.5707963;

// Configurables
var step = 30; // milliseconds

/*
 * Creates an animation object that can be run with run().
 * 
 * Parameters:
 * elem - either the element object itself, or an id string
 * properties - a dictionary of the following properties defining the animation:
 * 		cssprop* - any of 'top', 'left', 'right', 'bottom', 'height', 'width', 'opacity'
 * 		type - any of 'easein', 'easeout', or 'linear' (default is 'linear')
 * 		to* - the value to animate to
 * 		from - the value to animate from (default is current position)
 * 		duration* - length of the desired animation (in milliseconds)
 * 		delay - how long to delay the animation before it starts (default is 0)
 * 		onstart - an optional function to be called when the animation starts
 * 		onfinish - an optional function to be called when the animation ends
 * 		(* indicates required)
 *
 * Conditions:
 * No invalid parameters (some are checked, but not all)
 * CSS property is explicitly defined (i.e. style="left: 0px;")
 */
function Anim(elem, properties) {
	// Check for ID
	if (typeof(elem) == 'string') {
		elem = $(elem);
		if (!elem) return null;
	}
	else if (!elem.nodeType && elem.nodeType != 1) return null;
	
	// Save
	this.elem = elem;
	
	// Set up animation variables
	if (!properties.cssprop || !isFinite(properties.to) || !isFinite(properties.duration)) return null;
	
	this.cssprop = properties.cssprop;
	this.to = properties.to;
	this.duration = properties.duration;
	
	this.type = properties.type ? properties.type : 'linear';
	if (properties.from) this.from = properties.from;
	this.delay = properties.delay ? properties.delay : 0;
	this.onstart = properties.onstart && properties.onstart.call ? properties.onstart : function() {};
	this.onfinish = properties.onfinish && properties.onfinish.call ? properties.onfinish : function() {};
};

// Some global properties

/*
 * Runs the animation (note that the beginning of the animation may have a delay)
 */
Anim.prototype.run = function() {
	if (this.delay > 0) {
		var inst = this;
		this.timeout = setTimeout(function() { 
			if (delays.indexOf(inst) != -1) 
				inst.run(); 
		}, this.delay);
		this.delay = 0;
		delays.push(this);
		return this;
	}
	if (this.timeout) {
		delete this.timeout;
		delays.remove(this);
	}
	anims.push(this);
	if (!this.from) {
		this.from = parseFloat(this.elem.style[this.cssprop]);
	}
	
	this.gap = this.to - this.from;
	this.steps = Math.floor(this.duration / step); // Calculate number of loops
	this.loop = 0; // Incremented each loop
	
	if (this.onstart) this.onstart.call(this);
	if (!running) _process();
	
	return this;
};

/*
 * Used to stop the animation (i.e. cancel it).
 */
Anim.prototype.stop = function() {
	if (this.timeout) {
		clearTimeout(this.timeout);
	}
	anims.remove(this);
	delays.remove(this);

	// Check if we need to stop the loop
	if (anims.length == 0) {
		running = false;
		clearInterval(interval);
	}
	
	return this;
};

// Performs the next step of the animation
Anim.prototype._step = function() {
	this.loop++;
	this._setValue(this._getStep());
	if (this.loop >= this.steps) {
		this._setValue(this.to);
		this.stop();
		this.onfinish.call(this);
	}
};

// Sets the css property on the element
Anim.prototype._setValue = function(value) {
	if (this.cssprop == "opacity")
		setOpacity(this.elem, value);
	else 
		this.elem.style[this.cssprop] = value + "px";
};

// Returns the value for the current step of the animation
Anim.prototype._getStep = function() {
	switch (this.type) {
	case 'linear':
		var percent = (this.loop / this.steps);
		return  this.from + this.gap * percent;
	case 'easein':
		var percent = 1 - (this.loop / this.steps);
		var stepper = Math.cos(percent * pihalf);
		return this.from + (this.gap * stepper);
	case 'easeout':
		var percent = (this.loop / this.steps);
		var stepper = Math.cos(percent * pihalf);
		return this.from + this.gap - (this.gap * stepper);
	}
};
/*
 * Clears all current animations
 */
Anim.clear = function() {
	for (var i in delays) {
		clearTimeout(delays[i].timeout);
	}
	delays = new Array();
	anims = new Array();
};

var interval = null;

function _process() {
	if (anims.length == 0) {
		running = false;
		clearInterval(interval);
		return;
	}
	
	for (var i in anims) {
		try {
			if (anims[i]._step) anims[i]._step();
		}
		catch(err) {}
	}
	
	if (!running) {
		running = true;
		interval = setInterval(_process, step);
	}
};

