
if (!window.alchemy) throw "The 'alchemy.js' script is missing.";

alchemy.effects = {};
alchemy.effects.DEFAULT_DURATION = 1000;
alchemy.effects.DEFAULT_INTERVAL = 25;

// Base class for all effect types
alchemy.effects.Effect = function (element, duration, interval) {
	
	var effect = {};

	var element = element || null;
	var duration = duration || alchemy.effects.DEFAULT_DURATION;
	var interval = interval || alchemy.effects.DEFAULT_INTERVAL;
	var startTime = null;
	var running = false;
	
	// Start the effect
	effect.start = function () {
					
		// Setup the effect timer		
		function loop() {
			
			var interrupted = true;
			
			if (running) {
				var currentTime = new Date().getTime();
				var elapsedTime = currentTime - startTime;

				if (duration == null || elapsedTime < duration) {
					effect.loop(elapsedTime);
					setTimeout(loop, interval);
				}
				else {
					interrupted = false;
					running = false;
				}
			}

			if (!running) {
				effect.stopped(interrupted);
				alchemy.notify(effect, "stopped");
			}
		}
		
		// And start it
		running = true;
		this.started();
		alchemy.notify(effect, "started");
		
		startTime = new Date().getTime();
		loop();
	}

	// Stop the effect
	effect.stop = function () {
		running = false;
	}

	// Effect implementation
	effect.loop = function (elapsedTime) {};
	effect.started = function () {};
	effect.stopped = function () {};

	// The DOM element the effect operates on
	effect.getElement = function () { return element; }

	// The timestamp of the moment when the effect was started
	effect.getStartTime = function () { return startTime; }
	
	// Running state: true if the effect is being run, false otherwise
	effect.isRunning = function () { return running; }
	
	// The total duration of the effect, in ms
	effect.getDuration = function () { return duration; }
	effect.setDuration = function (value) { duration = value; }

	// The number of ms between effect iterations
	effect.getInterval = function () { return interval; }
	effect.setInterval = function (value) { interval = value; }
	
	return effect;
}

/* Slides
--------------------------------------------------------------------*/

alchemy.effects._Slide = function (element, xDest, yDest, duration, interval) {
	
	var slide = alchemy.effects.Effect(element, duration, interval);

	var xSrc = element.offsetLeft;
	var ySrc = element.offsetTop;
	var width = xDest - xSrc;
	var height = yDest - ySrc;

	slide.loop = function (elapsedTime) {		
		var step = this.getStep(elapsedTime);
		element.style.left = (xSrc + width * step) + "px";
		element.style.top = (ySrc + height * step) + "px";
	}

	slide.getStep = function (t) {}

	slide.stopped = function (interrupted) {
		if (!interrupted) {
			element.style.left = xDest + "px";
			element.style.top = yDest + "px";
		}		
	}

	return slide;
}

// A linear slide effect
alchemy.effects.LinearSlide = function (element, xDest, yDest, duration, interval) {

	var slide = alchemy.effects._Slide(element, xDest, yDest, duration, interval);
	
	slide.getStep = function (t) {
		return (1 / duration) * t;
	}

	return slide;
}

// A sinusoidal slide effect
alchemy.effects.SinSlide = function (element, xDest, yDest, duration, interval) {
	
	var slide = alchemy.effects._Slide(element, xDest, yDest, duration, interval);
	
	slide.getStep = function (t) {
		return Math.sin((Math.PI / (2 * duration)) * t);
	}

	return slide;
}

/* Fades
--------------------------------------------------------------------*/

// A fade-in/fade-out effect
alchemy.effects.Fade = function (element, startOpacity, endOpacity, duration, interval) {
	
	var fade = alchemy.effects.Effect(element, duration, interval);	
	var period = 1 / fade.getDuration();
	var width = endOpacity - startOpacity;
	
	fade.started = function () {
		element.style.opacity = startOpacity;
		element.style.visibility = "visible";
	}
	
	fade.loop = function (t) {
		var value = startOpacity + (width * period * t);
		alchemy.setOpacity(element, value);
	}

	fade.stopped = function (interrupted) {		
		if (!interrupted) {
			// ????
			//alchemy.setOpacity(element, endOpacity);
		}
	}
	
	return fade;
}

alchemy.effects.BorderColorizer = function (element, startColor, endColor, duration, interval) {
	
	var colorizer = alchemy.effects.Effect(element, duration, interval);
	var period = 1 / colorizer.getDuration();
	
	var rInc = (endColor[0] - startColor[0]) * period;
	var gInc = (endColor[1] - startColor[1]) * period;
	var bInc = (endColor[1] - startColor[1]) * period;
	
	colorizer.started = function () {
		element.style.borderColor = "rgb(" + startColor[0] + "," + startColor[1] + "," + startColor[2] + ")";
	}
	
	colorizer.loop = function (t) {
		
		var r = startColor[0] + rInc * t;
		if (r > 255) r = 255;
		
		var g = startColor[1] + gInc * t;
		if (g > 255) g = 255;
		
		var b = startColor[2] + bInc * t;
		if (b > 255) b = 255;
		
		element.style.borderColor = "rgb(" + r + "," + g + "," + b + ")";
	}
	
	colorizer.stopped = function (interrupted) {
		if (!interrupted) {
			element.style.borderColor = "rgb(" + endColor[0] + "," + endColor[1] + "," + endColor[2] + ")";
		}
	}
	
	return colorizer;
}

/* Grow and shrink
------------------------------------------------------------------------------------------------------*/
alchemy.effects.VERTICAL_RESIZE = 1;
alchemy.effects.HORIZONTAL_RESIZE = 2;
alchemy.effects.FULL_RESIZE = 3;

alchemy.effects.Resize = function (element, direction, change, duration, interval) {
	
	var resize = alchemy.effects.Effect(element, duration, interval);
	var direction = direction || alchemy.effects.VERTICAL_RESIZE;
	var width = 0;
	var height = 0;
	var period = 1 / resize.getDuration();
	var change = change ? change : 1;

	var resizeH = (direction != alchemy.effects.VERTICAL_RESIZE);
	var resizeV = (direction != alchemy.effects.HORIZONTAL_RESIZE);

	resize.started = function () {
		
		if (resizeH) {
			//element.style.width = "auto";
			width = element.offsetWidth || width;
			if (change == 1) element.style.width = "0px";
		}

		if (resizeV) {
			//element.style.height = "auto";
			height = element.offsetHeight || height;
			if (change == 1) element.style.height = "0px";
		}

		// Delay the menu appearence (courtesy of IE's rendering bugs...)
		setTimeout(function () { element.style.visibility = "visible"; }, 25);
	}
	
	resize.loop = function (t) {
		
		if (resizeH) {
			var size = Math.round(width * period * t);
			element.style.width = (change == 1 ? size : width - size) + "px";
		}
		if (resizeV) {
			var size = Math.round(height * period * t);
			element.style.height = (change == 1 ? size : height - size) + "px";
		}
	}

	resize.stopped = function (interrupted) {
		
		if (!interrupted) {
			
			// Completely hide shrinking elements after the animation ends
			if (change == -1) {
				element.style.visibility = "hidden";
			}
			
			if (resizeH) element.style.width = width + "px";
			if (resizeV) element.style.height = height + "px";
		}		
	}

	return resize;
}

