

/**
 * cmsCarousel fades images in and out and can display arbitrary html over
 * each image at arbitrary positions.
 *
 * Example:
 *
 * <!-- Required styles -->
 *
 * <style>
 *
 *	 #carousel-canvas {
 *		 position: relative;
 *	 }
 *
 *	 .carousel-image,
 *	 .carousel-overlay {
 *		 position: absolute;
 *	 }
 *
 * </style>
 *
 *
 * <!-- Required js. The order of the script tags matters. -->
 *
 * <script type="text/javascript" src="jquery.js"></script>
 * <script type="text/javascript" src="path/to/jquery.cmsCarousel.js"></script>
 *
 * <!-- The div, ul and script below can appear anywhere in the dom and in any order. -->
 *
 * <div id="carousel-canvas"><!-- nothing here --></div>
 *
 * <ul id="carousel-nav"><!-- nothing here --></ul>
 *
 * <script type="text/javascript">
 *
 *	 var config = {
 *		 imagePath: 'path/to/images',
 *		 width: '600px',
 *		 height: '400px',
 *		 canvas: 'carousel-canvas', // id of canvas div
 *		 nav: 'carousel-nav', // id of navigation ul
 *		 navTemplate: '<a href="#" id="{id}">{content}</a>',
 *		 items: [
 *			 {
 *				 image: 'image1.jpg',
 *				 overlay: '<h1>This is some arbitraty html shown over the image.</h1>',
 *				 overlayPosition: ['30px', '60px'],
 *				 navContent: 'Navigation Text',
 *				 transitionDuration: 1000, //ms
 *				 displayDuration: 1000, // ms
 *				 fadeType: 'easeNone', // see http://developer.yahoo.com/yui/docs/YAHOO.util.Easing.html for possible options
 *				 url: null
 *			 },
 *			 {
 *				 image: 'image2.jpg',
 *				 overlay: '<h1>Lorem ipsum....</h1>',
 *				 overlayPosition: ['70px', '12px'],
 *				 navContent: 'More Navigation Text',
 *				 transitionDuration: 1000, //ms
 *				 displayDuration: 1000, // ms
 *				 fadeType: 'easeNone',
 *				 url: 'http://www.google.com'
 *			 },
 *			 // more images
 *		 ]
 *	 };
 *
 *	 $('#carousel-canvas').cmsCarousel(config);
 *
 * </script>
 */



