
YE = YAHOO.util.Event;
YD = YAHOO.util.Dom;
YL = YAHOO.util.Lang;
YUA = YAHOO.env.ua;

CWS = {
	debug: false,
	log: function()
	{
		if(CWS.debug && typeof console === 'object' && typeof console.log !== 'undefined')
		{
			if(YUA.gecko > 0)
				console.log.apply(this, arguments);
			else if (YUA.ie > 0 || YUA.webkit)
				console.log(CWS.sprintf.apply(this, arguments));
		}
	},
	hasAllProperties: function(o, properties)
	{
		var hasAll = true;

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

		return hasAll;
	},
	sprintf: function() //from http://code.google.com/p/sprintf/
	{
		var i = 0, a, f = arguments[i++], o = [], m, p, c, x;
		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;
				p = m[5] ? CWS.str_repeat(c, x) : '';
				o.push(m[4] ? a + p : p + a);
			}
			else throw ("Huh ?!");
			f = f.substring(m[0].length);
		}
		return o.join('');
	},
	str_repeat: function(i, m)
	{
		for (var o = []; m > 0; o[--m] = i); return(o.join(''));
	}
};


/**
 * CWS.Carousel fades images in and out and can display arbitrary html over
 * each image at arbitrary positions.
 *
 * You should only need to call the init function. init() will add
 * a listener for the canvas id being added to the dom so you can call
 * init() whether the canvas div is present in the dom or not. The same is
 * true of the nav unordered list.
 *
 * 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="http://yui.yahooapis.com/combo?2.8.0r4/build/yahoo-dom-event/yahoo-dom-event.js&2.8.0r4/build/animation/animation-min.js"></script>
 * <script type="text/javascript" src="path/to/cws/carousel.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
 *		 ]
 *	 };
 *
 *	 CWS.Carousel.init(config);
 *
 * </script>
 */
