var Spry;

if(!Spry) Spry = {};

Spry.forwards = 1; // const
Spry.backwards = 2; // const

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

Spry.Effect.Transitions = {
	linearTransition: function(time, begin, change, duration) {
		if(time > duration) return change+begin;
		return begin + (time / duration) * change;
	},
	sinusoidalTransition: function(time, begin, change, duration) {
		if(time > duration) return change+begin;
		return begin + ((-Math.cos((time/duration)*Math.PI)/2) + 0.5) * change;
	},
	squareTransition: function(time, begin, change, duration) {
		if(time > duration) return change+begin;
		return begin + Math.pow(time/duration, 2) * change;
	},
	squarerootTransition: function(time, begin, change, duration) {
		if(time > duration) return change+begin;
		return begin + Math.sqrt(time/duration) * change;
	},
	fifthTransition: function(time, begin, change, duration) {
		if(time > duration) return change+begin;
		return begin + Math.sqrt((-Math.cos((time/duration)*Math.PI)/2) + 0.5) * change;
	},
	circleTransition: function(time, begin, change, duration) {
		if(time > duration) return change+begin;
		var pos = time/duration;
		return begin + Math.sqrt(1 - Math.pow((pos-1), 2))* change;
	},
	pulsateTransition: function(time, begin, change, duration) {
		if(time > duration) return change+begin;
		return begin + (0.5 + Math.sin(17*time/duration)/2) * change;
	},
	growSpecificTransition: function(time, begin, change, duration) {
		if(time > duration) return change+begin;
		var pos = time/duration;
		return begin + (5 * Math.pow(pos, 3) - 6.4 * Math.pow(pos, 2) + 2 * pos) * change;
	}
};
for (var trans in Spry.Effect.Transitions) {
	Spry[trans] = Spry.Effect.Transitions[trans];
}
///////////////////////////
// Spry.Effect.Registry
///////////////////////////

Spry.Effect.Registry = function() {
	this.effects = [];
};

Spry.Effect.Registry.prototype.getRegisteredEffect = function(element, options)  {
	var a = {};
	a.element = Spry.Effect.getElement(element);
	a.options = options;

	for (var i=0; i<this.effects.length; i++)
		if(this.effectsAreTheSame(this.effects[i], a))
			return this.effects[i].effect;

	return false;
};

Spry.Effect.Registry.prototype.addEffect = function(effect, element, options) {
	if(!this.getRegisteredEffect(element, options)) {
		var len = this.effects.length;
		this.effects[len] = {};
		var eff = this.effects[len];
		eff.effect = effect;
		eff.element = Spry.Effect.getElement(element);
		eff.options = options;
	}
};

Spry.Effect.Registry.prototype.effectsAreTheSame = function(effectA, effectB) {
	if(effectA.element != effectB.element)
		return false;

	var compare = Spry.Effect.Utils.optionsAreIdentical(effectA.options, effectB.options);
	// reset finish and setup functions
	if(compare) {
		if(typeof effectB.options.setup == 'function')
			effectA.options.setup = effectB.options.setup;

		if(typeof effectB.options.finish == 'function')
			effectA.options.finish = effectB.options.finish;
	}		

	return compare;
};

var SpryRegistry = new Spry.Effect.Registry;

//////////////////////
// Spry.Effect.Utils
//////////////////////

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

Spry.Effect.Utils.showError = function(msg) {
	alert('Spry.Effect ERR: ' + msg);
};
Spry.Effect.Utils.showInitError = function(effect){
	Spry.Effect.Utils.showError('The ' + effect + ' class can\'t be accessed as a static function anymore. '+ "\n" + 'Please read Spry Effects migration documentation.');
	return false;
};
Spry.Effect.Utils.Position = function() {
	this.x = 0; // left
	this.y = 0; // top
	this.units = "px";
};

Spry.Effect.Utils.Rectangle = function() {
	this.width = 0;
	this.height = 0;
	this.units = "px";
};

Spry.Effect.Utils.intToHex = function(integerNum)  {
	var result = integerNum.toString(16);
	if(result.length == 1)
		result = "0" + result;
	return result;
};

Spry.Effect.Utils.hexToInt = function(hexStr) {
	return parseInt(hexStr, 16);
};

Spry.Effect.Utils.rgb = function(redInt, greenInt, blueInt) {
	var intToHex = Spry.Effect.Utils.intToHex;
	var redHex = intToHex(redInt);
	var greenHex = intToHex(greenInt);
	var blueHex = intToHex(blueInt);
	compositeColorHex = redHex.concat(greenHex, blueHex).toUpperCase();
	compositeColorHex = '#' + compositeColorHex;
	return compositeColorHex;
};

