/********************************************************************
	Library:	Majik-anim
	Version:	1.0
	Date:		Nov 16 2008
	Author:		Daniel Wright
	Contact:	http://danwright.info/contact

	Now requires prototype.js.
*********************************************************************/

if (!majik) var majik = {};

majik.objectToString = function(obj)
{
	if (typeof Debug != "undefined")
		return Debug.objectToString(obj);
	return "";
};

majik.parseColor = function(color)
{
	var channels;
	if (color.slice(0,4) == 'rgb(')
		{
		channels = color.slice(4, color.length-1).split(',');
    	}
    else if (color.slice(0,5) == 'rgba(')
    	{
    	channels = color.slice(5, color.length-1).split(',');
    	}
    else if (color.slice(0,1) == '#')
    	{
		if (color.length == 4)
			{
			for (var i=1;i<4;i++)
				channels[i-1] = 257*parseInt(color.charAt(i),16);
			}
		else if (color.length == 7)
			{
			for (var i=1;i<4;i++)
				channels[i-1] = parseInt(color.slice(i,i+2),16);
			}
    	}

	var rgba = {r: 0, g: 0, b: 0, a: 1.0};
    if (channels.length >= 3)
    	{
		rgba.r = channels[0];
		rgba.g = channels[1];
		rgba.b = channels[2];
		if (channels.length == 4)
			rgba.a = channels[3];
		}
	return rgba;
};

majik.xLinear = function(fraction)
{
	return fraction;
};

majik.xSinoidal = function(fraction)
{
	return (-Math.cos(fraction*Math.PI)/2) + 0.5;
};

majik.xInstant = function(fraction)
{
	return 1.0;
};

majik.interpolateRGBA = function(start, end, fraction)
{
	var c0 = majik.parseColor(start);
	var c2 = majik.parseColor(end);
	
	var rgba = {r: (c0.r + (c1.r-c0.r)*fraction),
			    g: (c0.g + (c1.g-c0.g)*fraction),
			    b: (c0.b + (c1.b-c0.b)*fraction),
			    a: (c0.a + (c1.a-c0.a)*fraction)
			    };
	return rgba;
};

majik.rgba = function(rgba)
{
	return 'rgba('+rgba.r+','+rgba.g+','+rgba.b+','+rgba.a+')';
};

majik.rgb = function(rgba)
{
	var channels = [rgba.r, rgba.g, rgba.b];
	var color = '#';
	for (i = 0; i < 3; i++)
		{
		var x = channels[i].toString(16);
		if (x.length == 1)
			x += x;
		else if (x.length > 2)
			x = x.slice(0,2); // just get something
		color += x;
		}
	return color;
};

majik.bind = function() // (object, method, args...)
{
	var args = $A(arguments);
	var object = args.shift();
	var method = args.shift();
	return function() {
		return method.apply(object, args.concat($A(arguments)));
		}
};
	
