/**
 * Managed cycles - http://creapage.net/opensource/jquery-managedcycles/ .
 * Copyright (c) 2010 Thomas Mur
 * Licensed under the MIT or GPL Version 2 licenses, or Creative Commons "Attribution 3.0" http://creativecommons.org/licenses/by/3.0/ .
 * @author Thomas Mur http://creapage.net/
 * @version 2010-10
 * Using jQuery, and jquery.scrollTo (from Ariel Flesler http://flesler.blogspot.com).
 */

// ###
// ### Class McFadeTransition
// ###
function McFadeTransition(duration) {
	this.Duration = duration;
}

McFadeTransition.prototype = {
	init: function(slideshow, selector) {
		slideshow.$selector.css("position", "relative");
		var item = slideshow.FirstItem;
		do {
			item = item.NextItem;
			item.$item.hide();
			item.$item.css({"position": "absolute", "top": 0, "left": 0});
		} while(!item.IsLast);
	},

	cycle: function(slideshow, callback) {
		if (slideshow.CurrentItem.IsLast) {
			if (slideshow.OnShowCallback !== undefined)
				slideshow.OnShowCallback(slideshow.FirstItem.$item);
			slideshow.CurrentItem.$item.fadeOut(this.Duration, callback);
			slideshow.CurrentItem = slideshow.FirstItem;
		} else {
			var prevItem = slideshow.CurrentItem;
			slideshow.CurrentItem = slideshow.CurrentItem.NextItem;
			if (slideshow.OnShowCallback !== undefined)
				slideshow.OnShowCallback(slideshow.CurrentItem);
			slideshow.CurrentItem.$item.fadeIn(this.Duration, function() {
				try {
					if (!prevItem.IsFirst)
						prevItem.$item.hide();
				} catch(e) {
					alert(e);
				}
				callback();
			});
		}
	}
};

// ###
// ### Class McScrollTransition
// ###
function McScrollTransition(duration) {
	this.Duration = duration;
}

McScrollTransition.prototype = {
	init: function(slideshow, selector) {
		var $firstItem = slideshow.FirstItem.$item;
		var $ul = $(selector + " > ul");
		var $allLi = $(selector + " > ul > li");
		$allLi.css({"width": $firstItem.width(), "height": $firstItem.height()});
		$firstItem.clone().appendTo($ul);
		var offset1 = $firstItem.offset();
		var offset2 = slideshow.FirstItem.NextItem.$item.offset();
		this.ScrollWidth = offset2.left - offset1.left;
//$("div.d1").append("A" + this.ScrollWidth);
		this.scrollFirstItem = offset1.left - $ul.offset().left;
//$("div.d1").append(" first: " + this.scrollFirstItem);
		slideshow.$selector.attr("scrollLeft", this.scrollFirstItem);
	},

	cycle: function(slideshow, callback) {
		var nextScrollX = slideshow.$selector.attr("scrollLeft") + this.ScrollWidth;
//$("div.d1").append(" [" + nextScrollX + "]");
		if (nextScrollX < 0)
			throw "Next scroll X value should not be negative (" + nextScrollX + ")";
		var nextItem = slideshow.CurrentItem.IsLast ? slideshow.FirstItem : slideshow.CurrentItem.NextItem;
		if (slideshow.OnShowCallback !== undefined)
			slideshow.OnShowCallback(nextItem.$item);
		var gthis = this;
		slideshow.$selector.scrollTo(nextScrollX, this.Duration, {axis: 'x', onAfter: function() {
			try {
//$("div.d1").append(" A" + slideshow.$selector.attr("scrollLeft"));
				if (slideshow.CurrentItem.IsLast) {
//$("div.d1").append(" LAST");
					slideshow.$selector.attr("scrollLeft", gthis.scrollFirstItem);
				}
				slideshow.CurrentItem = nextItem;
			} catch(e) {
				alert(e);
			}
			callback();
		}});
	}
};

// ###
// ### Class McSlideshow
// ###
function McSlideshow(selector, transition) {
	// - properties
	this.Transition = transition;
	this.FirstItem = null;
	this.CurrentItem = null;
	this.Initialized = false;
	this.PauseOnHover = false;
	// - css
	this.$selector = $(selector);
	this.$selector.css("overflow", "hidden");
	// - create items
	var first = true, prevItem;
	var gthis = this;
	this.$allLi = $(selector + " > ul > li");
	this.$allLi.each(function() {
		var item = new McSlideshowItem($(this), first);
		if (first) {
			gthis.FirstItem = item;
			first = false;
		} else {
			prevItem.IsLast = false;
			prevItem.NextItem = item;
		}
		prevItem = item;
	});
	if (this.FirstItem == undefined || this.FirstItem.IsLast)
		return; // not initialized
	// - end
	this.$selector.children("div.no-js").hide();
	this.Transition.init(this, selector);
	this.Initialized = true;
}

McSlideshow.prototype = {

	internalSetCycleManager: function(cycleManager, periodInMs) {
		if (!this.Initialized)
			return;
		this.cycleManager = cycleManager;
		var gthis = this;
		var trans = this.Transition;
		this.cycleId = this.cycleManager.internalAddCycle(
				function(callback) { trans.cycle(gthis, callback); },
				periodInMs);
		this.CurrentItem = this.FirstItem;
	},
	
	setPauseOnHover: function(b) {
		if (b)
			this.enablePauseOnHover();
		else
			this.disablePauseOnHover();
	},
	
	enablePauseOnHover: function(htmlPause) {
		if (this.PauseOnHover)
			return;
		if (htmlPause !== undefined)
			this.$allLi.prepend("<span class=\"pause\" style=\"display: none\">" + htmlPause + "</span>");
		var gthis = this;
		this.$selector.mouseover(function() {
//$("div.d1").append(" PAUSE{" + gthis.cycleId + "}");
			gthis.cycleManager.pause(gthis.cycleId);
			gthis.$allLi.children("span.pause").show();
		});
		this.$selector.mouseout(function() {
//$("div.d1").append(" RESUME{" + gthis.cycleId + "}");
			gthis.cycleManager.resume(gthis.cycleId);
			gthis.$allLi.children("span.pause").hide();
		});
		this.PauseOnHover = true;
	},
	
	disablePauseOnHover: function() {
		if (!this.PauseOnHover)
			return;
		var gthis = this;
		this.$selector.unbind("mouseover mouseout");
		this.cycleManager.resume(gthis.cycleId);
		this.PauseOnHover = false;
	},
	
	onShow: function(callback) {
		this.OnShowCallback = callback;
	}
};