Spry.Effect.Utils.longColorVersion = function(color){
	if( color.match(/^#[0-9a-f]{3}$/i) ){
		var tmp = color.split('');
		var color = '#';
		for (var i = 1; i < tmp.length; i++){
			color += tmp[i] + '' + tmp[i];	
		}
	}
	return color;
};

Spry.Effect.Utils.camelize = function(stringToCamelize) {
	if(stringToCamelize.indexOf('-') == -1){
		return stringToCamelize;	
	}
	var oStringList = stringToCamelize.split('-');
	var isFirstEntry = true;
	var camelizedString = '';

	for(var i=0; i < oStringList.length; i++) {
		if(oStringList[i].length>0) {
			if(isFirstEntry) {
				camelizedString = oStringList[i];
				isFirstEntry = false;
			}
			else {
				var s = oStringList[i];
				camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
			}
		}
	}

	return camelizedString;
};

Spry.Effect.Utils.isPercentValue = function(value) {
	var result = false;
	if(typeof value == 'string' && value.length > 0 && value.lastIndexOf("%") > 0)
		result = true;

	return result;
};

Spry.Effect.Utils.getPercentValue = function(value) {
	var result = 0;
	try {
		result = Number(value.substring(0, value.lastIndexOf("%")));
	}
	catch (e) {Spry.Effect.Utils.showError('Spry.Effect.Utils.getPercentValue: ' + e);}
	return result;
};

Spry.Effect.Utils.getPixelValue = function(value) {
	var result = 0;
	if(typeof value == 'number') return value;
	var unitIndex = value.lastIndexOf("px");
	if( unitIndex == -1)
		unitIndex = value.length;
	try {
		result = parseInt(value.substring(0, unitIndex), 10);
	}
	catch (e){}
	return result;
};

Spry.Effect.Utils.getFirstChildElement = function(node) {
	if(node) {
		var childCurr = node.firstChild;
		while (childCurr) {
			if(childCurr.nodeType == 1) // Node.ELEMENT_NODE
				return childCurr;

			childCurr = childCurr.nextSibling;
		}
	}
	return null;
};

Spry.Effect.Utils.fetchChildImages = function(startEltIn, targetImagesOut) {
	if(!startEltIn  || startEltIn.nodeType != 1 || !targetImagesOut)
		return;

	if(startEltIn.hasChildNodes()) {
		var childImages = startEltIn.getElementsByTagName('img');
		var imageCnt = childImages.length;
		for(var i=0; i<imageCnt; i++) {
			var imgCurr = childImages[i];
			var dimensionsCurr = Spry.Effect.getDimensions(imgCurr);
			targetImagesOut.push([imgCurr,dimensionsCurr.width,dimensionsCurr.height]);
		}
	}
};

Spry.Effect.Utils.optionsAreIdentical = function(optionsA, optionsB) {
	if(optionsA == null && optionsB == null)
		return true;

	if(optionsA != null && optionsB != null) {
		var objectCountA = 0;
		var objectCountB = 0;

		for (var propA in optionsA) objectCountA++;
		for (var propB in optionsB) objectCountB++;

		if(objectCountA != objectCountB)
			return false;

		for (var prop in optionsA) {
			var typeA = typeof optionsA[prop];
			var typeB = typeof optionsB[prop];
			if( typeA != typeB || (typeA != 'undefined' && optionsA[prop] != optionsB[prop]))
				return false;
		}
		return true;
	}
	return false;
};

Spry.Effect.Utils.DoEffect = function (effectName, element, options) {
	if(!options)
		var options = {};

	options.name = effectName;
	var ef = SpryRegistry.getRegisteredEffect(element, options);
	if(!ef) {
		ef = new Spry.Effect[effectName](element, options);
		SpryRegistry.addEffect(ef, element, options);
	}
	ef.start();
	return true;
};
////////////////////////////
//  The notification class
////////////////////////////
if(!Spry.Utils) Spry.Utils = {};

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

Spry.Utils.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.Utils.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.Utils.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.Utils.Notifier.prototype.enableNotifications = function() {
	if(--this.suppressNotifications < 0) {
		this.suppressNotifications = 0;
		Spry.Effect.Utils.showError("Unbalanced enableNotifications() call!\n");
	}
};

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

/////////////////////////
// DHTML manipulation
////////////////////////
Spry.Effect.getElement = function(ele) {
	var element = ele;
	if(typeof ele == "string")
		element = document.getElementById(ele);

	if(element == null) 
		Spry.Effect.Utils.showError('Element "' + ele + '" not found.');
	
	return element;
};

Spry.Effect.getStyleProp = function(element, prop) {
	var value;
	var camelized = Spry.Effect.Utils.camelize(prop);
	try {
		if(element.style)
			value = element.style[camelized];

		if(!value) {
			if(document.defaultView && document.defaultView.getComputedStyle) {
				var css = document.defaultView.getComputedStyle(element, null);
				value = css ? css.getPropertyValue(prop) : null;
			}
			else if(element.currentStyle)  {
					value = element.currentStyle[camelized];
			}
		}
	}
	catch (e) {Spry.Effect.Utils.showError('Spry.Effect.getStyleProp: ' + e);}

	return value == 'auto' ? null : value;
};

Spry.Effect.setStyleProp = function(element, prop, value) {
	try {
		element.style[Spry.Effect.Utils.camelize(prop)] = value;
	}
	catch (e) {Spry.Effect.Utils.showError('Spry.Effect.setStyleProp: ' + e);}
};

Spry.Effect.getStylePropRegardlessOfDisplayState = function(element, prop, displayElement) {
	var refElement = displayElement ? displayElement : element;
	var displayOrig = Spry.Effect.getStyleProp(refElement, 'display');
	var visibilityOrig = Spry.Effect.getStyleProp(refElement, 'visibility');

	if(displayOrig == 'none') {
		Spry.Effect.setStyleProp(refElement, 'visibility', 'hidden');
		Spry.Effect.setStyleProp(refElement, 'display', 'block');

		if(window.opera) // opera needs focus to calculate the size for hidden elements
			refElement.focus();
	}

	var styleProp = Spry.Effect.getStyleProp(element, prop);
	if(displayOrig == 'none') { // reset the original values
		Spry.Effect.setStyleProp(refElement, 'display', 'none');
		Spry.Effect.setStyleProp(refElement, 'visibility', visibilityOrig);
	}
	return styleProp;
};

Spry.Effect.makePositioned = function(element) {
	var pos = Spry.Effect.getStyleProp(element, 'position');
	if(!pos || pos == 'static') {
		element.style.position = 'relative';
		// Opera returns the offset relative to the positioning context, when an
		// element is position relative but top and left have not been defined
		if(window.opera) {
			element.style.top = 0;
			element.style.left = 0;
		}
	}
};

Spry.Effect.isInvisible = function(element) {
	var propDisplay = Spry.Effect.getStyleProp(element, 'display');
	if(propDisplay && propDisplay.toLowerCase() == 'none')
		return true;

	var propVisible = Spry.Effect.getStyleProp(element, 'visibility');
	if(propVisible && propVisible.toLowerCase() == 'hidden')
		return true;

	return false;
};

Spry.Effect.enforceVisible = function(element) {
	var propDisplay = Spry.Effect.getStyleProp(element, 'display');
	if(propDisplay && propDisplay.toLowerCase() == 'none')
		Spry.Effect.setStyleProp(element, 'display', 'block');

	var propVisible = Spry.Effect.getStyleProp(element, 'visibility');
	if(propVisible && propVisible.toLowerCase() == 'hidden')
		Spry.Effect.setStyleProp(element, 'visibility', 'visible');
};

Spry.Effect.makeClipping = function(element) {
	var overflow = Spry.Effect.getStyleProp(element, 'overflow');
	if(!overflow || (overflow.toLowerCase() != 'hidden' && overflow.toLowerCase() != 'scroll')) {
		// IE 7 bug: set overflow property to hidden changes the element height to 0
		// -> therefore we save the height before changing the overflow property and set the old size back
		var heightCache = 0;
		var needsCache = /MSIE 7.0/.test(navigator.userAgent) && /Windows NT/.test(navigator.userAgent);
		if(needsCache)
			heightCache = Spry.Effect.getDimensionsRegardlessOfDisplayState(element).height;

		Spry.Effect.setStyleProp(element, 'overflow', 'hidden');

		if(needsCache)
			Spry.Effect.setStyleProp(element, 'height', heightCache+'px');
	}
};

Spry.Effect.cleanWhitespace = function(element)  {
	var childCountInit = element.childNodes.length;
  for (var i = childCountInit - 1; i >= 0; i--) {
  	var node = element.childNodes[i];
		if(node.nodeType == 3 && !/\S/.test(node.nodeValue))
			try {
				element.removeChild(node);
			}
			catch (e) {Spry.Effect.Utils.showError('Spry.Effect.cleanWhitespace: ' + e);}
	}
};

Spry.Effect.getComputedStyle = function(element) {
	return /MSIE/.test(navigator.userAgent) ? element.currentStyle : document.defaultView.getComputedStyle(element, null);
};

Spry.Effect.getDimensions = function(element) {
	var dimensions = new Spry.Effect.Utils.Rectangle;
	var computedStyle = null;

	if(element.style.width && /px/i.test(element.style.width))
		dimensions.width = parseInt(element.style.width, 10); // without padding
	else {
		computedStyle = Spry.Effect.getComputedStyle(element);
		var tryComputedStyle = computedStyle && computedStyle.width && /px/i.test(computedStyle.width);

		if(tryComputedStyle)
			dimensions.width = parseInt(computedStyle.width, 10); // without padding, includes css

		if(!tryComputedStyle || dimensions.width == 0) // otherwise we might run into problems on safari and opera (mac only)
			dimensions.width = element.offsetWidth;   // includes padding
	}

	if(element.style.height && /px/i.test(element.style.height))
		dimensions.height = parseInt(element.style.height, 10); // without padding
	else {
		if(!computedStyle)
			computedStyle = Spry.Effect.getComputedStyle(element);

		var tryComputedStyle = computedStyle && computedStyle.height && /px/i.test(computedStyle.height);

		if(tryComputedStyle)
			dimensions.height = parseInt(computedStyle.height, 10); // without padding, includes css

		if(!tryComputedStyle || dimensions.height == 0) // otherwise we might run into problems on safari and opera (mac only)
			dimensions.height = element.offsetHeight;   // includes padding
	}
	return dimensions;
};

Spry.Effect.getDimensionsRegardlessOfDisplayState = function(element, displayElement) {
	// If the displayElement display property is set to 'none', we temporarily set its
	// visibility state to 'hidden' to be able to calculate the dimension.

	var refElement = displayElement ? displayElement : element;
	var displayOrig = Spry.Effect.getStyleProp(refElement, 'display');
	var visibilityOrig = Spry.Effect.getStyleProp(refElement, 'visibility');

	if(displayOrig == 'none') {
		Spry.Effect.setStyleProp(refElement, 'visibility', 'hidden');
		Spry.Effect.setStyleProp(refElement, 'display', 'block');

		if(window.opera) // opera needs focus to calculate the size for hidden elements
			refElement.focus();
	}

	var dimensions = Spry.Effect.getDimensions(element);

	if(displayOrig == 'none') { // reset the original values
		Spry.Effect.setStyleProp(refElement, 'display', 'none');
		Spry.Effect.setStyleProp(refElement, 'visibility', visibilityOrig);
	}
	return dimensions;
};

Spry.Effect.getOpacity = function(element) {
  var o = Spry.Effect.getStyleProp(element, "opacity");
  if(typeof o == 'undefined' || o == null)
    o = 1.0;
  return o;
};

Spry.Effect.getBgColor = function(ele) {
  return Spry.Effect.getStyleProp(ele, "background-color");
};

Spry.Effect.intPropStyle = function(e, prop){
		var i = parseInt(Spry.Effect.getStyleProp(e, prop), 10);
		if(isNaN(i))
			return 0;
		return i;
};

Spry.Effect.getPosition = function(element) {
	var position = new Spry.Effect.Utils.Position;
	var computedStyle = null;

	if(element.style.left  && /px/i.test(element.style.left))
		position.x = parseInt(element.style.left, 10); // without padding
	else {
		computedStyle = Spry.Effect.getComputedStyle(element);
		var tryComputedStyle = computedStyle && computedStyle.left && /px/i.test(computedStyle.left);

		if(tryComputedStyle)
			position.x = parseInt(computedStyle.left, 10); // without padding, includes css

		if(!tryComputedStyle || position.x == 0) // otherwise we might run into problems on safari and opera (mac only)
			position.x = element.offsetLeft;   // includes padding
	}

	if(element.style.top && /px/i.test(element.style.top))
		position.y = parseInt(element.style.top, 10); // without padding
	else {
		if(!computedStyle)
			computedStyle = Spry.Effect.getComputedStyle(element);

    var tryComputedStyle = computedStyle && computedStyle.top && /px/i.test(computedStyle.top);

		if(tryComputedStyle)
			position.y = parseInt(computedStyle.top, 10); // without padding, includes css

		if(!tryComputedStyle || position.y == 0) // otherwise we might run into problems on safari and opera (mac only)
			position.y = element.offsetTop;   // includes padding
	}
	return position;
};

Spry.Effect.getOffsetPosition = Spry.Effect.getPosition; // deprecated

//////////////////////////
// Spry.Effect.Animator
// (base class)
//////////////////////////

Spry.Effect.Animator = function(options) {
	Spry.Utils.Notifier.call(this);
	
	this.name = 'Animator';
	this.element = null;
	this.startMilliseconds = 0;
	this.repeat = 'none';
	this.isRunning = false;
	this.timer = null;
	this.cancelRemaining = 0;

	if(!options)
		var options = {};

	if(options.toggle)
		this.direction = false;
	else
		this.direction = Spry.forwards;
	
	var self = this;
	if(options.setup != null)
		this.addObserver({onPreEffect: function(){try{self.options.setup(self.element, self);}catch(e){Spry.Effect.Utils.showError('Spry.Effect.Animator.prototype.start: setup callback: ' + e);}}});

	if(options.finish != null)
		this.addObserver({onPostEffect: function(){try{self.options.finish(self.element, self);}catch(e){Spry.Effect.Utils.showError('Spry.Effect.Animator.prototype.stop: finish callback: ' + e);}}});

	this.options = {
		duration: 1000,
		toggle: false,
		transition: Spry.linearTransition,
		interval: 16 // ca. 62 fps
	};

	this.setOptions(options);
	if(options.transition)
		this.setTransition(options.transition);

	if(options.fps)
		this.setFps(options.fps);
};
Spry.Effect.Animator.prototype = new Spry.Utils.Notifier();
Spry.Effect.Animator.prototype.constructor = Spry.Utils.Animator;

Spry.Effect.Animator.prototype.notStaticAnimator = true;

Spry.Effect.Animator.prototype.setOptions = function(options) {
	if(!options)
		return;
	for (var prop in options)
		this.options[prop] = options[prop];
};
Spry.Effect.Animator.prototype.setTransition = function(transition){
	if(typeof transition == 'number' || transition == "1" || transition == "2")
		switch (parseInt(transition,10)) {
			case 1: transition = Spry.linearTransition; break;
			case 2: transition = Spry.sinusoidalTransition; break;
			default: Spry.Effect.Utils.showError('unknown transition');
		}

	else if(typeof transition == 'string') {
		if(typeof window[transition] == 'function')
			transition = window[transition];
		else if(typeof Spry[transition] == 'function')
			transition = Spry[transition];
		else
			Spry.Effect.Utils.showError('unknown transition');
	}

	this.options.transition = transition;
	if(typeof this.effectsArray != 'undefined'){
		var l = this.effectsArray.length;
		for (var i = 0; i < l; i++)
				this.effectsArray[i].effect.setTransition(transition);
	}
};

Spry.Effect.Animator.prototype.setDuration = function(duration){
	this.options.duration = duration;
	if(typeof this.effectsArray != 'undefined') {
		var l = this.effectsArray.length;
		for (var i = 0; i < l; i++) {
			this.effectsArray[i].effect.setDuration(duration);
		}
	}
};

Spry.Effect.Animator.prototype.setFps = function(fps){
	this.options.interval = parseInt(1000 / fps, 10);
	this.options.fps = fps;
	if(typeof this.effectsArray != 'undefined') {
		var l = this.effectsArray.length;
		for (var i = 0; i < l; i++) {
			this.effectsArray[i].effect.setFps(fps);
		}
	}
};

Spry.Effect.Animator.prototype.start = function(withoutTimer) {
	if(!this.element)
		return;

	if(arguments.length == 0)
		withoutTimer = false;

	if(this.isRunning)
		this.cancel();

	this.prepareStart();
	var currDate = new Date();
	this.startMilliseconds = currDate.getTime();

	if(this.element.id)
		this.element = document.getElementById(this.element.id);

	if(this.cancelRemaining != 0 && this.options.toggle) {
		if(this.cancelRemaining < 1 && typeof this.options.transition == 'function') {
			var startTime = 0;
			var stopTime = this.options.duration;
			var start = 0;
			var stop = 1;
			var emergency = 0;
			this.cancelRemaining = Math.round(this.cancelRemaining * 1000) / 1000;
			var found = false;
			var middle = 0;
			while (!found) {
				if(emergency++ > this.options.duration) break;
				var half = startTime + ((stopTime - startTime) / 2);
				middle = Math.round(this.options.transition(half, 1, -1, this.options.duration) * 1000) / 1000;
				if(middle == this.cancelRemaining) {
					this.startMilliseconds -= half;
					found = true;
				}
				if(middle < this.cancelRemaining) {
					stopTime = half;
					stop = middle;
				}
				else {
					startTime = half;
					start = middle;
				}
			}
		}
		this.cancelRemaining = 0;
	}
	this.notifyObservers('onPreEffect', this);

	if(withoutTimer == false) {
		var self = this;
		this.timer = setInterval(function() { self.drawEffect(); }, this.options.interval);
	}
	this.isRunning = true;
};
Spry.Effect.Animator.prototype.stopFlagReset = function() {
	if(this.timer) {
		clearInterval(this.timer);
		this.timer = null;
	}
	this.startMilliseconds = 0;
};
Spry.Effect.Animator.prototype.stop = function() {
	this.stopFlagReset();
	this.notifyObservers('onPostEffect', this);
	this.isRunning = false;
};

Spry.Effect.Animator.prototype.cancel = function() {
	var elapsed = this.getElapsedMilliseconds();
	if(this.startMilliseconds > 0 && elapsed < this.options.duration)
		this.cancelRemaining = this.options.transition(elapsed, 0, 1, this.options.duration);

	this.stopFlagReset();
	this.notifyObservers('onCancel', this);
	this.isRunning = false;
};

Spry.Effect.Animator.prototype.drawEffect = function() {
	var isRunning = true;

	this.notifyObservers('onStep', this);
	var timeElapsed = this.getElapsedMilliseconds();

	if(typeof this.options.transition != 'function'){
		Spry.Effect.Utils.showError('unknown transition');
		return;
	}
	this.animate();

	if(timeElapsed > this.options.duration) {
		isRunning = false;
		this.stop();
	}
	return isRunning;
};

Spry.Effect.Animator.prototype.getElapsedMilliseconds = function() {
	if(this.startMilliseconds > 0) {
		var currDate = new Date();
		return (currDate.getTime() - this.startMilliseconds);
	}
	return 0;
};

Spry.Effect.Animator.prototype.doToggle = function() {
	if(!this.direction) {
		this.direction = Spry.forwards;
		return;
	}
	if(this.options.toggle == true) {
		if(this.direction == Spry.forwards) {
			this.direction = Spry.backwards;
			this.notifyObservers('onToggle', this);
		} 
		else if(this.direction == Spry.backwards) {
			this.direction = Spry.forwards;
		}
	}
};

Spry.Effect.Animator.prototype.prepareStart = function() {
		if(this.options && this.options.toggle)
			this.doToggle();
};

Spry.Effect.Animator.prototype.animate = function(){};
Spry.Effect.Animator.prototype.onStep = function(el) {
	if(el != this)
		this.notifyObservers('onStep', this);
};

/////////////////////////
// Spry.Effect.Opacity
/////////////////////////

Spry.Effect.Opacity = function(element, startOpacity, stopOpacity, options) {
	this.dynamicStartOpacity = false;
	if(arguments.length == 3) {
		options = stopOpacity;
		stopOpacity = startOpacity;
		startOpacity = Spry.Effect.getOpacity(element);
		this.dynamicStartOpacity = true;
	}

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

	this.name = 'Opacity';
	this.element = Spry.Effect.getElement(element);
	if(!this.element)
		return;

 	// make this work on IE on elements without 'layout'
	if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout))
		Spry.Effect.setStyleProp(this.element, 'zoom', '1');

	this.startOpacity = startOpacity;
	this.stopOpacity = stopOpacity;
	this.enforceVisible = Spry.Effect.isInvisible(this.element);
};

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

