/*
*	jPresentation plugin for jQuery. Tested working on jQuery 1.6.0.
*
*	Made by Adam Hutchinson
*/

(function($){
	
	// The methods belonging to jPresentation
	var methods = {
		init : function( options ){
			var $this = $( this ),
				config = $this.data( 'config' ) || {};
			
			/*
			*
			*
			*	DEFAULT CONFIG
			*
			*/
			var defaults = {
				showButtons : false,
				buttonClass : 'buttons',
				fadeTime : 1000
			};
			config = $.extend(defaults, config, options);
			
			$this.data('config', config);
			
			// Make this relatively positioned
			$this.css('position', 'relative');
			
			// Create buttons
			$('<div class="' + config.buttonClass  + '"></div>')
				.css({
					position: 'absolute',
					bottom: 10,
					right: 10,
					zIndex : 10
				})
				.hide().appendTo( $this );
		},
		/* 
		*	Loads & Parses xml file containing all presentation data
		*/
		loadXML : function( path, onComplete ){
			var $this = $(this);
			onComplete = onComplete || function(){};
			
			/* 
			*	Function to parse the xml into usable data
			*/
			var parseXML = function( xml ){
				
				// jQuery up in that xml
				var $xml = $( xml );
				
				// First parse the config - contains bg colour etc.
				var parseConfig = function( xml ){
					
					var config_node = xml.find('config')[0];
					var config = {};
					var attributes = config_node.attributes;
					$(attributes).each(function( idx, attr){
						// Convert val to relevant type.
						var val = attr.value;
						if(val.indexOf("0x") === 0){ // Value is hex -> remove the 0x part.
							val = val.substr(2);
						}else if(val == 'true'){
							val = true;
						}else if(val == 'false'){
							val = false;
						}

						// Add to config
						config[attr.name] = val;
					});

					$this.jPresentation( 'updateConfig', config );
					
				}( $xml );
				
				// Parse the banners.
				var parseBanners = function( xml ){
					
					// Get all banners & loop through 'em.
					var banners = xml.find('banner');
					banners.each( function(idx, banner){
						var $banner = $(banner);
						
						// Object we will use to add a slide(banner)
						var new_slide = {
							items : []
						};
						
						// Loop through items in this banner
						$banner.children().each( function(idx, item){
							var $item = $(item);

							// What type of node is it?
							if( item.nodeName == 'link' ){ 	// Link
								new_slide.link = item.textContent;
								
							}else{ 							// Image / Text
								
								var new_item = {
									animations : []
								};
								
								// Position
								new_item.x = $item.attr( 'x' );
								new_item.y = $item.attr( 'y' );
								
								// Get animations
								var animations = $item.find( "start_animation" );
								animations = animations.add( $item.find( "end_animation" ) ); // Doesn't look like find supports multiple selectors
								
								animations.each( function(idx, animation){
									var $animation = $(animation), new_animation = {};
									
									new_animation.type = $animation.attr( "type" );
									new_animation.time = parseInt( $animation.attr( "time" ), 10 ) * 1000; // Multiply by 1000 to get milliseconds
									new_animation.duration = parseInt( $animation.attr( "duration" ), 10) * 1000;
									new_animation.direction = $animation.attr( "direction" );
									
									new_item.animations.push( new_animation );
								});
								
								// Attributes specific to type
								if( item.nodeName == 'image' ){ 		// Image
									new_item.type = 'image';
									new_item.src = $item.attr( 'url' );
									
								}else if( item.nodeName == 'text' ){ 	// Text
									new_item.shadow = $item.attr( 'shadow' );
									new_item.width = $item.attr( 'width' );
									new_item.height = $item.attr( 'height' );
									new_item.blur = $item.attr( 'blur' );
									
									// Get the html and parse it into an xml document. Then make it valid html instead of <font crap>.
									var data = $item.find( 'data' )[0],
										$data = $( data );
										
									data = $.parseXML( $data.text() );

									// Styles from <p>
									data = $( data ).find( 'p' )[0];
									$data = $( data );	
									var html = $('<div></div>')
										.css({
											textAlign : $data.attr('align') || 'left'
										});
									
									// Styles from <font>
									data = $data.find( 'font' )[0];
									$data = $( data );
									html.css({
											fontFamily : $data.attr('face'),
											color : $data.attr('color'),
											fontSize : $data.attr('size') + 'px'
										})
										.html( $data.text() );
									
									new_item.html = html;
									
								}
								
								new_slide.items.push( new_item );
								
							}
						});
						
						// Finally: add this slide to presentation.
						$this.jPresentation( 'addSlide', new_slide );
						
					});
				
				// Call onComplete once parse is finished
				onComplete();
					
				}( $xml );
				
					
			};
			
			/*
			*	Function to return the XML using AJAX.
			*	Runs when parent function is called and calls parseXML to parse the data.
			*/
			(function( path ){
				
				// Pull in xml file using ajax
				$.ajax({
					type : "GET",
					url : path,
					dataType: "xml",
					success: parseXML
				});
				
			})( path );
			
		},
		
		/*
		*	Update the config settings and apply them where relevant
		*/
		updateConfig : function( config ){
			
			config = config || {};
			var $this = $(this),
				curr_config = $this.data( 'config' );
				
			// Get the current config settings and merge with passed config and default config
			config = $.extend({
				slideClass: 'slide'
			}, curr_config, config );
			
			$this.data({ 'config' : config });
			
			// Apply the config.
			$this.css({
				'backgroundColor' : "#" + config.backgroundColor,
				'width' : config.bannerWidth,
				'height' : config.bannerHeight,
				'overflow' : config.overflow || 'hidden'
			});
			
			// if showButtons === true then make buttons show on hover
			var buttons = $this.children('div.' + config.buttonClass),
				$buttons = $( buttons );
				
			// Hover in and out funcs
			$this.hover(function(){
				$buttons.stop(true,true).fadeIn(400);
			},function(){
				$buttons.fadeOut(400);
			});
						
		},
		
		/*
		*	Add a slide to the presentation
		*/
		addSlide : function( opt ){

			var $this = $( this ),
				config = $this.data( 'config' );
			
			/*
			*	Create a slide element
			*/
			var create_slide = function( opt ){
				return $('<div></div>')
					.css({ 
						width: config.bannerWidth,
						height: config.bannerHeight,
						position: 'absolute'
					})
					.attr({
						class: config.slideClass,
						onclick: 'document.location=\''+opt.link+'\''
					})
					.hide()
					.appendTo( $this );
			};
			var slide = create_slide( opt );
			
			/*
			*	Send array of items to addItem function for this slide
			*/
			var add_items = function(items){
				
				// Add items if we have any
				if( items ){
					$( items ).each( function(idx, item){
						$this.jPresentation( 'addItem', slide, item );
					});
				}
				
			}( opt.items );
			
			/*
			*	Add a button relating to this slide
			*/
			var add_button = function(){
				if (config.showButtons==true) {
					var button_holder = $this.children( 'div.' + config.buttonClass );
					var button_count = $(button_holder).children('div').size();
					$('<div>' /*+ (button_count + 1)*/ + '</div>')
						.css({
							'backgroundColor' : '#' + config.buttonColor,
							'cursor' : 'pointer'
						})
						.click(function(){
							$this.jPresentation('rotate', button_count);
							
						})
						.appendTo(button_holder);
				}
			}();
			
		},
		
		/*
		*	Add an item to a slide
		*/
		addItem : function( slide, opt ){
			var $this = $( this );
			
			// Create Element.
			var el;
			if( opt.type == 'image' ){
				el = $('<img />');
			}else{
				el = $('<div></div>');
			}
			
			el.data( 'opt', opt ) 	// Store opt with el
				.appendTo( slide ); // Add to slide
			
			// Call update item to apply css etc
			$this.jPresentation( "updateItem", el );
			
		},
		
		/* 
		*	Update an item's position etc
		*/
		updateItem : function( item ){
			var el = $( item ),
				opt = el.data( 'opt' );
							
			el.css({
				position: 'absolute',
				left: opt.x + 'px',
				top: opt.y + 'px',
				width: opt.width,
				height: opt.height,
				opacity : 1, // Remove opacity rules caused by fades.
				display : 'block'
			})
			.html( opt.html || "" )
			.data({
				src : opt.src || "", // Add src as data attribute so we can load it when needed
				animations : opt.animations
			});
			
			// if there is no width / height stored in opt, store the current width / height.
			// this will only happen on the first call of this function
			// If image, must be done in an onload function
			if( !opt.width ){
				var set_width = function(){
					opt.width=el.width();
					el.data('opt', opt);
				};
				
				if( opt.type == 'image' ){
					el.load(function(){
						set_width();
					});
				}else{
					set_width();
				}
			}
			
			if( !opt.height ){
				var set_height = function(){
					opt.height=el.height();
					el.data('opt', opt);
				};
				
				if( opt.type == 'image' ){
					el.load(function(){
						set_height();
					});
				}else{
					set_height();
				}
			}
								
		},
		
		/*
		*	Add an animation to an item
		*/
		addAnimation : function( item, opt ){
			var $item = $('item');
			$item.data( 'animations' ).push( opt );
		},
		
		/*
		*	show the slide indexed by idx 
		*
		*	@param 		idx		int		index of slide to display
		*/
		showSlide : function( idx, onComplete ){
			var $this = $( this ),
				config = $this.data('config'),
				slides = $this.children( 'div.' + config.slideClass );
				
			idx = idx || 0;
			onComplete = onComplete || function(){};
			
			
			// Get the slide specified by index
			var slide = typeof idx == 'object' ? idx : slides[idx],
				$slide = $( slide ),
				idx = $(slides).index($slide);
		
			// Load unloaded slide images
			var images = $slide.children( 'img' )
				.each( function(idx, img){
					var $img = $( img );
					
					// Set the src to the data.src attribute
					if( ! $img.attr( 'src' ) ){
						$img.attr( 'src', $img.data( 'src' ) );
					}
					
				});
			
			// Mark this slide as current slide
			$slide.addClass('curr_slide');
			
			// Highlight this slide's button
			var button_holder = $this.children( 'div.' + config.buttonClass );
			var buttons = $(button_holder).children()
			if (config.showButtons==true) {
				buttons.removeClass('active_button');
				$(buttons[idx]).addClass('active_button');
			}
			// Clone this slide. We will animate on the clone. Append it to the presentation, after current slide.
			var temp_slide = $slide.clone(true,true);
			$(temp_slide).removeClass('slide')
				.addClass('temp_slide')
				.appendTo( $this )
				.fadeIn( config.fadeTime );
			
			/*
			*	Animate the items within temp slide.
			*
			*/
			(function(){
				var $temp_slide = $(temp_slide);
				
				// Pull minimum slide duration from config.
				var slide_duration = config.slideMinDuration*1000;
				
				// We will store all timeouts so we can cancel them if we want to end this animation.
				var item_timeouts = [];

				// Get all items
				var items = $temp_slide.children();
				items.each( function(idx, item){
					var $item = $(item),
						animations = $(item).data('animations');
					
					// Loop through each animation and set a timeout that calls the animateItem function
					$(animations).each( function(idx, animation){
						// Check if this animation will increase slide duration
						var animation_duration = animation.time + animation.duration;
						if( animation_duration > slide_duration ){
							slide_duration = animation_duration;
						}
						
						// If animation start time is above 0 then we should hide it
						if( animation.time > 0 ){
							$item.hide();
						}
						
						var timeout = setTimeout(function(){ 
							$this.jPresentation('animateItem', item, animation.type, animation.direction, animation.duration );
						}, animation.time);
						
						item_timeouts.push( timeout );
						
					});
					
				});
				
				// Add a timeout to call the onComplete function when slide_duration is up
				item_timeouts.push(setTimeout(onComplete, slide_duration ));
				
				// Add item timeouts to this slide's data.
				$temp_slide.data( 'item_timeouts', item_timeouts );
			})();
			
		},
		
		/*
		*	Kills temp_slides
		*/
		hideSlides : function(){
			$(this).jPresentation('stopSlides');
			
			// Remove redundant slides
			$('.remove_slide').remove();
			
			// Remove curr_slide class
			$('.temp_slide').addClass('remove_slide');
			$('.curr_slide').removeClass('curr_slide');
			
		},
		
		/*
		*	Stops slides. Removes their timeouts
		*/
		stopSlides : function(){
			// Remove all incomplete timeouts
			$('.slide, .temp_slide').each(function(){
				var timeouts = $(this).data('item_timeouts');
				if(timeouts){
					$(timeouts).each(function(){
						clearTimeout(this);
					});
				}
			});
			
		},
		
		/*
		*	Animate an item.
		*/
		animateItem : function( el, type, direction, duration){
			
			if(el){
				var $el = $( el ),
					$els = $el.add($el.children()), // Includes children
					direction_multiply = 1;
	
				// Fix direction. Up, down => top, right,left = left. Use multiply to decide if it should move negatively or positively.
				switch( direction ){
					case "up":
						direction_multiply = -1;
					case "down":
						direction = 'top';
						break;
						
					case "left":
						direction_multiply = -1;
					case "right":
						direction = 'left';
						break;
				}

				/*
				*	Animation types. Extend this to add new animations
				*
				*	An animation is an object containing a before and after function.
				*	Before function sets the css applied to the element before animating.
				*	After sets the css applied to the element during animation.
				*
				*	Have access to $el, direction, direction_multiply, duration and curr_style.
				*/
				var animations = {
					fly_in : {
						before : function(){
							var style = {display:'block'};
							// Change style before animating						
							style[direction] = direction_multiply * $el.width();
							
							$el.css(style);
						},
						
						after : function(){
							var style = {};
							
							// Animation properties
							style[direction] = curr_style[direction];												
							
							$el.animate(style, duration);
						}
						
					},
					
					short_fly_in : {
						before: function(){
							var style = {display:'block'};
							
							style[direction] = direction_multiply * ( $el.width() / 4 );
							$el.css(style);
						},
						
						after : function(){
							var style = {};
							
							style[direction] = curr_style[direction];
							
							$el.animate(style, duration);
						}
					},
					
					fly_out : {
						before : function(){
							var style = {};
							
							style[direction] = curr_style[direction];
							
							$el.css(style);
						},
						
						after: function(){
							var style = {opacity:0};
							
							style[direction] = direction_multiply * ( $el.width() );
							
							$el.animate(style, duration);
						}
					},
	
	
					short_fly_out : {
						before : function(){
							var style = {};
							
							style[direction] = curr_style[direction];
							
							$el.css(style);
						},
						
						after: function(){
							var style = {opacity:0};
							
							style[direction] = direction_multiply * ( $el.width() / 4 );
							
							$el.animate(style, duration);
						}
					},
					
					fade_in : {
						after : function(){
							$el.fadeIn(duration);
						}
					},
					
					fade_out : {					
						after : function(){
							$el.fadeOut(duration);
						}
					},
					
					zoom_in : {
						before : function(){
							$els.css({
								width : $els.width() * 0.6, 
								height : $els.height() * 0.6,
								display : 'block',
								fontSize : curr_style.fontSize * 0.6
							});
						},
						
						after : function(){
							$els.animate({
								width: curr_style.width,
								height: curr_style.height,
								fontSize : curr_style.fontSize / 0.6
							});
						}
					},
					
					zoom_out : {
						after : function(){
							$els.animate({
									width : $els.width() * 1.4,
									height : $els.height() * 1.4,
									fontSize : curr_style.fontSize * 1.4
								}, { queue:false })
								.animate({
									width : curr_style.width * 0.4,
									height : curr_style.height * 0.4,
									fontSize : curr_style.fontSize * 0.4
								}).fadeOut(duration);
						}
					},
					
					magnify_in : {
						before : function(){
							var magnify_by = 3;

							$els.css({
								width : $els.width() * magnify_by,
								height : $els.height() * magnify_by,
								opacity:0,
								display:'block',
								left: parseInt(curr_style.left,10) - $els.width() * ((magnify_by-1) / 2), // Centre the newly enlarged thing
								top: parseInt(curr_style.top,10) - $els.height() * ((magnify_by-1) / 2)
							});
						},
						
						after : function(){

							$els.animate({
								width: $curr_el.width(),
								height : $curr_el.height(),
								opacity:100,
								top: curr_style.top,
								left: curr_style.left
							});
						}
					},
					
					magnify_out : {
						after : function(){
							var magnify_by = 3;
							
							$els.animate({
								width: $els.width() * magnify_by,
								height : $els.height() * magnify_by,
								opacity:0,
								left: parseInt(curr_style.left,10) - $els.width() * ((magnify_by-1) / 2), // Centre the newly enlarged thing
								top: parseInt(curr_style.top,10) - $els.height() * ((magnify_by-1) / 2)
							});
						}
					},
					
					toggle : {
						after : function(){
							$el.toggle();
						}
					}
					
					
				};
				
				// Run animation
				type = type || 'toggle'; // Default effect
	
				if ( animations[type] ){
					// Clone element so we can get styles off of it
					var $curr_el = $el.clone().show().prependTo(this),
					curr_el = $curr_el[0],
					curr_style = curr_el.style;
					
					// If we're working with an image with no width / height, get w/h
					if( curr_el.nodeName == 'IMG' && ( !curr_style.width || !curr_style.height || curr_style.width == 0 || curr_style.height == 0) ){
						var img = new Image();
						// Assign load function before src. So that fast loading images and slow js don't break stuff. (looking at you IE)
						$(img).load(function(){
							$curr_el.css({
								width : this.width,
								height : this.height
							});
							
							animations[type].before && animations[type].before();
							animations[type].after();
							
							$(this).remove();
						})
						.attr('src',curr_el.src);
						
					}else{
						// Run before and after css from animation functions
						animations[type].before && animations[type].before();
						animations[type].after();
					}
					
					// Destroy cloned element
					$curr_el.remove();
				}
			}	
						
		},
		
		/*
		*	Rotation logic
		*/
		rotate : function(idx){
						
			var $this = $( this ),
				stoppres = 0
				config = $this.data('config'),
				timeout = $this.data('timeout');
			
			// Pause rotation	
			$this.jPresentation('stopSlides');
			
				
			// Get next slide. Will be element after a visible child div of this/first child element of this
			var slides = $this.children( 'div.' + config.slideClass ),
				curr_slide = slides.filter( ".curr_slide" ),
				slide = !isNaN(idx) && $(slides[idx]),
				slide = slide || curr_slide.next( 'div.' + config.slideClass ),
				slide = slide.length > 0 ? slide : slides.first( 'div.' + config.slideClass ),
				slide2 = slide.next( 'div.' + config.slideClass );
			
			//check ahead for end of presentation if presentaton is set to no repeat
			if (slide2.length==0 && config.repeatpresentation==0) {
				stoppres = 1
			};
			
			var $slide = $( slide );
							
			// Hide current slide
			$this.jPresentation('hideSlides');
			
			// Show new slide if config allows
			$this.jPresentation('showSlide', $slide, function(){
				if (config.slideshowMode==true && stoppres==0) {
					$this.jPresentation('rotate');
				};
				
			});
			
		},
		
		// Wrapper for stopSlides
		stop : function(){
			$(this).jPresentation('stopSlides');
		}
		
	};
	
	$.fn.jPresentation = function(method){
		// Make sure we have a config
		if( ! $(this).data('config') ){
			methods.init.apply( this, arguments );
		}
		
		// Method calling logic
		if( methods[method] ){
			return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1));
		} else if( typeof method === 'object' || ! method ){
			return methods.init.apply( this, arguments );
		} else {
			$.error( 'Method ' + method + ' does not exist on jQuery.jPresentation' );
		}
		
	};
	
})( jQuery );