MajikEffect = function(elem, param)
{
	this.elem = Element.extend(elem);
	this.param = param;
	this.styleStart = elem.style;

	log("**** MajikEffect::MajikEffect");
	log("elem=" + elem.id);
	var debuggingIE = true;
	if (debuggingIE)
		{
		this.options = {};
		this.options.posStart = param.posStart || Element.cumulativeOffset(elem);
		this.options.sizeStart = {width: parseInt(elem.style.width), height: parseInt(elem.style.height)};
		this.options.colorBegin = elem.style.color || '#000000';
		this.options.bgcolorBegin = elem.style.backgroundColor || '#ffffff';
		this.options.opacityBegin = elem.getOpacity();
		this.options.borderWidthStart = elem.getStyle('borderWidth') || elem.getStyle('borderTopWidth');
		this.options.paddingLeftStart = elem.getStyle('paddingLeft') || '0px',
		this.options.paddingTopStart =  elem.getStyle('paddingTop') || '0px',
		this.options.paddingRightStart = elem.getStyle('paddingRight') || '0px',
		this.options.paddingBottomStart = elem.getStyle('paddingBottom') || '0px', 
		this.options.transform = majik.xSinoidal;
		}
	else
	this.options = {posStart:     param.posStart || Element.cumulativeOffset(elem),
					sizeStart: {width: parseInt(elem.style.width), height: parseInt(elem.style.height)},
					colorBegin:   elem.style.color || '#000000',
					bgcolorBegin: elem.style.backgroundColor || '#ffffff',
					opacityBegin: Element.getOpacity(elem),
					borderWidthStart:  elem.getStyle('borderWidth') || elem.getStyle('borderTopWidth'),
					paddingLeftStart:  elem.getStyle('paddingLeft') || '0px',
					paddingTopStart :  elem.getStyle('paddingTop') || '0px',
					paddingRightStart: elem.getStyle('paddingRight') || '0px',
					paddingBottomStart:elem.getStyle('paddingBottom') || '0px', 
					transform:    majik.xSinoidal
					};
	
	for (var opt in param)
		this.options[opt] = param[opt];
	if (param.opacity != null)
		{
		if (elem.getStyle('visibility') == 'hidden')
			{
			this.options.opacityBegin = 0.0;
			Element.setOpacity(elem, this.options.opacityBegin);
			elem.style.visibility = '';
			}
		}
	
	if (param.posEnd != null)
		{ // posPerpVector is perpendicular to our (main) movement, with same magnitude;
		this.posPerpVector = {dx: (this.options.posEnd.top - this.options.posStart.top),
							 dy: (this.options.posStart.left - this.options.posEnd.left)};
		}

	this.interpolations = [];
	
	var fx = this;
	if (this.options.color && this.colorEnd != elem.style.color)
		fx.interpolations.push(fx.interpolateColor.bind(fx));
	if (this.options.bgcolor && this.options.bgcolor != elem.style.backgroundColor)
		fx.interpolations.push(fx.interpolateBgColor.bind(fx));
	if (param.posEnd != null)
		fx.interpolations.push(fx.interpolatePosition.bind(fx));
	if (param.sizeEnd != null)
		fx.interpolations.push(fx.interpolateSize.bind(fx));
	if (param.opacity != null)
		fx.interpolations.push(fx.interpolateOpacity.bind(fx));
	if (param.borderWidth != null)
		fx.interpolations.push(fx.interpolateBorderWidth.bind(fx));
	if (param.paddingLeft != null || param.paddingTop != null)
		fx.interpolations.push(fx.interpolatePadding.bind(fx));
			
	elem.style.visibility = '';
//	log("**** MajikEffect::MajikEffect -- end");
};

MajikEffect.prototype.interpolate = function(fraction)
{
	for (var i=0; i<this.interpolations.length; i++)
		this.interpolations[i](fraction);
};

MajikEffect.prototype.interpolatePosition = function(fraction)
{
	var newTop  = this.options.posStart.top + (this.options.posEnd.top - this.options.posStart.top)*fraction;
	var newLeft = this.options.posStart.left+ (this.options.posEnd.left- this.options.posStart.left)*fraction;
	var r = Math.sin(fraction*Math.PI);
	newTop += r*this.posPerpVector.dy/5;
	newLeft += r*this.posPerpVector.dx/5;
	
	newTop = Math.round(newTop);
	newLeft = Math.round(newLeft);
//	log("MajikEffect.interpolatePosition("+fraction+"): elem.style.position=" + this.elem.style.position + ", newLeft=" + newLeft + 'px');
	this.elem.style.top = (newTop == NaN) ? '' : (newTop + 'px');
	this.elem.style.left= (newLeft == NaN) ? '' : (newLeft + 'px');
};

