/*
Image slideshow plugin for jQuery

Copyright 2011 BombayWorks (written by Johannes Koggdal)
http://bombayworks.com/

Currently only uses CSS transitions, and falls back to a Flash file

Requirements:
  jQuery
  Modernizr (http://www.modernizr.com/download/#-csstransforms-csstransitions-prefixed-testprop-testallprops-domprefixes)
    CSS 2D Transforms
    CSS Transitions
    Extensibility > prefixed()
*/
(function ($) {

	$.fn.slideshow = function (options) {
		
		// Replace default options with user-defined options
		options = $.extend({
			duration: 3000,
			fadeTime: 1000,
			easing: "ease-in-out",
			autoplay: true,

			// Ken Burns related options
			kenburns: "when supported",
			zoom: 1.1,
			offsetX: 0.03,
			offsetY: 0.02,
			direction: "random", // random|in|out
			originX: 0.5,
			originY: 0.5,

			images: {}

		}, options);
		
		// Replace kenburns option
		if (options.kenburns === "when supported") {
			if (~window.navigator.userAgent.indexOf("WebKit")) {
				options.kenburns = true;
			} else {
				options.kenburns = false;
			}
		}

		var setupImages = function (instance) {
				var wrapper = instance.wrapper,
					items = instance.items,
					wrapperHeight = wrapper.height();
				
				// Set all images on top of each other
				wrapper.css({ position: "relative" });
				items.css({ position: "absolute", top: 0, left: 0 });

				// Rearrange them so the first image is on top
				setZindex.call(this, instance);
			},

			setupSlideshow = function (instance) {
				var self = this,
					items = instance.items,
					transition = Modernizr.prefixed("transition"),
					transform = Modernizr.prefixed("transform"),
					transformOrigin = Modernizr.prefixed("transform-origin"),
					transEndEventNames = {
						'WebkitTransition': 'webkitTransitionEnd',
						'MozTransition': 'transitionend',
						'OTransition': 'oTransitionEnd',
						'msTransition': 'msTransitionEnd', // maybe?
						'transition': 'transitionEnd'
					},
					transEndEventName = transEndEventNames[transition];
				
				// Save the css property name
				instance.transitionCSSprop = transition;
				instance.transformCSSprop = transform;
				instance.transformOriginCSSprop = transformOrigin;
				instance.transformCSSvalue = transform.replace(/([A-Z])/g, function(str,m1){ return '-' + m1.toLowerCase(); }).replace(/^ms-/,'-ms-');

				// Start hardware acceleration
				items.css(transform, "translate3d(0,0,0)");
				
				// Do stuff when animation has ended
				items.bind(transEndEventName, function () {

					if ($(this).hasClass("slideshow-opacity")) {
						
						// Remove transitions for this element
						$(this).css(transition, "none").removeClass("slideshow-opacity");
						start.call(self, instance);
						reorderImages.call(self, instance);
					}
				});
			},

			setZindex = function (instance) {
				var items = instance.items,
					index = items.length;
				items.each(function () {
					$(this).css({ "z-index": index-- });
				});
			},

			reorderImages = function (instance) {

				// Move the first item to the end
				instance.items.last().after(instance.items.first());

				// Reset the items container to reflect the change
				instance.items = instance.wrapper.children();

				// Rearrange them so the first image is on top
				setZindex.call(this, instance);

				// Reset the opacity
				instance.items.last().css({ opacity: 1 });
			},

			timer = function (instance, onlyKenburns) {
				var item = instance.items.first(),
					nextItem = onlyKenburns ? item : item.next(),
					options = instance.options,
					imageOpts = options.images[instance.current],
					optOffsetX = (imageOpts && imageOpts.offsetX !== undefined) ? imageOpts.offsetX : options.offsetX,
					optOffsetY = (imageOpts && imageOpts.offsetY !== undefined) ? imageOpts.offsetY : options.offsetY,
					optDirection = (imageOpts && imageOpts.direction) ? imageOpts.direction : options.direction,
					optFadeTime = (imageOpts && imageOpts.fadeTime) ? imageOpts.fadeTime : options.fadeTime,
					optZoom = (imageOpts && imageOpts.zoom) ? imageOpts.zoom : options.zoom,
					optOriginX = (imageOpts && imageOpts.originX !== undefined) ? imageOpts.originX : options.originX,
					optOriginY = (imageOpts && imageOpts.originY !== undefined) ? imageOpts.originY : options.originY,
					transitionOpacity = "opacity " + optFadeTime + "ms " + options.easing;
				
				// Get the CSS set up for the ken burns effect
				if (options.kenburns || onlyKenburns) {
					var transitionKenburns = instance.transformCSSvalue + " " + (options.duration + optFadeTime * 3) + "ms linear",

						directions = ["in", "out"],
						direction = ~$.inArray(optDirection, directions) ? optDirection : "random",
						dir = direction === "random" ? directions[Math.floor(Math.random() * directions.length)] : direction;

						offsetX = Math.round(optOffsetX * nextItem.width()),
						offsetY = Math.round(optOffsetY * nextItem.height()),
						rndFactorX = (direction === "random") ? (Math.floor(Math.random() * 2) ? -1 : 1) : 1,
						rndFactorY = (direction === "random") ? (Math.floor(Math.random() * 2) ? -1 : 1) : 1,
						heightOffset = ((nextItem.height() - instance.wrapper.height()) * optOriginY) * -1,
						offX = (offsetX * rndFactorX),
						offY = (offsetY * rndFactorY) + heightOffset,
						rand = Math.floor(Math.random()),
						offsetStart = rand ? offX + "px, " + offY + "px" : "0," + heightOffset + "px",
						offsetEnd = !rand ? offX + "px, " + offY + "px" : "0," + heightOffset + "px",

						transformKenburnsOutStart = "translate(" + offsetStart + ") scale(" + (optZoom + 0.05) + ")",
						transformKenburnsInStart = "translate(" + offsetStart + ") scale(1.05)",
						transformKenburnsOutEnd = "translate(" + offsetEnd + ") scale(1.05)";
						transformKenburnsInEnd = "translate(" + offsetEnd + ") scale(" + (optZoom + 0.05) + ")",
						transformKenburnsStart = (dir === "out") ? transformKenburnsOutStart : transformKenburnsInStart,
						transformKenburnsEnd = (dir === "out") ? transformKenburnsOutEnd : transformKenburnsInEnd;
				}

				// Set up the element to use transitions
				if (!onlyKenburns) {
					item.addClass("slideshow-opacity");
					item.css(instance.transitionCSSprop, transitionOpacity + (options.kenburns ? ", " + transitionKenburns : ""));
				}

				// Trigger transition for ken burns effect
				if (options.kenburns || onlyKenburns) {
					nextItem.css(instance.transformOriginCSSprop, (Math.round(optOriginX * 1000) / 10) + "% " + (Math.round(optOriginY * 1000) / 10) + "%");
					nextItem.css(instance.transformCSSprop, transformKenburnsStart);

					// Set a timer to let the browser register the transform first
					setTimeout(function () {
						nextItem.css(instance.transitionCSSprop, transitionKenburns);
						nextItem.css(instance.transformCSSprop, transformKenburnsEnd);
					}, 10);
				}

				// Trigger transition for opacity
				if (!onlyKenburns) {
					item.css({ opacity: 0 });
				}
			},

			start = function (instance) {
				var self = this,
					images = instance.options.images,
					duration = (images && images[instance.current] && images[instance.current].duration) ? images[instance.current].duration : instance.options.duration;

				// Cancel any previous timers
				clearTimeout(instance.timer);

				// Run ken burns effect directly when we start, but after first image has loaded
				if (instance.options.kenburns && instance.first) {
					instance.first = false;
					if (instance.items.first().width() > 0 && instance.items.first().height() > 0) {
						setTimeout(function () {
							timer.call(self, instance, true);
						}, 1);
					} else {
						instance.items.first().load(function () {
							timer.call(self, instance, true);
						});
					}
				}

				// Set up a new timer
				instance.timer = setTimeout(function () {
					instance.current = (instance.current < instance.items.length) ? instance.current + 1 : 1;
					timer.call(self, instance);
				}, duration);
			},

			stop = function (instance) {
				clearTimeout(instance.timer);
			};
		
		return this.each(function (index) {

			// Set up an object which will store this instance's properties
			var instance = {
					wrapper: $(this),
					items: $(this).children(),
					options: $.extend({}, options),
					first: true,
					current: 1
				};
			
			// Initialize everything
			setupImages.call(this, instance);
			setupSlideshow.call(this, instance);

			// Return a public API if specified
			// This will be put in a variable defined in or outside the scope
			//   where the slideshow plugin was called
			if (options.instances) {
				var self = this,
					api = {
						start: function () {
							start.call(self, instance);
						},
						stop: function () {
							stop.call(self, instance);
						},

						options: instance.options
					};
				options.instances[index] = api;
			}

			// Start the slideshow if specified
			if (options.autoplay) {
				start.call(self, instance);
			}
		});
	};


	// Wrapper API with Flash fallback
	window.createSlideshow = function (options) {
		options = $.extend({
			width: options.selector ? $(options.selector).width() : 100,
			height: options.selector ? $(options.selector).height() : 100,
			swfobject: "swfobject-2.2.min.js"
		}, options);

		options.options = $.extend({
			duration: 3000,
			fadeTime: 1000,
			kenburns: "when supported",
			autoplay: true
		}, options.options);

		// We need to browser detect because of different renderings of sub-pixels
		//   WebKit renders sub-pixels in CSS transitions very smoothly,
		//   whereas other browsers renders it choppy
		if (~window.navigator.userAgent.indexOf("WebKit") && Modernizr.csstransitions && Modernizr.csstransforms) {

			// Create a slideshow of the images, using CSS transitions and transforms
			$(options.selector).slideshow(options.options);

		// If this is a browser that does not render sub-pixels in a good way,
		//   we will fall back to Flash solution
		} else {

			// Change the DOM container
			$(options.selector).html('<div />').children().attr("id", "flash-slideshow");
			
			// Include the Flash slideshow
			$.getScript(options.swfobject, function () {
				swfobject.embedSWF(options.flash, "flash-slideshow", options.width, options.height, "9.0.0", "expressInstall.swf");
			});
		}

	};

}(jQuery));
