/*
 * Effect Manager, binds an effect to an element.
 * WIP:
 * Slide, Fade
 */


/*

	Use Timeouts to avoid long loops with complex code making the browser freeze. 
	
	
	Timeouts do not perform the action AT the specific timeout, they perform it at the first available time afterwards. 
	
	Will be mostly slower than a normal loop but will stop the browser freezing, and seem better to the user because the browser wont freeze.
	
	
	
	
	
	Usage:
	
	new Loop(0, 100, function(i) {			//This function will get called multiple times and passed the current
		alert(i);							//Counter value as the first parameter
	});
	
	to replace:
	
	for (var i = 0; i < 100; i++) {
		alert(i);
	}
	
	
	However, it's not without limitations:
	
	var x = 0;
	
	new Loop(0, 10, function(i) { 
		x++;
	});
	
	alert(x); //x == 0 - Because the code will run after a time, the alert will happen before the timeout is reached.
	
	
	
	To workaround, this is possible:
	
	var x = 0;
	var loop1 = new Loop(0, 10, function(i) { 
		x++;
	});
	
	
	loop1.oncomplete = function() {
		alert(x); //x == 10 - Correct. This is because the variable 'x' exists and is a pointer to the same value in both closures used for the loop
	}
		
	

	
	To count downwards, use a larger minimum value than maximum value e.g.
	
	new Loop(10, 0, function(i) {});
	
	
	
	Limitations:

	it can't do preincrement loops such as 
	
	for (var i=0; i < 10; ++i) {
		//alert(i); //i == 1
	}
	
	It cant do greater than or equal too loops
	
	

*/


Loop.prototype.min = null;
Loop.prototype.max = null;
Loop.prototype.count = null;
Loop.prototype.callback = null;
Loop.prototype.modifier = null;
Loop.prototype.breaked = false;
Loop.prototype.complete = false;
Loop.prototype.oncomplete = function() { }
Loop.activeLoops = 0;
Loop.finalComplete = function() {}
Loop.always = function() {}
Loop.queue = new Array();
Loop.active = false;

function Loop(min, max, callback, modifier, delay) {
	Loop.activeLoops++;
	this.delay = delay || 1;	
	this.min = min;
	this.max = max;
	this.callback = callback;	

	if (min > max) {	
		this.modifier = 0-modifer || -1;
		this.count = this.max;
	}
	else if (max > min) {
		this.modifier =  modifier || 1;
		this.count = min;
	}
	else {
		this.modifier = 0;
		///Don't loop, min == max
		var self = this;
		this.complete = true;
		setTimeout(function() { self.oncomplete() }, 1);
		return;
	}
	
	var self = this;	
	//when a new loop is created add it to the end of the queue
	Loop.queue.push(this);
	if (!Loop.active) {
		Loop.run();	
	}
}

Loop.run = function() {
	if (Loop.queue.length > 0) {
		Loop.active = true;
		var lp = Loop.queue.shift();
		if (lp) {
			setTimeout(function() { lp.increment(); Loop.always(); Loop.run(); }, lp.delay);
		}
	}
	else {
		this.active = false;
		this.finalComplete();	
	}
}


Loop.prototype.increment = function() {
	//when more than one LOOP object is created at the same time, avoid problems with execution order.
	if (this.callback(this.count) == false)  {
		this.complete = true;
		Loop.activeLoops--;
		this.breaked = true;
		this.oncomplete();		
	}
	else {
		var self = this;
		if (this.count < this.max && this.count != (this.max - this.modifier) && !this.breaked)  {
			//Put this loop to the front of the queue
			Loop.queue.unshift(this);
						
			if (!Loop.active) {
				Loop.active = true;
				Loop.run();
			}
		}
		else {
			this.complete = true;
			Loop.activeLoops--;
			this.oncomplete();
		}		
	}
	this.count += this.modifier;		
}


function ElementEffect(effect) {
	this.effect = effect;

	this.run = function(element) {
		this.effect.element = element;
		
		if (this.effect.element.computedStyle().display == 'none') method = 'show';
		else method = 'hide';
		
		this.effect[method]();
	}
}