MajikEffect.prototype.interpolateSize = function(fraction)
{
	try {
		var w = this.options.sizeStart.width + (this.options.sizeEnd.width-this.options.sizeStart.width)*fraction;
		var h = this.options.sizeStart.height+ (this.options.sizeEnd.height-this.options.sizeStart.height)*fraction;
		this.elem.style.width = Math.round(w) + 'px';
		this.elem.style.height = Math.round(h) + 'px';
		}
	catch (error)
		{
		/* ignore */
		}
};

MajikEffect.prototype.interpolateColor = function(fraction)
{
	this.elem.style.color = majik.rgb(interpolateRGBA(this.options.colorBegin, this.options.color, fraction));
};

MajikEffect.prototype.interpolateBgColor = function(fraction)
{
	this.elem.style.backgroundColor = majik.rgb(interpolateRGBA(this.options.bgcolorBegin,this.options.bgcolor,fraction));
};

MajikEffect.prototype.interpolateOpacity = function(fraction)
{
	Element.setOpacity(this.elem, this.options.opacityBegin + (this.options.opacity-this.options.opacityBegin)*fraction);
};

MajikEffect.prototype.interpolateBorderWidth = function(fraction)
{
	var w = parseInt(this.options.borderWidthStart) + (parseInt(this.options.borderWidth)-parseInt(this.options.borderWidthStart))*fraction;
	assert(!isNaN(w), "borderWidth is Not a Number");
	w = Math.round(w);
	this.elem.style.borderWidth = w + 'px';
};

MajikEffect.prototype.interpolatePadding = function(fraction)
{
	['Left','Top','Right','Bottom'].each( function (side) 
		{
		side = 'padding' + side;
		try {
			if (this.options[side] && this.options[side + 'Start'])
				{
				var w = parseInt(this.options[side + 'Start']) + (parseInt(this.options[side])-parseInt(this.options[side+'Start']))*fraction;
				w = Math.round(w);
				this.elem.style[side] = w + 'px';
				}
			}
		catch (error)
			{
			}
		});
};

MajikAnim = function(param, options)
{
	this.param = $A(param);
	this.options = options || {};
	this.effects = new Array();
	this.param.each( (function(one) {
			this.effects.push(new MajikEffect(one.element,one.options || {}));
			}).bind(this) );

	this.animateFrame = 0.0;
	this.animatePulse = 30; // milliseconds
	var frameCountDesired = (param.fast ? 6 : 10);
	this.animateRate  = 1.0/frameCountDesired;
	this.transformFun = majik.xSinoidal;
	
	if (!this.options.dontStart)
		this.start();
		
	this.drawFrame();
};

MajikAnim.prototype.start = function()
{
	if (this.interval == null)
		this.interval = setInterval(this.animate.bind(this), this.animatePulse);
};

MajikAnim.prototype.reset = function()
{
	this.animateFrame = 0.0;
	this.drawFrame();
};

MajikAnim.prototype.cancel = function()
{
	if (this.interval != null)
		{
		clearInterval(this.interval);
		this.interval = null;
		}
};

MajikAnim.prototype.complete = function()
{
	this.animateFrame = 1.0;
	this.drawFrame();
	if (this.interval != null)
		{
		clearInterval(this.interval);
		this.interval = null;
		if (typeof this.options.finishFunction == 'function')
			this.options.finishFunction(this, this.param.finishParam);
		}
};

MajikAnim.prototype.animate = function()
{
	assert(typeof(this) == "object", "this is not set properly (MajikAnim.animate)");
	assert(this.animateRate >= 0.01, "this.animateRate is bogus or undefined");
	this.animateFrame += this.animateRate;
	log("animate: " + this.animateFrame);
	if (this.animateFrame >= 1.0)
		this.complete();
	else
		this.drawFrame();
};

MajikAnim.prototype.drawFrame = function()
{
	this.interpolate(this.transformFun(this.animateFrame));
};

MajikAnim.prototype.interpolate = function(fraction)
{
	this.effects.each( function (fx) { fx.interpolate(fraction); } );
};