Spry.Effect.Opacity.prototype.animate = function() {
	var opacity = 0;
	var elapsed = this.getElapsedMilliseconds();
	if(this.direction == Spry.forwards) 
		opacity = this.options.transition(elapsed, this.startOpacity, this.stopOpacity - this.startOpacity, this.options.duration);
	else if(this.direction == Spry.backwards) 
		opacity = this.options.transition(elapsed, this.stopOpacity, this.startOpacity - this.stopOpacity, this.options.duration);

	if(opacity < 0)
		opacity = 0;

	if(/MSIE/.test(navigator.userAgent)) {
		var tmpval = Spry.Effect.getStyleProp(this.element,'filter');
		if(tmpval){
			tmpval = tmpval.replace(/alpha\(opacity=[0-9]{1,3}\)/g, '');
		}
		this.element.style.filter = tmpval + "alpha(opacity=" + Math.floor(opacity * 100) + ")";
	}
	else
		this.element.style.opacity = opacity;

	if(this.enforceVisible) {
		Spry.Effect.enforceVisible(this.element);
		this.enforceVisible = false;
	}
};

Spry.Effect.Opacity.prototype.prepareStart = function() {
	if(this.options && this.options.toggle)
		this.doToggle();	

	if(this.dynamicStartOpacity == true) {
		this.startOpacity = Spry.Effect.getOpacity(this.element);
		this.opacityRange = this.startOpacity - this.stopOpacity;
	}
};

