/***********************************************************

	Castwide FX Library

	Author: Fred Snyder
	Company: Castwide Technologies
	URL: http://castwide.com
	Date Created: Nov 16, 2006
	Date Modified: Jan 18, 2007

	Required files:
	* prototype.js (http://prototypejs.org)

	1-12-2007	Added distance function
	1-18-2007	Optimized Fader and Mover objects;
				added cancel function and params argument
				to accept onComplete, onCancel, and
				onFrame functions
	1-22-2007	Fixed binding for on* functions in Animator
				objects, added element() function

***********************************************************/

/*
The CFX library provides base functions and constructors
for visual effects.  In application code, these features
should normally be accessed through the extended methods
that the $CFX function adds to HTML elements (see below).
*/

var CFX = new function() {

	// Granularity is the number of milliseconds between animation frames.
	// The lower the granularity, the greater the frames per second.
	// 50 is a good value for smooth animation, as it yields 20 fps.
	// If animations take longer than they should, a good compromise is a
	// granularity of 125, which yields 8 fps, a rate that is known in the
	// animation industry as the Yogi Bear Factor (or at least it should be).
	var granularity = 50;

	return {

		// Browser-independent methods to retrieve the
		// absolute X and Y positions of elements
		// X = left, Y = top

		getPosX: function(obj) {
			var curleft = 0;
			if (obj.offsetParent)
			{
				while (obj.offsetParent)
				{
					curleft += obj.offsetLeft
					obj = obj.offsetParent;
				}
			}
			else if (obj.x)
				curleft += obj.x;
			return curleft;
		},

		getPosY: function(obj) {
			var curtop = 0;
			if (obj.offsetParent)
			{
				while (obj.offsetParent)
				{
					curtop += obj.offsetTop
					obj = obj.offsetParent;
				}
			}
			else if (obj.y)
				curtop += obj.y;
			return curtop;
		},

		distance: function(x1, y1, x2, y2) {
			return Math.sqrt( Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2) );
		},

		// Mover constructor to move elements around the
		// screen

		Mover: function(el, startX, startY, endX, endY, millisec, params) {
			var posX = startX;
			var posY = startY;
			var offX = (endX - startX) / (millisec / granularity);
			var offY = (endY - startY) / (millisec / granularity);
			var aFrames = new Array();

			// Handle additional params, if any
			// Valid params are onComplete, onFrame,  and onCancel functions
			if (params) {
				if (params.onComplete && params.onComplete.call) {
					var onComplete = params.onComplete.bind(this);
				}
				if (params.onCancel && params.onCancel.call) {
					var onCancel = params.onCancel.bind(this);
				}
				if (params.onFrame && params.onFrame.call) {
					var onFrame = params.onFrame.bind(this);
				}
			}

			// Prepare the element for motion.  Elements
			// must be set to absolute position in order
			// to be movable.
			el.style.position = 'absolute';
			el.style.left = startX + 'px';
			el.style.top = startY + 'px';

			this.element = function() {
				return el;
			}

			var move = function() {
				if (aFrames.length == 0) {
					return;
				}
				if (onFrame) {
					onFrame();
				}
				var pos = aFrames.shift();
				el.style.left = Math.floor(pos.x) + 'px';
				el.style.top = Math.floor(pos.y) + 'px';
				if (aFrames.length == 0) {
					if (onComplete) {
						onComplete();
					}
				}
			}

			// Initialize the animation frames
			for (var i = 0; i < Math.floor(millisec / granularity) - 1; i++) {
				aFrames.push({x: posX, y: posY});
				posX += offX;
				posY += offY;
				setTimeout(move, granularity * i);
			}
			// Add the last frame
			aFrames.push({x: endX, y: endY});
			setTimeout(move, granularity * i);

			this.cancel = function() {
				if (aFrames.length > 0) {
					aFrames = new Array();
					if (onCancel) {
						onCancel();
					}
				}
			}
		},

		// Fader constructor to change the opacity of elements

		Fader: function(el, startOpac, endOpac, millisec, params) {
			var timer = 0;
			var curOpac = startOpac;
			var offOpac = (endOpac - startOpac) / (millisec / granularity);
			var aFrames = new Array();

			this.element = function() {
				return el;
			}

			// Handle additional params, if any
			// Valid params are onComplete and onCancel functions
			if (params) {
				if (params.onComplete && params.onComplete.call) {
					var onComplete = params.onComplete.bind(this);
				}
				if (params.onCancel && params.onCancel.call) {
					var onCancel = params.onCancel.bind(this);
				}
				if (params.onFrame && params.onFrame.call) {
					var onFrame = params.onFrame.bind(this);
				}
			}

			var fade = function() {
				if (aFrames.length == 0) {
					return;
				}
				if (onFrame) {
					onFrame();
				}
				var val = aFrames.shift();
				el.opacity.set(val);
				if (aFrames.length == 0) {
					if (onComplete) {
						onComplete();
					}
				}
			}

			// Starting opacity
			el.opacity.set(startOpac);

			// Initialize the animation frames
			for (var i = 0; i < (millisec / granularity) - 1; i++) {
				//setTimeout(this.fade, i * 100);
				aFrames.push(curOpac);
				curOpac += offOpac;
				setTimeout(fade, granularity * i);
			}
			// Add the last frame
			aFrames.push(endOpac);
			setTimeout(fade, granularity * i);

			this.cancel = function() {
				if (aFrames.length > 0) {
					aFrames = new Array();
					if (onCancel) {
						onCancel();
					}
				}
			}
		},

		// Animator constructor to perform generic style
		// transitions (essentially any numeric property)

		Animator: function(el, prop, unit, startVal, endVal, millisec, params) {
			var curVal = startVal;
			var offVal = (endVal - startVal) / (millisec / granularity);
			var aFrames = new Array();

			this.element = function() {
				return el;
			}

			// Handle additional params, if any
			// Valid params are onComplete and onCancel functions
			if (params) {
				if (params.onComplete && params.onComplete.call) {
					var onComplete = params.onComplete.bind(this);
				}
				if (params.onCancel && params.onCancel.call) {
					var onCancel = params.onCancel.bind(this);
				}
				if (params.onFrame && params.onFrame.call) {
					var onFrame = params.onFrame.bind(this);
				}
			}

			// Initialize the animation frames
			for (var i = 0; i < (millisec / granularity) - 1; i++) {
				//setTimeout(this.fade, i * 100);
				aFrames.push(curVal);
				curVal += offVal;
				setTimeout(animate, granularity * i);
			}
			// Add the last frame
			aFrames.push(endVal);
			setTimeout(animate, granularity * i);

			var animate = function() {
				if (aFrames.length == 0) {
					return;
				}
				if (onFrame) {
					onFrame();
				}
				var val = aFrames.shift();
				el.style[prop] = val + unit;
				if (aFrames.length == 0) {
					if (onComplete) {
						onComplete();
					}
				}

			}

			this.cancel = function() {
				if (aFrames.length > 0) {
					aFrames = new Array();
					if (onCancel) {
						onCancel();
					}
				}
			}
		}

	};
}