(function($){

//public methods
var methods = {

	stopAndShow: function(i)
	{
		var data = this.data('cmsCarousel');
	
		data.stop = true; //prevents further changes to canvas

		data.current.image.stop(); //stop any current animations
		data.current.overlay.stop();
		
		this.trigger('stop.cmsCarousel');

		showIndex(this, i);
	},
	
	play: function()
	{
		var data = this.data('cmsCarousel');
	
		if(data.stop)
		{
			data.stop = false;
			this.trigger('play.cmsCarousel');
			clearTimeout(data.showNextDelayTimeout);
			showNext(this);
		}
	},
	
	init: function(c)
	{
		return this.each(function()
		{
			var $this = $(this),
				data = $this.data('cmsCarousel');
				
			if (!data) { // plugin not initialized yet
			
				//validate config
			
				debug('carousel init');
		
				var configProperties = [
					'width', 'height', 'items', 'imagePath',
					'canvas', 'navTemplate', 'nav'
				];
		
				if(!hasAllProperties(c, configProperties))	{
					debug('Invalid configuration, stopping.');
					return;
				}
				
				if(c.items.length < 1) {
					debug('Empty carousel, will not attempt to load carousel.');
					return;
				}
		
				var itemProperties = [
					'image', 'overlay', 'overlayPosition', 'navContent',
					'transitionDuration', 'displayDuration', 'fadeType',
					'url'
				];
		
				for(var i = 0; i < c.items.length; i++)	{
					if(!hasAllProperties(c.items[i], itemProperties))	{
						debug('Invalid item at index %d, stopping.', i);
						return;
					}
				}			
			
				$(this).data('cmsCarousel', {
					canvas: $this,
					currentIndex: -1,
					imageInstances: [],
					readyImages: [],
					items: c.items,
					imagePath: c.imagePath,
					width: c.width,
					height: c.height,
					fadeinImage: null,
					fadeinOverlay: null,
					navTemplate: c.navTemplate,
					nav: '#'+c.nav,
					stop: true,
					a: null,
					b: null,
					current: null,
					notCurrent: null,
					showNextDelayTimeout: null
				});
				
				initCanvas($this);
				initNav($this);
			}	
		});
	}
};

//private methods

/**
* Called once the animation displaying a new image is finished.
*/
function delayShowNext($this)
{
	var data = $this.data('cmsCarousel');

	debug('Showing: index=%d, duration=%dms', data.currentIndex, data.items[data.currentIndex].displayDuration);

	data.notCurrent.image.css('opacity', 0);
	data.notCurrent.overlay.css('opacity', 0);

	showNextDelayTimeout = setTimeout(function(){showNext($this)}, data.items[data.currentIndex].displayDuration);
}

/**
* Called once displayDuration has passed.
*/
function showNext($this)
{
	var data = $this.data('cmsCarousel');

	if(data.stop)
	{
		debug('Aborting show next');
		return;
	}
	
	var nextI = nextIndex($this);
	
	if(data.readyImages[nextI] !== true)
	{
		debug('Delaying showNext until image %d loaded.', nextI);
	
		$(data.imageInstances[nextI]).bind('load', {}, function()
		{
			data.readyImages[nextI] = true
			showNext($this);
		});
	
		return;
	}

	var firstShowNext = (data.currentIndex == -1);

	increment($this);
	swapCurrent($this);
 
	data.current.image.html(getImageTag($this, data.currentIndex));
	data.current.overlay.html(data.items[data.currentIndex].overlay);
 
	setOverlayBackground($this);
 
	positionOverlay($this);
	preloadImage($this, nextIndex($this));
 
	if(firstShowNext)
	{
		delayShowNext($this);
		return;
	}

	var animDuration = parseInt(data.items[data.currentIndex].transitionDuration);
	
	debug('Fading in: index=%d, src=%s, duration=%dms', data.currentIndex, getImageSrc($this, data.currentIndex), animDuration);
 
	data.current.image.animate({opacity: 1}, animDuration, function(){delayShowNext($this)});
	//data.current.overlay.animate({opacity: 1}, animDuration);
	
	setTimeout(function() {
		setActiveNav($this);
		data.notCurrent.overlay.css('opacity', 0);
		data.current.overlay.css('opacity', 1);
	},
		data.items[data.currentIndex].transitionDuration/2
	);
}

/**
* Called when a nav item is clicked.
*/
function showIndex($this, i)
{
	var data = $this.data('cmsCarousel');
	
	debug('Show index (nav clicked): index=%d', i);
 
	data.currentIndex = i;
 
	preloadImage($this, i);
 
	swapCurrent($this);
 
	data.current.image.html(getImageTag($this, i));
	data.current.overlay.html(data.items[i].overlay);
 
	setOverlayBackground($this);
 
	positionOverlay($this);
 
	data.current.image.css('opacity', 1);
	data.notCurrent.image.css('opacity', 0);
	data.current.overlay.css('opacity', 1);
	data.notCurrent.overlay.css('opacity', 0);
 
	setActiveNav($this);
 
	preloadImage($this, nextIndex($this));
}

function setOverlayBackground($this)
{
	var data = $this.data('cmsCarousel');

	if ($.browser.msie) { // internet explorer, text gets ugly if there's no background
		//data.current.overlay.css('background-image', 'url('+getImageSrc($this, data.currentIndex)+')');
	}
}

function setActiveNav($this)
{
	var data = $this.data('cmsCarousel');
	
	//currentIndex will be -1 if initNav happens before initCanvas
	var i = (data.currentIndex == -1) ? 0 : data.currentIndex;
 
	$this.trigger('change', data.items[i]);
 
	if(typeof data.items[0].nav === 'undefined')
		return;
 
	for(var j = 0; j < data.items.length; j++)
	{
		data.items[j].nav.removeClass('carousel-nav-active');
	}
 
	data.items[i].nav.addClass('carousel-nav-active');
}

function swapCurrent($this)
{
	var data = $this.data('cmsCarousel');

	data.current.image.unbind('click');
	data.current.image.css('cursor', 'default');
 
	var t = data.current;
	data.current = data.notCurrent;
	data.notCurrent = t;
 
	if(typeof data.items[data.currentIndex].url === 'string' && data.items[data.currentIndex].url.length > 0)
	{
		data.current.image.click(function(){document.location = data.items[data.currentIndex].url});
		data.current.image.css('cursor', 'pointer');
	}
 
	data.current.image.css('z-index', 22);
	data.current.overlay.css('z-index', 24);
	data.notCurrent.image.css('z-index', 12);
	data.notCurrent.overlay.css('z-index', 14);
}

function positionOverlay($this)
{
	var data = $this.data('cmsCarousel');
	
	data.current.overlay.css('left', data.items[data.currentIndex].overlayPosition[0]);
	data.current.overlay.css('top', data.items[data.currentIndex].overlayPosition[1]);
 
	if ($.browser.msie) // internet explorer
	{
		//data.current.overlay.css('background-position', '-'+data.items[data.currentIndex].overlayPosition[0] + ' -' + data.items[data.currentIndex].overlayPosition[1]);
	}
}

function preloadImage($this, i)
{
	var data = $this.data('cmsCarousel');

	if(data.imageInstances[i]) // Image already preloaded
		return;
	
	debug('Preloading image: index=%d', i);
 
	data.imageInstances[i] = new Image(); 
	data.readyImages[i] = false;
 
	$(data.imageInstances[i]).bind('load', {}, function()
	{
		debug('Image %d ready', i);
		data.readyImages[i] = true
	});
 
	data.imageInstances[i].src = getImageSrc($this, i);
}

function increment($this)
{
	var data = $this.data('cmsCarousel');
	data.currentIndex = nextIndex($this);
}

function nextIndex($this)
{
	var data = $this.data('cmsCarousel');
	return (data.currentIndex+1) % data.items.length;
}

function getImageId($this, i)
{
	var data = $this.data('cmsCarousel');
	return 'carousel-image-'+i;
}

function getImageTag($this, i)
{
	var data = $this.data('cmsCarousel');
	return '<img '+
		'id="'+getImageId($this, i)+'" '+
		'src="'+getImageSrc($this, i)+'"/>';
}

function getImageSrc($this, i)
{
	var data = $this.data('cmsCarousel');
	var img = data.items[i].image;
	return data.imagePath+'/' + img;
}

function initCanvas($this)
{
	debug('initCavas');
	
	var data = $this.data('cmsCarousel');
 
	$this.html(''+
		'<div class="carousel-image-a carousel-image"></div>'+
		'<div class="carousel-overlay-a carousel-overlay"></div>'+
		'<div class="carousel-image-b carousel-image"></div>'+
		'<div class="carousel-overlay-b carousel-overlay"></div>'
	);
 
	data.notCurrent = data.a = {
		image: $this.find('.carousel-image-a'),
		overlay: $this.find('.carousel-overlay-a')
	};
 
	data.current = data.b = {
		image: $this.find('.carousel-image-b'),
		overlay: $this.find('.carousel-overlay-b')
	};
 
	$.each([data.a.image, data.b.image, $this], function(){
		this.css('width', data.width);
		this.css('height', data.height);
	});
 
	preloadImage($this, 0);
 
	$this.cmsCarousel('play');
}

function initNav($this)
{
	var data = $this.data('cmsCarousel');

	data.nav = $(data.nav);
	
	if (!data.nav.length)	{
		debug('Failed to find cmsCarousel nav container');
		return;
	}
 
	var navHTML = '', id = '';
 
	for(var i = 0; i < data.items.length; i++)
	{
		id = 'carousel-nav-item-'+i;
		navHTML += '<li>'+data.navTemplate.replace('{content}', data.items[i].navContent).replace('{id}', id)+'</li>';
		data.items[i].nav = '#'+id;
	}
 
	data.nav.html(navHTML);
 
	for(i = 0; i < data.items.length; i++) {
		data.items[i].nav = $(data.items[i].nav);
		data.items[i].nav.click(function(e) { setIndex($this, e) });
		if(i === data.items.length-1)
			data.items[i].nav.addClass('last');
	}
 
	setActiveNav($this, 0);
}

function setIndex($this, e)
{
	var target = $(e.currentTarget),
		data = $this.data('cmsCarousel');
	
	e.preventDefault();	

	for(var i = 0; i < data.items.length; i++)
	{
		if(data.items[i].nav.html() == target.html())
		{
			$this.cmsCarousel('stopAndShow', i);
		}
	}
}

function str_repeat(i, m) {
	for (var o = []; m > 0; o[--m] = i);
	return o.join('');
}

function sprintf() {
	var i = 0, a, f = arguments[i++], o = [], m, p, c, x, s = '';
	while (f) {
		if (m = /^[^\x25]+/.exec(f)) {
			o.push(m[0]);
		}
		else if (m = /^\x25{2}/.exec(f)) {
			o.push('%');
		}
		else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) {
			if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) {
				throw('Too few arguments.');
			}
			if (/[^s]/.test(m[7]) && (typeof(a) != 'number')) {
				a = parseInt(a);
			}
			switch (m[7]) {
				case 'b': a = a.toString(2); break;
				case 'c': a = String.fromCharCode(a); break;
				case 'd': a = parseInt(a); break;
				case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
				case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
				case 'o': a = a.toString(8); break;
				case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
				case 'u': a = Math.abs(a); break;
				case 'x': a = a.toString(16); break;
				case 'X': a = a.toString(16).toUpperCase(); break;
			}
			a = (/[def]/.test(m[7]) && m[2] && a >= 0 ? '+'+ a : a);
			c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
			x = m[5] - String(a).length - s.length;
			p = m[5] ? str_repeat(c, x) : '';
			o.push(s + (m[4] ? a + p : p + a));
		}
		else {
			throw('Huh ?!');
		}
		f = f.substring(m[0].length);
	}
	return o.join('');
}

function debug()
{
	if(typeof console === 'object' && typeof console.log !== 'undefined')
	{
		if (arguments[0])
			arguments[0] = 'cmsCarousel: '+arguments[0];
	
		if($.browser.mozilla)
			console.log.apply(this, arguments);
		else if ($.browser.msie || $.browser.webkit)
			console.log(sprintf.apply(this, arguments));
	}
}

function hasAllProperties(o, properties)
{
	var hasAll = true;

	for(var i = 0; i < properties.length; i++)
	{
		if(!(properties[i] in o))
		{
			$.error('cmsCarousel: Missing property: %s', properties[i]);
			hasAll = false;
		}
	}

	return hasAll;
}

$.fn.cmsCarousel = function(method)
{
	// Method calling logic
	if (methods[method]) {
		return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
	} else if (typeof method === 'object' || !method) {
		return methods.init.apply(this, arguments);
	} else {
		$.error('cmsCarousel: Method ' +  method + ' does not exist on jQuery.cmsCarousel');
	} 
};

})(jQuery);