/*
 * Slide Effect (C) Redsky 2009 
 */
function Effect_Slide() {
	this.element;
	this.speed = 50;
	this.step = 6;	
	
	this.show = function() {				
		this.element.style.display = 'block';
		this.element.style.height = '0px';
		this.element.style.overflow = 'hidden';
		
		var self = this;
		var height = 0;
		
		var slide = function() {
			height += self.step;
			self.element.style.height = height + 'px';
			self.element.scrollTop = self.element.scrollHeight;
			
			if (self.element.scrollTop > 0) setTimeout(slide, this.speed);
			else {
				self.element.style.height = 'auto';
			}	
		}
		slide();
	}

	this.hide = function() {
		var self = this;
		var height = this.element.offsetHeight;
		this.element.style.overflow = 'hidden';
		
		var slide = function() {
			height -= self.step;
			
			if (height > 0) self.element.style.height = height + 'px';
			self.element.scrollTop = self.element.scrollHeight;
			
			if (height < 1) self.element.style.display = 'none';
			
			if (self.element.offsetHeight > 0) setTimeout(slide, this.speed);
		}		
		slide();
	}
}
 
function Effect_Fade() {
	this.element;
	this.speed = 30;
	this.step = 6;
	this.opacity = 0;
	this.oncomplete = function() {};
	
	this.show = function() {
		
		
		
		if (this.element.fading) return;

		
		
		this.opacity = 0;
		this.element.style.opacity = 0;
		var self = this;
		
		var fade = function() {
		
			self.element.fading = true;
			if (self.opacity < 100) {
				self.opacity += self.step;
				self.element.setOpacity(self.opacity/100);
				setTimeout(fade, self.speed);
				
			}
			else {
				self.element.fading = false;
				self.oncomplete();
			}
		}
		
		fade();
	}
	
	this.hide = function() {
		if (this.element.fading) return;
		
		this.opacity = 100;
		var self = this;
		var fade = function() {			
			self.element.fading = true;
			if (self.opacity > 0) {
				self.opacity -= self.step;
				self.element.style.opacity = (self.opacity/100);
				setTimeout(fade, self.speed)
			}
			else {
				self.element.fading = false;
				self.oncomplete();
			}
		}
		
		fade();		
	}
}

$.elementPrototype.fade = function(args) {
	if (!args) args = {};
	var effect = new Effect_Fade();
	effect.element = this;
	
	for (var i in args) {
		effect[i] = args[i];
	}
	
	if (!args.type) args.type = 'show';
	
	if (args.type == 'show') effect.show();
	else if (args.type == 'hide') effect.hide();
	
}

function Effect_FadeCycle(elements) {
	this.index = 0;
	this.interval = 10000;
	
	this.elements = elements;
	this.elements.item(0).style.display = 'block';	

	this.run = function() {
		var self = this;
		this.elements.item(this.index).fade({type: 'hide', speed: 25, step: 1, oncomplete: function(){ self.next(); } });
	}
	
	this.start = function() {
		var self = this;
		setTimeout(function() { self.run(); }, this.interval);
	}
	
	this.next = function() {
		this.elements.item(this.index).style.display = 'none';
		this.index++
		if (!this.elements.item(this.index)) this.index = 0;
		
		
		this.elements.item(this.index).fade({type: 'show', speed: 25, step: 1});
		this.elements.item(this.index).style.display = 'block';
		var self = this;
		setTimeout(function() { self.run(); }, this.interval);
	}
}


function Effect_Move(element) {
	this.element = element;
	this.step = 5;
	this.speed = 20;
	
	this.slide = function(direction, amount) {
		
		
		var origMargin = parseInt(this.element.style.marginLeft.split('px')[0]);
		if (isNaN(origMargin)) origMargin = 0;
		
		if (direction == -1 && origMargin == 0) return;
		var pos = this.element.position();
		
		//console.log((Math.abs(origMargin) + direction )+ ' ' + (pos.width - amount))
		if ((Math.abs(origMargin) + direction) >= (pos.width - amount)) return;
		
		var self = this;
		var slide = function() {
			if (Math.abs(self.element.style.marginLeft.split('px')[0]) != Math.abs(Math.abs(origMargin) + (direction*amount))) {
				self.element.style.marginLeft = (self.element.style.marginLeft.split('px')[0] - (direction*self.step)) + 'px';
				//console.log(self.element.style.marginLeft);
				setTimeout(slide, self.speed);				
			}
		}
		
		slide();		
	}
	
	this.translate = function(x, y, timeout) {
		
	}
}