/*
The $CFX function extends elements with methods for
visual effects.  In order to use any of the effects, the
element or its ID must be passed to the $CFX function at
least once.

A simple example:

	$CFX('element-id').opacity.fade(100, 0, 1000);

A longer example:

	var ref = $CFX('element-id');
	ref.opacity.fade(100, 0, 1000);
	// Later...
	var ref2 = document.getElementById('element-id');
	ref2.opacity.fade(0, 100, 1000);

When the element is referenced via the ref2 variable in
the above example, it is not necessary to call the $CFX
function a second time.  Conversely, multiple passes to
the $CFX function will not hurt the element, either.  The
function checks to see if the extended methods exist
before adding them.

The following methods are implemented:

	POSITION METHODS
	element.position.getX();
	element.position.getY();
	element.position.set(x, y);
	element.position.move(startx, starty, endx, endy, milliseconds);
	element.position.moveTo(x, y, milliseconds);

	OPACITY METHODS
	element.opacity.set(opacity);
	element.opacity.fade(start, end, milliseconds);

	ANIMATE METHOD
	element.animate(property, unit, start, end, milliseconds);
*/

function $CFX(i) {
	// Get element by ID or assume argument is element
	if (!i.tagName) {
		var element = document.getElementById(i);
	} else {
		var element = i;
	}
	// Make sure the element has been extended by Prototype
	$(element);

	if (!element.opacity) element.opacity = {
		set: function(opac) {
			element.style.opacity = (opac / 100);
			element.style.MozOpacity = (opac / 100);
			element.style.KhtmlOpacity = (opac / 100);
			element.style.filter = "alpha(opacity=" + opac + ")";
		},

		fade: function(startOpac, endOpac, millisec, params) {
			return new CFX.Fader(element, startOpac, endOpac, millisec, params);
		}
	};

	if (!element.position) element.position = {
		set: function(x, y) {
			element.style.position = 'absolute';
			element.style.left = x + 'px';
			element.style.top = y + 'px';
		},

		getX: function() {
			return CFX.getPosX(element);
		},

		getY: function() {
			return CFX.getPosY(element);
		},

		move: function(sX, sY, eX, eY, millisec, params) {
			return new CFX.Mover(element, sX, sY, eX, eY, millisec, params);
		},

		moveTo: function(x, y, millisec, params) {
			return new CFX.Mover(element, element.position.getX(), element.position.getY(), x, y, millisec, params);
		}
	};

	if (!element.animate) element.animate = function(prop, unit, startVal, endVal, millisec, params) {
		return new CFX.Animator(element, prop, unit, startVal, endVal, millisec, params);
	}

	return element;
}