/////////////////////////
// Spry.Effect.Cluster
////////////////////////

Spry.Effect.Cluster = function(options) {
	Spry.Effect.Animator.call(this, options);

	this.name = 'Cluster';
	this.effectsArray = new Array();
	this.currIdx = -1;
	var _ClusteredEffect = function(effect, kind) {
		this.effect = effect;
		this.kind = kind; // "parallel" or "queue"
		this.isRunning = false;
	};

	this.ClusteredEffect = _ClusteredEffect;
};

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

Spry.Effect.Cluster.prototype.setInterval = function(interval){
	var l = this.effectsArray.length;
	this.options.interval = interval;
	for (var i = 0; i < l; i++) {
		this.effectsArray[i].effect.setInterval(interval);
	}
};
Spry.Effect.Cluster.prototype.drawEffect = function() {
	var isRunning = true;
	var allEffectsDidRun = false;
	var baseEffectIsStillRunning = false;
	var evalNextEffectsRunning = false;

	if((this.currIdx == -1 && this.direction == Spry.forwards) || (this.currIdx == this.effectsArray.length && this.direction == Spry.backwards))
		this.initNextEffectsRunning();

	var start = this.direction == Spry.forwards ? 0 : this.effectsArray.length-1;
	var stop = this.direction == Spry.forwards ? this.effectsArray.length : -1;
	var step = this.direction == Spry.forwards ? 1 : -1;
	for (var i = start; i != stop; i+=step) {
		if(this.effectsArray[i].isRunning == true) {
			baseEffectIsStillRunning = this.effectsArray[i].effect.drawEffect();
			if(baseEffectIsStillRunning == false && i == this.currIdx) {
				this.effectsArray[i].isRunning = false;
				evalNextEffectsRunning = true;
			}
		}
	}

	if(evalNextEffectsRunning == true)
		allEffectsDidRun = this.initNextEffectsRunning();

	if(allEffectsDidRun == true) {
		this.stop();
		isRunning = false;
		for (var i = 0; i < this.effectsArray.length; i++)
			this.effectsArray[i].isRunning = false;

		this.currIdx = this.direction == Spry.forwards ? this.effectsArray.length: -1;
	}
	return isRunning;
};