$.elementPrototype.move = function(args) {
	var effect = new Effect_Move(this);
	effect.slide(args.direction, args.amount);
}

$.elementPrototype.setOpacity = function(num) {
	this.style.opacity = num;
	this.style.filter = 'alpha(opacity=' + (num*100) + ')';
}


var Effect_CrossFade2 = function(elements) {
	
	
	
	this.elements = elements;

	this.start = function() {
		if (!this.front) return;
		document.body.appendChild(this.front);
		
		//if it's random, put a random one on top in the pool
		var self = this;
		//setTimeout(function() {self.fade();}, this.interval);
		this.fade();
		self.front.appendChild(self.elements.item(self.getNextImage()));
	}
	
	
	this.fade = function() {
		var self = this;
		//var next = this.getNextImage();
		this.front.style.display = 'block';
		this.front.fade({type: 'show',
			oncomplete: function() {
				//self.backgroundDiv.style.backgroundImage = 'url(' + self.elements.item(next).src + ')';
				//move back to pool
				for (var i = 0; i < self.back.childNodes.length; i++) self.pool.appendChild(self.back.childNodes[i]);
				//move front to back
				for (var i = 0; i < self.front.childNodes.length; i++) self.back.appendChild(self.front.childNodes[i]);
				//load a new image in front
				self.front.appendChild(self.elements.item(self.getNextImage()).cloneNode(true));
				self.front.style.display = 'none';
				//self.elements.item(next).style.display = 'none';	
				//
			}
		});
		
		
		setTimeout(function() {self.fade();}, self.interval);
	
	}
	
	
	
	this.getNextImage = function() {
		window.___DEBUG = this.elements;
		if (this.random) {
			var i = 0;
			var rand = Math.round(Math.random()*(this.elements.count()-1));
			while (i < 100 && this.currentImage == rand ) {
				//dont get same image twice
				rand = Math.ceil(Math.random()*this.elements.count()-1);
				i++; //safetey precaution
			}
			
			this.currentImage = rand;
			return this.currentImage;
		}
		else {
			if (this.currentImage = this.elements.count()-1 || this.currentImage == null) this.currentImage = 0;
			else this.currentImage++;			
			return this.currentImage;
		}
	}
	
	if (this.elements.count() == 0) return;
	this.front = new $e('div');
	this.back = new $e('div');
	this.interval = 10000;
	
	this.random = true;

	this.front.style.width = elements.item(0).position().width + 'px';
	
	this.front.style.height = elements.item(0).position().height + 'px';
	
	this.back.style.width = elements.item(0).position().width + 'px';
	this.back.style.height = elements.item(0).position().height + 'px';

	
	
	
	elements.item(0).parentNode.insertBefore(this.back, elements.item(0));
	

	this.pool = new $e('div');	
	
	for (var i = 0; i < elements.count(); i++) {
		this.pool.appendChild(elements.item(i));
	}
	
	
	
	//and put back item0
	//this.back.appendChild(elements.item(0));
	
	//Move front over back
	
	this.front.style.position = 'absolute';
	this.front.style.top = this.back.position().top + 'px';
	this.front.style.left = this.back.position().left + 'px';
	
}