// ###
// ### Class McSlideshowItem
// ###
function McSlideshowItem($item, isFirst) {
	this.$item = $item;
	this.IsFirst = isFirst;
	this.IsLast = true;
	this.NextItem = undefined;
}


// ###
// ### Class McCycleManager
// ###
function McCycleManager(singleThread) {
	this.cycles = [];
	this.wcycles = [];
	this.IsPlaying = false;
	this.SingleThread = singleThread;
}

McCycleManager.prototype = {

	// ---
	// --- public / init
	// ---
	addCycle: function(slideshow, periodInMs) {
		slideshow.internalSetCycleManager(this, periodInMs);
		return slideshow;
	},
	
	internalAddCycle: function(callback, periodInMs) {
		var cycleId = this.cycles.length;
		this.cycles[cycleId] = new McCycle(callback, periodInMs);
		return cycleId;
	},
	
	// ---
	// --- public
	// ---
	playAll: function() {
		var now = this.now();
		var count = this.cycles.length;
		for (var i = 0; i < count; i++) {
			var t = this.cycles[i];
			t.play(now);
			this.wcycles[this.wcycles.length] = t;
		}
		this.waitForNextCycle(now);
	},
	
//	stopAll: function() {
//	},
	
	pause: function(cycleId) {
		var t = this.cycles[cycleId];
		if (t !== undefined && t.IsPlaying)
			t.pause(this.now());
	},
	
	resume: function(cycleId) {
		var t = this.cycles[cycleId];
		if (t !== undefined && !t.IsPlaying) {
			t.resume(this.now());
			if (!this.IsPlaying)
				this.waitForNextCycle(this.now());
		}
	},
	
	// ---
	// --- private / internal use only
	// ---
	now: function() {
		return (new Date).getTime();
	},
	
	sortCycles: function(t1, t2) {
		if (!t1.IsPlaying) {
			return t2.IsPlaying ? 1 : 0;
		} else if (!t1.IsPlaying)
			return -1;
		return t1.NextTime - t2.NextTime;
	},
	
	waitForNextCycle: function(now) {
		if (this.wcycles.length == 0) {
			this.IsPlaying = false;
//$("div.d1").append(" [no cycles]STOPPED");
			return;
		}
		this.IsPlaying = true;
		this.wcycles.sort(this.sortCycles);
		var first = this.wcycles[0];
		if (first.IsPlaying) {
			var delayInMs = Math.max(0, first.NextTime - now);
			var gthis = this;
			setTimeout(function() { gthis.workCycle(first); }, delayInMs);
		} else {
			this.IsPlaying = false;
//$("div.d1").append(" STOPPED");
		}
	},
	
	workCycle: function(cycle) {
		if (!cycle.IsPlaying) {
			this.waitForNextCycle(this.now());
			return;
		}
		if (this.currentCycle !== undefined) {
			if (this.SingleThread && this.currentCycle.IsWorking) {
				this.waitEndOfCurrentCycle(0);
				return;
			} else
				this.currentCycle = undefined;
		}
		this.currentCycle = cycle;
		this.currentCycle.computeNextTime(this.now());
		this.currentCycle.work();
		this.waitForNextCycle(this.now());
	},
	
	waitEndOfCurrentCycle: function(count) {
		if (count > 20) {
			this.IsPlaying = false;
			alert("A cycle doesn't respond. The cycle manager is turned off.");
			return;
		}
		if (this.currentCycle !== undefined) {
			if (this.currentCycle.IsWorking) {
				var gthis = this;
//$("div.d1").append("A");
				setTimeout(function() { gthis.waitEndOfCurrentCycle(count + 1); }, 500);
			} else {
				//alert("A cycle has been waited...");
//$("div.d1").append(" W" + count);
				this.currentCycle = undefined;
				this.waitForNextCycle(this.now());
			}
		}
	}
};

// ###
// ### Class McCycle
// ###
function McCycle(callback, periodInMs) {
	this.Callback = callback;
	this.PeriodInMs = periodInMs;
	this.IsPlaying = false;
	this.IsWorking = false;
}

McCycle.prototype = {
	
	play: function(now) {
		this.NextTime = now + this.PeriodInMs * 1.4 + Math.floor(Math.random() * 2000);
		this.IsPlaying = true;
	},

	computeNextTime: function(now) {
		this.NextTime = now + this.PeriodInMs;
	},

	pause: function(now) {
		if (this.NextTime === undefined)
			return;
		this.IsPlaying = false;
		this.CurrentDelay = Math.max(0, this.NextTime - now);
		this.NextTime = undefined;
	},
	
	resume: function(now) {
		if (this.CurrentDelay === undefined)
			return;
		this.NextTime = now + this.CurrentDelay;
		this.CurrentDelay = undefined;
		this.IsPlaying = true;
	},
	
	work: function() {
		this.IsWorking = true;
		var gthis = this;
		this.Callback(function() {
			gthis.IsWorking = false;
		});
	}
};

