/* ======================
| VID_CAROUSEL.JS - single file for instantiating video carousel into page
|
| AUTHOR: Andy Croxal
|
| DEPENDENCIES:
| 	- X(HTML): whatever page is hosting the carousel
| 	- JS: jQuery
| 	- CSS: vid_carousel.css
|	- other: XML config file (specified in @config param at instantiation)
====================== */




function VideoCarousel(jdo) { var thiss = this; $(function() {

	
	/* ----------------------
	| PREP
	---------------------- */

	var IE = navigator.appName == 'Microsoft Internet Explorer';
	var earlyIE = /MSIE (6|7)/.test(navigator.appVersion);
	var errorStartText_instantiation = "Video carousel > instantiation error: ";
	var errorStartText_config = "Video carousel > XML config error: ";
	var error = false;
	var validTabPositions = ['top', 'right', 'bottom', 'left'];
	var validInfoAreaPositions = ['left', 'right', 'none'];
	var validTabTypes = ['thumbs', 'text'];
	var validTransitionTypes = ['slide left', 'slide right', 'slide up', 'slide down'];
	var container, slidesAndTabsContainer, tooltip, slidesUL, tabsUL, li, thumb;
	var interruptAutoInt = true;
	
	
	/* ----------------------
	| VALIDATE 1 - validate params passed in JDO
	---------------------- */
	
	if (!jdo|| typeof jdo != 'object')
		error = 'params not passed at instantiation';
	else if (!jdo.container || !(typeof jdo.container == 'string' || typeof jdo.container == 'object') || $(jdo.container).length == 0)
		error = 'no container specified for the carousel';
	else if (!jdo.configPath || typeof jdo.configPath != 'string')
		error = 'no config file specified';
		
	if (error) { alert(errorStartText_instantiation+error); return; }
	
	
	/* ----------------------
	| CONFIG - load config file stipulated in JDO
	---------------------- */
	
	$.ajax({
		url: jdo.configPath,
		async: false,
		success: function(xml) {
			if (typeof xml == 'string') {
				xml = xml.match(/<root>[\s\S]+<\/root>/);
				xml = VideoCarousel.parseXML(xml);
			}
			if ($(xml).children().length == 1 && $(xml).children().get(0).tagName.toLowerCase() == 'root')
				thiss.config = $(xml).children('root');
		}
		
	});
	
	if (!thiss.config) {
		alert(errorStartText_config+"couldn't load config file '"+jdo.configPath+"' - file not found, is badly parsed or doesn't contain <root> element");
		return;
	}
	
	
	/* ----------------------
	| VALIDATE 2 - validate config data in file
	---------------------- */

	if (thiss.config.children('settings').length != 1)
		error = 'No settings (root/settings) specified';
	else
		thiss.settings = thiss.config.children('settings');
		
	if (thiss.config.children('slides').length != 1 || thiss.config.find('slides slide').length == 0)
		error = "no slides data (root/slides) specified";
	else if (thiss.config.find('settings tabs').length != 1)
		error = "no tab settings (root/settings/tabs) specified";
	else if (thiss.config.find('settings tabs where').length != 1 || $.inArray(thiss.config.find('settings tabs where').text(), validTabPositions) == -1)
		error = "no or invalid tab position setting (root/settings/tabs/where) specified";
	else if (thiss.config.find('settings tabs type').length != 1 || $.inArray(thiss.config.find('settings tabs type').text(), validTabTypes) == -1)
		error = "no or invalid tab type setting (root/settings/tabs/type) specified";
	else if (thiss.config.find('settings slides').length != 1)
		error = "No slide settings (root/settings/slides) specified";
	else if (thiss.config.find('settings slides width').length != 1)
		error = "No slide width (root/settings/slides/width) specified";
	else if (thiss.config.find('settings slides height').length != 1)
		error = "No slide width (root/settings/slides/height) specified";
	else if (thiss.config.find('settings slides transition').length != 1)
		error = "No slide transition (root/settings/slides/transition) settings specified";
	else if (thiss.config.find('settings slides transition type').length != 1 || $.inArray(thiss.config.find('settings slides transition type').text(), validTransitionTypes) == -1)
		error = "no or invalid transition type setting (root/settings/slides/transition/type) specified";
	else if (thiss.config.find('settings slides transition duration').length != 1)
		error = "No transition duration (root/settings/slides/transition/duration) specified";
	else if (thiss.config.find('settings info_area').length != 1 || $.inArray(thiss.config.find('settings info_area').text(), validInfoAreaPositions) == -1)
		error = "no or invalid info area position (root/settings/info_area) specified";
	
	if (error) { alert(errorStartText_config+error); return; }
	
	
	/* ----------------------
	| SETTINGS OVERRIDES - process any overrides to default settings, specified at instantiation
	---------------------- */
	
	if (jdo.settingsOverrides && typeof jdo.settingsOverrides == 'object') {
		for(var y in jdo.settingsOverrides)
			thiss.config.children('settings').find(y).text(jdo.settingsOverrides[y]);
	}
	
	
	/* ----------------------
	| MASTER CONTAINER (DIV) to hold everything
	---------------------- */
	
	(container = $('<div>'))
		.appendTo(jdo.container)
		.addClass('videoCarousel');
	var containerPadding = {top: container.css('paddingTop'), right: container.css('paddingRight'), bottom: container.css('paddingBottom'), left: container.css('paddingLeft')};


	/* ----------------------
	| CONTAINER (DIV) for my main slides and tabs ULs
	---------------------- */

	(slidesAndTabsContainer = $('<div>'))
		.addClass('slidesAndTabsContainer')
		.appendTo(container);
		

	/* ----------------------
	| SLIDES LIST - main UL that holds slides.
	---------------------- */
		
	(slidesUL = $('<ul>'))
		.appendTo(slidesAndTabsContainer)
		.css({width: thiss.config.find('settings slides width').text()+'px', height: thiss.config.find('settings slides height').text()+'px'})
		.addClass('slidesList')
		.dimsCorrect();


	/* ----------------------
	| TOOLTIP for hover on tabs
	---------------------- */
		
	(tooltip = $('<div>'))
		.addClass('tabTooltip')
		.appendTo(container)
	$('<span>')
		.appendTo(tooltip);	
	$('<div>')
		.appendTo(tooltip);
		
		
	/* ----------------------
	| BUILD SLIDES (LI) and turn on first slide
	---------------------- */
	
	thiss.slidesData = thiss.config.find('slides slide');
	var it = thiss.slidesData.length;
	thiss.slidesData.each(function() {
		
		//validate slide
		var slide = $(this).children('slide_src');
		if (slide.length == 1) slide = slide.text(); else return;
		var vid = $(this).children('vid_src');
		vid = vid.length == 1 ? vid.text() : false;
		
		//build
		(li = $('<li>'))
			.css({zIndex: it})
			.appendTo(slidesUL)
			.dimsCorrect();
		
				
		//add picture OR movie, depending on whether there's a 'vid_src' node
		
		if (vid) {
			
			$('<div>').appendTo(li).attr('id', 'vidPlaceholder_'+it);
			jwplayer('vidPlaceholder_'+it).setup({
				flashplayer: 'jwplayer/player.swf',
				image: slide,
				file: vid,
				skin: "jwplayer/nacht.zip",
				icons: true,
				height: thiss.config.find('settings slides height').text(),
				width: thiss.config.find('settings slides width').text(),
				controlbar: 'bottom'
			});
			
		} else
			li.css({background: "url('"+slide+"')"});
			
		it--;
			
	});
	thiss.slides = slidesUL.children();
	thiss.slides.first().addClass('on').css('zIndex', 12);
	
	
	/* ----------------------
	| TABS CONTAINAER (UL - position and type depends on config)
	---------------------- */
	
	//where's it going?
	var where = thiss.config.find('settings tabs where').text();
	var axis = where == 'left' || where == 'right' ? 'y' : 'x';
	
	//build and append (or prepend if position == top or left)
	(tabsUL = $('<ul>'))
		.addClass('tabsList');
	where == 'right' || where == 'bottom' ? tabsUL.appendTo(slidesAndTabsContainer) : tabsUL.prependTo(slidesAndTabsContainer);
	
	
	//give class denoting position and type and set dimensions, dependent on which axis we're sitting on
	
	tabsUL.addClass(where);
	if (where == 'left' || where == 'right')
		tabsUL.css({height: slidesUL.get(0).offsetHeight, width: 315+'px'});
	else
		tabsUL.css({width: slidesUL.get(0).offsetWidth});
	tabsUL.dimsCorrect();
	
	//if axis == 'y', float it, and slides UL
	if (axis == 'y') tabsUL.add(slidesUL).css('float', 'left');
	
	//expand container to accommodate tabs UL so it doesn't overlay main presentation area
	//container.css(axis == 'x' ? 'height' : 'width', axis == 'x' ? container.height() + tabsUL.get(0).offsetHeight : container.width() + tabsUL.get(0).offsetWidth);
	
	
	/* ----------------------
	| TABS (LI)
	---------------------- */
	
	thiss.slidesData.each(function() {

		//build
		(li = $('<li>'))
			.appendTo(tabsUL)
		
				
		//set dimensions. Take into account any margin in the parent UL
		
		var setWhat = axis == 'y' ? 'height' : 'width';
		var val = Math.floor((axis == 'y' ? tabsUL.height() : tabsUL.width()) / 4);
		var marginTopOrLeft = li.css('margin'+(axis == 'y' ? 'Top' : 'Left'));
		var marginBottomOrRight = li.css('margin'+(axis == 'y' ? 'Bottom' : 'Right'));
		if (marginTopOrLeft == 'auto') marginTopOrLeft = 0;
		if (marginBottomOrRight == 'auto') marginBottomOrRight = 0;
		val -= parseInt(marginTopOrLeft) + parseInt(marginBottomOrRight);
		val += Math.floor((parseInt(marginTopOrLeft) + parseInt(marginBottomOrRight)) / thiss.slidesData.length);
		li.css(setWhat, val).dimsCorrect(null);
		
		//add thumb
		if ((thumb = $(this).children('thumb_src')).length == 1) li.css('background-image', "url('"+thumb.text()+"')");
		
		
		//on click:
		//	- do nothing if tab represents slide that's already on
		//	- hide hover tooltip
		//	- stop any vid the last-on slide was playing
		//	- return tab to original pos (will still be at top of hover bounce)
		//	- turn on, and turn off prev-on tab
		//	- show associated slide
		//	- interrupt auto interval (handled elsewhere)
		
		li.click(function() {
			if ($(this).is('.on')) return;
			tooltip.hide();
			var slideLastOn_video = thiss.slides.eq($(this).siblings('.on').index()).children('object');
			if (slideLastOn_video.length == 1) jwplayer(slideLastOn_video.get(0)).stop();
			$(this).animate({top: 0, left: 0, opacity: 1}, 'fast', null, function() { $(this).addClass('on'); });
			$(this).siblings().removeClass('on').animate({opacity: .5});
			thiss.showSlide($(this).index());
		})
		
		
		//on hover, if not 'on' tab, do bounce and show tooltip of headline. Also interrupt auto int (handled elsewhere)
		
		li.bind('mouseenter mouseleave', function(evt) {
			
			if ($(this).is('.on')) return;
			
			
			//bounce
			
			var animVal = evt.type == 'mouseenter' ? 4 : 0;
			switch(where) {
				case 'top': var animObj = {top: animVal}; break;
				case 'right': var animObj = {left: -animVal}; break;
				case 'bottom': var animObj = {top: -animVal}; break;
				case 'left': var animObj = {left: animVal}; break;
			}
			$(this).animate(animObj, 'fast');
			
			
			//tooltip (set position depending on position of tabs)
			
			if (evt.type == 'mouseenter') {

				var title = thiss.slidesData.eq($(this).index()).children('title');
				
				if (title.length == 1) {
					
					tooltip.children('span').text(title.text()).end().show();
					
					var tt_info = {w: tooltip.get(0).offsetWidth, h: tooltip.get(0).offsetHeight};
					var tab_info = {w: $(this).get(0).offsetWidth, h: $(this).get(0).offsetHeight, t: $(this).position().top + $(this).parent().position().top, l: $(this).position().left + $(this).parent().position().left};
					var tt_arrow_info = {w: tooltip.children('div').get(0).offsetWidth, h: tooltip.children('div').get(0).offsetHeight};
					
					var additCSS_ifYAxis = {top: (tab_info.h / 2) - (tt_info.h / 2)};
					var additCSS_ifXAxis = {left: (tab_info.h / 2) - (tt_info.h / 2)};
					
					switch(where) {
						case 'top':
							var CSS = {top: tab_info.t + 15 + tab_info.h, left: (tab_info.l + (tab_info.w / 2)) - (tt_info.w / 2)};
							var arrowCSS = {left: (tt_info.w / 2) - (tt_arrow_info.w / 2), top: -tt_arrow_info.h, backgroundPosition: -18};
							break;
						case 'right':
							var CSS = {top: tab_info.t + (tab_info.h / 2) - (tt_info.h / 2), left: tab_info.l - (15 + tt_info.w)};
							var arrowCSS = {right: -tt_arrow_info.w, top: (tt_info.h / 2) - (tt_arrow_info.h / 2), backgroundPosition: -9};
							break;
						case 'bottom':
							var CSS = {top: tab_info.t - (15 + tt_info.h), left: (tab_info.l + (tab_info.w / 2)) - (tt_info.w / 2)};
							var arrowCSS = {left: (tt_info.w / 2) - (tt_arrow_info.w / 2), bottom: -tt_arrow_info.h, backgroundPosition: -27};
							break;
						case 'left':
							var CSS = {top: tab_info.t + (tab_info.h / 2) - (tt_info.h / 2), left: tab_info.l + tab_info.w + 15};
							var arrowCSS = {left: -tt_arrow_info.w, top: (tt_info.h / 2) - (tt_arrow_info.h / 2), backgroundPosition: 0};
							break;
					}
					
					tooltip.css(CSS).children('div').css(arrowCSS);
				
				}
				
			} else
				tooltip.hide();
			
		});
			
		
	});
	
	//clear any left/top margin from first LI and right/bottom margin from last LI (which one in each case depends on axis)
	tabsUL.children('li:first').css('margin'+(axis == 'y' ? 'Top' : 'Left'), 0);
	tabsUL.children('li:last').css('margin'+(axis == 'y' ? 'Bottom' : 'Right'), 0);
	
	//turn on first tab (but no fade anim)
	tabsUL.children(':first').css('opacity', 1).addClass('on');
	
	
	/* ----------------------
	| INFO AREA - build info area, if config allows
	---------------------- */
	
	var ia = thiss.config.find('settings info_area');
	if (ia.length == 1 && ia.text() != 'none') {
		
		//prep
		thiss.infoArea_pos = ia.text() == 'left' ? 'Left' : 'Right';
		
		//build and add
		
		var infoArea;
		(infoArea = $('<div>'))
			.addClass('infoArea '+(thiss.infoArea_pos))
			.prependTo(container);
		infoArea.css(ia.text() == 'left' ? 'left' : 'right', container.css('padding'+thiss.infoArea_pos));
		
		//h2
		$('<h2>')
			.addClass('title')
			.appendTo(infoArea);
			
		//info
		$('<div>')
			.addClass('text')
			.appendTo(infoArea);
			
		//more button
		$('<button>')
			.addClass('more')
			.appendTo(infoArea);
			
		//clear
		$('<div>')
			.addClass('cl')
			.appendTo(infoArea);
		
			
		//extend container to accommodate and, if positioning on left, float slides/tabs container over to right
		
		container.css('width', 960+'px');
		if (thiss.infoArea_pos == 'Left') slidesAndTabsContainer.css('float', 'right');
		
	}	
	
	
	/* ----------------------
	| SHOW SLIDE - called when tab clicked. Expects slide index.
	---------------------- */
	
	VideoCarousel.prototype.showSlide = function(slideIndex, isOnloadNotClick) {
		
		
		//establish slide and transition type
		
		var slide = this.slides.eq(slideIndex);
		slide.css('zIndex', 10).siblings(':not(.on)').css('zIndex', 9);
		var transitionType = this.config.find('settings slides transition type').text();
		
		
		//SLIDE - if transition type is slide, work out anim coords depending on desired slide direction then effect
		
		if (transitionType.match(/^slide/)) {
		
			if (transitionType.match(/(left|right)$/)) {
				var animObj = {left: this.slides.parent().width()};
				if (transitionType.match(/left$/)) animObj.left = -animObj.left;
			} else if (transitionType.match(/(up|down)$/)) {
				var animObj = {top: this.slides.parent().height()};
				if (transitionType.match(/up$/)) animObj.top = -animObj.top;
			}
			
			this.slides.filter('.on').animate(animObj, !isOnloadNotClick ? parseInt(this.config.find('settings slides transition duration').text()) : 0, null, function() {
				$(this)
					.css({left: 0, top: 0, zIndex: 9})
					.removeClass('on');
				slide.addClass('on').css('zIndex', 12);
			});
			
		}
		
		
		//info area
		
		if (ia.text() != 'none') {
			var h2 = this.slidesData.eq(slideIndex).children('title');
			infoArea.children('.title').text(h2.length == 1 ? h2.text() : '');
			var info = this.slidesData.eq(slideIndex).children('info');
			infoArea.children('.text').html(info.length == 1 ? info.text() : '');
			var url = this.slidesData.eq(slideIndex).children('url');
			infoArea.children('.more').css('display', url.length == 1 && url.text() != '' ? 'block' : 'none').click(function() {
				if (url.length == 1) location.href = url.text();
			});
		}
		
	}
	thiss.showSlide(0, true);
	
	
	/* ----------------------
	| AUTO-ROTATE - set up auto-rotate, if config allows (interrupted on mouseenter/leave on/from tabs)
	---------------------- */
	
	var ar = thiss.config.find('settings slides auto_rotate');
	var ar_ms;
	
	if (ar.length == 1 && typeof (ar_ms = parseInt(ar.text())) == 'number')
		setInterval(function() {
			if (interruptAutoInt) return;
			var tabOn = tabsUL.children('.on');
			tabOn.index() < tabsUL.children().length-1 ? tabOn.next().click() : tabOn.siblings(':first').click();
		}, ar_ms)
		
	tabsUL.children().bind('mouseenter mouseleave', function() { interruptAutoInt = true; });
	
});}


