/**
 * jQuery tightbox plugin.
 * 
 * This plugin provides a highly customizable and skinnable lightbox clone.
 *
 * @name jquery-tightbox-0.1.js
 * @author Christopher Krohn
 * @version 0.1
 * @date Juni 15, 2009
 * @category jQuery plugin
 * @copyright (c) 2009 Christopher Krohn
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function($) 
{
	/**
	 * Definition of the tightbox plugin
	 */
	$.fn.tightbox = function(options)
	{			
		/**
		 * Internal options.
		 * Do not change anything here.
		 */
		$.fn.tightbox.internalOptions = 
		{
			/**
			 * Array containing the images
			 * used by the tightbox
			 */
			images : [],
			
			/**
			 * Currently active image
			 */
			currentImage : 0,
			
			/**
			 * Filetype
			 */
			fileType : null
		};
			
		/**
		 * jQuery object representing the whole tightbox
		 */
		$.fn.tightbox.html = null;
		
		/**
		 * Define default options.
		 */
		$.fn.tightbox.defaultOptions = 
		{
			/**
			 * Wait this number of milliseconds between
			 * switching the images.
			 */
			sleepTime : 50,
				
			/**
			 * Overlay configuration
			 */
			overlay : {
				enabled	:	true,
				opacity :	'0.9'
			},
			
			/**
			 * Configure the tightboxes
			 * position.
			 */
			position : {
				top  : false 
			},
			
			/**
			 * Enable thumbnail navigation
			 */
			enableThumbnails : false,
			
			/**
			 * Selectors
			 */
			selectors : {
				currentImage	 : '.tightbox-current-image',
				imageCount		 : '.tightbox-image-count',
				controlBar 		 : '.tightbox-controlbar',
				controlBarTop 	 : '.tightbox-controlbar-top',
				controlBarBottom : '.tightbox-controlbar-bottom',
				overlay 		 : '.tightbox-overlay',
				loading 		 : '.tightbox-loading',
				container 		 : '.tightbox-container',
				next	  		 : '.tightbox-next',
				previous		 : '.tightbox-prev',
				close			 : '.tightbox-close',
				image			 : '.tightbox-image',	
				dataContainer 	 : '.tightbox-data-container',
				window			 : '.tightbox-window',
				label			 : '.tightbox-label',
				thumbnails		 : '.tightbox-thumbs'
			},
			
			/**
			 * Animation speed for the container
			 * resizing animation.
			 */
			containerAnimationSpeed : 400,		
					
			/**
			 * Elements, that are hidden when
			 * the tightbox is opened.
			 */
			hiddenElements : [
			  'embed',
			  'object',
			  'select'
			],

			/**
			 * The customization hooks are a set of
			 * callback functions that can be used
			 * to infues custom behaviour to the tightbox.
			 */
			customizationHooks: {
			
				/**
				 * Callback used to resize the
				 * controls analog to the container.
				 */
				resizeControls : $.fn.tightbox.resizeControls,
				
				/**
				 * Callback used to animate the
				 * container on a custom way.
				 */
				containerAnimation : false,
				
				/**
				 * Called before initialization
				 */
				preInit   : false,
				
				/**
				 * Called after initialization
				 */
				postInit  : false,

				/**
				 * Called before opening
				 */
				preOpen   : false,
				
				/**
				 * Called after opening
				 */
				postOpen  : false,
				
				/**
				 * Called before closing
				 */
				preClose  : false,
				
				/**
				 * Called after closing
				 */
				postClose : false,
				
				/**
				 * Called when switching to
				 * the next image
				 */
				nextImage : false,
				
				/**
				 * Called when switching to
				 * the previous image
				 */
				prevImage : false,
				
				/**
				 * Callback function to retreive the
				 * interface markup on a custom way.
				 */
				getInterfaceMarkup : false
		 	},
					
			/**
			 * Keyboard configuration
			 */
			keyboardNavigation : {
		 		enabled : true,
				keys 	: {
					close 	 : ['c', 'esc'],
					next	 : ['n', 'right'],
					previous : ['p', 'left']
				}
		 	},
		 	
		 	/**
		 	 * Keyboard related settings.
		 	 */
		 	keyboard : {
		 		specialKeys : {
	 				'esc'	 :	27,
	 				'left'	 :	37,
	 				'right'	 :	39,
	 				'pgup'	 : 	33,
	 				'pgdown' :	34
		 		}
		 	},
		 	
		 	/**
		 	 * Specify different templates for 
		 	 * special file types.
		 	 */
		 	typeTemplates : {
		 		image    : 'templates/image.php',
		 		page     : 'templates/page.php',
		 		media    : 'templates/media.php',
		 		_default : 'templates/page.php' 
		 	},
		 	
		 	/**
		 	 * Supported types.
		 	 */
		 	types : {
		 		iframed : [{ 'iframed' : 'true' }],
				page  	: ['asp', 'aspx', 'cgi', 'htm', 'html', 'pl', 'php4', 'php3', 'php', 'php5', 'phtml', 'rhtml', 'shtml', 'txt', 'vbs', 'rb'],
				flash	: [{'flash' : 'true'}, 'swf', 'flv'],
				media 	: ['aif', 'aiff', 'asf', 'avi', 'divx', 'm1v', 'm2a', 'm2v', 'm3u', 'mid', 'midi', 'mov', 'moov', 'movie', 'mp2', 'mp3', 'mpa', 'mpa', 'mpe', 'mpeg', 'mpg', 'mpg', 'mpga', 'pps', 'qt', 'rm', 'ram', 'swf', 'viv', 'vivo', 'wav'],
				image 	: ['bmp', 'gif', 'jpg', 'jpeg', 'png', 'tiff', 'tif']
		 	},
		 	
		 	/**
		 	 * Type handler functions.
		 	 */
			typeHandlers : {
		 		page  	: __page,
				media 	: __media,
				flash	: __flash,
				image 	: __image,
				iframed : __iframed
			}
		};	
				
		/**
		 * Merge default options with the specified ones
		 */
		var settings = __mergeObjects(
			$.fn.tightbox.defaultOptions,
			options
		);
		$.fn.tightbox.settings = settings;
		
		/**
		 * Get the matched jQuery object
		 */
		var matchedObjects = this;
				
		/**
		 * Merge two objects recursively.
		 * 
		 * @param object object1
		 * @param object object2
		 * @retur object
		 */
		function __mergeObjects(obj1, obj2) 
		{
			for(var p in obj2) 
			{
				try 
				{
					if (obj2[p].constructor == Object) 
					{
						obj1[p] = __mergeObjects(obj1[p], obj2[p]);
					} 
					else 
					{
						obj1[p] = obj2[p];
					}
				} 
				catch(e) 
				{
					obj1[p] = obj2[p];
				}
			}
			return obj1;
		}
		
		/**
		 * Extract the filetype for the given path.
		 * 
		 * @param string
		 *            path
		 * @return string
		 */
		function __extractFileType(path)
		{
			var matches = path.match(/\.([a-zA-Z0-9]+)(\?.*)?$/);			
			if(matches !== null && matches[1] !== null)
			{
				return matches[1];
			}			
			
			return false;
		}
		
		/**
		 * Initialize the tightbox.
		 * 
		 * @param jQuery.event event
		 * @return false
		 */
		function __initTightbox(event)
		{
			event.stopPropagation();
			
			/**
			 * Execute preInit hook
			 */
			try
			{
				settings.customizationHooks.preInit();
			} 
			catch(e){}
			
			/**
			 * Hide some elements to avoid problems
			 */
			for(var i = 0; i < settings.hiddenElements; i++)
			{
				$(settings.hiddenElements[i]).css({ 'visibility' : 'hidden' });
			}

			/**
			 * Execute postInit hook
			 */
			try
			{
				settings.customizationHooks.postInit();
			} 
			catch(e){}
			
			/**
			 * Open the tightbox and specify the
			 * matched and clicked objects.
			 */
			__openTightbox(this, matchedObjects);

			/**
			 * Get requested filetype
			 */
			var href 	  = $(this).attr('href');
			var extension = __extractFileType(href);
			
			/**
			 * Get params
			 */
			$.fn.tightbox.internalOptions.params = __parseQueryString(href);
			
			/**
			 * Call the associated type handler function
			 */
			var typeHandler = __getTypeHandler(extension);
			__handleType(typeHandler, this, matchedObjects);
			
			/**
			 * Avoid the browser following the link 
			 */
			return false;
		}
		
		/**
		 * Retreive the type handling function 
		 * for the specified file extension.
		 * First a handler is searched in the user specified options.
		 * If no handler function or filetype is found, the default
		 * options are searched.
		 * 
		 * @param string extension
		 * @return function callback
		 */
		function __getTypeHandler(extension)
		{
			var handler;			
			if(options)
			{
				handler = __getTypeHandlerImpl(extension, options);
			}			
			if(!handler)
			{
				handler = __getTypeHandlerImpl(extension, settings);
			}
			return handler;
		}
		
		/**
		 * Implementation of getTypeHandler.
		 * 
		 * @param string extension
		 * @param object optionsObject
		 * @return function callback
		 */
		function __getTypeHandlerImpl(extension, optionsObject)
		{			 
			for(key in optionsObject.types)
			{
				var typeList = optionsObject.types[key];
				for(i in typeList)
				{
					var type = typeList[i];

					if(typeof type == 'object')
					{
						for(j in type)
						{
							if(
								$.fn.tightbox.internalOptions.params[j] &&
								$.fn.tightbox.internalOptions.params[j] == type[j]
							)
							{
								$.fn.tightbox.internalOptions.fileType = key;
								return settings.typeHandlers[key];								
							}
						}
					}
					else
					{					
						if(type == extension)
						{						
							$.fn.tightbox.internalOptions.fileType = key;
							return settings.typeHandlers[key];
						}
					}
				}
			}
		}
			 
		/**
		 * Set the currently active image
		 * 
		 * @return void
		 */
		function __setImage()
		{			
			showLoadingIndicator()
			var source = $.fn.tightbox.internalOptions.images[$.fn.tightbox.internalOptions.currentImage][0];			
			var image  = new Image();
			
			image.onload = function() 
			{
				$(settings.selectors.image + ' img', $.fn.tightbox.html).attr('src', source);
				__resizeContainer(image.width, image.height, __viewImage);
				image.onload = function() {};
			};
			image.src = source;
		}
		 
		/**
		 * Display the loading indicator
		 */
		function showLoadingIndicator()
		{
			$(settings.selectors.loading, $.fn.tightbox.html).show();
		}
	 
		/**
		 * Hide the loading indicator
		 */
		function hideLoadingIndicator()
		{
			$(settings.selectors.loading, $.fn.tightbox.html).hide();
		}

		/**
		 * View the currently active image.
		 * 
		 * @return void
		 */
		function __viewImage() 
		{
			hideLoadingIndicator();			
			$(settings.selectors.image, $.fn.tightbox.html).fadeIn(function()
			{
				__updateImageInfo();
				
				/**
				 * Hide previous image link when the
				 * first image is active.
				 */
				if($.fn.tightbox.internalOptions.currentImage == 0)
				{
					$(settings.selectors.previous, $.fn.tightbox.html).css('visibility', 'hidden');
				}
				else
				{
					$(settings.selectors.previous, $.fn.tightbox.html).css('visibility', 'visible');
				}
				
				/**
				 * Hide next image link when the
				 * last image is active.
				 */
				if(($.fn.tightbox.internalOptions.currentImage + 1) == $.fn.tightbox.internalOptions.images.length)
				{
					$(settings.selectors.next, $.fn.tightbox.html).css('visibility', 'hidden');
				}
				else
				{
					$(settings.selectors.next, $.fn.tightbox.html).css('visibility', 'visible');
				}
				
				$.fn.tightbox.displayControls(true);
			});						
			__preloadNeighborImages();
		};
		
		/**
		 * Resize the container with a fancy animation to
		 * the specified dimensions.
		 * 
		 * @param int imageWidth
		 * @param int imageHeight
		 * @return void
		 */
		function __resizeContainer(imageWidth, imageHeight, callback) 
		{
			/**
			 * Hide all controls when resizing the container.
			 */
			__hideControls(function()
			{
				var container 	  = $(settings.selectors.dataContainer, $.fn.tightbox.html);
				var currentWidth  = container.width();
				var currentHeight = container.height();
				
				var width  = (imageWidth);
				var height = (imageHeight);

				var diffW = currentWidth  - width;
				var diffH = currentHeight - height;

				$.fn.tightbox.resizeControls(width);
				try
				{
					/**
					 * Execute the animation hook instead of
					 * the default animation.
					 */
					settings.customizationHooks.containerAnimation(
						container,
						width, 
						height,
						settings.containerAnimationSpeed,
						function() 
						{ 
							callback(); 
						}
					);
				}
				catch(e)
				{
					/**
					 * If no container animation hook is defined,
					 * use the default resizing animation.
					 */
					container.animate({ 
						height: 	height 
					}, 
					settings.containerAnimationSpeed,
					function() 
					{ 	
						container.animate({ 
							width: 	width 
						}, 
						settings.containerAnimationSpeed,
						function() 
						{ 						
							/**
							 * Display the current image.
							 */
							 callback(); 
						});				
					});
				}
				
				/**
				 * If the containers destination size is the
				 * same like before, sleep for a few milliseconds.
				 */
				if(diffW == 0 && diffH == 0) 
				{
					if($.browser.msie)
					{
						__sleep(settings.sleepTime + 150);
					} 
					else 
					{
						__sleep(settings.sleepTime);	
					}
				} 
			});
		};
		
		/**
		 * Update the width of any controls.
		 * 
		 * @param int width
		 * @return void
		 */
		$.fn.tightbox.resizeControls = function(width)
		{
			var controls = $(settings.selectors.controlBar, $.fn.tightbox.html);
			if(controls)
			{
				$(settings.selectors.controlBar, $.fn.tightbox.html).css('width', width + 'px');
			}
		}
		
		/**
		 * Register click events for navigation controls.
		 * 
		 * @return void
		 */
		function __registerControls()
		{
			$(settings.selectors.previous).die('click').live('click', function()
			{
				__showPreviousImage();
			});
			$(settings.selectors.next).die('click').live('click', function()
			{
				__showNextImage();
			});
		}

		/**
		 * Show the previous image.
		 * 
		 * @return false
		 */
		function __showPreviousImage()
		{
			/**
			 * Execute prevImage hook
			 */
			try
			{
				settings.customizationHooks.prevImage($.fn.tightbox.html);
			} 
			catch(e){}
			
			if($.fn.tightbox.internalOptions.currentImage > 0)
			{
				$(settings.selectors.image, $.fn.tightbox.html).hide();
				$.fn.tightbox.internalOptions.currentImage = $.fn.tightbox.internalOptions.currentImage - 1;
				__setImage();
				__updateActiveThumb();
			}
			return false;
		}
		
		/**
		 * Show the next image.
		 * 
		 * @return false;
		 */
		function __showNextImage()
		{
			/**
			 * Execute nextImage hook
			 */
			try
			{
				settings.customizationHooks.nextImage($.fn.tightbox.html);
			} 
			catch(e){}
			
			if($.fn.tightbox.internalOptions.currentImage + 1 != $.fn.tightbox.internalOptions.images.length)
			{
				$(settings.selectors.image, $.fn.tightbox.html).hide();
				$.fn.tightbox.internalOptions.currentImage = $.fn.tightbox.internalOptions.currentImage + 1;
				__setImage();
				__updateActiveThumb();
			}
			return false;
		}

		/**
		 * Show the specified image.
		 * 
		 * @return false
		 */
		function __showImageByIndex(index)
		{
			if($.fn.tightbox.internalOptions.images.length >= index)
			{
				$(settings.selectors.image, $.fn.tightbox.html).hide();
				$.fn.tightbox.internalOptions.currentImage = index;
				__setImage();
				__updateActiveThumb();
			}
			return false;
		}

		/**
		 * Update image information such as the
		 * current image index and the images title.
		 */
		function __updateImageInfo()
		{		
			var currentImage = $.fn.tightbox.internalOptions.images[$.fn.tightbox.internalOptions.currentImage];
			$(settings.selectors.label, $.fn.tightbox.html).html(currentImage[1]);
			
			$(settings.selectors.currentImage, $.fn.tightbox.html).html(
				$.fn.tightbox.internalOptions.currentImage + 1
			);
		}
		
		/**
		 * Display tightbox controls.
		 * 
		 * @return void
		 */
		$.fn.tightbox.displayControls = function(animate)
		{		
			$(settings.selectors.controlBar + ' > *', $.fn.tightbox.html).show();
			var controls = $(settings.selectors.controlBar);
			if(controls.length > 0)
			{
				if(animate)
				{
					try
					{
						settings.customizationHooks.controlAnimation(controls);
					}
					catch(e)
					{						
						controls.each(function()
						{
							$this = $(this);
							if($this.hasClass(settings.selectors.controlBarTop))
							{
								$this.css('visibility', 'visible');
								$this.animate(
									{
										bottom: 0
									}, 
									'fast',
									function()
									{
										__resizeOverlay();
									}
								);
							}
							else
							{
								$this.slideDown(
									'fast',
									function()
									{
										__resizeOverlay();
									}
								);
							}
						});
					}
				}
				else
				{
					controls.show();
				}				
				
				__registerControls();
			}
			 
			/**
			 * Execute postOpen hook
			 */
			if(settings.customizationHooks.postOpen)
			{
				settings.customizationHooks.postOpen(
					$.fn.tightbox.html
				);
			}
		}
		 
		/**
		 * Hide tightbox controls.
		 * 
		 * @param function callback
		 * @return void
		 */
		function __hideControls(callback)
		{
			if($(settings.selectors.controlBar).length > 0)
			{
				$(settings.selectors.controlBar + ' > *', $.fn.tightbox.html).fadeOut(100);
				
				/**
				 * Hide the top control
				 */
				var controlTop = $(settings.selectors.controlBarTop, $.fn.tightbox.html);
				controlTop.animate(
					{
						bottom : -35
					}, 
					200,
					function()
					{
						controlTop.css('visibility', 'hidden');
					}
				);
				
				/**
				 * Hide the bottom control
				 */
				try
				{
					$(settings.selectors.controlBarBottom, $.fn.tightbox.html).slideUp(200, function()
					{
						window.setTimeout(callback, 200);
					});
				}
				catch(e) {}
			}
			else
			{
				callback();
			}
		}
		
		/**
		 * Preload the neighbor images of the current one.
		 * 
		 * @return void
		 */
		function __preloadNeighborImages() 
		{
			if (($.fn.tightbox.internalOptions.images.length -1) > $.fn.tightbox.internalOptions.currentImage) 
			{
				nextImage = new Image();
				nextImage.src = $.fn.tightbox.internalOptions.images[$.fn.tightbox.internalOptions.currentImage + 1][0];
			}
			if($.fn.tightbox.internalOptions.currentImage > 0 ) 
			{
				previousImage = new Image();
				previousImage.src = $.fn.tightbox.internalOptions.images[$.fn.tightbox.internalOptions.currentImage - 1][0];
			}
		}

		/**
		 * Wait for the specified number of milliseconds.
		 * 
		 * @return void
		 */
		function __sleep(ms) 
		{
			var date = new Date(); 
			curDate  = null;
			
			do 
			{ 
				var curDate = new Date(); 
			}
			while(curDate - date < ms);
		 };
		 
		/**
		 * Open the tightbox
		 */
		function __openTightbox(clickedObject, matchedObjects)
		{			 
			/**
			 * Keyboard navigation
			 */
			if(settings.keyboardNavigation.enabled)
			{
				$(document).unbind('keydown').keydown(function(event) 
				{
					__keyPressed(event);
				});
			}
			
			/**
			 * Bind close events
			 */
			if(settings.overlay.enabled)
			{
				$(settings.selectors.overlay).die('click').live('click', __closeTightbox);
			}			
			$(settings.selectors.close).live('click', function() 
			{ 
				__closeTightbox(); 
				return false; 
			});
		}

		/**
		 * Handle key press events.
		 * 
		 * @param jQuery.event eventObject
		 * @return void
		 */
		function __keyPressed(eventObject)
		{			
			/**
			 * Define some special key codes
			 */
			var specialKeys = settings.keyboard.specialKeys;
			
			/**
			 * Get key code of the pressed key
			 */
			if(eventObject == null) 
			{
				var keyCode = event.keyCode;
			}
			else
			{
				var keyCode = eventObject.keyCode;
			}
			var keyString = String.fromCharCode(keyCode).toLowerCase();
							
			/**
			 * Check if a key defined for the 'close' action was pressed.
			 */
			for(var i = 0; i < settings.keyboardNavigation.keys.close.length; i++)
			{
				var key = settings.keyboardNavigation.keys.close[i];			
				if(key == keyString || key == keyCode || specialKeys[key] == keyCode)
				{
					__closeTightbox();
					return;
				}
			}
							
			/**
			 * Check if a key defined for the 'previous' action was pressed.
			 */
			for(var i = 0; i < settings.keyboardNavigation.keys.previous.length; i++)
			{
				var key = settings.keyboardNavigation.keys.previous[i];			
				if(key == keyString || key == keyCode || specialKeys[key] == keyCode)
				{
					__showPreviousImage();
					return;
				}
			}
							
			/**
			 * Check if a key defined for the 'next' action was pressed.
			 */
			for(var i = 0; i < settings.keyboardNavigation.keys.next.length; i++)
			{
				var key = settings.keyboardNavigation.keys.next[i];			
				if(key == keyString || key == keyCode || specialKeys[key] == keyCode)
				{
					__showNextImage();
					return;
				}
			}
		}
		
		/**
		 * Close the tightbox and remove it from the dom.
		 * 
		 * @return void
		 */
		function __closeTightbox()
		{
			/**
			 * Execute preClose hook
			 */
			if(settings.customizationHooks.preClose)
			{
				settings.customizationHooks.preClose(
					$.fn.tightbox.html
				);
			}

			$(settings.selectors.container).remove();			
			if(settings.overlay.enabled)
			{
				$(settings.selectors.overlay).fadeOut(350, function() 
				{ 
					$(settings.selectors.overlay).remove(); 
					
					/**
					 * Execute postClose hook
					 */
					if(settings.customizationHooks.postClose)
					{
						settings.customizationHooks.postClose(
							$.fn.tightbox.html
						);
					}
				});
			}
			else
			{
				/**
				 * Execute postClose hook
				 */
				if(settings.customizationHooks.postClose)
				{
					settings.customizationHooks.postClose(
						$.fn.tightbox.html
					);
				}
			}

			for(var i = 0; i < settings.hiddenElements; i++)
			{
				$(settings.hiddenElements[i]).css({ 'visibility' : 'visible' });
			}
		}
		 
		/**
		 * Append the tightbox interface to the dom.
		 * 
		 * @param function callback
		 * @return void
		 */
		$.fn.tightbox.appendTightboxMarkup = function(callback)
		{
			/**
			 * If a concrete file type template is 
			 * defined, use it.
			 */
			if(settings.typeTemplates[$.fn.tightbox.internalOptions.fileType])
			{
				var templateUrl = settings.typeTemplates[$.fn.tightbox.internalOptions.fileType];
			}
			/**
			 * Otherwise use the default template
			 */
			else
			{
				var templateUrl = settings.typeTemplates._default;
			}

			/**
			 * Try to get markup via the hook function getInterfaceMarkup().
			 */
			if(settings.customizationHooks.getInterfaceMarkup)
			{
				return __showTightbox(
					settings.customizationHooks.getInterfaceMarkup(),
					callback
				);
			}
						
			/**
			 * Try to receive the html template
			 * via ajax from a serverside file.
			 */	
			__getTemplateByAjax(templateUrl, callback);
		}
		
		/**
		 * Get the interface markup via ajax.
		 * 
		 * @param string templateUrl
		 * @param function callback
		 * @return void
		 */
		requestFailed = false;
		function __getTemplateByAjax(templateUrl, callback)
		{			
			$.ajax({
				url		 :	templateUrl,
				type	 : 	"POST",
				global	 :  false,
				data	 :  {
					'imageCount'   : $.fn.tightbox.internalOptions.images.length,
					'images[]'	   : $.fn.tightbox.internalOptions.images
				},
				dataType :	"html",
				success  :  function(html)
				{
					if(html != "")
					{
						return __showTightbox(html, callback);
					} 
				},
				/**
				 * In case of an error try to retreive
				 * the markup from the default template.
				 */
				error : function()
				{
					if(!requestFailed)
					{
						return __getTemplateByAjax(settings.typeTemplates._default, callback);						
					}
					requestFailed = true;
				}
			});
		}
		 
		/**
		 * Display the tightbox.
		 * 
		 * @param string html
		 * @param function callback
		 * @return void
		 */
		function __showTightbox(html, callback)
		{
			/**
			 * Append the template to the documents body
			 */
			$('body').prepend(html);
			$.fn.tightbox.html = $(settings.selectors.container);
			
			/**
			 * Execute preOpen hook
			 */
			if(settings.customizationHooks.preOpen)
			{
				settings.customizationHooks.preOpen($.fn.tightbox.html);
			}
			
			/**
			 * Set number of images
			 */
			$(settings.selectors.imageCount).html(
				$.fn.tightbox.internalOptions.images.length
			);
			 			
			/**
			 * Configure and display overlay
			 */
			if(settings.overlay.enabled)
			{				
				__resizeOverlay();
				$(settings.selectors.overlay).fadeIn(359);
				
				/**
				 * Execute callback
				 */
				//window.setTimeout(callback, 100); 
				callback();
			}
			else
			{		 				 
				/**
				 * Execute callback
				 */
				callback();
			}
			
			/**
			 * Configure and display container
			 */
			var pageScroll 	   = __getPageScroll();
			var pageDimensions = __getPageSize();
			var top;
			
			if(settings.position.top != false)
			{
				top = settings.position.top;
			}
			else
			{
				top = (pageScroll[1] + (pageDimensions[3] / 14));
			}
			
			/**
			 * Vertical positioning of
			 * the container.
			 */
			$(settings.selectors.container).css({
				top		: top
			}).fadeIn();
			
			$(window).resize(__handleContainerResize);
		}
		 
		/**
		 * Resize the overlay to fit page dimensions.
		 * 
		 * @return void
		 */
		function __resizeOverlay()
		{
			var pageDimensions = __getPageSize();
			if(settings.overlay.enabled)
			{
				$(settings.selectors.overlay).css(
				{
					opacity:		 settings.overlay.opacity,
					width:			 pageDimensions[0],
					height:			 pageDimensions[1]
				});
			}
		}
		 
		/**
		 * Resize the container when resizing
		 * the window.
		 * 
		 * @return void
		 */
		function __handleContainerResize()
		{
			var pageScroll 	   = __getPageScroll();
			var pageDimensions = __getPageSize();
			$(settings.selectors.container).css({
				top:	pageScroll[1] + (pageDimensions[3] / 10),
				left:	pageScroll[0]
			});
			__resizeOverlay();
		}
			 
		/**
		 * Return an array with page dimensions.
		 * 
		 * @return array
		 */
		function __getPageSize() 
		{
			var xScroll, yScroll;		
			if(window.innerHeight && window.scrollMaxY) 
			{
				xScroll = window.innerWidth + window.scrollMaxX;
				yScroll = window.innerHeight + window.scrollMaxY;
			} 
			else if(document.body.scrollHeight > document.body.offsetHeight) 
			{
				xScroll = document.body.scrollWidth;
				yScroll = document.body.scrollHeight;
			} 
			else 
			{
				xScroll = document.body.offsetWidth;
				yScroll = document.body.offsetHeight;
			}
			
			var windowWidth, windowHeight;
			if (self.innerHeight) 
			{
				if (document.documentElement.clientWidth) 
				{
					windowWidth = document.documentElement.clientWidth;
				} 
				else 
				{
					windowWidth = self.innerWidth;
				}
				windowHeight = self.innerHeight;
			} 
			else if(document.documentElement && document.documentElement.clientHeight) 
			{
				windowWidth = document.documentElement.clientWidth;
				windowHeight = document.documentElement.clientHeight;
			} 
			else if(document.body) 
			{
				windowWidth  = document.body.clientWidth;
				windowHeight = document.body.clientHeight;
			}

			if(yScroll < windowHeight) 
			{
				pageHeight = windowHeight;
			} 
			else 
			{
				pageHeight = yScroll;
			}
			
			if(xScroll < windowWidth) 
			{
				pageWidth = xScroll;
			} 
			else 
			{
				pageWidth = windowWidth;
			}
			arrayPageSize = new Array(pageWidth, pageHeight, windowWidth, windowHeight);
			
			return arrayPageSize;
		}

		/**
		 * Return the scroll offset.
		 * 
		 * @return array
		 */
		function __getPageScroll() 
		{
			var xScroll, yScroll;
			
			if(self.pageYOffset) 
			{
				yScroll = self.pageYOffset;
				xScroll = self.pageXOffset;
			} 
			else if(document.documentElement && document.documentElement.scrollTop) 
			{
				yScroll = document.documentElement.scrollTop;
				xScroll = document.documentElement.scrollLeft;
			} 
			else if(document.body) 
			{
				yScroll = document.body.scrollTop;
				xScroll = document.body.scrollLeft;
			}
			arrayPageScroll = new Array(xScroll, yScroll);
			
			return arrayPageScroll;
		}

		/**
		 * Display flash movies.
		 * 
		 * @param jQuery clickedObject
		 * @param jQuery[] matchedObjects
		 * @return void
		 */
		function __flash(clickedObject, matchedObjects, params)
		{
			$.fn.tightbox.appendTightboxMarkup(function()
			{
				showLoadingIndicator();
				width  = params['width'];
				height = params['height'];

				// $.fn.tightbox.updateDimensions(width, height);			
										
				/**
				 * Display flahs
				 */
				$(settings.selectors.dataContainer).flash(
				{ 
					src: String(clickedObject),
					width : width,
					height : height
				},
				{ 
					version: 8 
				}).show();

				$(settings.selectors.label).html(clickedObject.getAttribute('title'));
				
				$.fn.tightbox.resizeControls(width);
				$(settings.selectors.dataContainer + ' embed').hide();
				__resizeContainer(width, height, function()
				{
					$(settings.selectors.dataContainer + ' embed').show();
					window.setTimeout("$.fn.tightbox.displayControls(true)", 750);	
					hideLoadingIndicator();
				});								
			});
		}
		 
		/**
		 * Display media files.
		 * 
		 * @param jQuery clickedObject
		 * @param jQuery[] matchedObjects
		 * @return void
		 */
		function __media(clickedObject, matchedObjects)
		{		 
		}

		/**
		 * Display content retreived via ajax.
		 * 
		 * @param function handler
		 * @param jQuery clickedObject
		 * @param jQuery[] matchedObjects
		 * @return void
		 */
		function __handleType(handler, clickedObject, matchedObjects)
		{		 
			var url    = $(clickedObject).attr('href');

			return handler(
				clickedObject, 
				matchedObjects,
				$.fn.tightbox.internalOptions.params
			);
		}
		 
		/**
		 * Display content in an iframe.
		 * 
		 * @param jQuery clickedObject
		 * @param jQuery[] matchedObjects
		 * @param object param
		 * @return void
		 */
		function __iframed(clickedObject, matchedObjects, params)
		{		 
			$.fn.tightbox.appendTightboxMarkup(function()
			{
				// $.fn.tightbox.updateDimensions(params.width, params.height);

				var href  = String(clickedObject);
				var qs    = href.split("?");
				var url   = qs[0];
				var parts = qs[1].split("&");
				
				var paramList   = {};
				var queryString = "?"; 
				
				/* 
				// @TODO: REMOVE TIGHTBOX PARAMS 
				for(i in parts)
				{
					var pair = parts[i].split("=");

					if(
						!$.fn.tightbox.internalOptions.params[pair[0]] ||
						$.fn.tightbox.internalOptions.params[pair[0]] != pair[1]
					)
					{
						paramList[pair[0]] = paramList[pair[1]];
					}
				}
				for(i in paramList)
				{
					queryString += i + "=" + paramList[i];
				}
				alert(queryString);
				
				url += queryString;
				*/

				if(params.width)
				{
					var width = params.width;
				}
				else
				{
					var width = 400;
				}
				if(params.height)
				{
					var height = params.height;
				}
				else
				{
					var height = 400;
				}
				
				var iframe = '<iframe style="display: none;" id="tightbox-iframe" width="' +width+ '" height="' + height + '" src="' +url+ '"></iframe>';

				if($('#tightbox-iframe'))
				{
					$('#tightbox-iframe').remove();
				}
				$(settings.selectors.label).html(clickedObject.getAttribute('title'));
				$(
					$.fn.tightbox.settings.selectors.dataContainer, 
					$.fn.tightbox.html
				).append(iframe);
				
				showLoadingIndicator();
				$.fn.tightbox.resizeControls(width);
				__resizeContainer(width, height, function()
				{
					hideLoadingIndicator();
					$('#tightbox-iframe').show();
					window.setTimeout("$.fn.tightbox.displayControls(true)", 750);
				});
				
				/**
				 * Display and resize controls
				 */
				//window.setTimeout("$.fn.tightbox.displayControls(true)", 750);	
				//$.fn.tightbox.resizeControls(params.width, params.height);
			});
			return false;
		}
		 
		/**
		 * Display content retreived via ajax.
		 * 
		 * @param jQuery clickedObject
		 * @param jQuery[] matchedObjects
		 * @param object param
		 * @return void
		 */
		function __page(clickedObject, matchedObjects, params)
		{		 
			$.fn.tightbox.appendTightboxMarkup(function()
			{	
				/**
				 * Get specified width
				 */
				if(params['width'])
				{
					boxWidth = params['width'];
				}
				else
				{
					boxWidth = 500;
				}
				
				/**
				 * Get specified height
				 */
				if(params['height'])
				{
					boxHeight = params['height'];
				}
				else
				{
					boxHeight = 500;
				}

				$.fn.tightbox.updateDimensions(boxWidth, boxHeight);
				 
				$.get(
					$(clickedObject).attr('href'),
					function(data)
					{
						/**
						 * Append data to tightbox
						 */
						$(settings.selectors.dataContainer).append(data).show();
						window.setTimeout("$.fn.tightbox.displayControls(true)", 750);	
					}
				);
			});
		}
		 
		/**
		 * Update tightbox dimensions.
		 *
		 * @param int width
		 * @param int height
		 */
		$.fn.tightbox.updateDimensions = function(width, height)
		{
			// Resize controls
			$.fn.tightbox.resizeControls(width);
			
			// Resize window
			$(settings.selectors.window).css({
				'width':	width  + 'px'
			});
			
			// Resize container
			$(settings.selectors.dataContainer).css({
				'width':	width  + 'px',
				'height':	height + 'px'
			});
		}
		
		/**
		 * Display an image gallery.
		 * 
		 * @param jQuery clickedObject
		 * @param jQuery[] matchedObjects
		 * @return void
		 */
		function __image(clickedObject, matchedObjects, params)
		{
			$.fn.tightbox.internalOptions.images.length = 0;
			$.fn.tightbox.internalOptions.currentImage  = 0;
			
			/**
			 * Only one matched element
			 */
			if(matchedObjects.length == 1)
			{
				$.fn.tightbox.internalOptions.images.push(
					new Array(
						clickedObject.getAttribute('href'),
						clickedObject.getAttribute('title')
					)
				);
			}
			
			/**
			 * A set of matched elements
			 */
			else
			{
				for(var i = 0; i < matchedObjects.length; i++) 
				{					
					$.fn.tightbox.internalOptions.images.push(
						new Array(
							matchedObjects[i].getAttribute('href'),
							matchedObjects[i].getAttribute('title')
						)
					);
				}
			}
			 
			/**
			 * Set the clicked image as active
			 */
			while($.fn.tightbox.internalOptions.images[$.fn.tightbox.internalOptions.currentImage][0] != clickedObject.getAttribute('href')) 
			{
				$.fn.tightbox.internalOptions.currentImage++;
			}
		 
			 $.fn.tightbox.appendTightboxMarkup(function()
			{
				/**
				 * Register close event to the image
				 */
				$(settings.selectors.image).die('click').live('click', __closeTightbox);
				
				/**
				 * View image
				 */
				__setImage();
				__updateActiveThumb();
				 
				/**
				 * Thumbnail navigation
				 */
				var thumbnails = $(settings.selectors.thumbnails + ' li');
				if(thumbnails.length > 0 && settings.enableThumbnails)
				{
					thumbnails.each(function(index)
					{
						$(this).click(function()
						{
							__showImageByIndex(index);
						});
					});
				}
			});
		}
		
		/**
		 * Parse the query string and return an
		 * object containing the data.
		 * 
		 * @param string url
		 * @return object
		 */
		function __parseQueryString(url)
		{
			var parts = url.split("?");				
			if(parts[1])
			{
				var queryString = parts[1];
			}
			else
			{
				var queryString = url;
			}
			
			var pairs  = queryString.split("&");
			var result = {}
			
			for(i in pairs)
			{
				var pair = pairs[i].split("=");
				result[pair[0]] = pair[1];
			}
			
			return result;
		}
		 
		/**
		 * Update the currently active thumb
		 * 
		 * @param index
		 * @return void
		 */
		function __updateActiveThumb()
		{
			var thumbnails = $(settings.selectors.thumbnails + ' li');
			if(thumbnails.length > 0 && settings.enableThumbnails)
			{
				thumbnails.each(function(index)
				{
					$(this).removeClass('active-image');
					if(index == $.fn.tightbox.internalOptions.currentImage)
					{
						$(this).addClass('active-image');
					}
				});
			}
		}
				
		/**
		 * Handle click event
		 */
		return this.unbind('click').click(__initTightbox);
	}	
})(jQuery); 