
/**
 * roCarousel
 * jQuery plugin for a content carousel.
 * @version 2.1 - Added "bubbles", w/ special positioning for viewport width
 * @requires jQuery 1.3.2+
 * @author Spencer Sundell
 */
(function($) {
	$.fn.roCarousel = function (options) {
		var opts = $.extend({}, $.fn.roCarousel.defaults, options);
		function makeEmptyItems(cnt) {
			var emptyItem = '<li class="'+ opts.item.replace('.','') +' '+ opts.emptyClass +'"><div class="'+ opts.highlighter.replace('.','') +'"></div></li>';
	        return new Array(cnt+1).join(emptyItem);
	    };
	    return this.each(function () {
			/* Internal methods */
			var Bubbles={
				params: {
					'linkTitle':	'Click for more about this project',
					'mainClass':	'bubble',
					'bodyClass':	'bubble-body',
					'topClass':		'bubble-top',
					'bottomClass':	'bubble-bottom'
				},
				init: function(Items){
					if (!Items || typeof Items != 'object' || Items.length <= 0) { return false; }
					This = this;
					Items.each(function(){
						var $This = $(this),
							Link = $This.find('a').first(),
							Bubble = $This.find('div.'+ This.params.mainClass),
							BubbleId = Bubble.attr('id'),
							$This = null;
							
						if (Bubble.length == 1){
							/* Add formatting wrapper divs */
							Bubble.wrapInner('<div class="'+ This.params.bodyClass +'"/>')
								.prepend('<div class="'+ This.params.topClass +'"/>')
								.append('<div class="'+ This.params.bottomClass +'"/>');
							/* Ensure the bubble has an ID */
							if (BubbleId == false){
								Bubble.attr('id', Bubbles.makeId());
								BubbleId = Bubble.attr('id');
							}
							/* 
								Circumvent positioning issues (absolute elems inside relative wrapper):
								Copy Bubble into link's jQuery.data() object, and store dimensions and ID for later reference.
								We'll then display the $.data() copy on hover.
							*/
							Link.attr('title',This.params.linkTitle).data({
								'BubbleId': BubbleId,
								'Bubble': Bubble,
								'BubbleWidth': Bubble.outerWidth(),
								'BubbleHeight': Bubble.outerHeight()
							});
							/* Remove the original from the DOM so we don't have duplicate IDs. */
							Bubble.remove();
							/* Bind hover events and overlay click event.  DO NOT use 'overlay' class on links! */
							Link.bind({
								mouseover: function(){ Bubbles.mouseover(this) },
								mouseout: function(){ Bubbles.mouseout(this) }
							}).colorbox(opts.overlayConfig);
						}
					});
					/* Make sure we don't leave any orphans behind */
					$(window).unload(function(){ 
						$('div.'+ This.params.mainClass).remove();
					});
				},
				mouseover: function(obj){
					var viewport = $(window).width(),
						obj = $(obj),
						bubble = obj.data('Bubble'),
						bubbleWidth = obj.data('BubbleWidth'),
						bubbleHeight = obj.data('BubbleHeight'),
						width = ( parseInt( obj.width() /2) ),
						pos = obj.offset(),
						left = pos.left + width,
						top = pos.top,
						pos = null,
						BubbleLeft = parseInt( ( left - (bubbleWidth /2) ) + 100),
						BubbleTop = (top - (bubbleHeight + 40)),
						right = bubbleWidth + BubbleLeft;
						
					/* Jigger positioning in various IE versions. */
					if ($.browser.msie){
						var IEver = $.browser.version.substr(0,1);
						if (IEver >='8'){
							BubbleTop = BubbleTop - 20;
						} else if (IEver == '7') {
							BubbleTop = BubbleTop + 38;
							BubbleLeft--;
						} else if (IEver == '6') {
							BubbleTop = BubbleTop + 40;
							BubbleLeft--;
						}
					}
					/* Ensure we don't exceed the available width */
					if (right > viewport) {
						var diff = parseInt(right - viewport);
						BubbleLeft -= diff;
						if ($.browser.msie){
							if (diff > 60){
								bubble.addClass('bubbleAltRight');
							} else {
								bubble.addClass('bubbleAlt');
								BubbleLeft -= 30;
							}
						} else {
							if (diff > 55){
								bubble.addClass('bubbleAltRight');
							} else {
								bubble.addClass('bubbleAlt');
								//BubbleLeft -= 40;
							}
						}
					}
					
					/* Add positioned bubble to the DOM */
					bubble.css({
						'left': BubbleLeft,
						'top': BubbleTop
					}).appendTo('body');
					/* clean up */
					obj=null;
				},
				mouseout: function(obj){
					var obj = $(obj);
					$('#'+ obj.data('BubbleId')).removeClass('bubbleAlt').remove();
					obj=null;
				},
				makeId: function() {
					var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz1234567890',
						len = 16,
						out = 'b-'; /* IDs must start w/ a letter */
					for (var i=0; i<len; i++) {
						var rnum = Math.floor(Math.random() * chars.length);
						out += chars.substring(rnum,rnum+1);
					}
					return out;
				}
			},
			/* Helper methods for the carousel */
			methods={
				listOrder: function(){
					var start = opts.start -1; /* adjust for array order */
					if (start <= 0) { return false; }
					
					/* Convert $items object to a real array for re-sorting. */
					/* @todo Find a way to resort directly in jQ w/out a whole new array? Seems tricky. */
					var stuff = [];
					for (var i=0; i<$itemsLen; i++){
						stuff.push($items[i]);
					}
					var first = stuff.slice(start),
						second = stuff.slice(0, start),
						newOrder = first.concat(second),
						len = stuff.length,
						first=second=null,
						tmpUL = $ul;
					/* optimized DOM manipulation */
					tmpUL.remove();
					tmpUL.empty();
					for (var i=0; i<len; i++){
						tmpUL.append(newOrder[i]);
					}
					$wrapper.append(tmpUL);
					UL=newOrder=len=null;
					/* re-select to global var, in our new order */
					$items = $ul.find('li'+ opts.item);
				},
				setContainerWidths: function(){
					/* 
						First, set item widths according to param.
						Apply to buffer Div (to accommodate its padding, if any).
					*/
					$highlighters.each(function(){
						$(this).css({'width':opts.itemWidth});
					});
					/* Get outerwidth of buffer to apply to parent LI via dynamic stylesheet, added later. */
					highlightWidth = $highlighters.filter(':first').outerWidth(true);
					styles.push(
						opts.highlighter +' {width:'+ opts.itemWidth +'px;}',
						opts.item +' {width:'+ highlightWidth +'px;}'
					);
					/* Set critical global var: itemWidth */
					itemWidth = $items.filter(':first').outerWidth(true);
					
					/* 
						Now set widths of the proper wrapper divs.
						Make sure items will fit evenly in the available space!
						If not, round the master width down to avoid spill-over in the surrounding layout.
						Later paging calculations will accommodate as needed.
					*/
					var outerWidth = $This.outerWidth(),
						margin = parseInt($wrapper.css('marginLeft').replace('px','')) + parseInt($wrapper.css('marginRight').replace('px','')),
						innerWidth = outerWidth - margin,
						check = innerWidth / itemWidth,
						checkRnd = Math.round(check);
					if (check !== checkRnd ){
						/* Item width doesn't evenly divide into innerWidth, so round down: */
						innerWidth = (itemWidth * Math.floor(check));
					} 
					$wrapper.width(innerWidth);
					$This.width(innerWidth + (borderHeight + margin));
				},
				setHeights: function(){
					var wrapperHeight = 0,
						itemInnerHeight = 0,
						$chromeDivs = $This.find(opts.chromePads),
						chromePadding = 0;
					$items.each(function(){
						var $t = $(this),
							tmp = $t.outerHeight(true),
							inner = $t.innerHeight();
						if (tmp > wrapperHeight) {
							wrapperHeight = tmp;
							itemInnerHeight = inner - itemPadding;
						}
						$t = null
					});
					
					if ($chromeDivs.length > 0){
						$chromeDivs.each(function(){
							var $t = $(this);
							chromePadding += parseInt($t.css('paddingTop').replace('px','')) + parseInt($t.css('paddingTop').replace('px','')) || 0;
							$t=null;
						});
					}
					$chromeDivs = null;
					
					/* Set heights for both the wrapper and its parent */
					$wrapper.height(wrapperHeight).parent().height(wrapperHeight -(chromePadding - borderHeight));
					
					if (opts.highlight === true) {
						var h = itemInnerHeight - highlightPadVert;
						styles.push(
							opts.highlighter +' {height: '+h+'px;}',
							opts.highlighter +':hover {background-color: '+ opts.highlightColor +';}',
							'.'+ opts.emptyClass +' '+ opts.highlighter +':hover {background-color: transparent;}'
						);
					}
				},
				addStylesheet: function(){
					var len = styles.length || 0;
					if (len == 0) { return false }
					var out = '<style type="text/css">';
					for (var i=0; i<len; i++){
						out += '#'+ $ThisID +' '+ styles[i]+' ';
					}
					out += '</style>';
					$('head').append(out);
					out=null;
				},
		        pageScroll: function(page) {
					if (!page || page == null) { var page = 0; }
		            var dir = (page < currentPage) ? -1 : 1,
		                n = Math.abs(currentPage - page),
		                left = itemWidth * dir * pageLen * n;
		            
		            $wrapper.filter(':not(:animated)').animate({
		                scrollLeft : '+=' + left
		            }, 500, function () {
		                if (page == 0) {
		                    $wrapper.scrollLeft(itemWidth * pageLen * pages);
		                    page = pages;
		                } else if (page > pages) {
		                    $wrapper.scrollLeft(itemWidth * pageLen);
		                    page = 1;
		                } 
		                currentPage = page;
		            });
		            return false;
		        }
			};
			
			/** ******************************
			 * Begin our core carousel plugin logic.
			 */
			var $This = $(this),
				$ThisID = $This.attr('id'),
				$wrapper = $This.find('div'+ opts.wrapper).css('overflow', 'hidden'),
	            $ul = $wrapper.find('ul'+ opts.ul),
	            $items = $ul.find('li'+ opts.item),
				$itemsLen = $items.length,
				$highlighters = $items.find(opts.highlighter),
				highlightSingle = $highlighters.filter(':first'),
				/* Explicitly specify zero as fall-back, else IE returns NaN. */
				highlightPadVert = parseInt(highlightSingle.css('paddingTop')) + parseInt(highlightSingle.css('paddingBottom')) || 0,
				highlightPadHoriz = parseInt(highlightSingle.css('paddingRight')) + parseInt(highlightSingle.css('paddingLeft')) || 0,
				highlightSingle=null,
				borderHeight = parseInt($wrapper.css('borderTopWidth').replace('px','')) + parseInt($wrapper.css('borderBottomWidth').replace('px','')) || 0, 
				$i = $items.filter(':first'),
				itemPadding = parseInt($i.css('paddingTop').replace('px','')) + parseInt($i.css('paddingBottom').replace('px','')) || 0,
				$i=null,
	            currentPage = 1,
				styles = [],
				itemWidth,
				pageLen,
				pages;
			
			/* perform cosmetic formatting */
			if (opts.shadow != false){
				$This.after('<div class="'+ opts.shadow +'"></div>');
			}
			/* Prepare our stuff */
			methods.listOrder();
			methods.setContainerWidths();
			methods.setHeights();
			methods.addStylesheet();
			
			/* Initialize the bubbles before going any further. */
			if (opts.makeBubbles == true){
				Bubbles.init($items);
	        }
			
			/* calculate paging */
			pageLen = Math.ceil($wrapper.innerWidth() / itemWidth),
			pages = Math.ceil($itemsLen / pageLen);
			
	        /* Add empties to accommodate paging, if needed */
	        if (($itemsLen % pageLen) != 0) {
	            $ul.append(makeEmptyItems(pageLen - ($itemsLen % pageLen)));
	            $items = $ul.find(opts.item);
	        }
			
			/* Implement endless scrolling: clone copies of visible # of items to top and end of list. */
	        $items.filter(':first').before($items.slice(- pageLen).clone().addClass(opts.cloneClass));
	        $items.filter(':last').after($items.slice(0, pageLen).clone().addClass(opts.cloneClass));
	        $items = $ul.find(opts.item);
	        
	        /* Be kind: rewind */
	        $wrapper.scrollLeft(itemWidth * pageLen);
			
			/* Add nav */
	        $wrapper.after('<a class="'+ opts.arrowClass +' '+ opts.backClass +'">&lt;</a><a class="'+ opts.arrowClass +' '+ opts.forwardClass +'">&gt;</a>');
	        $('a.'+ opts.backClass, this).click(function(){
	            return methods.pageScroll(currentPage - 1);                
	        });
	        $('a.'+ opts.forwardClass, this).click(function(){
	            return methods.pageScroll(currentPage + 1);
	        });
			
	    });  
	};
	$.fn.roCarousel.defaults={
		makeBubbles:	false,
		start:			1,	/* Start item, using logical number (not array index) */
		itemWidth:		99, /* Indiv. item, in pixels; can override via std. invocation syntax */
		highlight:		false,
		highlightColor:	'#eeeeee',
		wrapper: 		'.roCarousel-wrapper',
		highlighter:	'.roCarousel-buffer',
		ul:				'.roCarousel-holder',
		item:			'.roCarousel-item',
		shadow:			'roCarousel-shadow',
		emptyClass:		'roCarousel-empty',
		arrowClass:		'roCarousel-arrow',
		forwardClass:	'roCarousel-forward',
		backClass:		'roCarousel-back',
		cloneClass:		'roCarousel-clone',
		chromePads:		'.roCarousel-chrome1, .roCarousel-chrome3', /* Classes of chrome Divs w/ vertical padding */
		overlayConfig: {
			iframe: true, 
			innerWidth: 880, 
			height: '95%',
			scalePhotos: false,
			close: 'Close'
		}
	};
})(jQuery);

$(document).ready(function () {
	$('#filmstrip').roCarousel({
		makeBubbles: true
	});
});
	