var Effect_CrossFade = function(elements) {
	this.interval = 1000;
	this.backgroundDiv = new $e('div');	
	
	this.backgroundDiv.style.width = elements.item(0).position().width + 'px';
	this.backgroundDiv.style.height = elements.item(0).position().height + 'px';
 

	this.elements = elements;
	elements.item(0).parentNode.insertBefore(this.backgroundDiv, elements.item(0));
	
	for (var i = 0; i < elements.count(); i++) {
		this.backgroundDiv.appendChild(elements.item(i));
	}
	
	
	elements.apply(function() { this.style.display = 'none'; });
	
	this.random = false;
	this.currentImage = null;
	
	this.getNextImage = function() {
		if (this.random) {
			var i = 0;
			var rand = Math.round(Math.random()*(this.elements.count()-1));
			while (i < 100 && this.currentImage == rand ) {
				//dont get same image twice
				rand = Math.ceil(Math.random()*this.elements.count()-1);
				i++; //safetey precaution
			}
			
			this.currentImage = rand;
			return this.currentImage;
		}
		else {
			if (this.currentImage = this.elements.count()-1 || this.currentImage == null) this.currentImage = 0;
			else this.currentImage++;			
			return this.currentImage;
		}
	}
	
	this.start = function() {
		var img = this.getNextImage();
		this.backgroundDiv.style.backgroundImage = 'url(' + this.elements.item(img).src + ')';
		for (var s in img.style) {
			try {
				if (s != 'display') this.backgroundDiv.style[s] = img.style[s];
			}
			catch (e) {
				
			}
		}
		var self = this;
		setTimeout(function() {self.fade();}, this.interval);
	}
	
	this.fade = function() {
		var self = this;
		var next = this.getNextImage();
		this.elements.item(next).fade({type: 'show',
			oncomplete: function() {
				self.backgroundDiv.style.backgroundImage = 'url(' + self.elements.item(next).src + ')';
				
				var img = self.elements.item(next);
				//this.backgroundDiv.style.backgroundImage = 'url(' + self.elements.item(next).src + ')';
				for (var s in img.style) {
					try {
						//if (s != 'display') self.backgroundDiv.style[s] = img.style[s];
					}
					catch (e) {
					}
				}
				
				self.elements.item(next).style.display = 'none';	
				setTimeout(function() {self.fade();}, self.interval);
			}
		});
		
		
		
	}
}


$.elementPrototype.slide = function(args) {
	if (!args) args = {};
	var effect = new Effect_Slide();
	effect.element = this;
	
	for (var i in args) {
		effect[i] = args[i];
	}
	
	if (!args.type && this.computedStyle().display == 'none') args.type = 'show';
	else if (!args.type && this.computedStyle().display != 'none') args.type = 'hide';


	
	if (!this.slideState) {
		//If it's visible
		if (this.offsetHeight > 0) this.slideState = 'shown';
		else this.slideState = 'hidden';
	}

	
	if (args.type == 'show' && this.slideState == 'hidden') effect.show();
	else if (args.type == 'hide' && this.slideState == 'shown') effect.hide();	
}


$.addElementPrototype('toggle', function(args) {
	
	if (this.offsetHeight == 0) var method = 'show';
	else var method = 'hide';

	this[args.type]({type: method, step: 15});
});



/*
 * Slide Effect (C) Redsky 2009 
 */
function Effect_Slide() {
	this.element;
	this.speed = 10;
	this.step = 4;	
	
	this.show = function() {
		this.element.slideState = 'shown';
		this.element.style.display = 'block';
		this.element.style.height = '0px';
		this.element.style.overflow = 'hidden';
		
		var self = this;
		var height = 0;
		
		var slide = function() {
			height += self.step;
			self.element.style.height = height + 'px';
			self.element.scrollTop = self.element.scrollHeight;
			
			if (self.element.scrollTop > 0) setTimeout(slide, this.speed);
			else {
				self.element.style.height = 'auto';
			}
		
		}		
		slide();	
	}
		
	this.hide = function() {
		var self = this;
		this.element.slideState = 'hidden';
		var height = this.element.offsetHeight;
		this.element.style.overflow = 'hidden';
		
		var slide = function() {
			height -= self.step;
			
			if (height > 0) self.element.style.height = height + 'px';
			self.element.scrollTop = self.element.scrollHeight;
			
			if (height < 1) self.element.style.display = 'none';
			
			if (self.element.offsetHeight > 0) setTimeout(slide, this.speed);
		}		
		slide();
	}
}
 


