/* wonderBanner plugin - v1.4.1
 * 
 * Copyright 2011, Daved Artemik
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 * 
 * Rev 1.4.1 
 * Fixed: Wrapped css function in try...catch to avoid cross domain security error
 *
 * Rev 1.4
 * Added: Ability to preload images before starting animation
 * Added: Async animateIn/Out allows for blended animations 
 * Fixed: Resolved issue with custom paging index
 * 
 * Rev 1.2
 * Added: Custom paging selector
 * Added: Ability to toggle prev and next buttons
 * Added: Ability to toggle scrolling/slideshow
 * Updated: Simplified animation functions
 * Fixed: Paging bug 
 * TODO: Add image caching - (preLoad)
 * TODO: Add blending effect - (async)
 *
 * Rev 1.0
 * Initial Build
 * TODO: Allow custom paging
 * TODO: Update and clean up animation funcs. 
 */

(function($) {
	var wonderBanner = function(e,options) {
		var s = $.extend({
			source: null,			// source selector or jQuery object for slide elements
			inCss : 'stepIn',		// css for animate in
			outCss: 'stepOut',		// css for animate out
			eleDelay: 0,		// time between animating each item
			slideDelay: 3500,		// time between slide transitions
			animateInSpeed: 700,		// animation speed of slide appearing - only animation speed for async 
			animateOutSpeed: 300,		// animation speed of slide hiding
			autoScroll: true,			// toggle automatic slideshow effect
			autoPaging: true,		// create paging buttons
			pageSource: null,		// specifies selector for page indicator per slide
			prevNext: true,		// true/false for previous and next arrows - or specify selectors {prevBtn:'', nextBtn: ''}
			preLoad: true,			// load all slide images before starting
			async: true		// true/false to start animateIn/Out at same time - or specify value in ms - true sets delay to 0
		},options);
		
		var $src = '',		// internal vars - makes it easier
			inCss = s.inCss,		// easier to ref in
			outCss = s.outCss,	// and out css
			animated = new Array(),	// items being animated
			playTimer = null,		// time for slideshow
			pagePrev,	// used for custom prev
			pageNext,		// and next
			asyncDelay = 0	// delay between animateIn/Out actions - 
		;
		
		if (e.length <= 0) { return e; }	// stop methods if no element
		if (typeof s.prevNext == 'object') {
			pagePrev = s.prevNext.prevBtn || null;	// used for custom prev
			pageNext = s.prevNext.nextBtn || null;	// and next
			s.prevNext = false;	// easier check
		}
		if (typeof s.async == 'number') {	// if async is set to time value
			if (s.async > 0) { 	// greater than 0
				asyncDelay = s.async;	// set delay time
				s.async = true;	// and set async to true - easier for conditions
			}
			else { s.async = false; }	// if time is 0 set false
		}
		this.init = function() {
			if (s.source != undefined && s.source != null) {
				$src = $(s.source);		// make sure it's a jquery object
				$src.hide(0);	// verify the source is hidden
				var $d = $($src.filter('.default')[0] || $src.first());	// grab default item or first matched
				animated = new Array();			// clear animation array
				e.find('[rel]').each(function() { 
					$(this).data('baseCss', $(this).attr('class'));
				});
				
				if (s.autoPaging) {
					setAutoPaging($src.length);		// create paging
				}
				else if (s.pageSource != null) {
					setCustomPaging($src);		// initialize custom paging if set
				}
				e.append('<input type="hidden" class="pageIndex" id="pageIndex" />');
				e.find('#pageIndex')
					.val($src.index($d)+1)		// set index val
					.end().find('.paging a[id="page_'+($src.index($d)+1)+'"]')
					.addClass('bannerLinkSel');	// set current slide page button;
				if (s.preLoad) {	// if preloading
					var imgs = s.source.find('img').clone().removeAttr('id');	// create copy of images
					var ic = imgs.length;	// use length to reference loading
					var pl = $(document.createElement('div')).attr('id','bannerPL').css({
						position: 'absolute',
						left: '-8000px',	// position image div offscreen so all
						top: '-8000px'		// browsers load images (ff doesn't load "display:none" img)
					}).append(imgs);
					$('body').append(pl);	// append images to body to load
					imgs.load(function() {
						ic -= 1;	// decrement for each loaded image
						if (ic <= 0) {
							animateIn(e,$d);	// start animation and slideshow if set
							if (s.autoScroll) {	// when all images are loaded
								setAutoScroll();
							}
						}
					});
				}
				else {	// if not preloading
					animateIn(e,$d);	// show first/default item
					if (s.autoScroll) {
						setAutoScroll();	// start slideshow if set
					}
				} 		
			}					
			return e;
		};
		this.changeSlide = function(i) {
			return changeSlide(i);	// allows outside calls
		};
		this.togglePause = function(tf) {
			return togglePause(tf);	// allows outside calls
		};
		function setAutoScroll() {
			e.hover(
				function() {
					togglePause(true);	// toggle paused on mouse over
				},
				function() {
					togglePause(false);	// resume on mouse out
				});
			togglePause(false);		// start slideshow
		}
		function changeSlide(i, callBack) {	// i = slide index
			if (i > $src.length) { i = 1; }		// account for max and min	
			else if (i < 1) { i = $src.length; }	
			var $slide = $src.eq(i - 1);	// get slide from source array
			if (!s.async) { 
				animateOut(e, null, function() {	// animate current slide out
					animateIn(e, $slide, callBack);	// trigger animateIn when animation group completes
				});
			}
			else {	// call to animate In/Out
				animateASync(e, $slide, callBack);
			}
			e.find('.paging a').removeClass('bannerLinkSel');	// clear current page button
			e.find('#pageIndex').val(i);			// set index value
			e.find('.paging a[id="page_'+($src.index($slide)+1)+'"]').addClass('bannerLinkSel');	// set current button as selected
			return e;
		}
		function togglePause(tf) {
			if (tf) {	// if timer is running 
				if (playTimer != null) { 
					clearInterval(playTimer); 	// clear and
					playTimer = null; 			// set null
				}
			}
			else {
				if (playTimer == null) {	// if timer not running
					var idx = parseInt(e.find('#pageIndex').val()) + 1;		// get next index
					playTimer = setTimeout(function() {		// set timeout for next slide
						changeSlide(idx, function() { 	// utilize callBack for recurrence
							clearTimeout(playTimer);	// clear timer for
							playTimer = null;			// better timing
							togglePause(false);		// recurrence
						});
					}, s.slideDelay);	// provided delay
				}
			}
			return e;
		}
		function animateOut($ele, src, callBack) {
			var g = $ele.find('[rel]');
			if (g.length > 0) {	// check our group and animate out if found
				animateGroup(g, src, 'out', s.animateOutSpeed, s.eleDelay, callBack);
			}
		}
		function animateIn($ele, src, callBack) {
			var g = $ele.find('[rel]');
			if (g.length > 0) {	// check our group and animate in if found
				animateGroup(g, src, 'in', s.animateInSpeed, s.eleDelay, callBack);
			}
		}
		function animateASync($ele, src, callBack) {
			var g1 = $ele.find('[rel]');	// animation elements
			var g2 = g1.clone(true)	// clone, reset styles
				.attr('class',function() {return $(this).data('baseCss') + '-' + inCss;})
				.removeAttr('style')		
				.appendTo(g1.parent());	// append to parent element
			animateGroup(g1, null, 'out', s.animateInSpeed, s.eleDelay, callBack);
			window.setTimeout(function() {
				animateGroup(g2, src, 'in', s.animateInSpeed, s.eleDelay, function() { // after animate, replace and clean styles
					g1.replaceWith(g2).removeAttr('style').attr('class',function() {return $(this).data('baseCss');});
					if (typeof callBack == 'function') {
						callBack();		// exec callback if set
					}
				}); 
			}, asyncDelay);
		}
		function animateGroup($group, src, mode, speed, delay, callBack) {
			// this animates a group of items so we first ensure all items are done animating
			if ((!s.async && animated.length <= 0) || (s.async)) {
			
				for (var j = 1; j<= $group.length; j++) {	// loop through items - faster than $.each
					var item = $group.eq(j - 1);	// set each item from and to class	
					var fromCss = item.data('baseCss') + (mode == 'in'? '-'+inCss :'');
					var toCss = item.data('baseCss') + (mode == 'in'? '': '-'+outCss);
					animated.push(toCss);	// add item to array and animate 
					animateToClass(item, src, fromCss, toCss, speed, j*delay, callBack);
				}
			}
		}
		function animateToClass($ele, src, fromCss, toCss, speed, delay, callBack) {
			if ($ele.length > 0) {	// if we have elements
				if (fromCss != null && fromCss != '') {	// reset to match class if provided
					$ele.attr('class', fromCss).removeAttr('style');
				}
				if (src != null) {	// if we have a new source, set it
					var $newE = $(src).find('.' + $ele.attr('rel'));
					if ($newE.length > 0) {	$ele.html($newE.html()); }
				}
				$ele.delay(delay)		// create timed delay for animation
					.animate(getStyle(toCss),	// get style for class
						speed,	// speed in ms
						function() {	// after animation remove item from array to 
							animated.splice($.inArray(toCss,animated),1);	// indicate animation complete
							if (typeof callBack == 'function') {
								callBack();		// exec callback if set
							}
						}
					)
				;
			}
		}
		function setAutoPaging(count) {
			if (count > 0) {	// only build if items
				var str = '';
				for (var i = 1;i<= count; i++) {	// build paging for each slide
					str += '<a href="#" id="page_'+i+'">' + i + '</a>';
				}
				if (s.prevNext && count > 1) {	// only add prev and next if more than 1
					str = '<a href="#" id="page_p" class="prev">&lt;</a>' + str + '<a href="#" id="page_n" class="next">&gt;</a>';
				}
				else if (pagePrev && pageNext && count > 1) {
					var p = $(pagePrev).html();	// if using custom prev & next and more than 1
					var n = $(pageNext).html();	// get custom btns
					str = '<a href="#" id="page_p" class="prev">'+p+'</a>' + str + '<a href="#" id="page_n" class="next">'+n+'</a>';
				}
				bindPaging(str);		// add paging to section
			}
		}
		function setCustomPaging(src) {
			// do loop through each item and grab paging item
			if (s.pageSource != null && s.pageSource != '') {
				var str = '';
				var i = 0;
				for (i; i < src.length; i++) {
					var t = src.eq(i);
					var val = '';
					if (t.find(s.pageSource).length > 0) {
						 val = t.find(s.pageSource).html();
					}
					str += '<a href="#" id="page_'+(i+1)+'">' + val + '</a>';
				}
				if (s.prevNext && i > 1) {	// only add prev and next if more than 1
					str = '<a href="#" id="page_p" class="prev">&lt;</a>' + str + '<a href="#" id="page_n" class="next">&gt;</a>';
				}
				else if (pagePrev && pageNext && i > 1) {
					var p = $(pagePrev).html();	// if using custom prev & next and more than 1
					var n = $(pageNext).html();	// get custom btns
					str = '<a href="#" id="page_p" class="prev">'+p+'</a>' + str + '<a href="#" id="page_n" class="next">'+n+'</a>';
				}	
				bindPaging(str);		// add paging to section
			} 
		}
		function bindPaging(str) {	
			e.find('.paging').append(function() {	// add paging string 
				return $(str).click(function() {	// converted to jQuery object with click
					if (animated.length <= 0) {		// event bound
						var idx = $(this).attr('id').replace('page_','');
						if (idx.match(/\D/g)){	// match non-numeric
							var cur = $.trim(e.find('#pageIndex').val());	// for prev & next
							if (cur != undefined && cur != null && cur != '' && !cur.match(/\D/g)) {
								if (idx == 'p') { idx = parseInt(cur) - 1; }
								else { idx = parseInt(cur) + 1; }
							}
							else { idx = 1; }
						}
						changeSlide(idx);
					}
					return false;
				});
			});
		}
		function getStyle(sel) {
			var validProp = new Array(	// list by w3c of valid properties for animate
				'backgroundPosition', 'borderWidth', 'borderBottomWidth', 'borderLeftWidth', 'borderRightWidth', 
				'borderTopWidth', 'borderSpacing', 'margin', 'marginBottom', 'marginLeft', 'marginRight', 'marginTop', 
				'outlineWidth', 'padding', 'paddingBottom', 'paddingLeft', 'paddingRight', 'paddingTop', 'height', 
				'width', 'maxHeight', 'maxWidth', 'minHeight', 'minWidth', 'font', 'fontSize', 'bottom', 'left', 'right', 
				'top', 'letterSpacing', 'wordSpacing', 'lineHeight', 'textIndent', 'opacity'
			);
			sel = ($.inArray('.',sel) != 0?'.' + sel:sel);
			var style = {};	// style object is returned
			$(document.styleSheets).each(function(idx,ss) {	// iterate through doc css
				try {
					var css = ss.rules || ss.cssRules;		// ie && ff support
					for (var i = 0; i < css.length; i++) {		// loop through definitions
						var def = css[i];
						if (def.selectorText && def.style.cssText) {	// if a selector and style is defined
							if (def.selectorText == sel) {		// if matching our style
								var s = def.style.cssText.toLowerCase().split(';');	// style is ; delimited string - split for loop
								for (var j = 0; j < s.length;  j++) {
									if ($.trim(s[j]) != '') {	// iterate each style and clean spaces
										var x = s[j].split(':');	// split style def from value
										if (~$.inArray('-',x[0])) {	// for jQuery we need camelCase 
											var y = x[0].split('-');	// split at - and capitalize
											for (k in z = y.slice(1)) { 	// first letter after first word
												z[k] = z[k].toString();
												z[k] = z[k].charAt(0).toUpperCase() + z[k].substr(1);
											}
											x[0] = y[0].toString() + z.join('');	// assemble camelCase style
										}
										if (~$.inArray($.trim(x[0]),validProp)) {
											style[$.trim(x[0])] = $.trim(x[1]);	// add to return object
										}
									}
								}
								break;
							}
						}
					}
				}
				catch(e) {}
			});
			return style;
		}
	};
	$.fn.wonderBanner = function(o) {
		return new wonderBanner(this,o);
	};
})(jQuery);

