
/**
 * This is the main Carousel. Carousel.Init is the main function of this class.
 * This "class" doesn't actually create instances using the "new" operator, it 
 * instead passes around "slideEle" to each functions to avoid using "new". All
 * properties of slideEle are instead store in the DOM. The basic hierarchy a
 * carousel is: .slideCarousel > .slideViewport > .slides > .slide
 * 
 * @author Adam S. Kirschner adams.kirschner@ogilvy.com
 */
var Carousel = {
	/**
	 * This is used to init all of the actual slide elements used on the page. Calls Carousel.Create
	 */
	Init: function() {
		// all carousels have the slideCarousel class
		$$(".slideCarousel").each( Carousel.Create )
	},
	
	/**
	 * This will init all of the features of a Carousel, setting events.
	 * 
	 * @param {HtmlElement} slideEle This is the main div of a carousel.
	 */
	Create: function( slideEle ) {
		// viewports need to be init'ed first, after this it doesn't matter
		Carousel.Viewports.Init( slideEle );
		
		// these are all of the features that carousels can have
		// if a specific carousel does not have the HTML for that feature, it will be ignored
		Carousel.Advertise.Init(slideEle);
		Carousel.Pager.Init( slideEle );
		Carousel.SlideStatus.Init( slideEle );
		Carousel.Thumbnails.Init( slideEle );
		Carousel.Overlay.Init( slideEle );
		Carousel.Title.Init( slideEle );
		Carousel.AutoScroll.Init( slideEle );
	},
	/**
	 * 
	 * 
	 */
	AutoScroll: { 
		/**
		 * 
		 * @param {Object} slideEle
		 */
		Init: function( slideEle ) {
			if( slideEle.hasClassName( 'heroCarousel') ) {
				setTimeout( function() { Carousel.AutoScroll.Handler( slideEle ); }, 5000 );
			}
		}, 
		/**
		 * 
		 * @param {Object} slideEle
		 */
		Handler: function( slideEle ) {
			if (!slideEle.__DISABLE_AUTO_SCROLL) {
				Carousel.Pager.Next(slideEle);
				if (!slideEle.__DISABLE_AUTO_SCROLL) {
					setTimeout(function(){
						Carousel.AutoScroll.Handler(slideEle);
					}, 5000);
				}
			}
		}
	},
	
	/**
	 * Thumbnails are the images (usually on the bottom of a carousel) that represent 
	 * which of all of the slides is currently being shown by an 'active' css class
	 */
	Thumbnails: {
		/**
		 * This will go through each slideEle and assign the necessary private variables needed
		 * and will also update them with the correct 'active' status (depending on which slide is shown)
		 * 
		 * @param {HTMLElemenet} slideEle @see Carousel.Create	
		 */
		Init: function( slideEle ) {
			// the thumbnail ele's are really the LI element, this is what active gets set on it
			slideEle.__SLIDE_THUMBS = slideEle.select('.slideThumbnails li');
		
			// if thumbnails don't exist then die gracefully
			if (slideEle.__SLIDE_THUMBS.length > 0) {
				
				// go through each thumbnail and set the 'click' event
				slideEle.__SLIDE_THUMBS.each(function(thumbEle, thumbIndex){
					
					// we want to set the event on the link of each thumbnail, not the LI
					var linkEle = thumbEle.selectFirst('a');
					
					linkEle.voidLink();
					
					linkEle.observe('click', function(){
						linkEle.blur();
						
						slideEle.__DISABLE_AUTO_SCROLL = true;
						
						// when each thumb is clicked, we need to reset the highlighting
						Carousel.Thumbnails.Highlight(slideEle, thumbIndex);	
						
						// this allows us to have a custom call back function that when the 
						// thumbnails are clicked to do something else, this is primarily used
						// on the photogallery
						// TODO: update comment
						// the not fire means that if we return true, then don't execute any further
						if( ! Carousel.Common.CallBack.Fire( slideEle, thumbIndex, 0, 'thumb_click' ) ) {
							// if the class 'slideOnThumbClick' is applied to 'slideEle', then 
							// it will not fade on click but will slide
							if (slideEle.hasClassName('slideOnThumbClick')) {
								Carousel.Common.Show.Slide(slideEle, thumbIndex);
							} else {
								Carousel.Common.Show.Fade(slideEle, thumbIndex);
							}
						}
					});
				});
				
				// get the active thumb
				var activeThumb = slideEle.__SLIDE_THUMBS[0].selectFirst('.active');
				
				// if there isn't one, set it to the first one
				if (!activeThumb) {
					slideEle.__SLIDE_THUMBS[0].addClassName('active');
				}
			}
		},
		
		/**
		 * This will highlight the appropriate thumbnail image
		 * 
		 * @param {HtmlElement} slideEle
		 * @param {int} slideIndex the index of the thumbnail that needs to highlighted
		 */
		Highlight: function(slideEle, slideIndex){
			if (slideEle.__SLIDE_THUMBS.length > 0) {
				slideEle.__SLIDE_THUMBS.invoke('removeClassName', 'active');
				slideEle.__SLIDE_THUMBS[slideIndex].addClassName('active');
			}
		}
	},
	
	/**
	 * Pagers are the left and right arrows on the top of the Carousel.
	 * 
	 * @param {HtmlElement} slideEle
	 */
	Pager: { 
		/**
		 * Init sets the events on the arrow elements, will also retrive them and set a reference to them
		 * in slideEle
		 * 
		 * @param {HtmlElement} slideEle 
		 */
		Init: function(slideEle){

			// the pager element is wrapped in a 'slideNav' class
			var slideNav = slideEle.selectFirst(".slideNav");
			
			if (slideNav) {
				
				// get the left pager
				var leftPager = slideNav.selectFirst('.left');
				leftPager.voidLink();
				
				// call the pager's previous event when clicked
				leftPager.observe( 'click', function(){
					slideEle.__DISABLE_AUTO_SCROLL = true;
					leftPager.blur();
					Carousel.Pager.Previous(slideEle);
				});
				
				// get the right pager
				var rightPager = slideNav.selectFirst('.right');
				rightPager.voidLink();
				
				// call the pager's next event when clicked
				rightPager.observe( 'click', function(){
					slideEle.__DISABLE_AUTO_SCROLL = true;
					rightPager.blur();
					Carousel.Pager.Next(slideEle);
				});
			}
		},
		
		/**
		 * Previous will take slideEle and show the slide to its left. If it is showing the first slide, 
		 * it will wrap and show the last one.
		 * 
		 * @param {HtmlElement} slideEle
		 */
		Previous: function( slideEle ) {
			// grab any viewport since they should all be showing the same location anyway
			var viewportEle = slideEle.__VIEWPORTS[0];
			
			// index starts at -1, if it is break.
			var currentIndex = -1;
			
			// get the index of which element is currently being shown.
			viewportEle.__SLIDES.each( function( singleSlideEle, slideIndex ) {
				if( singleSlideEle.hasClassName('active') ) {
					currentIndex = slideIndex;
				}
			});

			// if it's at the beginning
			if( currentIndex == -1 ) {
				alert( 'something really bad happened' );
				return false;
			} else if( currentIndex == 0 ) {
				
				// if a css class 'slideAtEnds' is on slideEle, then the Carousel will 
				// slide to the other end instead of fading
				if( slideEle.hasClassName('slideAtEnds') ) {
					Carousel.Common.Show.Slide( slideEle, viewportEle.__SLIDES.length - 1 );
				} else {
					Carousel.Common.Show.Fade( slideEle, viewportEle.__SLIDES.length - 1 );
				}
			} else {
				// otherwise just go back one
				if( slideEle.hasClassName('fadeOnSlideChange' ) ) {
					Carousel.Common.Show.Fade( slideEle, currentIndex - 1 );	
				} else {
					Carousel.Common.Show.Slide( slideEle, currentIndex - 1 );
				}
			}
		},
		
		/**
		 * Next will take slideEle and show the slide to its right. If it is showing the last slide
		 * it will wrap and show the first one.
		 * 
		 * @param {HtmlElement} slideEle
		 */
		Next: function( slideEle ) {
			// grab any viewport since they should all be showing the same location anyway
			var viewportEle = slideEle.__VIEWPORTS[0];
			
			// get the index of which element is currently being shown.
			
			// get the index of which element is currently being shown.
			viewportEle.__SLIDES.each( function( singleSlideEle, slideIndex ) {
				if( singleSlideEle.hasClassName('active') ) {
					currentIndex = slideIndex;
				}
			});
			
			// if it's at the end
			if( currentIndex == viewportEle.__SLIDES.length - 1 ) {
				
				// if a css class 'slideAtEnds' is on slideEle, then the Carousel will 
				// slide to the other end instead of fading
				if( slideEle.hasClassName('slideAtEnds') ) {
					Carousel.Common.Show.Slide( slideEle, 0 );
				} else {
					Carousel.Common.Show.Fade( slideEle, 0 );
				}				
			} else {
				// otherwise just go forward one
				if( slideEle.hasClassName('fadeOnSlideChange' ) ) {
					Carousel.Common.Show.Fade( slideEle, currentIndex + 1 );	
				} else {
					Carousel.Common.Show.Slide( slideEle, currentIndex + 1 );
				}
			}
		}
	},
	/**
	 * SlideStatus is the X of Y paging status usually located on the bottom of a Carousel. The SlideStatus.Update will
	 * usually get called from the Common.NewSlideIsShown method
	 */
	SlideStatus: {
		Init: function ( slideEle ) {
			
			// the slide status element is wrapped in a 'slideStatus' class
			var slideStatus = slideEle.selectFirst(".slideStatus");
			
			if( slideStatus ) {
				
				// 'current' and 'total' are the css classes that are used to store each value (respectively)
				slideEle.__SLIDE_STATUS = {
					'Current': slideStatus.selectFirst('.current'),
					'Total': slideStatus.selectFirst('.total') 
				};
				
				// update the slide element to show the initial values
				Carousel.SlideStatus.Update( slideEle );
			}
		},
		
		/**
		 * Update will set the appropriate numbers in slide status. The HTML is physically updated. The status 
		 * is updated based on the first viewport that is found. @see Carousel.Viewports
		 */
		Update: function(slideEle){
			if (slideEle.__SLIDE_STATUS) {
				// grab the first viewport since they should all be showing the same location anyway
				var viewportEle = slideEle.__VIEWPORTS[0];
				
				// get the index of which element is currently being shown.
				var currentIndex = viewportEle.__SLIDES.indexOf( slideEle.selectFirst('.active') );
				
				slideEle.__SLIDE_STATUS.Current.innerHTML = currentIndex + 1;
				slideEle.__SLIDE_STATUS.Total.innerHTML = viewportEle.__SLIDES.length;
			}
		}
	},
	
	/**
	 * A Viewport is the closest wrapper div to all of the slides that show. This DIV will have the full width set and 
	 * becomes the 'filmstrip' to slide and show the appropriate slide. These two DIVs are required:
	 * 	- the parent div ('.slideViewport') will have { overflow: hidden; width: WIDTHpx; } where WIDTH equals the width of a slide
	 *  - the inner div ('.slides') will have a width that is equal to all of the widths of the slides so it can slide left/right
	 */
	Viewports: {
		/**
		 * Init sets the sizes of the viewports and retains reference of them in slideEle. 
		 * The slide count and slides width are used to determine the width of the viewport.
		 * These calculations take place here.
		 * 
		 * @param {HtmlElement} slideEle
 		 */
		Init: function( slideEle ) {
			
			// the topic hero carousel has its own "image viewport", so create this first
			// before we init all of the viewports for each slide
			if( slideEle.hasClassName( 'topicHeroCarousel' ) ) {
				Carousel.Viewports.ImageInit( slideEle );	
			}
			
			// most modules only have one viewport, however you can have more than one (like the topic hero)
			slideEle.__VIEWPORTS = slideEle.select('.viewport .slides');

			// each viewport has the same functionalilty, thats why everything is pretty much in a loop
			slideEle.__VIEWPORTS.each( function( viewportEle ) {

				// viewports are made up of a set slides
				viewportEle.__SLIDES = viewportEle.select('.slide');
				
				// get the width of one of those slides
				viewportEle.__SLIDE_WIDTH = viewportEle.__SLIDES[0].getStyle('width').toInt();

				// set the viewport's width so it becomes a strip
				viewportEle.setStyle({ 'width':( viewportEle.__SLIDES.length * viewportEle.__SLIDE_WIDTH ) + 'px' });
				
				// if there's no active slide, highlight the first one
				var activeSlide = viewportEle.selectFirst('.active');

				if( ! activeSlide ) {
					viewportEle.__SLIDES[0].addClassName('active');
				}
				
			});
		},
		
		/**
		 * The topic hero carousel is setup in a specific way to have the image
		 * of the slide to scroll separately from the rest of the slides, this function
		 * is used to move all the 'photo' elements into a new 'div.imageViewport' 
		 * @param {HtmlElement} slideEle
		 */
		ImageInit: function( slideEle ){			
			// go grab all the photo elements in this slide
			var photoEles = slideEle.select('div.photo')
			
			// create the div's, a viewport has two parts to it, @see Carousel.Viewports
			var imageViewport = new Element('div', { 'class': 'imageViewport viewport' });
			var slides = new Element( 'div', { 'class': 'slides' });
					
			// create each slide
			photoEles.each(function(ele){
				var newSlide = new Element('div',{ 'class': 'slide' });
				newSlide.appendChild( ele );
				slides.appendChild( newSlide );
			});
			
			// append it
			imageViewport.appendChild( slides );
			slideEle.appendChild( imageViewport );
		}
	},
	
	/**
	 * The Title of each slide shows in the carousels title. This will simply copy the title.
	 */
	Title: {
		/**
		 * Init will get the carousel's title element and retains reference.
		 * 
		 * @param {HtmlElement} slideEle
		 */
		Init: function( slideEle ) {
			
			// find the title element
			var title = slideEle.selectFirst('.titleBar .titleText');
			
			// if we found it, keep it and update it
			if( title ) {
				slideEle.__TITLE = title;
				Carousel.Title.Update( slideEle );
			}
		},
		
		/**
		 * This will update the carousel's title.
		 * 
		 * @param {HtmlElement} slideEle
		 */
		Update: function( slideEle ) {
			if( slideEle.__TITLE ) {
				var currentTitle = slideEle.selectFirst('.slides .active .titleText');
				slideEle.__TITLE.innerHTML = currentTitle.innerHTML;
			}
		}
	},
	
	/**
	 * Carousels can have this small overaly that appears on the bottom of the main photo.
	 * This will initialize the click events so that it either shows in a 'minimized' view or not.
	 */
	Overlay: { 
		/**
		 * Overlays are captions that appear on the bottom of the pictures. You can
		 * click on these overlays and show them in a "minimized" state. The overlay state
		 * is shared across that set of slides.
		 * 
		 * @param {HtmlElement} slideEle
		 */
		Init: function( slideEle ) {
			// go find a '.slides' element that has this 'overlayToggleSet' class.
			slideEle.select('.overlayToggleSet').each(function( ele ){

				// within each of those, an '.overlayToggle' element will exist
				ele.select('.overlayToggle').each( function( toggleEle ) {
					
					// set this click action to just toggle the 'minimized' class on the '.slides' element
					toggleEle.observe('click', function(){
						ele.toggleClassName('minimized');
					});
				});
				
			});
		}
	},
	
	/**
	 * This Carousel.Common namespace is used for general carousel purposes.
	 */
	Common: {
		/**
		 * There are two ways to show a new slide, either to fade it in, or to slide it in.
		 * These two functions, Show/Fade do the samething at the end but with different animation
		 * techniques. Both take a slideEle and a slideIndex. The end result being slideIndex showing.
		 */
		Show: {
			/**
			 * Slide will transition the filmstrip and move it left/right), therefore causing a shift.
			 * 
			 * @param {Object} slideEle
			 * @param {Object} slideIndex
			 */
			Slide: function( slideEle, slideIndex ) {
				var direction = 1;
				// there can be more than one viewport, we have to maintain all of them
				slideEle.__VIEWPORTS.each( function( viewportEle ) {
				
					// clear and reset the active states
					viewportEle.__SLIDES.invoke( 'removeClassName', 'active' );
					viewportEle.__SLIDES[ slideIndex ].addClassName('active');
					
					// figure out how far left we need to go
					var leftOffset = viewportEle.__SLIDE_WIDTH * slideIndex;

					// check to see if we are going left, if we are set -1
					if( -leftOffset > viewportEle.getStyle('left').toInt() ) {
						direction = -1;
					}
					// animate it
					new Effect.Morph( viewportEle, {
						'style': {
							'left': -leftOffset + "px"
						},
						'duration': 0.5
					});
				});
				
				// call back to handle any new 'plugins' that need to be updated
				// pass direction so any callback can know if we went to the next or previous slide
				Carousel.Common.NewSlideIsShown( slideEle, slideIndex, direction );
			},
			
			/**
			 * Fade will transition the element by setting 'opacity' on the slide from 1 to 0 and then from 0 to 1.
			 * 
			 * @param {HtmlElement} slideEle
			 * @param {HtmlElement} slideIndex
			 */
			Fade: function( slideEle, slideIndex ) {
				// assume we are going to the next slide
				var direction = 1;
				
				// there can be more than one viewport, we have to maintain all of them
				slideEle.__VIEWPORTS.each( function( viewportEle ) {
					
					// clear and reset the active states
					viewportEle.__SLIDES.invoke( 'removeClassName', 'active' );
					viewportEle.__SLIDES[ slideIndex ].addClassName('active');
					
					// figure out how far left we need to go					
					var leftOffset = viewportEle.__SLIDE_WIDTH * slideIndex;

					// check to see if we are going left, if we are set -1
					if( -leftOffset > viewportEle.getStyle('left').toInt() ) {
						direction = -1;
					}

					// animate it (2 steps, fade out, fade in)
					viewportEle.fade({ 'from': 1, 'to': 0, 'duration': 0.25,
						'afterFinish': function(){
							
							// after faded out, move it left and then fade it back in 
							// TODO: why does prototype set { display: none; opacity: 1; } on fade out?
							viewportEle.setStyle({ 'left': -leftOffset + "px", 'opacity': 0, 'display': 'block' });
							viewportEle.fade({ 'from': 0, 'to': 1, 'duration': 0.25 });
						}
					});
				});

				// call back to handle any new 'plugins' that need to be updated
				// pass direction so any callback can know if we went to the next or previous slide
				Carousel.Common.NewSlideIsShown( slideEle, slideIndex, direction );
			}
		},
		
		/**
		 * This is a call back used whenever a Carousel is showing a different slide. Use this to add different
		 * features into a Carousel.
		 * 
		 * @param {HtmlElement} slideEle
		 * @param {HtmlElement} slideIndex
		 */
		NewSlideIsShown: function( slideEle, slideIndex, direction ) {
			// TODO: fix comments
			// if we did not return true, then execute normally
			Carousel.Common.CallBack.Fire(slideEle, slideIndex, direction, 'new_slide')
			
			// this is really a list of "update" methods for some of the carousel's components
			Carousel.SlideStatus.Update(slideEle);
			Carousel.Thumbnails.Highlight(slideEle, slideIndex);
			Carousel.Title.Update(slideEle);

			// this allows for each carousel to have a custom callback to run
			// this is primarily used for the photogallery			
			
		},
		CallBack: {
			Init: function(slideEle) {
				slideEle.__CALLBACKS = {
					'new_slide': [],
					'thumb_click': []
				};
				
				slideEle.__CALLBACKS_INIT = true;
			},
			Add: function( slideEle, callbackType, callback ) {
				if( ! slideEle.__CALLBACKS_INIT ) { Carousel.Common.CallBack.Init(slideEle); }
				
				slideEle.__CALLBACKS[ callbackType ].push( callback );
			},
			Fire: function( slideEle, slideIndex, direction, callbackType ) {				
				if (slideEle.__CALLBACKS) {
					if( slideEle.__CALLBACKS[ callbackType ] ) { 
						var stopOthers = false;
						slideEle.__CALLBACKS[callbackType].each(function(callbackFunction){
							var retVal = false;
							if (!stopOthers) {
								retVal = callbackFunction(slideEle, slideIndex, direction);
							}
	
							// if anything comes back as true, it will not allow any other callbacks to be made
							if( ! stopOthers && retVal ) { 
								stopOthers = true; 
							}
						});
					}
					return stopOthers;
				}
			}
		} 
	},
	Advertise: {
		Init: function( slideEle ) {
			var adOverlayEle = slideEle.selectFirst('.adOverlay');
			if( adOverlayEle ) {
				slideEle.__AD_ELE = adOverlayEle;
				slideEle.__AD_ELE.hideElement();
				
				var counter = 0;
				
				Carousel.Common.CallBack.Add( slideEle, 'new_slide', function( slideEle, slideIndex, direction ) {
					counter++;

					if( counter % 3 == 0 && counter != 0 ) { 
						counter = -1;

						if( direction > 0 ) {
							Carousel.Pager.Previous( slideEle );
						} else {
							Carousel.Pager.Next( slideEle );
						}
						
						Carousel.Advertise.Show( slideEle );
					}
					
					if( slideEle.__AD_SHOWING ) {
						Carousel.Advertise.Hide( slideEle );
					}
				});
			}
		},
		Show: function( slideEle ) {
			slideEle.__VIEWPORTS.each( function( viewportEle ) {
				viewportEle.fade({ 'from': 1, 'to': 0, 'duration': 0.25,
					'beforeStart': function() {
						slideEle.__AD_ELE.setStyle({'opacity': '0', 'display':'block'});
						slideEle.__AD_ELE.showElement();
					},
					'afterFinish': function(){
						viewportEle.hideElement();
						viewportEle.setStyle({ 'display': 'none', 'visibility': 'hidden' });
						slideEle.__AD_ELE.fade({ 'from': 0, 'to': 1, 'duration': 0.25 });
						slideEle.__AD_SHOWING = true;
					}
				});
			});
			
			slideEle.__SKIP_THUMB_SHOW = true;
		},
		Hide: function( slideEle ) {
			slideEle.__AD_ELE.fade({ 'from': 1, 'to': 0, 'duration': 0.25,
				'afterFinish': function() { 
					slideEle.__AD_ELE.hideElement();
					slideEle.__VIEWPORTS.each( function( viewportEle ) {
						viewportEle.showElement();
						viewportEle.setStyle({'display': 'block', 'visibility': 'visible'});
						viewportEle.fade({ 'from': 0, 'to': 1, 'duration': 0.25 });
					});
				}
			});
			
			slideEle.__AD_SHOWING = false;
		}
	}
	
};


