var Azexis = Azexis || {};

Azexis.Slideshow = function(banner, images, options) {
	var banner = $(banner);
	var fader;
	
	Azexis.Slideshow.defaults = {
		imagesDir: "",
		duration: 5000,
		userDuration: 1000,
		wait: 5000,
		width: banner.innerWidth(),
		height: banner.innerHeight(),
		backgroundPosition: "top"
	};
	
	var options = $.extend({}, Azexis.Slideshow.defaults, options);
	
	// Current image is the index of the image that is fully loaded and moved to
	// the background div.
	var nxt = 1;
    var listeners = [];
	
	function mod(a,b) { var n = a%b; return n < 0 ? n+b : n; }
	function currentImage() { if(images) return mod(nxt-1,images.length); }
	function nextImage() { if(images) return mod(nxt,images.length); }
	function advanceImage() { nxt += 1; }

    function onImageChange(e){
        for(i=0; i<listeners.length; i++){
            listeners[i](e);
        }
    }

	// User manually changes to another slide.
	function changeNext(n) {
		nxt = n;
		
		// Don't interrupt in-progress animations, but switch immediately if we can.
		if (!animating) {
			resetFader();
		} else {
			// Hurry along the current fade.
			fx.stop();
			fx.setOptions({duration: springDuration});
			fx.start(1);
		}
		
		// User interation always force a single move even if the animation is paused.
		forceSingle = true;
		eventHandler();
	}
	
	var springDuration = 500; // Spring a partially completed fade to completion.
	
	var images = images;
	var cachedImages = [true];
	var failedInARow = 0;
	
	function imageToCSS(n) {
		return "url(" + options.imagesDir + images[n] + ")";
	}
	
	function loadNextImage() {
		var img = new Image;
		
		// Store the current imageCache and number in a closure. If they change
		// during the time it takes to load, the next cache won't get the incorrect
		// values written to it.
		img.onload = (function(cache, n) {
			return function() {
				failedInARow = 0;
				fader.css("background-image", imageToCSS(n));
				cache[n] = true;
				eventHandler();
			}
		})(cachedImages, nextImage());
		
		img.onerror = function() {
			// Can't load the image, just try the next.
			if (failedInARow > 10) {
				// Don't keep looking forever.
				return;
			}
			failedInARow++;
			advanceImage();
			resetFader();
		}
		
		// Start load process.
		if(images) {
			img.src = options.imagesDir + images[nextImage()];
		}
	}
	
	function updateCurrentImage() {
		$(banner).css("background-image", fader.css("background-image"));
	}
	
	function resetFader() {
		fader.css("opacity", 0);
		loadNextImage();
	}
	
	fader = $("<div>").css({
		position: "absolute",
		'z-index':1,
		top: 0,
		left: 0,
		width: options.width,
		height: options.height,
		backgroundRepeat: "no-repeat",
		backgroundPosition: options.backgroundPosition
	});
	
	banner.append(fader);
	resetFader();
	
	// Actually mid-fade, which is never stopped.
	var animating = false;
	// Don't start any new fades automatically, but user interaction can still
	// force a single fade.
	var paused = false;
	var forceSingle = false;
	var lastTime = new Date;
	
	
	function eventHandler() {
		if (animating) {
			return;
		}
		
		if (paused && !forceSingle) {
			return;
		}
		
		if (!forceSingle && ((new Date) - lastTime) < options.wait) {
			// Come back when the time is right.
			setTimeout(eventHandler, options.wait - ((new Date) - lastTime));
			return;
		}
		
		if (!cachedImages[nextImage()]) {
			// Not cached yet, the event handler will be called when the cache is
			// complete.
			return;
		}
		
		var fadeDuration = options.duration;
		if (forceSingle) {
			forceSingle = false;
			fadeDuration = options.userDuration;
		} else {
            e = { imageId: nxt };
            onImageChange(e);
		}
		
		fader.animate({opacity: 1}, fadeDuration, "linear", function() {
			lastTime = new Date;
			updateCurrentImage();
			
			// Delay added to prevent flicker during image swap.
			setTimeout(function() {
				animating = false;
				resetFader();
				eventHandler();
			}, 100);
		});
		animating = true;
		advanceImage();
	}
	eventHandler();
	
	return {
		loadImageSequence: function(seq) {
			images = seq;
			changeNext(0);
			cachedImages = [];
		},
		
		pause: function() {
			paused = true;
		},
		
		resume: function() {
			paused = false;
			eventHandler();
		},
		
		moveTo: function(n) {
			changeNext(n);
		},
		
		moveToNext: function() {
			changeNext(currentImage()+1);
		},
		
		moveToPrevious: function() {
			changeNext(currentImage()-1);
		},

        addListener: function(fn){
            listeners[listeners.length] = fn;
        }

	};
}

jQuery(function($) {
	if (!Azexis.banners) {
		return;
	}
	
	var slideshow = Azexis.Slideshow($("#banner-large"),
		Azexis.banners,
		{
			//imagesDir: Azexis.baseUrl + "/content/assets/",
		}
	);
});