CWS.Carousel = function() 
{
	// private properties of CWS.Carousel

	var that = null,
		imageInstances = [],
		readyImages = [],
		items = [],
		imagePath = null,
		width = null,
		height = null,
		canvas = null,
		currentIndex = null,
		fadeinImage = null,
		fadeinOverlay = null,
		navTemplate = null,
		nav = null,
		stop = true,
		a = null,
		b = null,
		current = null,
		notCurrent = null,
		showNextDelayTimeout = null;

	/**
	 * Called once the animation displaying a new image is finished.
	 */
	var delayShowNext = function()
	{
		CWS.log('Showing: index=%d, duration=%dms', currentIndex, items[currentIndex].displayDuration);

		YD.setStyle(notCurrent.image, 'opacity', 0);
		YD.setStyle(notCurrent.overlay, 'opacity', 0);

		showNextDelayTimeout = setTimeout(showNext, items[currentIndex].displayDuration);
	};

	/**
	 * Called once displayDuration has passed.
	 */
	var showNext = function()
	{
		if(stop)
		{
			CWS.log('Aborting show next');
			return;
		}

		var nextI = nextIndex();

		if(readyImages[nextI] !== true)
		{
			CWS.log('Delaying showNext until image %d loaded.', nextI);

			YE.on(imageInstances[nextI], 'load', function()
			{
				readyImages[nextI] = true
				showNext();
			});

			return;
		}

		var firstShowNext = (currentIndex == -1);

		increment();
		swapCurrent();

		current.image.innerHTML = getImageTag(currentIndex);
		current.overlay.innerHTML = items[currentIndex].overlay;

		setOverlayBackground();

		positionOverlay();
		preloadImage(nextIndex());

		if(firstShowNext)
		{
			delayShowNext();
			return;
		}

		var animationType = 'YAHOO.util.Easing.'+items[currentIndex].fadeType;
		var animDuration = items[currentIndex].transitionDuration;

		CWS.log('Fading in: index=%d, src=%s, duration=%dms, type=%s', currentIndex, getImageSrc(currentIndex), animDuration, animationType);

		animDuration = animDuration/1000;

		animationType = window[animationType];

		fadeinImage = new YAHOO.util.Anim(
			current.image,
			{opacity: {from: 0, to: 1}},
			animDuration,
			animationType
		);

		fadeinImage.onComplete.subscribe(delayShowNext);

		fadeinOverlay = new YAHOO.util.Anim(
			current.overlay,
			{opacity: {from: 0, to: 1}},
			animDuration,
			animationType
		);

		fadeinImage.animate();
		fadeinOverlay.animate();

		setTimeout(setActiveNav, items[currentIndex].transitionDuration/2);
	};

	/**
	 * Called when a nav item is clicked.
	 */
	var showIndex = function(i)
	{
		CWS.log('Show index (nav clicked): index=%d', i);

		currentIndex = i;

		preloadImage(i);

		swapCurrent();

		current.image.innerHTML = getImageTag(i);
		current.overlay.innerHTML = items[i].overlay;

		setOverlayBackground();

		positionOverlay();

		YD.setStyle(current.image, 'opacity', 1);
		YD.setStyle(notCurrent.image, 'opacity', 0);
		YD.setStyle(current.overlay, 'opacity', 1);
		YD.setStyle(notCurrent.overlay, 'opacity', 0);

		setActiveNav();

		preloadImage(nextIndex());
	};

	var setOverlayBackground = function()
	{
		if (YUA.ie > 0) { // internet explorer, text gets ugly if there's no background
			YD.setStyle(current.overlay, 'background-image', 'url('+getImageSrc(currentIndex)+')');
		}
	};

	var setActiveNav = function()
	{
		//currentIndex will be -1 if initNav happens before initCanvas
		var i = (currentIndex == -1) ? 0 : currentIndex;

		that.onChange.fire(items[i]);

		if(typeof items[0].nav === 'undefined')
			return;

		for(var j = 0; j < items.length; j++)
		{
			YD.removeClass(items[j].nav, 'carousel-nav-active');
		}

		YD.addClass(items[i].nav, 'carousel-nav-active');
	}

	var swapCurrent = function()
	{
		YE.removeListener(current.image, 'click');
		YD.setStyle(current.image, 'cursor', 'default');

		var t = current;
		current = notCurrent;
		notCurrent = t;

		if(YL.isString(items[currentIndex].url) && items[currentIndex].url.length > 0)
		{
			YE.on(current.image, 'click', function(){document.location = items[currentIndex].url});
			YD.setStyle(current.image, 'cursor', 'pointer');
		}

		YD.setStyle(current.image, 'z-index', 22);
		YD.setStyle(current.overlay, 'z-index', 24);
		YD.setStyle(notCurrent.image, 'z-index', 12);
		YD.setStyle(notCurrent.overlay, 'z-index', 14);
	};

	var positionOverlay = function()
	{
		YD.setStyle(current.overlay, 'left', items[currentIndex].overlayPosition[0]);
		YD.setStyle(current.overlay, 'top', items[currentIndex].overlayPosition[1]);

		if (YUA.ie > 0) // internet explorer
		{
			YD.setStyle(current.overlay, 'background-position', '-'+items[currentIndex].overlayPosition[0] + ' -' + items[currentIndex].overlayPosition[1]);
		}
	};

	var preloadImage = function(i)
	{
		if(YD.get(getImageId(i)))
		{
			CWS.log('Image id %s present already?', getImageId(i));
		}

		if(YL.isObject(imageInstances[i]))
		{
			CWS.log('Image already preloaded: index=%d', i);
			return;
		}

		CWS.log('Preloading image: index=%d', i);

		imageInstances[i] = new Image();

		readyImages[i] = false;

		YE.on(imageInstances[i], 'load', function()
		{
			CWS.log('Image %d ready', i);
			readyImages[i] = true
		});

		imageInstances[i].src = getImageSrc(i);
	};

	var increment = function()
	{
		currentIndex = nextIndex();
	};

	var nextIndex = function()
	{
		return (currentIndex+1) % items.length;
	}

	var getImageId = function(i)
	{
		return 'carousel-image-'+i;
	};

	var getImageTag = function(i)
	{
		return '<img '+
			'id="'+getImageId(i)+'" '+
			'src="'+getImageSrc(i)+'" '+
			'height="'+height+'" '+
			'width="'+width+'"/>';
	};

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

	var initCanvas = function()
	{
		canvas = YD.get(canvas);

		canvas.innerHTML = ''+
			'<div id="carousel-image-a" class="carousel-image"></div>'+
			'<div id="carousel-overlay-a" class="carousel-overlay"></div>'+
			'<div id="carousel-image-b" class="carousel-image"></div>'+
			'<div id="carousel-overlay-b" class="carousel-overlay"></div>';

		notCurrent = a = {
			image: YD.get('carousel-image-a'),
			overlay: YD.get('carousel-overlay-a')
		};

		current = b = {
			image: YD.get('carousel-image-b'),
			overlay: YD.get('carousel-overlay-b')
		};

		var containers = [a.image, b.image, canvas];

		YD.setStyle(containers, 'width', width);
		YD.setStyle(containers, 'height', height);

		preloadImage(0);

		that.play();
	};

	var initNav = function()
	{
		nav = YD.get(nav);

		var navHTML = '';

		for(var i = 0; i < items.length; i++)
		{
			var id = 'carousel-nav-item-'+i;
			items[i].nav = id;
			navHTML += '<li>'+navTemplate.replace('{content}', items[i].navContent).replace('{id}', id)+'</li>';
		}

		nav.innerHTML = navHTML;

		for(i = 0; i < items.length; i++)
		{
			items[i].nav = YD.get(items[i].nav);
			YE.on(items[i].nav, 'click', setIndex);
			if(i === items.length-1)
				YD.addClass(items[i].nav, 'last');
		}

		setActiveNav(0);
	};

	var setIndex = function(e)
	{
		YE.preventDefault(e);

		for(var i = 0; i < items.length; i++)
		{
			if(items[i].nav==this)
			{
				that.stopAndShow(i);
			}
		}

		return false;
	};

	return {

		// public properties of CWS.Carousel

		stopAndShow: function(i)
		{
			stop = true; //prevents further changes to canvas

			if(YL.isObject(fadeinOverlay) && fadeinOverlay.isAnimated())
			{
				CWS.log('Stopping animation');
				fadeinOverlay.stop();
				fadeinImage.stop();
			}

			that.onStop.fire();

			showIndex(i);
		},

		play: function()
		{
			if(stop)
			{
				stop = false;
				that.onPlay.fire();
				clearTimeout(showNextDelayTimeout);
				showNext();
			}
		},

		onChange: null,

		onPlay: null,

		onStop: null,

		init: function(config)
		{
			that = this;

			this.onChange = new YAHOO.util.CustomEvent('onChange');
			this.onPlay = new YAHOO.util.CustomEvent('onPlay');
			this.onStop = new YAHOO.util.CustomEvent('onStop');

			var configProperties = [
				'width', 'height', 'items', 'imagePath',
				'canvas', 'navTemplate', 'nav'
			];

			if(!CWS.hasAllProperties(config, configProperties))
			{
				CWS.log('Invalid configuration, stopping.');
				return;
			}

			width = config.width;
			height = config.height;
			items = config.items;
			imagePath = config.imagePath;
			canvas = config.canvas;
			navTemplate = config.navTemplate;
			nav = config.nav;

			if(items.length < 1)
			{
				CWS.log('Empty carousel, will not attempt to load carousel.');
				return;
			}

			var itemProperties = [
				'image', 'overlay', 'overlayPosition', 'navContent',
				'transitionDuration', 'displayDuration', 'fadeType',
				'url'
			];
			
			for(var i = 0; i < items.length; i++)
			{
				if(!CWS.hasAllProperties(items[i], itemProperties))
				{
					CWS.log('Invalid item at index %d, stopping.', i);
					return;
				}
			}

			currentIndex = -1;

			YE.onContentReady(canvas, initCanvas);
			YE.onContentReady(nav, initNav);
		}
	};
}();



