// SpryDOMEffects.js - version 0.6 - Spry Pre-Release 1.7
//
// Copyright (c) 2007. Adobe Systems Incorporated.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//   * Neither the name of Adobe Systems Incorporated nor the names of its
//     contributors may be used to endorse or promote products derived from this
//     software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

(function() { // BeginSpryComponent
	
if (typeof Spry == "undefined" || !Spry.Utils || !Spry.$$)
{
	alert("SpryDOMEffects.js requires SpryDOMUtils.js");
	return;
}

if (!Spry.Effect) Spry.Effect = {};

Spry.Effect.Animator = function(opts)
{
	Spry.Effect.Animator.Notifier.call(this);

	this.animatorID = Spry.Effect.Animator.nextID++;
	this.dropFrames = true;
	this.fps = 60; // frames per-second
	this.duration = 500; // msecs
	this.timer = 0;
	this.startTime = 0; // Used only when dropFrames is true.
	this.currentFrame = 0;
	this.easeFunc = Spry.Effect.Animator.defaultEaseFunc;
	this.stopped = false;

	Spry.Effect.Animator.copyProps(this, opts);

	this.interval = 1000 / this.fps;
	this.numFrames = (this.duration / 1000) * this.fps;

	if (this.onComplete)
	{
		var self = this;
		this.addObserver({ onAnimationComplete: function(){ self.onComplete(); } });
	}
};

Spry.Effect.Animator.nextID = 1;

Spry.Effect.Animator.copyProps = function(dst, src)
{
	if (src)
	{
		for (prop in src)
			dst[prop] = src[prop];
	}
	return dst;
};

Spry.Effect.Animator.getElement = function(element)
{
	if (arguments.length > 1)
	{
		for (var i = 0, elements = [], length = arguments.length; i < length; i++)
			elements.push(Spry.Effect.Animator.getElement(arguments[i]));
		return elements;
	}
	if (typeof element == 'string')
		element = document.getElementById(element);
	return element;
};

Spry.Effect.Animator.defaultEaseFunc = function(time, begin, finish, duration) { time /= duration; return begin + ((2 - time) * time * finish); };

Spry.Effect.Animator.Notifier = function()
{
	this.observers = [];
	this.suppressNotifications = 0;
};

Spry.Effect.Animator.Notifier.prototype.addObserver = function(observer)
{
	if (!observer)
		return;

	// Make sure the observer isn't already on the list.

	var len = this.observers.length;
	for (var i = 0; i < len; i++)
	{
		if (this.observers[i] == observer)
			return;
	}
	this.observers[len] = observer;
};

Spry.Effect.Animator.Notifier.prototype.removeObserver = function(observer)
{
	if (!observer)
		return;

	for (var i = 0; i < this.observers.length; i++)
	{
		if (this.observers[i] == observer)
		{
			this.observers.splice(i, 1);
			break;
		}
	}
};

Spry.Effect.Animator.Notifier.prototype.notifyObservers = function(methodName, data)
{
	if (!methodName)
		return;

	if (!this.suppressNotifications)
	{
		var len = this.observers.length;
		for (var i = 0; i < len; i++)
		{
			var obs = this.observers[i];
			if (obs)
			{
				if (typeof obs == "function")
					obs(methodName, this, data);
				else if (obs[methodName])
					obs[methodName](this, data);
			}
		}
	}
};

Spry.Effect.Animator.Notifier.prototype.enableNotifications = function()
{
	if (--this.suppressNotifications < 0)
	{
		this.suppressNotifications = 0;
		Spry.Debug.reportError("Unbalanced enableNotifications() call!\n");
	}
};

Spry.Effect.Animator.Notifier.prototype.disableNotifications = function()
{
	++this.suppressNotifications;
};

Spry.Effect.Animator.prototype = new Spry.Effect.Animator.Notifier;
Spry.Effect.Animator.prototype.constructor = Spry.Effect.Animator;

Spry.Effect.Animator.prototype.start = function()
{
	this.stopped = false;
	this.currentFrame = 0;
	this.startTime = (new Date()).getTime();

	this.notifyObservers("onAnimationStart");

	var self = this;
	this.timer = setTimeout(function(){ self.onStepAnimation(); }, this.interval);
};

Spry.Effect.Animator.prototype.stop = function()
{
	if (this.timer)
		clearTimeout(this.timer);
	this.timer = 0;
	this.stopped = true;

	this.notifyObservers("onAnimationStopped");
};

Spry.Effect.Animator.prototype.onStepAnimation = function()
{
	var obj = {};

	if (this.dropFrames)
	{
		obj.duration = this.duration;
		obj.elapsed = ((new Date).getTime()) - this.startTime;
		if (obj.elapsed > obj.duration)
			obj.elapsed = obj.duration;
	}
	else
	{
		obj.duration = this.numFrames;
		obj.elapsed = ++this.currentFrame;
	}

	obj.easingConst = this.easeFunc(obj.elapsed, 0, 1, obj.duration)

	this.notifyObservers("onPreDraw", obj);
	this.draw(obj.elapsed, obj.duration, obj.easingConst);
	this.notifyObservers("onPostDraw", obj);

	if (!this.stopped)
	{
		if (obj.elapsed < obj.duration)
		{
			var self = this;
			this.timer = setTimeout(function(){ self.onStepAnimation(); }, this.interval);
		}
		else
		{
			this.stop();
			this.notifyObservers("onAnimationComplete");
		}
	}
};

Spry.Effect.Animator.prototype.draw = function(elapsed, duration, easingConst)
{
	// The default draw method does nothing. It is assumed that
	// derived classes will provide their own implementation of this
	// method.

	debug.log("elapsed: " + elapsed + " -- duration: " + duration + " -- easingConst: " + easingConst);
};


///////////////////////////////////////////////////////////////////////////////

Spry.Effect.CSSAnimator = function(elements, styleStr, opts)
{
	this.animationSets = [];

	Spry.Effect.Animator.call(this, opts);

	this.add(elements, styleStr);
};

Spry.Effect.CSSAnimator.prototype = new Spry.Effect.Animator();
Spry.Effect.CSSAnimator.prototype.constructor = Spry.Effect.CSSAnimator;

Spry.Effect.CSSAnimator.prototype.add = function(elements, styleStr)
{
	// The first argument for the CSSAnimator can be
	// the id of an element, an element node, or an array of
	// elements and/or ids.

	elements = Spry.$$(elements);

	if (elements.length < 1)
		return;

	var animSet = { elements: elements, cssProps: []};

	this.animationSets.push(animSet);

	// Convert the styleStr into an object.

	var toObj = Spry.Utils.styleStringToObject(styleStr);
	for (var p in toObj)
	{
		var obj = new Object;
		var v = toObj[p];
		obj.value = new Number(v.replace(/[^-\d\.]+/g, ""));
		obj.units = v.replace(/[-\d+\.]/g, "");
		toObj[p] = obj;
	}

	for (var i = 0; i < elements.length; i++)
	{
		var obj = animSet.cssProps[i] = new Object;
		for (var p in toObj)
		{
			var pFuncs = Spry.Effect.CSSAnimator.stylePropFuncs[p];
			if (!pFuncs)
				pFuncs = Spry.Effect.CSSAnimator.stylePropFuncs["default"];

			obj[p] = new Object;
			obj[p].from = new Number(pFuncs.get(elements[i], p).replace(/[^-\d\.]+/g, ""));
			obj[p].to = toObj[p].value;
			obj[p].distance = obj[p].to - obj[p].from;
			obj[p].units = toObj[p].units;
		}
	}
};

Spry.Effect.CSSAnimator.prototype.start = function()
{
	for (var s = 0; s < this.animationSets.length; s++)
	{
		var animSet = this.animationSets[s];
		var elements = animSet.elements;
		var cssProps = animSet.cssProps;

		for (var i = 0; i < elements.length; i++)
		{
			var ele = elements[i];
	
			var eleProps = ele.spryCSSAnimatorProps;
			if (!eleProps)
				eleProps = ele.spryCSSAnimatorProps = new Object;
	
			var obj = cssProps[i];
			for (var p in obj)
				eleProps[p] = this.animatorID;
		}
	}

	return Spry.Effect.Animator.prototype.start.call(this);
};

Spry.Effect.CSSAnimator.prototype.stop = function()
{
	for (var s = 0; s < this.animationSets.length; s++)
	{
		var animSet = this.animationSets[s];
		var elements = animSet.elements;
		var cssProps = animSet.cssProps;

		for (var i = 0; i < elements.length; i++)
		{
			var ele = elements[i];
			var obj = cssProps[i];
	
			var eleProps = ele.spryCSSAnimatorProps;
			for (var p in obj)
			{
				if (eleProps[p] == this.animatorID)
					delete eleProps[p];
			}
		}
	}

	return Spry.Effect.Animator.prototype.stop.call(this);
};

Spry.Effect.CSSAnimator.prototype.draw = function(elapsed, duration, easingConst)
{
	for (var s = 0; s < this.animationSets.length; s++)
	{
		var animSet = this.animationSets[s];
		var elements = animSet.elements;
		var cssProps = animSet.cssProps;

		for (var i = 0; i < elements.length; i++)
		{
			var ele = elements[i];
			var eleProps = ele.spryCSSAnimatorProps;
			var obj = cssProps[i];
			for (var p in obj)
			{
				if (eleProps[p] == this.animatorID)
				{
					var pFuncs = Spry.Effect.CSSAnimator.stylePropFuncs[p];
					if (!pFuncs)
						pFuncs = Spry.Effect.CSSAnimator.stylePropFuncs["default"];
	
					if (elapsed > duration)
						pFuncs.set(ele, p, obj[p].to + obj[p].units);
					else
						pFuncs.set(ele, p, obj[p].from + (obj[p].distance * easingConst) + obj[p].units);
				}
			}
		}
	}
};

Spry.Effect.CSSAnimator.stylePropFuncs = {};

Spry.Effect.CSSAnimator.stylePropFuncs["default"] = {
	get: function(ele, prop)
	{
		return ele.style[prop];
	},

	set: function(ele, prop, val)
	{
		ele.style[prop] = val;
	}
};

Spry.Effect.CSSAnimator.stylePropFuncs["opacity"] = {
	get: function(ele, prop)
	{
		var val = 1;
		
		if (ele.style.opacity)
			val = ele.style.opacity;
		else if (ele.style.filter)
		{
			var strVal = ele.style.filter.replace(/.*alpha\(opacity=(\d+)\).*/, "$1");
			if (strVal)
				val = parseInt(strVal) / 100;
		}
		return val + "";
	},

	set: function(ele, prop, val)
	{
		ele.style.opacity = "" + val;
		ele.style.filter = "alpha(opacity=" + (val * 100) + ")";
	}
};

///////////////////////////////////////////////////////////////////////////////

Spry.$$.Results.defaultEaseFunc = function(time, begin, finish, duration) { time /= duration; return begin + ((2 - time) * time * finish); };

Spry.$$.Results.animatePropertyTo = function(propName, to, options)
{
	var opts = { interval: 10, duration: 1000, onComplete: null, transition: Spry.$$.Results.defaultEaseFunc };
	Spry.Effect.Animator.copyProps(opts, options);

	var objs = [];
	for (var i = 0; i < this.length; i++)
	{
		var obj = objs[i] = new Object;
		obj.ele = this[i];
		obj.from = obj.ele[propName];
		obj.distance = to - obj.from;
	}

	var startTime = (new Date).getTime();

	var animateFunc = function()
	{
		var elapsedTime = ((new Date).getTime()) - startTime;

		if (elapsedTime > opts.duration)
		{
			for (var i = 0; i < objs.length; i++)
				objs[i].ele[propName] = to;
			if (opts.onComplete)
				opts.onComplete();
		}
		else
		{
			for (var i = 0; i < objs.length; i++)
			{
				var obj = objs[i];
				obj.ele[propName] = opts.transition(elapsedTime, obj.from, obj.distance, opts.duration);
			}
			setTimeout(animateFunc, opts.interval);
		}
	};

	setTimeout(animateFunc, opts.interval);
	return this;
};

Spry.$$.Results.animateStyleTo = function(styleStr, options)
{
	var a = new Spry.Effect.CSSAnimator(this, styleStr, options);
	a.start();
	return this;
};

})(); // EndSpryComponent