/*! sanebox lightbox plugin
* Version: 0.76.1
* Author: Dave Short (http://daveshort.org) (dave at daveshort dot org)
*/

(function($){
	// grab global variables
	// Get the path to the sanebox script. This allows us to know
	// the path to necessary resources (flash player, swf object, icons)
	var saneboxScripts = document.getElementsByTagName('script');
	var saneboxPath = saneboxScripts[saneboxScripts.length-1].getAttribute("src");

	// add sanebox to jQuery plugins
	$.fn.sanebox = function(method, options) 
	{
		// handle case where no method is called
		if ((!options) && (typeof(method) == "object" || !method))
		{
			options = method;
			method = "init";
		}
				
		// get a handle so we can always refer to 'this' correctly, even inside closures
		var sanebox = this;
		
		// set up plugin global variables, fi they don't already exist
		if (!$.sanebox)
		{
			$.sanebox = {};
			$.sanebox.groups = {};
			$.sanebox.currentGroup = null;
			$.sanebox.current = null;
			$.sanebox.defaultVideoWidth = 400;
			$.sanebox.defaultVideoHeight = 300;
			$.sanebox.idCount = 0;
			$.sanebox.allItems = {};
		}
		
		// extensions for different filetypes
		$.sanebox.fileTypes = {
			"image" : ["gif", "png", "jpg", "jpeg"],
			"flash" : ["flv"],
			"video" : ["ogg", "ogv", "mp4", "webm"]
		}
		
		// save the path to this script file
		this.scriptPath = saneboxPath;	
		// save the directory this script file resides in
		//this.scriptDirectory = sanebox.methods.getFileDirectory(this.scriptPath);
		var dir = this.scriptPath.split('/');
		dir.pop();
		dir = dir.join('/') + '/';
		this.scriptDirectory = dir;
		
		// import swfobject script for video, if not already done
		if (!$.sanebox.swfImported)
		{
			$.getScript(this.scriptDirectory + 'swfobject.js');
			$.sanebox.swfImported = true;
		}
		
		// add the markup to the current page for the sanebox stylesheet
		// we have to use prepend because the html document is still loading
		// (and since the <script> tag is executing, we're in the middle of the
		// <head>. Append() doesn't work since the <head> tag hasn't been closed
		// yet
		if (!$.sanebox.cssImported)
		{
			$("head").prepend("<link rel=\"stylesheet\" type=\"text/css\" href=\"" + this.scriptDirectory + "sanebox.css\" />");
			$.sanebox.cssImported = true;
		}
		
		
		// set up default options, if they're not already set
		if (!$.sanebox.defaults)
		{	
			$.sanebox.defaults = {
				// sanebox presentation
				'caption' : 'alt',
				'consumeCaptions' : true,
				'title' : 'title',
				'consumeTitles' : true,
				'closebox' : true,
				'group' : false,
				
				// image-related
				'autoshrink' : true,
				'altRelRequired' : true,
				
				// video-related
				'autostart' : false,
				'controls' : true,
				
				// callbacks
				'open' : false,
				'load' : false,
				'unload' : false,
				'close' : false
			}
		}
				
		// define a custom class that represents a group of gallery items
		if (!$.sanebox.GalleryGroup)
		{
			// GalleryGroup class
			$.sanebox.GalleryGroup = function (groupName) 
			{
				// the name of this group
				this.groupName = groupName;
				// the total number of media items in this group
				this.total = 0;
				// the current item
				this.current = 1;
				// all items in this group
				// we use an object instead of an array because it is 1-based, not 0-based
				this.galleryItems = {};
				
				this.addItem = function (item, options)
				{
					var group = this;
					this.total++;
					var link = $("a:first", item.element);
					if (link.size() == 0 && item.element.tagName.toLowerCase() == "a")
						link = $(item.element);
					this.galleryItems[this.current] = item;
					item.position = this.current;
					this.current++;
					
					if (options['defaultGroup'])
						$(item.link).data('sanebox.groupToOpen', options['defaultGroup']);
					else
						$(item.link).data('sanebox.groupToOpen', groupName);
					
					item.group = this;
					
				};
				
				// Removes an item from this group.
				this.removeItem = function (item)
				{
					var found = false;
					var galleryItem = null;
					if (typeof(item) == "string" && item in this.galleryItems)
					{
						galleryItem = this.galleryItems[item];
						delete(this.galleryItems[item]);
						found = true;
					}
					else
					{
						for (var i in this.galleryItems)
						{
							if (item == this.galleryItems[i])
							{
								galleryItem = this.galleryItems[i];
								found = i;
								break;
							}
						}
						if (found !== false)
						{
							delete(this.galleryItems[i]);
						}
					}
					if (found)
					{
						this.current = 1;
						this.total--;
						// reassign positions for each item in this group
						var newItems = [];
						for (var i in this.galleryItems)
						{
							this.galleryItems[i].position = this.current;
							newItems[this.current] = this.galleryItems[i];
							this.current++;
						}
						this.galleryItems = newItems;
						// delete item from 'all items' global
						delete($.sanebox.allItems[galleryItem.id]);
					}
					return false;
				};
				
				this.cancel = function ()
				{
					for (var i in this.galleryItems)
					{
						var galleryItem = this.galleryItems[i];
						$(galleryItem.link).unbind("click.sanebox");
						if ($.sanebox.allItems[galleryItem.id])
							delete($.sanebox.allItems[galleryItem.id]);
					}
				}
				
				this.getItem = function (index)
				{
					// put into range
					// could be done with MODs, but I feel this code is easier to read
					if (index < 1)
					{
						while (index <= 0)
							index += this.total;
					}
					while (index > this.total)
						index -= this.total;
					return this.galleryItems[index];
				}
								
				return this; 
			} // end GalleryGroup class definition
		} // end if GalleryGroup class not created
		
		// GalleryItem Class
		// Represents a gallery item (in a group) that can be called up and displayed as a Sanebox.
		if (!$.sanebox.GalleryItem)
		{
			$.sanebox.GalleryItem = function (item, options)
			{
				this.item = item;
				this.options = null;
				this.title = null;
				this.caption = null;
				this.group = null;
								
				this.element = null;
				this.position = null;
				
				this.afterPresent = null;
				
				var o = $.extend({}, options);
				var groupKey = false;
				if (o['group'])
					groupKey = o['group'];
				
				this.isLink = false;
				this.link = null;
				
				this.id = ($(this.item).attr('id') ? $(this.item).attr('id') : 'sanebox-' + ($.sanebox.idCount++));
				$.sanebox.allItems[this.id] = this;
								
				var galleryItem = this;
								
				// get the first link inside the element
				var firstLink = $("a:first", $(item));
				// done on an individual a
				if (firstLink.size() == 0 && item.tagName.toLowerCase() == "a")
				{
					firstLink = $(item);
					o['_isLink'] = true;
					this.isLink = true;
				}
				this.element = item;
				this.link = firstLink.get(0);
				
				// if the element has a 'rel' attribute, then parse it to use as an
				// object's overriding parameters
				var elementParams = sanebox.methods.splitElementParameters($(this.link).attr('rel'));
				this.elementParams = elementParams;
				//$(firstLink).data('sanebox.elementParams', elementParams);
				
				// add any rel-based attributes to the options passed to the init function
				$.extend(o, elementParams);
				this.options = o;
								
				// set up the default group that is shown when an item is clicked
				// first we see if a default group is explicitly defined
				// then we see if the element is part of a group
				// this his the side effect that if a gallery item is added to more than one gallery,
				// then clicking it will bring it up as part of the first gallery it was assigned to
				// unless a default group is explicitly assigned
				if (elementParams['defaultGroup'])
					$(firstLink).data('defaultGroup', elementParams['defaultGroup']);
				else if ('defaultGroup' in o && !$(firstLink).data('defaultGroup'))
					$(firstLink).data('defaultGroup', o['defaultGroup']);
				else if (o['group'])
					$(firstLink).data('defaultGroup', o['group']);
				else
					$(firstLink).data('defaultGroup', false);
				
				// save options and this object to item's data
				if (firstLink.data('sanebox.groupAssociations'))
				{
					var d = firstLink.data('sanebox.groupAssociations');
					d[groupKey] = this;
					firstLink.data('sanebox.groupAssociations', d);
				}
				else
				{
					var d = {};
					d[groupKey] = this;
					firstLink.data('sanebox.groupAssociations', d);
				}
								
				var frame = $("<div class=\"sanebox-window\" />");
				var content = $("<div class=\"sanebox-content\" />");
				this.frame = frame;
				
				
				// get the title, save it to the item
				if (o['title'] == 'link')
				{
					this.title = $(this.link).attr('title');
					if (!this.title)
						this.title = "&nbsp;"
				}
				else if (o['title'] == 'title')
				{
					this.title = $(this.element).attr('title');
					if (!this.title)
						this.title = "&nbsp;";
				}
				else if (sanebox.methods.getSelectorString(o['title']))
				{
					var selector = sanebox.methods.getSelectorString(o['title']);
					var element = $(selector, this.element);
					if (element.size() == 0)
						this.title = "&nbsp;";
					else 
					{
						element = element.get(0);
						this.title = $(element).html();
					}
				}
				else if (o['title'] == 'alt') // image alt
				{
					this.title = $("img:first", this.element).attr('alt');
					if (this.title == '')
						this.title = '&nbsp;'
				}
				else if (o['title'] != 'none') // string literal
				{
					this.title = o['title'];
				}
				else
				{
					this.title = null;
				} // end title getting
								
				// get the caption, if necessary
				if (o['caption'] == 'next')
				{
					this.caption = $(this.link).next().html();
					if (options['consumeCaptions'])
						$(this.link).next().hide();
				}
				else if (o['caption'] == 'title')
				{
					this.caption = $(this.element).attr('title');
				}
				else if (o['caption'] == 'link')
				{
					this.caption = $(this.link).attr('title');
				}
				else if (o['caption'] == 'alt')
				{
					this.caption = $("img:first", this.link).attr('alt');
				}
				else if (sanebox.methods.getSelectorString(o['caption']))
				{
					var selector = sanebox.methods.getSelectorString(o['caption']);
					var element = $(selector, this.element);
					if (element.size() == 0)
						this.title = "&nbsp;";
					else 
					{
						element = element.get(0);
						this.caption = $(element).html();
						if (options['consumeCaptions'])
							$(element).hide();
					}
				} // end caption getting
				
				// set up media type
				this.media = {};
				this.media.url = $(this.link).attr('href');
				this.media.extension = this.media.url.split('.').pop();
	
				
				// determine which type of media this item is (from its extension)
				for (var type in $.sanebox.fileTypes)
					if ($.inArray(this.media.extension, $.sanebox.fileTypes[type]) != -1)
						this.media.type = type;
				if (!type in this.media)
					this.media.type = 'unknown';
					
				this.flashAfter = function ()
				{
					var playerPath = sanebox.scriptDirectory + 'player.swf';
					swfobject.embedSWF(playerPath,'ph' + this.randomID, this.media.width, this.media.height, '9.0.115', false,
						this.media.flashvars, this.media.params, this.media.attributes, function(e)
						{
							if (!e.success)
							{
								$("#ph" + this.randomID).text("There was an error loading the Flash video.");
								var bu = '<embed flashvars="file=' + this.media.url + '&autostart=' + galleryItem.options['autostart'] + '&controlbar=' + (galleryItem.options['controls'] ? 'bottom' : 'none') + '" allowfullscreen="true" allowscripaccess="always" id="saneboxFlash' + this.randomID + '" name="saneboxFlash' + this.randomID + '" src="' + playerPath + '" width="' + this.media.width + '" height="' + this.media.height + '" />';
								$("#ph" + this.randomID).html(bu);
							} // end if flash didn't load
						} // end post-swfobject callback
					);
				};
				
				this.videoAfter = function ()
				{
					if (this.lastResort)
						this.lastResort();
					var content = $(".sanebox-content", this.frame).get(0);
					var html = $(content).html();
					$(content).html("");
					$(content).html(html);
				};
				
				this.setVideoDimensions = function ()
				{
					// videos need their dimensions defined in the optional parameters
					if (this.elementParams && this.elementParams.width && this.elementParams.height)
					{
						this.media.width = this.elementParams.width;
						this.media.height = this.elementParams.height;
					}
					else if (!this.elementParams)
					{
						this.media.width = $.sanebox.defaultVideoWidth;
						this.media.height = $.sanebox.defaultVideoHeight;
					}
					if (!this.media.width)
						this.media.width = $.sanebox.defaultVideoWidth;
					if (!this.media.height)
						this.media.height = $.sanebox.defaultVideoHeight;
				}; // end setVideoDimensions
					
				// gather alternatives if this is an image
				if (this.media.type == "image")
				{
									
					if (this.elementParams['width'] || this.elementParams['height'])
						this.media.explicitSize = true;
					else this.media.explicitSize = false;
					
					this.media.alternatives = [];
					var alternatives;
					if (this.options.altRelRequired)
						alternatives = $(this.link).siblings("a[rel=alternative]");
					else alternatives = $(this.link).siblings("a");
					
					// we add one for the original image, which is not a sibling of itself
					// and thus wasn't selected above
					this.media.alternativeTotal = alternatives.size() + 1;
					this.media.alternatives.push(this.link);
					this.media.currentAlternative = 0;
					alternatives.each(function () {
						galleryItem.media.alternatives.push(this);
					});
					alternatives.hide();
				}
				else if (this.media.type == "video")
				{
					this.setVideoDimensions();
					
					this.afterPresent = this.videoAfter;
					// processing code here
					// if this is an HTML 5 video, see what alternative sources are available
					var alternatives = $(this.link).siblings("a");
					// set up list of sources
					this.media.sources = [
						sanebox.methods.buildSource(this.media.url)
					];
				
					// backup items for falling back to Flash video if all the sources
					// are not supported by the browser
					this.media.backup = null;
					this.media.backupFLV = null;
					
					// loop through each alternative source
					$(alternatives).each(function () 
					{
						// get an rel attributes of this alternative source
						var params = sanebox.methods.splitElementParameters($(this).attr('rel'));
						params['src'] = $(this).attr('href');
						var ext = params['src'].split('.').pop().toLowerCase();
						
						// if it's a link to an .flv file, then set up the backup object
						if (ext == 'flv')
						{
							// create the backup flv <embed> element
							// using an <embed> tag inside the <video> tag allows fallback
							// if <video> is not supported
							galleryItem.media.backup = $("<embed style=\"width: " + galleryItem.media.width +"px;height: " + galleryItem.media.height + "px;\"></embed>");
							var rand = Math.floor(Math.random() * 1e15);
							var flashvars = "file=" + params['src'] + "&autostart=" + galleryItem.options['autostart'] +
							'&controlbar=' + (galleryItem.options['controls'] ? 'bottom' : 'none');
							backupFLV = params['src'];
							$(galleryItem.media.backup).attr({
								'flashvars' : flashvars,
								'allowfullscreen' : 'true',
								'allowscriptaccess' : 'always',
								'id' : 'backup' + rand,
								'name' : 'backup' + rand,
								'src' : sanebox.scriptDirectory + 'player.swf',
								'width' : galleryItem.media.width,
								'height' : galleryItem.media.height
							});
							$(this).hide();
						}
						else // the alternative is an html5-format video
						{
							galleryItem.media.sources.push(sanebox.methods.buildSource(params['src'], params));
							//sources.push(source);
							$(this).hide();
						}
					}); // end loop over alternative sources
				}
				else if (this.media.type == "flash")
				{
					this.setVideoDimensions();
					this.afterPresent = this.flashAfter;
				}
				
				// Close the sanebox
				this.close = function ()
				{
					// try to stop any video content
					// Firefox will not stop auto-playing videos when they're removed from the DOM
					$(".sanebox-content").find("*").each(function () {
						try
						{
							if (this.tagName.toLowerCase() == "video")
							{
								if (!this.paused)
									this.pause();
							}
						}
						catch (e) {}
					}); // end pause video function
					
					// remove sanebox key handlers
					$(document).unbind("keydown.sanebox");
					
					// handle unload and close callbacks
					if (this.options['unload'] && typeof(this.options['unload']) == 'function')
					{
						this.options['unload'].call(this, this.group);
					}
					if (this.options['close'] && typeof(this.options['close']) == 'function')
					{
						this.options['close'].call(this, this.link, this.group);
					}
					
					// unset data pointing to currently-open sanebox (since now NONE is open)
					$.sanebox.current = null;
					
					// fade out frame and remove items
					$(".sanebox-overlay").fadeOut(function () {
						$(".sanebox-overlay").remove();
						$(".sanebox-window").remove();
					});
				}; // end close method
				
				this.buildTitlebar = function ()
				{
					// create the tag and add it to the frame
					var titlebar = $("<div class=\"title\" />");
					var titleP = $("<p />");
					$(titleP).append(this.title);
					$(titlebar).append(titleP);
					
					// if the options specify that there should be a close box, create and
					// add it to the title bar
					if (this.options['closebox'])
					{
						var cb = $("<img src=\"" + sanebox.scriptDirectory + "cancel.png\" class=\"closebox\" />");
						$(titlebar).prepend(cb);
						$(cb).bind('click.sanebox', function () {
							galleryItem.close();
						});
					} // end if there's a close box
					
					return titlebar;
				}; // end buildTitlebar
				
				this.buildNavigation = function ()
				{
					// create the navigation div
					var navDiv = $("<div class=\"navigation\" />");
					$(navDiv).text("Item " + this.position + " of " + this.group.total);
					
					var prevNav = new Image();
					$(prevNav).attr('src', sanebox.scriptDirectory + 'resultset_previous.png').addClass('navButton');
					$(prevNav).click(function () {
						galleryItem.changeSanebox.call(galleryItem, -1);
					});
					$(navDiv).prepend(prevNav);
					
					// set up next / prev buttons
					var nextNav = new Image();
					$(nextNav).attr('src', sanebox.scriptDirectory + 'resultset_next.png').addClass('navButton');
					$(nextNav).click(function () {
						galleryItem.changeSanebox.call(galleryItem, 1);
					});
					$(navDiv).append(nextNav);
					
					return navDiv;
				}; // end buildNavigation
				
				// Builds the caption box and fills it with this item's caption
				this.buildCaption = function ()
				{
					var captionBox = $("<div class=\"caption\" />");
					$(captionBox).html(this.caption);
					return captionBox;
				}; // end buildCaption
				
				this.buildFrame = function (content) {
					var frame = $("<div class=\"sanebox-window\" />");
					
					this.frameID = "sanebox-frame" + this.randomID;
					$(frame).attr('id', this.frameID);
					
					$(frame).append(this.titlebar);
					
					// if the item has a title assigned to it, create the title object
					if (this.title !== null)
					{
						var titlebar = this.buildTitlebar();
						$(frame).append(titlebar);
					} // end if item has a title
					
					// add content to the frame
					$(frame).append(content);
					
					// set up navigation, if item is part of a gallery
					if (this.group && this.group.total > 1 && this.position > 0)
					{
						var navDiv = this.buildNavigation();
						$(frame).append(navDiv);
					} // end if we're viewing a gallery
					
					$(frame).append(this.buildCaption());
					
					return frame;
				}
				
				this.presentFrame = function (frame)
				{
					// create overlay and window, if not already there
					if ($(".sanebox-overlay").size() == 0)
					{
						var overlay = $("<div class=\"sanebox-overlay\" />");
						$(overlay).click(function () {
							galleryItem.close();
						});
						if (this.options.open && typeof(this.options.open) == "function")
							this.options.open.call(galleryItem, this, group);
						$("body").append(overlay);
					}
					
					// size the frame horizontally, and move it off-screen
					// this allows us to add all the items to the frame while
					// it's on-screen. Then we can get its width and height.
					// moving it off-screen ensures that there's no 'flash' of
					// the frame before it's shown
					$(frame).css('width', this.media.width + 'px');
					$(frame).css('left', '-10000px');
					$(frame).css('top', '-10000px');
					
					// hide and remove any other sanebox windows
					$(".sanebox-window").each(function () {
						$(this).slideUp(function () {
							$(this).remove();
						});
					});
					
					// Add frame to the document. Now its dimensions can be computed.
					$("body").append(frame);
					
					// hide and center the frame
					$(frame).hide();
					sanebox.methods.centerFrame(frame, $(frame).height(), parseInt(this.media.width));
					
					// save data about currently open sanebox for posterity
					$.sanebox.current = {
						'frame' : frame,
						'item' : this
					}
					
					// update gallery item with current frame
					this.frame = frame;
					
					// now show it
					$(frame).slideDown(function ()
					{
						// run the 'after present' function, necessary for some browsers to
						// properly display the content (see each function for more info)
						if (galleryItem.afterPresent)
							galleryItem.afterPresent.call(galleryItem);
						
						// setup key handlers
						$(document).unbind("keydown.sanebox");
						$(document).bind("keydown.sanebox", function (event)
						{
							switch (event.which)
							{
								case 27: // escape
									galleryItem.close();
									return false;
								case 37: // left arrow, 'prev'
									galleryItem.changeSanebox(-1);
									return false;
								case 39: // right arrow, 'next'
									galleryItem.changeSanebox.call(galleryItem, 1);
									return false;
							} // end switch over event key
						}); // end binding sanebox keydown to document
						
						// run the 'load' handler, if it's defined
						if (galleryItem.options['load'] && typeof(galleryItem.options['load']) == 'function')
							galleryItem.options['load'].call(galleryItem, galleryItem.link, galleryItem.group);
						
					}); // end 'after effect' callback
				} // end presentFrame
				
				// switches the sanebox (which is a member of a gallery) to show the
				// specified item in the gallery (relative to the current one)
				this.changeSanebox = function (amount)
				{
					if (this.options.unload && typeof(this.options.unload) == "function")
						this.options.unload.call(this, this.frame, this.link);
					sanebox.methods.showSanebox(this.group.getItem(this.position + amount));
				} // end changeSanebox
				
				// callback for when an image is clicked and should be loaded
				this.clickImage = function () {
					var galleryItem = this;
					galleryItem.media.image = new Image();
					// we have to wait until the image is loaded to know its dimensions
					$(this.media.image).load(function () {
						galleryItem.media.width = galleryItem.media.image.width;
						galleryItem.media.height = galleryItem.media.image.height;
						// actually displaying the item is deferred until the image is pre-loaded
						var content = galleryItem.buildContent.call(galleryItem);
						var frame = galleryItem.buildFrame.call(galleryItem, content);
						galleryItem.presentFrame(frame);
					});
					// triggers a load of the image, allows the load() event to fire even
					// if the image is cached
					this.media.image.src = "";
					this.media.image.src = this.media.url;
				} // end clickImage
								
				// callback for when a video link is clicked and should be loaded
				this.clickVideo = function () {
				
					var content = galleryItem.buildContent.call(galleryItem);
					var frame = galleryItem.buildFrame.call(galleryItem, content);
					galleryItem.presentFrame(frame);
				} // end clickVideo
				
				// builds the content that goes
				this.buildContent = function ()
				{					
					var galleryItem = this;
					// generate a random DOM id for the item
					var rand = Math.floor(Math.random() * 1e15);
					this.randomID = rand;
					var id = 'saneboxMedia' + rand;
					
					var content = $("<div class=\"sanebox-content\" />");
					
					// display the media
					if (this.media.type == 'image')
					{
						var image = this.media.image;
						$(image).remove();
						$(image).attr('id', id);
												
						// if a width and height are specified for the image, then midSize will be
						// set to true. Assume it's false for now
						var midSize = false;
						
						// assign a width or height, if they're specified in the element's parameters (the rel tag)
						if (this.elementParams)
						{
							if ('width' in this.elementParams && this.elementParams['width'])
							{
								$(image).css('width', this.elementParams['width'] + 'px');
								midSize = true;
							}
							if ('height' in this.elementParams && this.elementParams['height'])
							{
								$(image).css('height', this.elementParams['height'] + 'px');
								midSize = true;
							}
						}
												
						// see if we should autoshrink it
						if (!this.media.explicitSize && this.options['autoshrink'])
						{
							var width = this.media.width;
							var height = this.media.height;
							var windowWidth = $(window).width();
							var windowHeight = $(window).height();
							if (width > windowWidth || height > windowHeight)
							{			
								var scaledWidth, scaledHeight, scale;
								if (width / height > windowWidth / windowHeight)
								{
									scale = windowWidth / width;
									scaledWidth = 0.95 * windowWidth;
									scaledHeight = 0.95 * height * scale;
								}
								else
								{
									scale = windowHeight / height;
									scaledWidth = 0.9 * width * scale;
									scaledHeight = 0.9 * windowHeight;
								}
								scaledWidth = Math.floor(scaledWidth);
								scaledHeight = Math.floor(scaledHeight);
								$(image).css('width', scaledWidth + 'px');
								$(image).css('height', scaledHeight + 'px');
								this.elementParams.width = scaledWidth;
								this.elementParams.height = scaledHeight;
								midSize = true;
							}
						}
						
						$(content).append(this.media.image);
						
						// if it is a midsize image, shrink it and add the appropriate handlers
						// so that user can click it to enlarge
						if (midSize)
						{
							// resize the frame horizontally
							if (this.elementParams && this.elementParams['width'])
							{
								this.media.fullWidth = this.media.width;
								this.media.width = this.elementParams['width'];
								//$(this.frame).css('width', this.elementParams['width'] + 'px');
							}
							
							// add click handler and GUI hints that image can be further enlarged
							$(this.media.image).attr('title', 'Click to see the image at full size.');
							$(this.media.image).addClass('sanebox-zoom');
							$(this.media.image).bind("click.sanebox", function () {
								$(galleryItem.media.image).css({'width' : 'auto', 'height' : 'auto'});
								$(galleryItem.media.image).css({'width' : 'auto', 'height' : 'auto'});
								$(galleryItem.media.image).removeClass('sanebox-zoom');
								$(galleryItem.media.image).unbind("click.sanebox");
								$(galleryItem.media.image).bind("click.sanebox", function ()
								{
									galleryItem.close();
								});
								$("#" + galleryItem.frameID).width(galleryItem.media.fullWidth);
								sanebox.methods.centerFrame(null, $(galleryItem.frame).height(), galleryItem.media.fullWidth);
							});
						}
						// otherwise, make it so clicking the image closes the sanebox
						else
						{						
							if (this.media.alternatives && this.media.alternatives.length > 1)
							{
								this.media.currentAlternative = 0;
								var clickAlternative = function ()
								{
									
									galleryItem.media.currentAlternative = (galleryItem.media.currentAlternative + 1) % galleryItem.media.alternativeTotal;
									var next = galleryItem.media.alternatives[galleryItem.media.currentAlternative];
									var nextImage = $(next).attr('href');
									var toLoad = new Image();
									$(toLoad).load(function () {
										$(galleryItem.media.image).attr('src', nextImage);
										var newWidth = this.width;
										sanebox.methods.centerFrame(null, $(galleryItem.frame).height(), newWidth);
										var additionalText = $(next).text();
										$(".title p", this.frame).empty().append(galleryItem.title).append(" ").append(additionalText);
									});
									$(toLoad).attr('src', nextImage);
								}
								$(this.media.image).click(clickAlternative);
								$(this.media.image).addClass('sanebox-zoom');
								
							}
							else
							{
								$(this.media.image).bind("click.sanebox", function () {
									galleryItem.close();
									//sanebox.methods.closeGallery(galleryItem.options);
								 });
							}
						}
					} // end if item is an image
					else if (this.media.type == 'flash')
					{
						// set up variables for the JW Player object
						var flashvars = {
							'file' : this.media.url,
							'autostart' : this.options.autostart,
							'controlbar' : (this.options['controls'] ? 'bottom' : 'none')
						};
						
						// set up parameters for SWFObject
						var params = {
							'allowfullscreen' : 'true', 
							'allowscriptaccess' : 'always'
						};
						
						// set up HTML element attributes of created Flash object
						var attributes = {
							'id' : id,
							'name' : id

						};
						
						this.media.flashvars = flashvars;
						this.media.params = params;
						this.media.attributes = attributes;
						
						// set up placeholder div, which will be replaced by video when frame
						// is built and positioned
						var placeholder = $("<p>Please install Flash to see video.</p>");
						$(placeholder).attr('id', 'ph' + rand);
						$(placeholder).css({'width' : this.media.width + 'px', 'height' : this.media.height + 'px'});
						$(content).append(placeholder);
						
						var playerPath = sanebox.scriptDirectory + 'player.swf';
						$(placeholder).append(playerPath);
		
					}
					else if (this.media.type == 'video') // html5 video
					{	
						// set up a placeholder element
						var placeHolder = $("<div />", {
						});
						$(placeHolder).css({
							'width' : this.media.width + 'px',
							'height' : this.media.height + 'px'
						});
						
						var player;
						var videoID = "sanebox-video-" + Math.floor(Math.random() * 1e12);
						// track whether or not we s uccesfully added the fallback <embed> element
						var backupSuccess = true;
					
					
						if (this.media.backup)
						{
							// IE and jQuery have a problem adding items to a <video> element...
							// so we need to add the fallback data as a string
							var wrapper = $("<div />");
							$(wrapper).append(this.media.backup);
							var wrapperHTML = $(wrapper).html();
							
							// create the player
							var playerHTML = '<video id=\"' + videoID + '\" width="' + this.media.width + '" height="' + this.media.height + (this.options.autostart ? '" autoplay="autoplay"' : '') + '" controls="controls"></video>';
											
							player = $(playerHTML);
							try
							{
								$(player).append(this.media.backup);
							}
							catch (e)
							{
								backupSuccess = false;
							}
						}
						else
						{
							player = $('<video id=\"' + videoID + '\" width=' + this.media.width + '" height="' + this.media.height + '" ' +
									   (this.options.autostart ? 'autoplay="autoplay" ' : '') + '" ' +
										'controls="controls">You must have a browser capable of playing HTML5 video to see this video.</video>');
						}
						
						// now add any alternate source to the video
						for (var idx in this.media.sources)
						{
							try
							{
								$(player).append(this.media.sources[idx]);
							}
							catch (e)
							{
							}
						}
											
						$(placeHolder).append(player);
						$(content).append(placeHolder);
												
						// if there is a backup and adding it failed, then we go to the last resort
						// which is to create a SWF objeect
						if (this.media.backup && !backupSuccess)
						{
							var playerPath = sanebox.scriptDirectory + 'player.swf';
							var rand = Math.floor(Math.random() * 1e15);
							var id ='sanebox.backup-' + rand;
							
							var flashvars = {
								'file' : backupFLV,
								'autostart' : this.options.autostart
							};
						
							// set up parameters for SWFObject
							var params = {
								'allowfullscreen' : 'true', 
								'allowscriptaccess' : 'always'
							};
						
							// set up HTML element attributes of created Flash object
							var attributes = {
								'id' : id,
								'name' : id
							};
							
							var lastResort = $("<div />", {
								id : videoID + 'lr'
							});
							$(lastResort).width(this.media.width);
							$(lastResort).height(this.media.height);
							$(placeHolder).before(lastResort);
							$(placeHolder).remove();
							galleryItem.lastResort = function () {
								swfobject.embedSWF(playerPath, videoID + 'lr', this.media.width, this.media.height, '9.0.115', false,
											flashvars, params, attributes);
							};
						}
					} // end if it's an html5 video
					
					return content;
				}
				
				switch (this.media.type)
				{
					case 'image':
						this.loader = this.clickImage;
					break;
					case 'flash':
					case 'video':
						this.loader = this.clickVideo;
					break;
				}
				
				
				this.clickSanebox = function ()
				{
					var galleryItem = sanebox.methods.getGalleryItemForElement(this);
					sanebox.methods.showSanebox(galleryItem);
					/*
					var groupName = $(this).data('sanebox.groupToOpen');
					var group = $.sanebox.groups[groupName];
					
					var groupAssociations = $(this).data('sanebox.groupAssociations');
					var galleryItem;
					if (groupName in groupAssociations)
					{
						galleryItem = groupAssociations[groupName];
						if (galleryItem.options.open && typeof(galleryItem.options.open) == "function")
							galleryItem.options.open.call(galleryItem, this, group);
						sanebox.methods.showSanebox(galleryItem);
					}
					*/
					return false;
				}
				
				// unbind any other sanebox click handlers, then add this one
				$(firstLink).unbind("click.sanebox");
				$(firstLink).bind("click.sanebox", this.clickSanebox);
				
				
			};
		}
		
		// set up plugin methods
		this.methods = {
						
			// Gets the directory of the given file
			getFileDirectory : function (path)
			{
				var dir = path.split('/');
				dir.pop();
				dir = dir.join('/') + '/';
				return dir;
			}, // end getFileDirectory ()
			
			
			
			// Given a GalleryItem object, shows it in a new Sanebox
			showSanebox : function (galleryItem)
			{
				if (galleryItem.group)
					$.sanebox.currentGroup = galleryItem.group;
				$.sanebox.currentItem = galleryItem;	
				galleryItem.loader.call(galleryItem);
			},
			
			// centers the given sanebox in the browser window
			// param frame: The frame to center in the window
			// param calculatedHeight: The calculated height of the frame
			centerFrame : function (frame, calculatedHeight, desiredWidth)
			{
				if (!frame)
				{
					frame = $(".sanebox-window").get(0);
				}
				
				// correct width of all items
				
				$(frame).children().each(function () {
					sanebox.methods.setWidth(this, desiredWidth);
				});
				
				$(frame).width(desiredWidth);

				
				
				// find the center of the window first
				// take into account the current scroll position
				var left = $(window).scrollLeft() + Math.floor($(window).width() / 2);
				var top = $(window).scrollTop() + Math.floor($(window).height() / 2);
				
				// now adjust the position to take into account the size of the frame
				// otherwise the frame would appear with its top left corner in the center
				// of the window
				left -= Math.floor($(frame).width() / 2);
				top -= Math.floor($(frame).height() / 2);
				
				// if we calculate negative positions, adjust 
				if (!left)
					left = 0;
				if (!top)
					top = 0;
					
				if (left < 0)
					left = 0;
				if (top < 0)
					top = 0;
	
				// move the window where it needs to go
				$(frame).css({
					'position' : 'absolute',
					'left' : left + 'px',
					'top' : top + 'px',
					'overflow' : 'hidden'
				});
				
			}, // end centerFrame ()
			
			// Builds a <source> element (for HTML 5 video) from a given URL
			// and array of attributes.
			// param source: The URL of the video stream.
			// param params: A map of attributes for the source.
			buildSource : function (source, params)
			{
				if (!params)
					params = {};
				if ('type' in params && 'codecs' in params)
				{
					html = "<source src=\"" + source + "\" type='" + params['type'] +
					"; codecs=\"" + params['codecs'] + "\"' />";
				}
				else if ('type' in params)
				{
					html = "<source src=\"" + source + "\" type=\"" + params['type'] + "\" />";
				}
				else
				{
					html = "<source src=\"" + source + "\" />";
				}

				// create the source attribute and add it to the list, then
				// remove it from the page
				return $(html);
			},
			
			// Sets an item's absolute width
			// margin + border-width + padding + width
			setWidth : function (item, desiredWidth)
			{
				var leftPadding = parseInt($(item).css('padding-left'));
				if (!leftPadding)
					leftPadding = 0;
				var rightPadding = parseInt($(item).css('padding-right'));
				if (!rightPadding)
					rightPadding = 0;
				var leftMargin = parseInt($(item).css('margin-left'));
				if (!leftMargin)
					leftMargin = 0;
				var rightMargin = parseInt($(item).css('margin-right'));
				if (!rightMargin)
					rightMargin = 0;
				var leftBorder = parseInt($(item).css('borderLeftWidth'));
				if (!leftBorder)
					leftBorder = 0;
				var rightBorder = parseInt($(item).css('borderRightWidth'));
				if (!rightBorder)
					rightBorder = 0;
					
				// webkit bugfix
				// webkit will return the offset of the right sides of the item and its
				// parent if margin-right is set in a SS. This code corrects for that.
				var pa1 = parseInt($(item).css('margin-right'));
				if (!pa1) pa1 = 0;
				var pa2 = parseInt($(item).parent().css('margin-right'));
				if (!pa2) pa2 = 0;
				if (pa1 - pa2 == rightMargin)
					rightMargin = 0;
				
				var total = leftPadding + rightPadding + leftMargin + rightMargin + leftBorder + rightBorder;
				$(item).width(desiredWidth - total);
			},
			
			// Closes the gallery and hides the overlay
			closeGallery : function (options)
			{
				$(document).unbind("keydown.sanebox");
				
				// handle unload and close callbacks
				if (options['unload'] && typeof(options['unload']) == 'function')
				{
					options['unload'].call($.sanebox.current.frame, $.sanebox.current.item, $.sanebox.currentGroup);
				}
				if (options['close'] && typeof(options['close']) == 'function')
				{
					options['close'].call($.sanebox.current.frame, $.sanebox.current.item, $.sanebox.currentGroup);
				}
				
				$.sanebox.current = null;
				
				$(".sanebox-overlay").fadeOut('fast', function () {
					$(".sanebox-overlay").remove();
					$(".sanebox-window").remove();
				});
			},
			
			// splits a parameter string.
			// param rel: The string to split.
			splitElementParameters : function (rel)
			{
				var ret = {};
				if (rel)
				{
					var paramStrs = rel.split(';');
					for (var i in paramStrs)
					{
						var paramStr = paramStrs[i];
						var param = paramStr.split('=');
						if (param.length == 2)
							ret[param[0]] = param[1];
					}
				}
				return ret;
			}, // end splitElementParameters ()
			
			// Checks whether a given string is a 'selector string.' 'Selector strings'
			// start out with a "$" and represent a special type of string that should be
			// treated like a jQuery selector.
			// param str: The string to check.
			// returns: Either the selector (with the initial "$" omitted), or boolean false
			// if the string is not a selector string.
			getSelectorString : function (str)
			{
				if (str && str.substr(0,1) == "$")
				{
					return str.substr(1);
				}
				return false;
			},
			
			// Enables sanebox on the jQuery items selected
			init : function (inOptions)
			{
				// make a copy of the default options
				var options = $.extend({}, $.sanebox.defaults);
				$.extend(options, inOptions);
								
				// set up options to be stored and associated with this group
				sanebox.options = options;
				
				var groupKey = false;
				var group;
				
				// set up gallery grouping
				if (options['group'])
				{
					groupKey = options['group'];
					// if a group name is specified
					if (options['group'] !== false)
					{
						if ($.sanebox.groups[options['group']])
						{
							group = $.sanebox.groups[options['group']];
						}
						else
						{
							group = new $.sanebox.GalleryGroup(options['group']);
							$.sanebox.groups[options['group']] = group;
						}
					}
					else
					{
						group = false;
					}
				}
				else group = false;
				
				// loop over each selected jquery element
				return sanebox.each(function() {  
					var o = $.extend({}, options);					
					var item = this;
					
					var galleryItem = new $.sanebox.GalleryItem(item, o);
					galleryItem.group = group;
														
					if (group)
					{
						group.addItem(galleryItem, o);
					}
					
					
				}); // end loop over items
				
			}, // end init ()
			
			open : function (options)
			{
				var galleryItem = sanebox.methods.getGalleryItem(options);
				sanebox.methods.showSanebox(galleryItem);
				return sanebox;
			}, // end open
			
			// Returns a GalleryItem.
			// param	options:	either a string specifying the id of a single gallery item you want,
			//					 	or false, specifying that the jQuery selector should be used
			//			multiple:	Only necessary if options is false. This specifies whether an array
			//						of all matching GalleryItems should be returned, or just the first
			//						matching element itself should be returned.
			getGalleryItem : function (options, multiple)
			{
				if (!multiple)
					multiple = false;
				
				if (options && typeof(options) == "string")
				{
					if ($.sanebox.allItems[options])
					{
						var galleryItem = $.sanebox.allItems[options];
						return galleryItem;
					}
				}
				else
				{
					if (multiple)
					{
						var ret = [];
						sanebox.each(function () {
							var galleryItem = sanebox.methods.getGalleryItemForElement(this);
							if (galleryItem)
							{
								ret.push(galleryItem);
							}
						});
						return ret;
					}
					else
					{
						var el = sanebox.get(0);
						var link;
						if (el.tagName.toLowerCase() == "a")
							link = el;
						else  link = $("a:first", el).get(0);
						
						if (link)
						{
							var galleryItem = sanebox.methods.getGalleryItemForElement(link);
							if (galleryItem)
							{
								return galleryItem;
							}
						}
					}
				}
				return null;
			}, // ene getGalleryItem
			
			getGalleryItemForElement : function (element)
			{
				var groupName = $(element).data('sanebox.groupToOpen');
				if (!groupName)
					groupName = false;
				
				var group = $.sanebox.groups[groupName];
				var groupAssociations = $(element).data('sanebox.groupAssociations');
				var galleryItem;
				if (groupName in groupAssociations)
				{
					galleryItem = groupAssociations[groupName];
					return galleryItem;
				}
				return false;
			}, // end getGalleryItemForElement
			
			
			cancel : function (options)
			{
				if (typeof(options) == "string")
				{
					var galleryItem = $.sanebox.allItems[options];
					if (galleryItem)
						galleryItem.group.removeItem(galleryItem);
				}
				else if ($.isArray(options))
				{
					for (var i in options)
					{
						var groupName = options[i];
						if (groupName in $.sanebox.groups)
						{
							$.sanebox.groups[groupName].cancel();
							delete($.sanebox.groups[groupName]);
						}
					}
				}
				else
				{
					sanebox.each(function () {
						var link;
						if (this.tagName.toLowerCase() == "a")
							link = this;
						else link = $("a:first", this).get(0);
						var assoc = $(link).data('sanebox.groupAssociations');
						if (assoc)
						{
							for (var key in assoc)
							{
								var galleryItem = assoc[key];
								galleryItem.group.removeItem(galleryItem);
							}
						}
						$(link).unbind("click.sanebox");
					});
				}
				sanebox.methods.cleanupGroups();
			},
			
			cleanupGroups : function ()
			{
				for (var i in $.sanebox.groups)
				{
					var group = $.sanebox.groups[i];
					if (group.total == 0)
						delete($.sanebox.groups[i]);
				}
			},
			
			closeCurrent : function ()
			{
				var current = $.sanebox.current;
				if (current.item)
				{
					var galleryItem = current.item;
					galleryItem.close();
				}
			}
			
		} // end methods
				
		// process the methods
		switch (method)
		{
			case "init":
				return sanebox.methods.init(options);
			case "defaults":
				$.extend($.sanebox.defaults, options);
				return sanebox;
			case "open":
				return sanebox.methods.open(options);
			case "get":
				return sanebox.methods.getGalleryItem(options, true);
			case "cancel":
				sanebox.methods.cancel(options);
				return sanebox;
			case "close":
				sanebox.methods.closeCurrent();
				return sanebox;
		} // end switch to hadle methods
		
	}; // end sanebox plugin

})(jQuery);