/* ----------------------
| UTILITY - correct for fact that border and padding make offset width/height differ from set width/height. e.g. if an element has a width
| of 100px set, and padding-right of 10px, this code will reduce its width to 90px, so its width is still 100px. Screw you, box model!
| ignore is an array of params to ignore from the calculation, e.g. ['paddingLeft']
---------------------- */

$.fn.dimsCorrect = function(ignore) {
	
	var affectsWidth = ['paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'];
	var affectsHeight = ['paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'];
	
	for(var e in affectsWidth) {
		if (!ignore || ($.inArray(affectsWidth[e], ignore) == -1) && $.inArray(affectsWidth[e].match(/[a-z]+/)[0], ignore) == -1) {
			var curr = parseInt($(this).css(affectsWidth[e]));
			if (isNaN(curr)) curr = 0;
			$(this).css('width', $(this).width() - curr);
		}
	}
	
	for(var e in affectsHeight) {
		if (!ignore || ($.inArray(affectsHeight[e], ignore) == -1) && $.inArray(affectsHeight[e].match(/[a-z]+/)[0], ignore) == -1)
			var curr = parseInt($(this).css(affectsHeight[e]));
			if (isNaN(curr)) curr = 0;
			$(this).css('height', $(this).height() - curr);
	}
	
}


/* ----------------------
| UTILITY - parse XML. Sometimes jQuery won't parse XML, depending on MIME type/browser/feed file ext. etc. Force here.
---------------------- */

VideoCarousel.parseXML = function(xmlStr) {
	if (window.ActiveXObject) {
		var doc = new ActiveXObject('Microsoft.XMLDOM');
		doc.loadXML(xmlStr);
		return doc;
	} else if (window.DOMParser)
		return (new DOMParser).parseFromString(xmlStr, 'text/xml');
	return xmlStr;
}
