/*
	file:			jquery.slider.js
	version:		1.0
	author:			sam jackson
*/


(function(jQuery)
{

	// sliderDefaults contains default options for the plugin
	var sliderDefaults =
	{
		minHeight:				400,
		maxHeight:				400,
		slideSpeed:				1000,
		loopInterval:			8000
	};

	// sliderIdentifiers contains string id's which are to be attached to certain elements
	var sliderIdentifiers =
	{
		mainContainer:			'container-main',
		innerContainer:			'container-inner',
		slidesContainer:		'container-slides',
		controlsContainer:		'container-controls'
	};

	// sliderClasses contains string classes which are to be given to certain elements
	var sliderClasses =
	{
		controlsContainer:		'container-controls',
		slideContainer:			'container-slide',
		innerSlideContainer:	'container-inner-slide',
		slideContentContainer:	'container-slide-content',
		slideImageContainer:	'container-slide-image',
		slideExtraContainer:	'container-slide-extra',
		extraElement:			'element-extra',
		navigationList:			'list-navigation',
		navigationButton:		'button-navigation'
	};

	// sliderData contains functions used for getting and setting data unique to the plugins parent element
	var sliderData =
	{
		DATA_NAME:				'sliderdata',
		
		// set is used to assign data to the specified element
		set:					function(element, data)
								{
									if (data)
									{
										var oldData = sliderData.get(element);
										var extendedData = jQuery.extend({}, oldData, data);
										jQuery.data(element.get(0), sliderData.DATA_NAME, extendedData);
										return data;
									}
									return false;
								},
								
		// get is used to return saved data from the specified element
		get:					function(element)
								{
									return jQuery.data(element.get(0), sliderData.DATA_NAME) || {};
								},
								
		// slides is used to get or set slides				
		slides:					function(element, slides)
								{
									if (slides != undefined) return sliderData.set(element, {slides: slides}).slides;
									return sliderData.get(element).slides;
								},
								
		// sliderOptions is used to get or set options
		sliderOptions:			function(element, options)
								{
									if (options != undefined) return sliderData.set(element, {sliderOptions: options}).sliderOptions;
									return sliderData.get(element).sliderOptions;	
								},
								
		// sliderClasses is used to get or set classes
		sliderClasses:			function(element, classes)
								{
									if (classes != undefined) return sliderData.set(element, {sliderClasses: classes}).sliderClasses;
									return sliderData.get(element).sliderClasses;	
								},			
								
		// currentIndex is used to set the current slide index
		currentIndex:			function(element, index)
								{
									if (index != undefined) return sliderData.set(element, {currentIndex: index}).currentIndex;
									return sliderData.get(element).currentIndex;
								}
	};
	
	// sliderFind is used to find and return certain elements
	var sliderFind =
	{
		elementById:			function(slider, identifier)
								{
									return slider.find("#" + identifier);
								},
								
		elementsByClass:		function(slider, cls)
								{
									return slider.find("." + cls);	
								},
								
		slidesContainer:		function(slider)
								{
									return sliderFind.elementById(slider, sliderIdentifiers.slidesContainer);
								},
								
		controlsContainer:		function(slider)
								{
									return sliderFind.elementById(slider, sliderIdentifiers.controlsContainer);
								},
								
		navigationButtons:		function(slider, classes)
								{
									if (classes == undefined) classes = sliderData.sliderClasses(slider);
									return sliderFind.elementsByClass(slider, classes.navigationButton);
								}
	};
	
	// sliderCreate is used to create and return new elements with some default settings: id, class, css, children
	var sliderCreate =
	{
		mainContainer:			function(classes, minHeight)
								{
									return $("<div></div>")
										.attr('id', sliderIdentifiers.mainContainer)
										.css({	overflow:	'hidden',
												position:	'relative',
												height:		minHeight,
												width:		'100%'		});
								},
								
		innerContainer:			function(classes)
								{
									return $("<div></div>")
										.attr('id', sliderIdentifiers.innerContainer)
										.css({	overflow:	'hidden',
												position:	'relative',
												width:		'100%'		});
								},
								
		slidesContainer:		function(classes, slider, slidesCount, maxHeight)
								{
									return $("<div></div>")
										.attr('id', sliderIdentifiers.slidesContainer)
										.css({	overflow:	'hidden',
												height:		maxHeight,
												position:	'relative',
												width:		slider.width() * slidesCount	});
								},
								
		controlsContainer:		function(classes)
								{
									return $("<div></div>")
										.attr('id', sliderIdentifiers.controlsContainer)
										.addClass(classes.controlsContainer)
										.css({	overflow:	'hidden',
												position: 	'relative',
												width:		'100%'			});
								},
								
		slideContainer:			function(classes, slider, maxHeight, containerIndex)
								{
									return $("<div></div>")
										.addClass(classes.slideContainer)
										.addClass(classes.slideContainer + containerIndex)
										.css({	overflow:	'hidden',
												float:		'left',
												height:		maxHeight * 2,
												width:		slider.width()		});
								},
								
		innerSlideContainer:	function(classes)
								{
									return $("<div></div>")
										.addClass(classes.innerSlideContainer)
										.css({	overflow:	'hidden',
												position:	'relative',
												height:		'50%'		});
								},
								
		slideImageContainer:	function(classes)
								{
									return sliderCreate.innerSlideContainer(classes)
										.css({	width:		'100%'	})
										.addClass(classes.slideImageContainer);
								},
								
		slideContentContainer:	function(classes)
								{
									return sliderCreate.innerSlideContainer(classes)
										.addClass(classes.slideContentContainer);
								},
								
		slideExtraContainer:	function(classes, containerIndex)
								{
									return $("<div></div>")
										.addClass(classes.slideExtraContainer)
										.addClass(classes.slideExtraContainer + containerIndex);
								},
								
		h1:						function(classes, text)
								{
									return $("<h1></h1>").text(text);
								},
								
		h2:						function(classes, text)
								{
									return $("<h2></h2>").text(text);
								},
								
		p:						function(classes, text)
								{
									return $("<p></p>").html(text);
								},
								
		list:					function(classes)
								{
									return $("<ul></ul>");
								},
								
		navigationList:			function(classes)
								{
									return sliderCreate.list(classes)
										.addClass(classes.navigationList);
								},
								
		navigationButton:		function(classes)
								{
									return $("<li></li>")
										.addClass(classes.navigationButton);
								},
								
		image:					function(classes, src, dimensions)
								{
									var dimensionsText = "";
									if (dimensions != undefined)
									{
										if (dimensions.HEIGHT != undefined) dimensionsText = "height='" + dimensions.HEIGHT + "'";
										if (dimensions.WIDTH != undefined) dimensionsText += " width='" + dimensions.WIDTH + "'";	
									}
									
									return $("<img " + dimensionsText + "></img>")
										.attr('src', src);
								},
								
		flash:					function(classes, src, dimensions)
								{
									var html = "<object width='" + dimensions.WIDTH + "' height='" + dimensions.HEIGHT + "'><param name='wmode' value=\'transparent\'><param name='movie' value='" + src + "'><embed type='application/x-shockwave-flash' src='" + src + "' wmode='transparent' width='" + dimensions.WIDTH + "' height='" + dimensions.HEIGHT + "'></embed></object>";					
									return $(html);
								},
								
		makeIntoLink:			function(classes, element, href, alt)
								{
									if (alt == undefined) alt = '';
									return $("<a></a>")
										.attr('href', href)
										.attr('alt', href)
										.html(element);
								}
	};
	
	// sliderBinds contains functions used in binds and all accept the event argument
	sliderBinds = 
	{
		// slideTo is bound to the button representation of slides, when a button is clicked the slide will be shown
		slideTo:				function(ev)
								{
									var slider = ev.data.slider;
									var index = ev.data.index;
									
									slideTo(slider, index);
								}
	};

	// this is the starting point of the plugin
	jQuery.fn.slider = function(command, args)
	{
		// loop through each of the passed elements and then return them for chaining
		return this.each(function()
		{
			var slider = $(this);
			
			// determine what command was given and act on it appropriately
			
			if (typeof(command) == 'string')
			{
				switch (command)
				{
					case 'initialize':
						initialize(slider, args.slides, args.options, args.classes);
						break;
				}
			}
		});
	};
	
	// initialize is used to setup the specified container as a slider display, it will build
	// child elements, position and populate them.
	function initialize(slider, slides, options, classes)
	{		
		// read and store xml data
		slides = sliderData.slides(slider, readXML(slides));
		
		// extend options with default and save them
		options = sliderData.sliderOptions(slider, jQuery.extend({}, sliderDefaults, options));
		
		// extend the default classes with the specified classes and then store them
		classes = sliderData.sliderClasses(slider, jQuery.extend({}, sliderClasses, classes));	
	
		// build the containers used in the slider
		buildContainers(slider, slides.length, options, classes);
	
		// add the slides to the slider
		addSlides(slider, slides, options, classes);
		
		// add the navigation buttons to the controls container
		addNavigationButtons(slider, classes);
		
		// slide to default slide
		slideTo(slider, 0, 0);
		
		// begin the loop
		startLoop(slider, options.loopInterval);
		
		// bind the sliders hover events
		bindSliderHover(slider);
	};
	
	// readXML is used to read an xml file and return an array of objects with the xml data
	function readXML(xmlFile)
	{
		var slides = [];
		
		jQuery.ajax(
		{
			type:		'GET',
			url:		xmlFile,
			dataType:	'xml',
			async:		false,
			success:	function(xml)
						{
							jQuery(xml).find('slide').each(function()
							{
								var slide = $(this);
								slides[slides.length] = xmlToObject(slide);
							});
						}
		});
		
		return slides;
	};
	
	// xmlToObject will return the passed slide xml as a slide object
	function xmlToObject(slide)
	{
		var imageChild = slide.children('image');
		var slideDetails =
		{
			IMAGE:
			{
				FILE:			imageChild.children('file').text(),
				DIMENSIONS:
				{
					WIDTH:		imageChild.children('dimensions').children('width').text() || undefined,
					HEIGHT:		imageChild.children('dimensions').children('height').text() || undefined
				}
			},
			HEADING:		slide.children('heading').text(),
			TEXT:			slide.children('text').text(),
			EXPANDABLE:		slide.children('expandable').text() || false,
			HIDEOVERLAY:	slide.attr('hideOverlay') || false,
			EXTRA:			[]
		};
		
		// loop through each extra field in the slide xml
		var extraChild = slide.children('extra');
		extraChild.each(function()
		{
			var extra = [{TYPE: $(this).attr('type'), HREF: $(this).attr('href')}];
			
			var children = $(this).children("*");
			children.each(function()
			{
				var tag = this.tagName.toUpperCase();				
				extra[extra.length] =
				{
					TAG:		tag,
					HREF:		$(this).attr('href'),
					TEXT:		$(this).text()
				};
			});
			
			slideDetails.EXTRA[slideDetails.EXTRA.length] = extra;
		});
		
		return slideDetails;
	};
	
	// buildContainers is used to build all of the child containers needed to layout the slider display
	function buildContainers(slider, slidesCount, options, classes)
	{
		// create the containers
		var mainContainer = sliderCreate.mainContainer(classes, options.minHeight);
		var innerContainer = sliderCreate.innerContainer(classes);
		var slidesContainer = sliderCreate.slidesContainer(classes, slider, slidesCount, options.maxHeight);
		var controlsContainer = sliderCreate.controlsContainer(classes);
		
		// add the containers to the slider
		mainContainer.appendTo(slider);
		innerContainer.appendTo(mainContainer);
		slidesContainer.appendTo(innerContainer);
		controlsContainer.appendTo(innerContainer);
		
		// move the controls container into position
		positionControlsContainer(slider, innerContainer, controlsContainer);
	};
	
	function positionControlsContainer(slider, innerContainer, controlsContainer)
	{
		var top = innerContainer.height() - slider.height();
		controlsContainer.css('top', -top);
	};
	
	// addSlides is used to loop through each of the specified slides and add them to the slider
	function addSlides(slider, slides, options, classes)
	{
		for (var i = 0; i < slides.length; i++)
		{
			var slide = slides[i];
			
			// determine if slide contains and image or flash object
			var index = slide.IMAGE.FILE.lastIndexOf('.');
			var fileType = slide.IMAGE.FILE.substr(index + 1, slide.IMAGE.FILE.length - index);
			
			// create the image/swf object
			switch(fileType)
			{
				case 'swf':
					var image = sliderCreate.flash(classes, slide.IMAGE.FILE, slide.IMAGE.DIMENSIONS);
					break;
				case 'jpg': case 'jpeg': case 'bmp': case 'gif': case 'png':
					var image = sliderCreate.image(classes, slide.IMAGE.FILE, slide.IMAGE.DIMENSIONS);
					break;
			}

			// get the slides container
			var slidesContainer = sliderFind.slidesContainer(slider);

			// create the slide containers
			var slideContainer = sliderCreate.slideContainer(classes, slider, options.maxHeight, i);
			var slideContentContainer = sliderCreate.slideContentContainer(classes);
			var slideImageContainer = sliderCreate.slideImageContainer(classes);
			
			// add the slide containers to the slider
			slideContainer.appendTo(slidesContainer);
			slideImageContainer.appendTo(slideContainer);
			slideContentContainer.appendTo(slideContainer);
			
			// create the slide content
			var heading = sliderCreate.h1(classes, slide.HEADING);
			var text = sliderCreate.p(classes, slide.TEXT);
			
			// add the slide content to the slide
			heading.appendTo(slideContentContainer);
			text.appendTo(slideContentContainer);
			
			// create and add the extras
			addExtras(slider, slideContentContainer, slide.EXTRA, classes);
			
			// add the image/flash to the slide
			image.appendTo(slideImageContainer);
			
			// move the slide content container into position
			positionSlideContentContainer(slideContentContainer);
			
			// hide the overlay is specified
			if (slide.HIDEOVERLAY) slideContentContainer.css('visibility', 'hidden');
		}
	};
	
	// positionSlideContentContainer will position the slideContentContianer so that it sits inside the slider container and not out of sight
	function positionSlideContentContainer(slideContentContainer)
	{
		slideContentContainer.css('top', -slideContentContainer.height());
	};
	
	// addNavigationButtons will add a button to the slider representing each slide in the specified slides
	function addNavigationButtons(slider, classes)
	{
		slidesCount = sliderData.slides(slider).length;
		controlsContainer = sliderFind.controlsContainer(slider);
		
		var navigationList = sliderCreate.navigationList(classes);
		navigationList.appendTo(controlsContainer);
		
		for (var i = 0; i < slidesCount; i++)
		{
			var button = sliderCreate.navigationButton(classes);
			button.appendTo(navigationList);
			button.bind('click', {slider: slider, index: i}, sliderBinds.slideTo);
		}
	};
	
	// addExtras will loop through each of the extra elements in the slide and add them to the slides display
	function addExtras(slider, slideContentContainer, extras, classes)
	{
		for (i = 0; i < extras.length; i++)
		{
			// create extra container
			var slideExtraContainer = sliderCreate.slideExtraContainer(classes, i);
			
			var extra = extras[i];
			for (n = 1; n < extra.length; n++)
			{
				switch (extra[n].TAG)
				{
					case 'HEADING':
						var element = sliderCreate.h2(classes, extra[n].TEXT);
						break;
					case 'TEXT':
						var element = sliderCreate.p(classes, extra[n].TEXT);
						break;
					case 'IMAGE':
						var element = sliderCreate.image(classes, extra[n].TEXT);
						break;
				}
				
				// add class to the element
				element.addClass(classes.extraElement);
				element.addClass(classes.extraElement + n);
				
				// if the element has an href then make it a link
				if (extra[n].HREF > '') element = sliderCreate.makeIntoLink(classes, element, extra[n].HREF);
				
				// add the element to the container
				element.appendTo(slideExtraContainer);
			}
			
			// add the extra's type as a class
			slideExtraContainer.addClass(extra[0].TYPE);

			// if the extra has an href then make it a link
			if (extra[0].HREF > '')
			{
				slideExtraContainer = sliderCreate.makeIntoLink(classes, slideExtraContainer, extra[0].HREF);
				/*
				slideExtraContainer.bind('click', 
					function(ev)
					{
						ev.preventDefault();
						window.location = extra[0].HREF;
					});
				*/
			}

			// add the container to the slider
			slideExtraContainer.appendTo(slideContentContainer);
		}
	};
	
	// slideTo will slide the slider to the specified slide index
	function slideTo(slider, index, speed)
	{
		if (speed == undefined) speed = sliderData.sliderOptions(slider).slideSpeed;
		
		slidesContainer = sliderFind.slidesContainer(slider);
		slidesContainer.animate({left: -(slider.width() * index)}, speed);
		
		sliderData.currentIndex(slider, index);
		updateButtons(slider);
	};
	
	// updateButtons will add or remove the 'selected' class from the nav buttons depending on which slide is shown
	function updateButtons(slider)
	{
		var index = sliderData.currentIndex(slider);
		var buttons = sliderFind.navigationButtons(slider);
		
		for(var i = 0; i < buttons.length; i++)
		{
			var button = $(buttons[i]);
			if (index == i) {button.addClass('selected');}
			else {button.removeClass('selected');}
		}						
	};
	
	// startLoop will begin a loop which will make the slider slide to the next slide each iteration
	function startLoop(slider, interval)
	{	
		if (interval == undefined) interval = sliderData.sliderOptions(slider).loopInterval;
		
		if (interval > 0)
		{
			slider.get(0).loop = window.setInterval(
				function()
				{
					slideTo(slider, getNextIndex(slider));
				}, interval);
		}
	};
	
	// stopLoop will stop the slider loop
	function stopLoop(slider)
	{
		clearInterval(slider.get(0).loop);
	};
	
	// getNextIndex will return the next valid index, if the last index is reached it will return to 0
	function getNextIndex(slider)
	{
		var slidesLength = sliderData.slides(slider).length - 1;
		var index = sliderData.currentIndex(slider);
		
		if (index >= slidesLength) return 0;
		return ++index;
	};
	
	// bindSlideHover binds the hover and unhover event of the slider to start and stop the loop
	function bindSliderHover(slider)
	{
		slider.hover(
			function()
			{
				stopLoop(slider);
			},
			function()
			{
				startLoop(slider);
			});
	};

})(jQuery);