Spry.Effect.Cluster.prototype.initNextEffectsRunning = function() {
	var allEffectsDidRun = false;
	var step = this.direction == Spry.forwards ? 1 : -1;
	var stop = this.direction == Spry.forwards ? this.effectsArray.length : -1;
	this.currIdx+=step;
	if( (this.currIdx > (this.effectsArray.length - 1) && this.direction == Spry.forwards) || (this.currIdx < 0 && this.direction == Spry.backwards))
		allEffectsDidRun = true;
	else
		for (var i = this.currIdx; i != stop; i+=step) {
			if((i > this.currIdx && this.direction == Spry.forwards || i < this.currIdx && this.direction == Spry.backwards) && this.effectsArray[i].kind == "queue")
				break;
			this.effectsArray[i].effect.start(true);
			this.effectsArray[i].isRunning = true;
			this.currIdx = i;
		}

	return allEffectsDidRun;
};

Spry.Effect.Cluster.prototype.toggleCluster = function() {
	if(!this.direction) {
		this.direction = Spry.forwards;
		return;
	}

	if(this.options.toggle == true) {
		if(this.direction == Spry.forwards) {
			this.direction = Spry.backwards;
			this.notifyObservers('onToggle', this);
			this.currIdx = this.effectsArray.length;
		}
		else if(this.direction == Spry.backwards) {
			this.direction = Spry.forwards;
			this.currIdx = -1;
		}
	}
	else {
		if(this.direction == Spry.forwards)
			this.currIdx = -1;
		else if(this.direction == Spry.backwards)
			this.currIdx = this.effectsArray.length;
	}
};

