/**
 * @fileOverview
 * @author ruppegw6
 * @version 0.4
 * @requires jQuery 1.2.6
 */

;(function($) {
	/**
	 * @class elgallery is a jquery plugin that creates a lightbox gallery for Top Gear, including a carousel, etc
	 * @returns jQuery object of the selector
	 */
	$.fn.elgallery = function(options) {
		var defaults = {
			// name & path of the HTML template for the gallery
			htmlTemplateUrl: 'elgallery.html',
			// image index query string parameter name
			imageIndex: 'i',
			// used html classes - change if HTML is changed
			mainImageClass: 'main-image',
			numberOfPicsClass: 'all-pics-number',
			currentPicClass: 'current-pic-number',
			captionClass: 'caption',
			// element used for the title of the gallery
			heading: 'h1',
			// lightbox jquery html wrapper
			$lightbox: $('<div id="lightbox"></div>'),
			// json object if passed in rather than ajax collected
			json: null
		};
		
		var options = $.extend(defaults, options);

		// private attributes
		var title,
			href,
			initialImage,
			galleryData; // json object with the gallery data
		
		// cached jQuery objects
		var $gallery,
			$next,
			$prev,
			$controls,
			$slideshow,
			$currentImageNumber, // jQuery object of span that displays number of current picture
			$caption;
		
		/**
		 * sets up the event listener for the jQuery object that has been clicked
		 */
		this.click(function (e) {
			// attach event handler when the links are clicked
			var i, t;
			// we need to check whether the parent is an anchor or another object (i.e. image)
			// if it's not an anchor, then we have to look for the parent anchor
			if (!$(e.target).is('a')) {
				t = $(e.target).parents('a')[0];
			} else {
				t = e.target;
			}

			// get the title or text of the link: this will constitute the gallery title
			title = t.title || $(t).text();
			href  = t.href;
			// if we have a query string of defautls.imageIndex (set in the defaults), we use that for the number of the current/first image
			if ((i = href.indexOf(defaults.imageIndex + '=')) !== -1) {
				initialImage = parseInt(href.substring(i).split('&')[0].split('=')[1], 10);
			} else {
				initialImage = 0;
			}
			// initialise the gallery
			init(title, href, initialImage);
			return false;
		});
		
		/**
		 * initialises a lightbox gallery when a link is clicked
		 * @private
		 * @param {String} title Title of the gallery
		 * @param {String} href link to the gallery
		 * @param {String} initialImage the first image to be shown (gallery doesn't have to be shown from image 1)
		 */		
		function init(title, href, initialImage) {
			// load the lightbox HTML & set everything up
			options.$lightbox.load(defaults.htmlTemplateUrl, function(){
				setupHTML();
				setupEventHandlers();
				getGalleryJSON();
			});
		};

		/**
		 * sets up all the event handlers
		 * @private
		 */		
		function setupEventHandlers() {
			// setup event handlers for keycommands
			$(document).keyup(function(e) {
				// close the gallery
				if (e.keyCode === 27) { return close(); }
				// goto previous image
				if (e.keyCode === 37 || e.keyCode === 80) { return prev(); }
				// goto next image
				if (e.keyCode === 39 || e.keyCode === 78) { return next(); }
			});
			
			options.$lightbox.click(function (e) { return close(); });
			
			$gallery.click(function(e) { 
				// stop the lightbox from closing controls in it are clicked
				e.stopPropagation();
				var $t = $(e.target);
				if ($t.hasClass('disabled')) {return false; };
				if ($t.hasClass('next')) { return next(); };
				if ($t.hasClass('prev')) { return prev(); };
				if ($t.hasClass('close'))	{ return close(); };			
			});
			
		};
			
		/**
		 * sets up the HTML for the gallery
		 * @private
		 */		
		function setupHTML() {
			// add the overlay
			$('body').addOverlay();
			// put the lightbox & gallery into the body
			options.$lightbox.appendTo('body');
			
			// cache the objects
			cacheObjects();
			
			// set the gallery title
			$gallery.find(options.heading).text(title);
			// show the gallery & lightbox
			var offset = getPageScroll()[1] + 40;
			$gallery.css({position:'relative', top: offset + 'px' });
			$gallery.hide().fadeIn('slow');
			options.$lightbox.show();

		};
		
		/**
		 * caches all the objects in to object attributes
		 * @private
		 */		
		function cacheObjects() {
			$gallery = options.$lightbox.find('.gallery');
			$slideshow = $gallery.find('.slideshow');
			$controls = $gallery.find('.controls');
			$currentImageNumber = $gallery.find('.' + options.currentPicClass);
			$caption = $gallery.find('.' + options.captionClass);
		};
		
		/**
		 * get the json object for the gallery
		 * @private
		 */		
		function getGalleryJSON() {
			// get the JSON for the current gallery
			if (options.json) {
				galleryData = options.json;
				postJSONCleanUp(galleryData);
			} else {
				$.getJSON(href, {render: 'ajax'}, function(json) {
					galleryData = json;
					postJSONCleanUp(galleryData);
				});				
			}
		};
		
		/**
		 * sets the number of images in the gallery, shows the first image & creates the carousel
		 * @private
		 * @param {Object} data json object with the gallerydata
		 */		
		function postJSONCleanUp(data) {
			// set the number of pics
			$('.' + options.numberOfPicsClass, $gallery).html(galleryData.length);
			image.show(initialImage, {isNewGallery:true});
			// create carousel
			carousel.init(initialImage, galleryData.length);
		};
		

		// gallery controls
		
		/**
		 * closes the gallery
		 * @private
		 */		
		function close() {
			// close the gallery
			$('body').removeOverlay();
			options.$lightbox.fadeOut('slow', function() {
				$(this).remove();	
			});
			// remove event listeners
			$(document).unbind('keyup');
			options.$lightbox.unbind('click');
			$gallery.unbind('click');
			return false;
		};
	
		/**
		 * goes to next image
		 * @private
		 */
		function next() {
			carousel.goTo(image.index + 1);
			return image.show(image.index + 1);
		};
	
		/**
		 * goes to previous image
		 * @private
		 */		
		function prev() {
			carousel.goTo(image.index - 1);
			return image.show(image.index - 1);
		};

		
		/**
		 * disables prev/next if we are on the 1st or last picture
		 * @private
		 */		
		function cleanControls() {
			$('.disabled').removeClass('disabled');
			if (image.index === 0) {
				$gallery.find('.prev').addClass('disabled');
			} else if (image.index === (galleryData.length - 1)) {
				$gallery.find('.next').addClass('disabled');
			}
		};
		
		/**
		 * gets the x + y coordinates of the where the page is scrolled to
		 * @private
		 * @author <a href="http://www.quirksmode.com">PPK</a>
		 * @return {Array} Return an array with x,y page scroll values.
		 */
		function getPageScroll() {
			var xScroll, yScroll;
			if (self.pageYOffset) {
				yScroll = self.pageYOffset;
				xScroll = self.pageXOffset;
			} else if (document.documentElement && document.documentElement.scrollTop) {	 // Explorer 6 Strict
				yScroll = document.documentElement.scrollTop;
				xScroll = document.documentElement.scrollLeft;
			} else if (document.body) {// all other Explorers
				yScroll = document.body.scrollTop;
				xScroll = document.body.scrollLeft;	
			}
			arrayPageScroll = new Array(xScroll,yScroll);
			return arrayPageScroll;
		};
		
		/**
		 * image object puts the methods needed to show, load & transition to images
		 * @static
		 */
		var image = {
			index: 0,

			/**
			 * shows the image with the specified index, updates the image info & makes sure that the controls are in the correct state
			 * @param {Number} i index of the image to be shown
			 * @param {Object} options options that are passed in:
			 * options.isNewGallery argument is only needed if it is a new gallery, i.e. 1st call of show();
			 * @returns {Boolean} returns false to stop event propagation & default browser action
			 */
			show: function(i, options) {
				var options = options || {};
				this._load(i, options.isNewGallery);
				this._info();
				cleanControls();
				return false;
			},
			
			/**
			 * load the image specified
			 * @private
			 * @param {Number} i index of the image to be shown
			 * @param {Boolean} isNewGallery true if it is a new gallery, otherwise false or more usual undefined
			 * @returns {Boolean} returns false to stop event propagation
			 */
			_load: function(i, isNewGallery) {
				var that = this;
				var $img = $(new Image());
				if (galleryData[i]){
					$img.prependTo('.slideshow');
					// wire up the image load event before setting the source because of IE cache issues
					$img.load(function() {
						// once the image is loaded, transition to it if it's not a new gallery
						if (isNewGallery === undefined || !isNewGallery) {
							that._transitionTo($img);							
						}
					});
					$img.attr({
						'src': galleryData[i].src,
						'alt': galleryData[i].alt,
						'width': 712,
						'height': 400
					});
					// remember the current (new) image index
					this.index = i;
					return false;		
				} else {
					// alert('Sorry but this image doesn\'t exist (anymore?!?)');
					return false;
				}
			},
			
			/**
			 * transitions to the new image
			 * @private
			 * @param {Object} $newImage jQuery object of the new image to be transitioned to
			 */
			_transitionTo: function($newImage) {
				var $oldImage = $('img:eq(1)', $slideshow);
				$oldImage.fadeOut('slow', function() {
					$(this).remove();
				});
			},
			
			/**
			 * update information about the picture
			 * @private
			 */
			_info: function() {				
				$currentImageNumber.text(this.index + 1);
				$caption.html(galleryData[this.index].title+"<br />"+galleryData[this.index].summary);
			}
		};
		
		/**
		 * carousel object: responsible for all things carousel related!
		 * @static
		 */
		var carousel = {
			/** visible: how many items are visible in the carousel */
			options: {
				visible: 5 
			},
			
			/**
			 * initialises carousel
			 * @param {Number} imgIndex index of the 1st item in the carousel to be shown
			 * @param {Number} size number of items in the gallery
			 */
			init: function(imgIndex, size) {
				var that = this;
				// setup the html needed
				this._setupHtml();
								
				// make sure that we really need a carousel before we create it
				if (size > this.options.visible) {
					this._createCarousel(imgIndex, size);
				}
				
				// set up the hover states of the carousel images
				$('.gallery-carousel-wrapper a').hover(function() {
					$(this).addClass('hovered');
				}, function() {
					$(this).removeClass('hovered');
				});
				
				// show the image clicked in the carousel
				$('.gallery-carousel-wrapper a').click(function () {
					return that._showSelected(this);
				});
				
				// enables & disables controls depending on what whether we are at the start or end of the carousel
				this._cleanControls();
				// highlight the currently selected image
				this._highlightSelected(imgIndex);
				
			},
			
			/**
			 * only create a carousel if we really need it
			 * @private
			 */			
			_createCarousel: function(imgIndex, size) {
				var that = this;
				var extCtrls = this._externalControls(size);
				$('<div class="gallery-carousel-controls"><a href="#" class="c-prev">Previous</a><a href="#" class="c-next">Next</a></div>').appendTo('.gallery-carousel');
				// set up the carousel
				$('.gallery-carousel-wrapper').jCarouselLite({
					btnNext: ".c-next",
					btnPrev: ".c-prev",
					visible: that.options.visible,
					scroll: that.options.visible - 1,
					circular: false,
					start: imgIndex,
					btnGo: extCtrls,
					afterEnd: function(o) {
						// $('.gallery-carousel-wrapper a').removeClass('selected');
						// var e = o.filter('li:eq(0)').find('a').addClass('selected');
						that._cleanControls();
					}
				});
				
			},
			
			/**
			 * show the selected image
			 * @private
			 * @param {Object} o selected object (anchor that refers to an image)
			 * @returns {Boolean} false to stop event propagation
			 */
			_showSelected: function(o) {
				var index = this._getIndex(o.rel);
				this._highlightSelected(index);
				image.show(index);
				return false;
			},
			
			/**
			 * adds a selected class to the currently displayed image
			 * @private
			 * @param {Number} i index of the image that is currenly shown
			 */
			_highlightSelected: function(i) {
				$('.gallery-carousel-wrapper a').removeClass('selected');
				$('.gallery-carousel ul li:eq(' + i + ')').find('a').addClass('selected');
			},
			
			/**
			 * moves the carousel to the image given by the index
			 * @public
			 * @param {Number} imgindex index of the image to go to
			 */
			goTo: function(imgindex) {
				if ($('.c-external-controls li').length > 0) {
					$('.c-external-controls li:eq(' + imgindex + ')').triggerHandler('click');					
				}

				this._highlightSelected(imgindex);
				this._cleanControls();
			},
			
			/**
			 * enables & disables controls depending whether we are at the start or the end of the carousel
			 * @private
			 * @param {String|Object|Array|Boolean|Number} paramName Describe this parameter
			 * @returns {String|Object|Array|Boolean|Number} Describe what it returns
			 */
			_cleanControls: function() {
				var ulPosition,
					itemWidth,
					visibleWidth,
					ulWidth;
				ulPosition = parseInt($('.gallery-carousel-wrapper ul').css('left'), 10);
				itemWidth = $('.gallery-carousel-wrapper ul li').outerWidth(true);
				visibleWidth = itemWidth * this.options.visible;
				ulWidth = itemWidth * $('.gallery-carousel-wrapper ul li').length;
				
				// if we are at the beginning of the carousel we disable the arrow
				if (ulPosition === 0) {
					$('.c-prev').css('display', 'none');
					
				// if we are at the end of the carousel we disable the next arrow
				} else if ( ulPosition === -(ulWidth - visibleWidth)) {
					$('.c-next').css('display', 'none');
				} else {
					$('.c-prev').css('display', 'block');
					$('.c-next').css('display', 'block');
				}
			},

			/**
			 * builds up the external html controls for the carousel & the array with the css selectors
			 * for the carousel plugin
			 * @private
			 * @param {Number} size the amount of images in the gallery
			 * @returns array with the selectors for the carousel plugin
			 * @type Array
			 */
			_externalControls: function(size) {
				var i = 0,
					extCtrlArray = [],
					currentSelector,
					imgNumber = 0; 
				var $ol = $('<ol class="c-external-controls" style="display:none;"></ol>');
				for (i; i < size; i++) {
 					imgNumber = i + 1;
					$('<li class="img' + i + '">Goto Image ' + imgNumber +'</li>').appendTo($ol);
					currentSelector = '.c-external-controls .img' + i;
					extCtrlArray.push(currentSelector);
				}
				$gallery.append($ol);
				return extCtrlArray;
			},

			/**
			 * sets up the html needed for the carousel
			 * @private
			 * @param {Number} size size of the gallery/carousel
			 */			
			_setupHtml: function(size) {
				// sets up the html for the carousel
				var $carouselWrapperHtml = $('<div class="gallery-carousel"><div class="gallery-carousel-wrapper"><ul></ul></div></div>');
				var i = 0;
				for (i; i < galleryData.length; i++) {
					var $img = $('<li><a href="' + galleryData[i].src + '" title="' + galleryData[i].title + '" rel="' + 'img-' + i + '"><span></span><img src="' + galleryData[i].thumbnail + '" width="128" height="72" alt="' + galleryData[i].alt + '" /></a></li>');
					$carouselWrapperHtml.find('ul').append($img);
				}
				$carouselWrapperHtml.insertAfter($controls);
			},
			
			/**
			 * gets the index of the image to be shown from a string of the format 'xxx img-INDEX'
			 * @private
			 * @param {String} str string of format xxxxx img-INDEX
			 * @returns {Number} index
			 */
			_getIndex: function(str) {
				return parseInt(str.split('img-')[1], 10);
			}
			
		};
		
		return this;
	};
})(jQuery);