Spry.Effect.Cluster.prototype.doToggle = function() {
	this.toggleCluster();

	// toggle all effects of the cluster, too
	for (var i = 0; i < this.effectsArray.length; i++) {
		if(this.effectsArray[i].effect.options && (this.effectsArray[i].effect.options.toggle != null))
			if(this.effectsArray[i].effect.options.toggle == true)
				this.effectsArray[i].effect.doToggle();
	}
};

Spry.Effect.Cluster.prototype.cancel = function() {
	for (var i = 0; i < this.effectsArray.length; i++)
		if(this.effectsArray[i].effect.isRunning)
			this.effectsArray[i].effect.cancel();
	
	var elapsed = this.getElapsedMilliseconds();
	if(this.startMilliseconds > 0 && elapsed < this.options.duration)
		this.cancelRemaining = this.options.transition(elapsed, 0, 1, this.options.duration);
	this.stopFlagReset();
	this.notifyObservers('onCancel', this);
	this.isRunning = false;
};

Spry.Effect.Cluster.prototype.addNextEffect = function(effect) {
	effect.addObserver(this);
	this.effectsArray[this.effectsArray.length] = new this.ClusteredEffect(effect, "queue");
	if(this.effectsArray.length == 1) {
		// with the first added effect we know the element
		// that the cluster is working on
		this.element = effect.element;
	}
};

Spry.Effect.Cluster.prototype.addParallelEffect = function(effect) {
	if(this.effectsArray.length == 0 || this.effectsArray[this.effectsArray.length-1].kind != 'parallel')
		effect.addObserver(this);

	this.effectsArray[this.effectsArray.length] = new this.ClusteredEffect(effect, "parallel");
	if(this.effectsArray.length == 1) {
		// with the first added effect we know the element
		// that the cluster is working on
		this.element = effect.element;
	}
};

Spry.Effect.Cluster.prototype.prepareStart = function() {
	this.toggleCluster();
};

/////////////////////////
// Combination effects
// Custom effects can be build by combining basic effect bahaviour
// like Move, Size, Color, Opacity
/////////////////////////

Spry.Effect.Fade = function (element, options)  {
	if(!this.notStaticAnimator)
		return Spry.Effect.Utils.showInitError('Fade');

	Spry.Effect.Cluster.call(this, options);

	this.name = 'Fade';
	var element = Spry.Effect.getElement(element);
	this.element = element;
	if(!this.element)
		return;
	var durationInMilliseconds = 1000;
	var fromOpacity = 0.0;
	var toOpacity = 100.0;
	var doToggle = false;
	var transition = Spry.fifthTransition;
	var fps = 60;
	var originalOpacity = 0;
	if(/MSIE/.test(navigator.userAgent))
		originalOpacity = parseInt(Spry.Effect.getStylePropRegardlessOfDisplayState(this.element, 'filter').replace(/alpha\(opacity=([0-9]{1,3})\)/g, '$1'), 10);
	else
		originalOpacity = parseInt(Spry.Effect.getStylePropRegardlessOfDisplayState(this.element, 'opacity') * 100, 10);

	if(isNaN(originalOpacity))
		originalOpacity = 100;

	if(options) {
		if(options.duration != null) durationInMilliseconds = options.duration;
		if(options.from != null){
			if(Spry.Effect.Utils.isPercentValue(options.from))
				fromOpacity = Spry.Effect.Utils.getPercentValue(options.from) * originalOpacity / 100;
			else
				fromOpacity = options.from;
		}
		if(options.to != null) {	
			if(Spry.Effect.Utils.isPercentValue(options.to))
				toOpacity = Spry.Effect.Utils.getPercentValue(options.to) * originalOpacity / 100;
			else
				toOpacity = options.to;
		}
		if(options.toggle != null) doToggle = options.toggle;
		if(options.transition != null) transition = options.transition;
		if(options.fps != null) fps = options.fps;
		else this.options.transition = transition;
	}

	fromOpacity = fromOpacity/ 100.0;
	toOpacity = toOpacity / 100.0;

	options = {duration: durationInMilliseconds, toggle: doToggle, transition: transition, from: fromOpacity, to: toOpacity, fps: fps};
	var fadeEffect = new Spry.Effect.Opacity(element, fromOpacity, toOpacity, options);
	this.addNextEffect(fadeEffect);
};

Spry.Effect.Fade.prototype = new Spry.Effect.Cluster();
Spry.Effect.Fade.prototype.constructor = Spry.Effect.Fade;

////////////////////////////////////////////////
// The names of some of the static effect functions 
// changed in Spry 1.5. These wrappers will insure that we
// remain compatible with previous versions of Spry.
////////////////////////////////////////////////

Spry.Effect.DoFade = function (element, options) {
	return Spry.Effect.Utils.DoEffect('Fade', element, options);
};
