/**
 * The Shadowbox class.
 * 
 * This file is part of Shadowbox.
 * 
 * Shadowbox is an online media viewer application that supports all of the
 * web's most popular media publishing formats. Shadowbox is written entirely in
 * JavaScript and CSS and is highly customizable. Using Shadowbox, website
 * authors can showcase a wide assortment of media in all major browsers without
 * navigating users away from the linking page.
 * 
 * Shadowbox is released under version 3.0 of the Creative Commons Attribution-
 * Noncommercial-Share Alike license. This means that it is absolutely free for
 * personal, noncommercial use provided that you 1) make attribution to the
 * author and 2) release any derivative work under the same or a similar
 * license.
 * 
 * If you wish to use Shadowbox for commercial purposes, licensing information
 * can be found at http://mjijackson.com/shadowbox/.
 * 
 * @author Michael J. I. Jackson <mjijackson@gmail.com>
 * @copyright 2007-2008 Michael J. I. Jackson
 * @license http://creativecommons.org/licenses/by-nc-sa/3.0/
 * @version SVN: $Id: shadowbox.js,v 1.1 2010/05/18 22:11:09 plemarch Exp $
 */

if (typeof Shadowbox == 'undefined') {
	throw 'Unable to load Shadowbox, no base library adapter found';
}

/**
 * The Shadowbox class. Used to display different media on a web page using a
 * Lightbox-like effect.
 *
 * Useful resources:
 *
 * - http://www.alistapart.com/articles/byebyeembed
 * - http://www.w3.org/TR/html401/struct/objects.html
 * - http://www.dyn-web.com/dhtml/iframes/
 * - http://www.apple.com/quicktime/player/specs.html
 * - http://www.apple.com/quicktime/tutorials/embed2.html
 * - http://www.howtocreate.co.uk/wrongWithIE/?chapter=navigator.plugins
 * - http://msdn.microsoft.com/en-us/library/ms532969.aspx
 * - http://support.microsoft.com/kb/316992
 *
 * @class       Shadowbox
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 * @singleton
 */
( function() {

	/**
	 * The current version of Shadowbox.
	 *
	 * @var         String
	 * @private
	 */
	var version = '2.0';

	/**
	 * Contains the default options for Shadowbox.
	 * 
	 * @var Object
	 * @private
	 */
	var options = {
		/**
		 * Enable all animations besides fades.
		 *
		 * @var     Boolean
		 */
		animate : true,

		/**
		 * Enable fade animations.
		 *
		 * @var     Boolean
		 */
		animateFade : true,

		/**
		 * Specifies the sequence of the height and width animations. May be
		 * 'wh' (width then height), 'hw' (height then width), or 'sync' (both
		 * at the same time). Of course this will only work if animate is true.
		 *
		 * @var     String
		 */
		animSequence : 'wh',

		/**
		 * The path to flvplayer.swf.
		 *
		 * @var     String
		 */
		flvPlayer : 'js/shadowbox/flvplayer.swf',

		/**
		 * Listen to the overlay for clicks. If the user clicks the overlay,
		 * it will trigger Shadowbox.close().
		 *
		 * @var     Boolean
		 */
		modal : false,

		/**
		 * The color to use for the modal overlay (in hex).
		 *
		 * @var     String
		 */
		overlayColor : '#142952',

		/**
		 * The opacity to use for the modal overlay.
		 *
		 * @var     Number
		 */
		overlayOpacity : 0.8,

		/**
		 * The default background color to use for Flash movies (in hex).
		 *
		 * @var     String
		 */
		flashBgColor : '#000000',

		/**
		 * Automatically play movies.
		 *
		 * @var     Boolean
		 */
		autoplayMovies : true,

		/**
		 * Enable movie controllers on movie players.
		 *
		 * @var     Boolean
		 */
		showMovieControls : true,

		/**
		 * A delay (in seconds) to use for slideshows. If set to anything other
		 * than 0, this value determines an interval at which Shadowbox will
		 * automatically proceed to the next piece in the gallery.
		 *
		 * @var     Number
		 */
		slideshowDelay : 0,

		/**
		 * The duration of the resizing animations (in seconds).
		 *
		 * @var     Number
		 */
		resizeDuration : 0.55,

		/**
		 * The duration of the fading animations (in seconds).
		 *
		 * @var     Number
		 */
		fadeDuration : 0.35,

		/**
		 * Show the navigation controls.
		 *
		 * @var     Boolean
		 */
		displayNav : true,

		/**
		 * Enable continuous galleries. When this is true, users will be able
		 * to skip to the first gallery image from the last using next and vice
		 * versa.
		 *
		 * @var     Boolean
		 */
		continuous : false,

		/**
		 * Display the gallery counter.
		 *
		 * @var     Boolean
		 */
		displayCounter : true,

		/**
		 * This option may be either 'default' or 'skip'. The default counter is
		 * a simple '1 of 5' message. The skip counter displays a link for each
		 * piece in the gallery that enables a user to skip directly to any
		 * piece.
		 *
		 * @var     String
		 */
		counterType : 'default',

		/**
		 * Limits the number of counter links that will be displayed in a "skip"
		 * style counter. If the actual number of gallery elements is greater
		 * than this value, the counter will be restrained to the elements
		 * immediately preceeding and following the current element.
		 *
		 * @var     Number
		 */
		counterLimit : 10,

		/**
		 * The amount of padding to maintain around the viewport edge (in
		 * pixels). This only applies when the image is very large and takes up
		 * the entire viewport.
		 *
		 * @var     Number
		 */
		viewportPadding : 20,

		/**
		 * How to handle content that is too large to display in its entirety
		 * (and is resizable). A value of 'resize' will resize the content while
		 * preserving aspect ratio and display it at the smaller resolution. If
		 * the content is an image, a value of 'drag' will display the image at
		 * its original resolution but it will be draggable within Shadowbox. A
		 * value of 'none' will display the content at its original resolution
		 * but it may be cropped.
		 *
		 * @var     String
		 */
		handleOversize : 'resize',

		/**
		 * An exception handling function that will be called whenever
		 * Shadowbox should throw an exception. Will be passed the error
		 * message as its first argument.
		 *
		 * @var     Function
		 */
		handleException : null,

		/**
		 * The mode to use when handling unsupported media. May be either
		 * 'remove' or 'link'. If it is 'remove', the unsupported gallery item
		 * will merely be removed from the gallery. If it is the only item in
		 * the gallery, the link will simply be followed. If it is 'link', a
		 * link will be provided to the appropriate plugin page in place of the
		 * gallery element.
		 *
		 * @var     String
		 */
		handleUnsupported : 'link',

		/**
		 * The initial height of Shadowbox (in pixels).
		 *
		 * @var     Number
		 */
		initialHeight : 160,

		/**
		 * The initial width of Shadowbox (in pixels).
		 *
		 * @var     Number
		 */
		initialWidth : 320,

		/**
		 * Enable keyboard control.
		 *
		 * @var     Boolean
		 */
		enableKeys : true,

		/**
		 * A hook function to be fired when Shadowbox opens. The single argument
		 * will be the current gallery element.
		 *
		 * @var     Function
		 */
		onOpen : null,

		/**
		 * A hook function to be fired when Shadowbox finishes loading its
		 * content. The single argument will be the current gallery element on
		 * display.
		 *
		 * @var     Function
		 */
		onFinish : null,

		/**
		 * A hook function to be fired when Shadowbox changes from one gallery
		 * element to the next. The single argument will be the current gallery
		 * element that is about to be displayed.
		 *
		 * @var     Function
		 */
		onChange : null,

		/**
		 * A hook function that will be fired when Shadowbox closes. The single
		 * argument will be the gallery element most recently displayed.
		 *
		 * @var     Function
		 */
		onClose : null,

		/**
		 * Skips calling Shadowbox.setup() in init(). This means that it must
		 * be called later manually.
		 *
		 * @var     Boolean
		 */
		skipSetup : false,

		/**
		 * An object containing names of plugins and links to their respective
		 * download pages.
		 *
		 * @var     Object
		 */
		errors : {

			fla : {
				name : 'Flash',
				url : 'http://www.adobe.com/products/flashplayer/'
			},

			qt : {
				name : 'QuickTime',
				url : 'http://www.apple.com/quicktime/download/'
			},

			wmp : {
				name : 'Windows Media Player',
				url : 'http://www.microsoft.com/windows/windowsmedia/'
			},

			f4m : {
				name : 'Flip4Mac',
				url : 'http://www.flip4mac.com/wmv_download.htm'
			}

		},

		/**
		 * A map of players to the file extensions they support. Each member of
		 * this object is the name of a player (with one exception), whose value
		 * is an array of file extensions that player will "play". The one
		 * exception to this rule is the "qtwmp" member, which contains extensions
		 * that may be played using either QuickTime or Windows Media Player.
		 *
		 * - img: Image file extensions
		 * - swf: Flash SWF file extensions
		 * - flv: Flash video file extensions (will be played by JW FLV player)
		 * - qt: Movie file extensions supported by QuickTime
		 * - wmp: Movie file extensions supported by Windows Media Player
		 * - qtwmp: Movie file extensions supported by both QuickTime and Windows Media Player
		 * - iframe: File extensions that will be display in an iframe
		 *
		 * IMPORTANT: If this object is to be modified, it must be copied in its
		 * entirety and tweaked because it is not merged recursively with the
		 * default. Also, any modifications must be passed into Shadowbox.init
		 * for speed reasons.
		 *
		 * @var     Object      ext
		 */
		ext : {
			img : [ [ 'png', 'jpg', 'jpeg', 'gif', 'bmp' ], [] ],
			swf : [
					[ 'swf' ],
					[ 'http://www\.youtube\.com*',
							'http://www\.dailymotion\.com*',
							'http://www\.metacafe\.com*',
							'http://europarltv\.europa\.eu*' ] ],
			flv : [ [ 'flv', 'mp4' ], [] ],
			qt : [ [ 'dv', 'mov', 'moov', 'movie' ], [] ],
			wmp : [ [ 'asf', 'wm', 'wmv' ], [] ],
			qtwmp : [ [ 'avi', 'mpg', 'mpeg' ], [] ],
			iframe : [
					[ 'asp', 'aspx', 'cgi', 'cfm', 'htm', 'html', 'pl', 'php',
							'php3', 'php4', 'php5', 'phtml', 'rb', 'rhtml',
							'shtml', 'txt', 'vbs' ], [] ]
		}

	};

	// shorthand
	var SB = Shadowbox;
	var SL = SB.lib;

	/**
	 * Stores the default set of options in case a custom set of options is used
	 * on a link-by-link basis so we can restore them later.
	 * 
	 * @var Object
	 * @private
	 */
	var default_options;

	/**
	 * An object containing some regular expressions we'll need later. Compiled
	 * up front for speed.
	 * 
	 * @var Object
	 * @private
	 */
	var RE = {
		domain : /:\/\/(.*?)[:\/]/, // domain prefix
		inline : /#(.+)$/, // inline element id
		rel : /^(light|shadow)box/i, // rel attribute format
		gallery : /^(light|shadow)box\[(.*?)\]/i, // rel attribute format for gallery link
		unsupported : /^unsupported-(\w+)/, // unsupported media type
		param : /\s*([a-z_]*?)\s*=\s*(.+)\s*/, // rel string parameter
		empty : /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i
	// elements that don't have children
	};

	/**
	 * A cache of options for links that have been set up for use with
	 * Shadowbox.
	 * 
	 * @var Array
	 * @private
	 */
	var cache = [];

	/**
	 * An array containing the gallery objects currently being viewed. In the
	 * case of non-gallery items, this will only hold one object.
	 * 
	 * @var Array
	 * @private
	 */
	var gallery;

	/**
	 * The array index of the current gallery that is currently being viewed.
	 * 
	 * @var Number
	 * @private
	 */
	var current;

	/**
	 * The current content object.
	 * 
	 * @var Object
	 * @private
	 */
	var content;

	/**
	 * The id to use for content objects.
	 * 
	 * @var String
	 * @private
	 */
	var content_id = 'shadowbox_content';

	/**
	 * Holds the current dimensions of Shadowbox as calculated by
	 * setDimensions(). Contains the following properties:
	 *  - height: The total height of #shadowbox - width: The total width of
	 * #shadowbox - inner_h: The height of #shadowbox_body - inner_w: The width
	 * of #shadowbox_body - top: The top to use for #shadowbox - resize_h: The
	 * height to use for resizable content - resize_w: The width to use for
	 * resizable content - drag: True if dragging should be enabled (oversized
	 * image)
	 * 
	 * @var Object
	 * @private
	 */
	var dims;

	/**
	 * Keeps track of whether or not Shadowbox has been initialized. We never
	 * want to initialize twice.
	 * 
	 * @var Boolean
	 * @private
	 */
	var initialized = false;

	/**
	 * Keeps track of whether or not Shadowbox is activated.
	 * 
	 * @var Boolean
	 * @private
	 */
	var activated = false;

	/**
	 * The timeout id for the slideshow transition function.
	 * 
	 * @var Number
	 * @private
	 */
	var slide_timer;

	/**
	 * Keeps track of the time at which the current slideshow frame was
	 * displayed.
	 * 
	 * @var Number
	 * @private
	 */
	var slide_start;

	/**
	 * The delay on which the next slide will display.
	 * 
	 * @var Number
	 * @private
	 */
	var slide_delay = 0;

	/**
	 * These parameters for simple browser detection. Adapted from Ext.js.
	 * 
	 * @var Object
	 * @private
	 */
	var ua = navigator.userAgent.toLowerCase();
	var client = {
		isStrict : document.compatMode == 'CSS1Compat',
		isOpera : ua.indexOf('opera') > -1,
		isIE : ua.indexOf('msie') > -1,
		isIE7 : ua.indexOf('msie 7') > -1,
		isSafari : /webkit|khtml/.test(ua),
		isWindows : ua.indexOf('windows') != -1 || ua.indexOf('win32') != -1,
		isMac : ua.indexOf('macintosh') != -1 || ua.indexOf('mac os x') != -1,
		isLinux : ua.indexOf('linux') != -1
	};
	client.isBorderBox = client.isIE && !client.isStrict;
	client.isSafari3 = client.isSafari && !!(document.evaluate);
	client.isGecko = ua.indexOf('gecko') != -1 && !client.isSafari;

	/**
	 * You're not sill using IE6 are you?
	 * 
	 * @var Boolean
	 * @private
	 */
	var ltIE7 = client.isIE && !client.isIE7;

	/**
	 * Contains plugin support information. Each property of this object is a
	 * boolean indicating whether that plugin is supported.
	 *  - fla: Flash player - qt: QuickTime player - wmp: Windows Media player -
	 * f4m: Flip4Mac plugin
	 * 
	 * @var Object
	 * @private
	 */
	var plugins;

	// detect plugin support
	if (navigator.plugins && navigator.plugins.length) {
		var detectPlugin = function(plugin_name) {
			var detected = false;
			for ( var i = 0, len = navigator.plugins.length; i < len; ++i) {
				if (navigator.plugins[i].name.indexOf(plugin_name) > -1) {
					detected = true;
					break;
				}
			}
			return detected;
		};
		var f4m = detectPlugin('Flip4Mac');
		plugins = {
			fla : detectPlugin('Shockwave Flash'),
			qt : detectPlugin('QuickTime'),
			wmp : !f4m && detectPlugin('Windows Media'), // if it's Flip4Mac, it's not really WMP
			f4m : f4m
		};
	} else {
		var detectPlugin = function(plugin_name) {
			var detected = false;
			try {
				var axo = new ActiveXObject(plugin_name);
				if (axo)
					detected = true;
			} catch (e) {
			}
			return detected;
		};
		plugins = {
			fla : detectPlugin('ShockwaveFlash.ShockwaveFlash'),
			qt : detectPlugin('QuickTime.QuickTime'),
			wmp : detectPlugin('wmplayer.ocx'),
			f4m : false
		};
	}

	/**
	 * Applies all properties of e to o.
	 *
	 * @param   Object      o       The original object
	 * @param   Object      e       The extension object
	 * @return  Object              The original object with all properties
	 *                              of the extension object applied
	 * @private
	 */
	var apply = function(o, e) {
		for (var p in e)
			o[p] = e[p];
		return o;
	};

	/**
	 * Determines if the given object is an anchor/area element.
	 * 
	 * @param mixed
	 *            el The object to check
	 * @return Boolean True if the object is a link element
	 * @private
	 */
	var isLink = function(el) {
		return el
				&& typeof el.tagName == 'string'
				&& (el.tagName.toUpperCase() == 'A' || el.tagName.toUpperCase() == 'AREA');
	};

	/**
	 * Gets the height of the viewport in pixels. Note: This function includes
	 * scrollbars in Safari 3.
	 * 
	 * @return Number The height of the viewport
	 * @public
	 * @static
	 */
	SL.getViewportHeight = function() {
		var h = window.innerHeight; // Safari
		var mode = document.compatMode;
		if ((mode || client.isIE) && !client.isOpera) {
			h = client.isStrict ? document.documentElement.clientHeight
					: document.body.clientHeight;
		}
		//alert(SL.get('shadowbox').offsetHeight);
		// alert(SL.get('shadowbox').offsetHeight +" / "+ window.outerHeight);
		// if(SL.get('shadowbox').offsetHeight >= window.outerHeight) return
		// h-40;
		return h;
	};

	/**
	 * Gets the width of the viewport in pixels. Note: This function includes
	 * scrollbars in Safari 3.
	 * 
	 * @return Number The width of the viewport
	 * @public
	 * @static
	 */
	SL.getViewportWidth = function() {
		var w = window.innerWidth; // Safari
		var mode = document.compatMode;
		if (mode || client.isIE) {
			w = client.isStrict ? document.documentElement.clientWidth
					: document.body.clientWidth;
		}
		return w;
	};

	/**
	 * Creates an HTML string from an object representing HTML elements. Based
	 * on Ext.DomHelper's createHtml.
	 * 
	 * @param Object
	 *            obj The HTML definition object
	 * @return String An HTML string
	 * @public
	 * @static
	 */
	SL.createHTML = function(obj) {
		var o = gallery[current];
		var html = '<' + obj.tag;
		for ( var attr in obj) {
			if (attr == 'tag' || attr == 'html' || attr == 'children')
				continue;
			if (attr == 'cls') {
				html += ' class="' + obj['cls'] + '"';
			} else {

				html += ' ' + attr + '="' + obj[attr] + '"';
			}
		}
		if (obj.tag == 'img')
			html += ' alt="' + o.description + '" ';
		if (RE.empty.test(obj.tag)) {
			html += '/>';
		} else {
			html += '>';
			var cn = obj.children;
			if (cn) {
				for ( var i = 0, len = cn.length; i < len; ++i) {
					html += this.createHTML(cn[i]);
				}
			}
			if (obj.html)
				html += obj.html;
			html += '</' + obj.tag + '>';
		}
		return html;
	};

	/**
	 * Easing function used for animations. Based on a cubic polynomial.
	 * 
	 * @param Number
	 *            x The state of the animation (% complete)
	 * @return Number The adjusted easing value
	 * @private
	 * @static
	 */
	var ease = function(x) {
		return 1 + Math.pow(x - 1, 3);
	};

	/**
	 * Animates any numeric (not color) style of the given element from its
	 * current state to the given value. Defaults to using pixel-based
	 * measurements.
	 * 
	 * @param HTMLElement
	 *            el The DOM element to animate
	 * @param String
	 *            p The property to animate (in camelCase)
	 * @param mixed
	 *            to The value to animate to
	 * @param Number
	 *            d The duration of the animation (in seconds)
	 * @param Function
	 *            cb A callback function to call when the animation completes
	 * @return void
	 * @private
	 * @static
	 */
	var animate = function(el, p, to, d, cb) {
		var from = parseFloat(SL.getStyle(el, p));
		if (isNaN(from))
			from = 0;

		if (from == to) {
			if (typeof cb == 'function')
				cb();
			return; // nothing to animate
		}

		var delta = to - from;
		var op = p == 'opacity';
		var unit = op ? '' : 'px'; // default unit is px
		var fn = function(ease) {
			SL.setStyle(el, p, from + ease * delta + unit);
		};

		// cancel the animation here if set in the options
		if (!options.animate && !op || op && !options.animateFade) {
			fn(1);
			if (typeof cb == 'function')
				cb();
			return;
		}

		d *= 1000; // convert to milliseconds
		var begin = new Date().getTime();
		var end = begin + d;

		var timer = setInterval( function() {
			var time = new Date().getTime();
			if (time >= end) { // end of animation
					clearInterval(timer);
					fn(1);
					if (typeof cb == 'function')
						cb();
				} else {
					fn(ease((time - begin) / d));
				}
			}, 10); // 10 ms interval is minimum on WebKit
	};

	/**
	 * A utility function used by the fade functions to clear the opacity style
	 * setting of the given element. Required in some cases for IE.
	 * 
	 * @param HTMLElement
	 *            el The DOM element
	 * @return void
	 * @private
	 */
	var clearOpacity = function(el) {
		var s = el.style;
		if (client.isIE) {
			if (typeof s.filter == 'string' && (/alpha/i).test(s.filter)) {
				// careful not to overwrite other filters!
				s.filter = s.filter.replace(/[\w\.]*alpha\(.*?\);?/i, '');
			}
		} else {
			s.opacity = '';
			s['-moz-opacity'] = '';
			s['-khtml-opacity'] = '';
		}
	};

	/**
	 * Gets the computed height of the given element, including padding and
	 * borders.
	 * 
	 * @param HTMLElement
	 *            el The element
	 * @return Number The computed height of the element
	 * @private
	 */
	var getComputedHeight = function(el) {
		var h = Math.max(el.offsetHeight, el.clientHeight);
		if (!h) {
			h = parseInt(SL.getStyle(el, 'height'), 10) || 0;
			if (!client.isBorderBox) {
				h += parseInt(SL.getStyle(el, 'padding-top'), 10)
						+ parseInt(SL.getStyle(el, 'padding-bottom'), 10)
						+ parseInt(SL.getStyle(el, 'border-top-width'), 10)
						+ parseInt(SL.getStyle(el, 'border-bottom-width'), 10);
			}
		}
		return h;
	};

	/**
	 * Determines the player needed to display the file at the given URL. If the
	 * file type is not supported, the return value will be 'unsupported'. If
	 * the file type is not supported but the correct player can be determined,
	 * the return value will be 'unsupported-*' where * will be the player
	 * abbreviation (e.g. 'qt' = QuickTime).
	 * 
	 * @param String
	 *            url The url of the file
	 * @return String The name of the player to use
	 * @private
	 */
	var getPlayer = function(url) {
		var m = url.match(RE.domain);
		// var d = m && document.domain == m[1]; // same domain
		var d = false; // accept other domain
		if (url.indexOf('#') > -1 && d)
			return 'inline';
		var q = url.indexOf('?');
		if (q > -1)
			url = url.substring(0, q); // strip query string for player
										// detection purposes
		if (RE.img.test(url))
			return 'img';
		if (RE.swf.test(url))
			return plugins.fla ? 'swf' : 'unsupported-swf';
		if (RE.flv.test(url))
			return plugins.fla ? 'flv' : 'unsupported-flv';
		if (RE.qt.test(url))
			return plugins.qt ? 'qt' : 'unsupported-qt';
		if (RE.wmp.test(url)) {
			if (plugins.wmp)
				return 'wmp';
			if (plugins.f4m)
				return 'qt';
			if (client.isMac)
				return plugins.qt ? 'unsupported-f4m' : 'unsupported-qtf4m';
			return 'unsupported-wmp';
		} else if (RE.qtwmp.test(url)) {
			if (plugins.qt)
				return 'qt';
			if (plugins.wmp)
				return 'wmp';
			return client.isMac ? 'unsupported-qt' : 'unsupported-qtwmp';
		} else if (!d || RE.iframe.test(url)) {
			return 'iframe';
		}
		return 'unsupported'; // same domain, not supported
	};

	/**
	 * Functions used to display and enable specific Resource links buttons such as HD version of a picture, link to linked page, etc.
	 * ghooghe 20100420
	 */
	var getAttribute = function(node, att) {
		if (node.attributes != undefined) {
			for (var x = 0; x < node.attributes.length; x++) {
				if (node.attributes[x].nodeName.toLowerCase() == att) {
					return node.attributes[x].nodeValue;
				}
			}
		}
	}
	
	/**
	 * ghooghe
	 */
	var hasClass = function(node, className) {
		if (getAttribute(node, 'class') != undefined) {
			if (getAttribute(node, 'class').toLowerCase() == className) {
				return true;
			} 
		}
		return false;
	}
	
	/**
	 * Set the href value of the link identified by childLinkId with value
	 * Also removes the link if value is empty
	 * ghooghe
	 */
	var setResourceLinkURL = function(childLinkId, linkValue) {
		var childLink = SL.get(childLinkId);
		if (childLink != undefined) {
			if (linkValue != null && linkValue.trim().length > 0) {
				childLink.setAttribute('href', '' + linkValue);
			} else {
				// Remove this useless link
				childLink.parentNode.removeChild(childLink);
			}
		}
	}

	/**
	 * Handles all clicks on links that have been set up to work with Shadowbox
	 * and cancels the default event behavior when appropriate.
	 *
	 * @param   {Event}         ev          The click event object
	 * @return  void
	 * @private
	 */
	var handleClick = function(ev) {
		// get anchor/area element
		var link;
		if (isLink(this)) {
			link = this; // jQuery, Prototype, YUI
		} else {
			link = SL.getTarget(ev); // Ext, standalone
			while (!isLink(link) && link.parentNode) {
				link = link.parentNode;
			}
		}

		//SL.preventDefault(ev); // good for debugging
		
		if (link && !link.shadowBoxOff) {
			SB.open(link);
			if (gallery.length) {
				SL.preventDefault(ev); // stop event
			}
		}
	};

	/**
	 * Toggles the display of the nav control with the given id on and off.
	 * 
	 * @param String
	 *            id The id of the navigation control
	 * @param Boolean
	 *            on True to toggle on, false to toggle off
	 * @return void
	 * @private
	 */
	var toggleNav = function(id, on) {
		var el = SL.get('shadowbox_nav_' + id);
		if (el)
			el.style.display = on ? '' : 'none';
	};

	/**
	 * Builds the content for the title and information bars.
	 * 
	 * @param Function
	 *            cb A callback function to execute after the bars are built
	 * @return void
	 * @private
	 */
	var buildBars = function(cb) {
		var obj = gallery[current];
		var title_i = SL.get('shadowbox_title_inner');
		var title_i_img = SL.get('shadowbox_title_img');
		var title_contener = SL.get('shadowbox_title');
		title_contener.style.height = '';
		title_i.innerHTML = "";
		title_i_img.style.display = 'block';

		if (gallery.length > 1)
			title_i.innerHTML = Shadowbox.LANG.title_slideshow;
		else {
			if (obj.player == 'img')
				title_i.innerHTML = Shadowbox.LANG.title_photo;
			else if (obj.player != 'iframe' && obj.player != 'inline')
				title_i.innerHTML = Shadowbox.LANG.title_video;
			else
				title_i_img.style.display = 'none';

		}

		if (title_i.innerHTML == "" && obj.player == 'iframe')
			title_contener.style.height = '1px';
		else
			title_i.style.margin = '';

		// build the nav
		var nav = SL.get('shadowbox_nav');
		if (nav) {
			var c, n, pl, pa, p;

			// need to build the nav?
			if (options.displayNav) {
				c = true;
				// next & previous links
				var len = gallery.length;
				if (len > 1) {
					if (options.continuous) {
						n = p = true; // show both
					} else {
						n = (len - 1) > current; // not last in gallery, show
													// next
						p = current > 0; // not first in gallery, show
											// previous
					}
				}
				// in a slideshow?
				if (options.slideshowDelay > 0 && hasNext()) {
					pa = slide_timer != 'paused';
					pl = !pa;
				}
			} else {
				c = n = pl = pa = p = false;
			}

			toggleNav('close', c);
			toggleNav('next', n);
			toggleNav('play', pl);
			toggleNav('pause', pa);
			toggleNav('previous', p);
		}
		var title_img = SL.get('shadowbox_title_img');
		// build the title
		title_img.innerHTML = obj.title || '';

		// build the counter
		var counter = SL.get('shadowbox_counter');
		if (counter) {
			counter.style.height = "";
			counter.style.padding = "";
			var co = '';

			// need to build the counter?
			if (options.displayCounter && gallery.length > 1) {
				if (options.counterType == 'skip') {
					// limit the counter?
					var i = 0, len = gallery.length, end = len;
					var limit = parseInt(options.counterLimit);
					if (limit < len) { // support large galleries
						var h = Math.round(limit / 2);
						i = current - h;
						if (i < 0)
							i += len;
						end = current + (limit - h);
						if (end > len)
							end -= len;
					}
					while (i != end) {
						if (i == len)
							i = 0;
						co += '<a onclick="Shadowbox.change(' + i + ');"';
						if (i == current)
							co += ' class="shadowbox_counter_current"';
						co += '>' + (++i) + '</a>';
					}
				} else { // default
					co = (current + 1) + ' ' + SB.LANG.of + ' ' + len;
				}
			} else {
				counter.style.height = "0";
				counter.style.padding = "0";
			}

			counter.innerHTML = co;
		}
		
		// build the resource special links buttons - ghooghe
		if (obj.resourceSpecialLinks != undefined) {
			setResourceLinkURL('shadowbox_hd_link', obj.resourceSpecialLinks.hd);
			setResourceLinkURL('shadowbox_linked_page', obj.resourceSpecialLinks.linkedPage);
		}

		cb();
	};

	/**
	 * Hides the title and info bars.
	 * 
	 * @param Boolean
	 *            anim True to animate the transition
	 * @param Function
	 *            cb A callback function to execute after the animation
	 *            completes
	 * @return void
	 * @private
	 */
	var hideBars = function(anim, cb) {
		var obj = gallery[current];
		var title = SL.get('shadowbox_title');
		var info = SL.get('shadowbox_info');
		var title_i = SL.get('shadowbox_title_inner');
		var info_i = SL.get('shadowbox_info_inner');

		// build bars after they are hidden
		var fn = function() {
			buildBars(cb);
		};

		var title_h = getComputedHeight(title);
		var info_h = getComputedHeight(info) * -1;
		if (anim) {
			// animate the transition
			animate(title_i, 'margin-top', title_h, 0.35);
			animate(info_i, 'margin-top', info_h, 0.35, fn);
		} else {
			SL.setStyle(title_i, 'margin-top', title_h + 'px');
			SL.setStyle(info_i, 'margin-top', info_h + 'px');
			fn();
		}
	};

	/**
	 * Shows the title and info bars.
	 * 
	 * @param Function
	 *            cb A callback function to execute after the animation
	 *            completes
	 * @return void
	 * @private
	 */
	var showBars = function(cb) {
		var title_i = SL.get('shadowbox_title_inner');
		var info_i = SL.get('shadowbox_info_inner');
		var t = title_i.innerHTML != ''; // is there a title to display?

		if (t)
			animate(title_i, 'margin-top', 0, 0.35);
		animate(info_i, 'margin-top', 0, 0.35, cb);

		// Modif PE (XB) : Donne le focus au premier lien visible de la
		// shadowbox
		/*
		 * var links =
		 * document.getElementById('shadowbox').getElementsByTagName('a');
		 * if(links.length > 0){ var l = links[0]; var counter = 0;
		 * while(l.style.display == 'none' && counter < links.length){
		 * counter++; l = links[counter]; } l.focus(); }
		 */
		// Finalement donne le focus au div comportant le titre du type de
		// player
		SL.get('shadowbox_title_inner').focus();
	};

	/**
	 * Loads the Shadowbox with the current piece.
	 * 
	 * @return void
	 * @private
	 */
	var loadContent = function() {
		var obj = gallery[current];
		
		if (!obj)
			return; // invalid

		var changing = false;
		if (content) {
			content.remove(); // remove old content first
			changing = true; // changing from some previous content
		}

		// determine player, inline is really just HTML
		var p = obj.player == 'inline' ? 'html' : obj.player;

		// make sure player is loaded
		if (typeof SB[p] != 'function') {
			SB.raise('Unknown player ' + obj.player);
		}
		content = new SB[p](content_id, obj); // instantiate new content
												// object

		listenKeys(false); // disable the keyboard temporarily
		toggleLoading(true);

		hideBars(changing, function() { // if changing, animate the bars transition
					if (!content)
						return;

					// if opening, clear #shadowbox display
				if (!changing) {
					SL.get('shadowbox').style.display = '';
				}

				var fn = function() {
					resizeContent( function() {
						if (!content)
							return;

						showBars( function() {
							if (!content)
								return;
							// append content just before hiding the loading
							// layer
							SL.get('shadowbox_body_inner').innerHTML = SL
									.createHTML(content.markup(dims));

							toggleLoading(
									false,
									function() {
										if (!content)
											return;

										if (typeof content.onLoad == 'function') {
											content.onLoad(); // call onLoad
																// callback if
																// present
										}
										if (options.onFinish
												&& typeof options.onFinish == 'function') {
											options.onFinish(gallery[current]); // fire
																				// onFinish
																				// handler
										}
										if (slide_timer != 'paused') {
											SB.play(); // kick off next slide
										}
										listenKeys(true); // re-enable the
															// keyboard
									});
						});
					});
				};

				if (typeof content.ready != 'undefined') { // does the object have a ready property?
					var id = setInterval( function() { // if so, wait for the object to be ready
								if (content) {
									if (content.ready) {
										clearInterval(id); // clean up
										id = null;
										fn();
									}
								} else { // content has been removed
									clearInterval(id);
									id = null;
								}
							}, 100);
				} else {
					fn();
				}
			});

		// preload neighboring gallery images
		if (gallery.length > 1) {
			var next = gallery[current + 1] || gallery[0];
			if (next.player == 'img') {
				var a = new Image();
				a.src = next.content;
			}
			var prev = gallery[current - 1] || gallery[gallery.length - 1];
			if (prev.player == 'img') {
				var b = new Image();
				b.src = prev.content;
			}
		}
		//Modif PE (XB) : Repositionne le bouton "close" dans le cas de l'iframe
		var title_contener = SL.get('shadowbox_title');
		if (obj.player == 'iframe' && title_contener.style.height == '1px') {
			SL.get('shadowbox_nav_close').style.top = '10px';
		}
		var title_i = SL.get('shadowbox_title_inner');
		title_i.style.margin = '';

	};

	/**
	 * Calculates the dimensions for Shadowbox, taking into account the borders
	 * and surrounding elements of the shadowbox_body. If the height/width
	 * combination is too large for Shadowbox and handleOversize option is set
	 * to 'resize', the resized dimensions will be returned (preserving the
	 * original aspect ratio). Otherwise, the originally calculated dimensions
	 * will be used. Stores all dimensions in the private dims variable.
	 * 
	 * @param Number
	 *            height The content player height
	 * @param Number
	 *            width The content player width
	 * @param Boolean
	 *            resizable True if the content is able to be resized. Defaults
	 *            to false.
	 * @return void
	 * @private
	 */
	var setDimensions = function(height, width, resizable) {
		resizable = resizable || false;

		var sb = SL.get('shadowbox_body');
		var h = height = parseInt(height);
		var w = width = parseInt(width);
		var view_h = SL.getViewportHeight();
		var view_w = SL.getViewportWidth();

		// calculate the max width
		var border_w = parseInt(SL.getStyle(sb, 'border-left-width'), 10)
				+ parseInt(SL.getStyle(sb, 'border-right-width'), 10);
		var extra_w = border_w + 2 * options.viewportPadding;
		if (w + extra_w >= view_w) {
			w = view_w - extra_w;
		}

		// calculate the max height
		var border_h = parseInt(SL.getStyle(sb, 'border-top-width'), 10)
				+ parseInt(SL.getStyle(sb, 'border-bottom-width'), 10);
		var bar_h = getComputedHeight(SL.get('shadowbox_title'))
				+ getComputedHeight(SL.get('shadowbox_info'));
		var extra_h = border_h + 2 * options.viewportPadding + bar_h;

		if (gallery.length > 1)
			extra_h += 40; // Ajout PE (XB): Pour un bon affichage sous FF pour
							// image trop grande

		if (h + extra_h >= view_h) {
			h = view_h - extra_h;
		}

		// handle oversized content
		var drag = false;
		var resize_h = height;
		var resize_w = width;
		var handle = options.handleOversize;
		if (resizable && (handle == 'resize' || handle == 'drag')) {
			var change_h = (height - h) / height;
			var change_w = (width - w) / width;
			if (handle == 'resize') {
				if (change_h > change_w) {
					w = Math.round((width / height) * h);
				} else if (change_w > change_h) {
					h = Math.round((height / width) * w);
				}
				// adjust resized height or width accordingly
				resize_w = w;
				resize_h = h;
			} else {
				// drag on oversized images only
				var link = gallery[current];
				if (link)
					drag = link.player == 'img'
							&& (change_h > 0 || change_w > 0);
			}
		}

		// update dims
		dims = {
			height : h + border_h + bar_h,
			width : w + border_w,
			inner_h : h,
			inner_w : w,
			top : (view_h - (h + extra_h)) / 2 + options.viewportPadding,
			resize_h : resize_h,
			resize_w : resize_w,
			drag : drag
		};
	};

	/**
	 * Resizes Shadowbox to the given height and width. If the callback
	 * parameter is given, the transition will be animated and the callback
	 * function will be called when the animation completes. Note: The private
	 * content variable must be updated before calling this function.
	 * 
	 * @param Function
	 *            cb A callback function to execute after the content has been
	 *            resized
	 * @return void
	 * @private
	 */
	var resizeContent = function(cb) {
		if (!content)
			return; // no content

		// set new dimensions
		setDimensions(content.height, content.width, content.resizable);

		if (cb) {

			switch (options.animSequence) {
			case 'hw':
				adjustHeight(dims.inner_h, dims.top, true, function() {
					adjustWidth(dims.width, true, cb);
				});
				break;
			case 'wh':
				adjustWidth(dims.width, true, function() {
					adjustHeight(dims.inner_h, dims.top, true, cb);
				});
				break;
			case 'sync':
			default:
				adjustWidth(dims.width, true);
				adjustHeight(dims.inner_h, dims.top, true, cb);

			}

		} else { // window resize
			adjustWidth(dims.width, false);
			adjustHeight(dims.inner_h, dims.top, false);
			var c = SL.get(content_id);
			if (c) {
				// resize resizable content when in resize mode
				if (content.resizable && options.handleOversize == 'resize') {
					c.height = dims.resize_h;
					c.width = dims.resize_w;
				}
				// fix draggable positioning if enlarging viewport
				if (gallery[current].player == 'img'
						&& options.handleOversize == 'drag') {
					var top = parseInt(SL.getStyle(c, 'top'));
					if (top + content.height < dims.inner_h) {
						SL.setStyle(c, 'top', dims.inner_h - content.height
								+ 'px');
					}
					var left = parseInt(SL.getStyle(c, 'left'));
					if (left + content.width < dims.inner_w) {
						SL.setStyle(c, 'left', dims.inner_w - content.width
								+ 'px');
					}
				}
			}
		}
	};

	/**
	 * Adjusts the height of #shadowbox_body and centers #shadowbox vertically
	 * in the viewport.
	 * 
	 * @param Number
	 *            height The height to use for #shadowbox_body
	 * @param Number
	 *            top The top to use for #shadowbox
	 * @param Boolean
	 *            anim True to animate the transition
	 * @param Function
	 *            cb A callback to use when the animation completes
	 * @return void
	 * @private
	 */
	var adjustHeight = function(height, top, anim, cb) {
		height = parseInt(height);

		// adjust the height
		var sb = SL.get('shadowbox_body');
		if (anim) {
			animate(sb, 'height', height, options.resizeDuration);
		} else {
			SL.setStyle(sb, 'height', height + 'px');
		}

		// adjust the top
		var s = SL.get('shadowbox');
		if (anim) {
			animate(s, 'top', top, options.resizeDuration, cb);
		} else {
			SL.setStyle(s, 'top', top + 'px');
			if (typeof cb == 'function')
				cb();
		}
	};

	/**
	 * Adjusts the width of #shadowbox.
	 * 
	 * @param Number
	 *            width The width to use for #shadowbox
	 * @param Boolean
	 *            anim True to animate the transition
	 * @param Function
	 *            cb A callback to use when the animation completes
	 * @return void
	 * @private
	 */
	var adjustWidth = function(width, anim, cb) {
		width = parseInt(width);

		// adjust the width
		var s = SL.get('shadowbox');
		if (anim) {
			animate(s, 'width', width, options.resizeDuration, cb);
		} else {
			SL.setStyle(s, 'width', width + 'px');
			if (typeof cb == 'function')
				cb();
		}
	};

	/**
	 * Sets up a listener on the document for keystrokes.
	 * 
	 * @param Boolean
	 *            on True to enable the listener, false to turn it off
	 * @return void
	 * @private
	 */
	var listenKeys = function(on) {
		if (!options.enableKeys)
			return;
		SL[(on ? 'add' : 'remove') + 'Event'](document, 'keydown', handleKey);
	};

	/**
	 * A listener function that is fired when a key is pressed.
	 * 
	 * @param mixed
	 *            e The event object
	 * @return void
	 * @private
	 */
	var handleKey = function(e) {
		var code = SL.keyCode(e);

		// attempt to prevent default key action
		if (code != 9 && code != 13)
			SL.preventDefault(e);

		if (code == 81 || code == 88 || code == 27) { // q, x, or esc
			SB.close();
		} else if (code == 37) { // left arrow
			SB.previous();
		} else if (code == 39) { // right arrow
			SB.next();
		} else if (code == 32) { // space bar
			SB[(typeof slide_timer == 'number' ? 'pause' : 'play')]();
		}
	};

	/**
	 * Toggles the visibility of the "loading" layer.
	 * 
	 * @param Boolean
	 *            on True to toggle on, false to toggle off
	 * @param Function
	 *            cb The callback function to call when toggling completes
	 * @return void
	 * @private
	 */
	var toggleLoading = function(on, cb) {
		var loading = SL.get('shadowbox_loading');
		if (on) {
			loading.style.display = '';
			if (typeof cb == 'function')
				cb();
		} else {
			var p = gallery[current].player;
			var anim = (p == 'img' || p == 'html'); // fade on images & html
			var fn = function() {
				loading.style.display = 'none';
				clearOpacity(loading);
				if (typeof cb == 'function')
					cb();
			};
			if (anim) {
				animate(loading, 'opacity', 0, options.fadeDuration, fn);
			} else {
				fn();
			}
		}
	};

	/**
	 * Sets the top of the container element. This is only necessary in IE6
	 * where the container uses absolute positioning instead of fixed.
	 * 
	 * @return void
	 * @private
	 */
	var fixTop = function() {
		SL.get('shadowbox_container').style.top = document.documentElement.scrollTop + 'px';
	};

	/**
	 * Sets the height of the overlay element to the full viewport height. This
	 * is only necessary in IE6 where the container uses absolute positioning
	 * instead of fixed, thus restricting the size of the overlay element.
	 * 
	 * @return void
	 * @private
	 */
	var fixHeight = function() {
		SL.get('shadowbox_overlay').style.height = SL.getViewportHeight() + 'px';
	};

	/**
	 * Determines if there is a next piece to display in the current gallery.
	 * 
	 * @return bool True if there is another piece, false otherwise
	 * @private
	 */
	var hasNext = function() {
		return gallery.length > 1
				&& (current != gallery.length - 1 || options.continuous);
	};

	/**
	 * Toggles the visibility of #shadowbox_container and sets its size (if on
	 * IE6). Also toggles the visibility of elements (<select>, <object>, and
	 * <embed>) that are troublesome for semi-transparent modal overlays. IE has
	 * problems with <select> elements, while Firefox has trouble with
	 * <object>s.
	 * 
	 * @param Function
	 *            cb A callback to call after toggling on, absent when toggling
	 *            off
	 * @return void
	 * @private
	 */
	var toggleVisible = function(cb) {
		var els, v = (cb) ? 'hidden' : 'visible';
		var hide = [ 'select', 'object', 'embed' ]; // tags to hide
		for ( var i = 0; i < hide.length; ++i) {
			els = document.getElementsByTagName(hide[i]);
			for ( var j = 0, len = els.length; j < len; ++j) {
				els[j].style.visibility = v;
			}
		}

		// resize & show container
		var so = SL.get('shadowbox_overlay');
		var sc = SL.get('shadowbox_container');
		var sb = SL.get('shadowbox');
		if (cb) {
			// set overlay color/opacity
			SL.setStyle(so, {
				backgroundColor : options.overlayColor,
				opacity : 0
			});
			if (!options.modal)
				SL.addEvent(so, 'click', SB.close);
			if (ltIE7) {
				// fix container top & overlay height before showing
				fixTop();
				fixHeight();
				SL.addEvent(window, 'scroll', fixTop);
			}

			// fade in animation
			sb.style.display = 'none'; // will be cleared in loadContent()
			sc.style.visibility = 'visible';
			animate(so, 'opacity', parseFloat(options.overlayOpacity),
					options.fadeDuration, cb);
		} else {
			SL.removeEvent(so, 'click', SB.close);
			if (ltIE7)
				SL.removeEvent(window, 'scroll', fixTop);

			// fade out effect
			sb.style.display = 'none';
			animate(so, 'opacity', 0, options.fadeDuration, function() {
				sc.style.visibility = 'hidden';
				sb.style.display = '';
				clearOpacity(so);
			});
		}
	};

	/**
	 * Initializes the Shadowbox environment. Loads the skin (if necessary),
	 * compiles the player matching regular expressions, and sets up the window
	 * resize listener.
	 * 
	 * @param Object
	 *            opts (optional) The default options to use
	 * @return void
	 * @public
	 * @static
	 */
	Shadowbox.init = function(opts) {
		// don't initialize twice
		if (initialized)
			return;

		// make sure language is loaded
		if (typeof SB.LANG == 'undefined') {
			SB.raise('No Shadowbox language loaded');
			return;
		}
		// make sure skin is loaded
		if (typeof SB.SKIN == 'undefined') {
			SB.raise('No Shadowbox skin loaded');
			return;
		}

		// apply custom options
		apply(options, opts || {});

		// add markup
		var markup = SB.SKIN.markup.replace(/\{(\w+)\}/g, function(m, p) {
			return SB.LANG[p];
		});
		var bd = document.body || document.documentElement;
		SL.append(bd, markup);

		// several fixes for IE6
		if (ltIE7) {
			// give the container absolute positioning
			SL.setStyle(SL.get('shadowbox_container'), 'position', 'absolute');
			// give shadowbox_body "layout"...whatever that is
			SL.get('shadowbox_body').style.zoom = 1;
			// use AlphaImageLoader for transparent PNG support
			var png = SB.SKIN.png_fix;
			if (png && png.constructor == Array) {
				for ( var i = 0; i < png.length; ++i) {
					var el = SL.get(png[i]);
					if (el) {
						var match = SL.getStyle(el, 'background-image').match(
								/url\("(.*\.png)"\)/);
						if (match) {
							SL
									.setStyle(
											el,
											{
												backgroundImage : 'none',
												filter : 'progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true,src=' + match[1] + ',sizingMethod=scale);'
											});
						}
					}
				}
			}
		}

		// compile file type regular expressions here for speed
		for ( var e in options.ext) {

			if (options.ext[e][1].length > 0) {
				RE[e] = new RegExp('\.(' + options.ext[e][0].join('|')
						+ ')\s*$' + '|' + options.ext[e][1].join('|'), 'i');
			} else {
				RE[e] = new RegExp(
						'\.(' + options.ext[e][0].join('|') + ')\s*$', 'i');
			}

			//alert("RE[e] = "+RE[e]+"   e="+e);
		}

		// set up window resize event handler
		var id;
		SL.addEvent(window, 'resize', function() {
			// use 50 ms event buffering to prevent jerky window resizing
				if (id) {
					clearTimeout(id);
					id = null;
				}
				id = setTimeout( function() {
					if (ltIE7)
						fixHeight();
					resizeContent();
				}, 50);
			});

		if (!options.skipSetup)
			SB.setup();
		initialized = true;
	};

	/**
	 * Dynamically loads the specified skin for use with Shadowbox. If the skin
	 * is included already in the page via the appropriate <script> and <link>
	 * tags, this function does not need to be called. Otherwise, this function
	 * must be called before window.onload.
	 * 
	 * @param String
	 *            skin The directory where the skin is located
	 * @param String
	 *            dir The directory where the Shadowbox skin files are located
	 * @return void
	 * @public
	 * @static
	 */
	Shadowbox.loadSkin = function(skin, dir) {
		if (!(/\/$/.test(dir)))
			dir += '/';
		skin = dir + skin + '/';

		// Safari 2.0 fails using DOM, use document.write instead
		document
				.write('<link rel="stylesheet" type="text/css" href="' + skin + 'skin.css">');
		document
				.write('<scr' + 'ipt type="text/javascript" src="' + skin + 'skin.js"><\/script>');
	};

	/**
	 * Dynamically loads the specified language file to be used with Shadowbox.
	 * If the language file is included already in the page via the appropriate
	 * <script> tag, this function does not need to be called. Otherwise, this
	 * function must be called before window.onload.
	 * 
	 * @param String
	 *            lang The language abbreviation (e.g. en)
	 * @param String
	 *            dir The directory where the Shadowbox language file(s) is
	 *            located
	 * @return void
	 * @public
	 * @static
	 */
	Shadowbox.loadLanguage = function(lang, dir) {
		if (!(/\/$/.test(dir)))
			dir += '/';

		// Safari 2.0 fails using DOM, use document.write instead
		document.write('<scr' + 'ipt type="text/javascript" src="' + dir
				+ 'shadowbox-' + lang + '.js"><\/script>');
	};

	/**
	 * Dynamically loads the specified player(s) to be used with Shadowbox. If
	 * the needed player(s) is already included in the page via the appropriate
	 * <script> tag(s), this function does not need to be called. Otherwise,
	 * this function must be called before window.onload.
	 * 
	 * @param Array
	 *            players The player(s) to load
	 * @param String
	 *            dir The director where the Shadowbox player file(s) is located
	 * @return void
	 * @public
	 * @static
	 */
	Shadowbox.loadPlayer = function(players, dir) {
		if (typeof players == 'string')
			players = [ players ];
		if (!(/\/$/.test(dir)))
			dir += '/';

		for ( var i = 0, len = players.length; i < len; ++i) {
			// Safari 2.0 fails using DOM, use document.write instead
			document.write('<scr' + 'ipt type="text/javascript" src="' + dir
					+ 'shadowbox-' + players[i] + '.js"><\/script>');
		}
	};

	/**
	 * Sets up listeners on the given links that will trigger Shadowbox. If no
	 * links are given, this method will set up every anchor element on the page
	 * with the appropriate rel attribute. Note: Because AREA elements do not
	 * support the rel attribute, they must be explicitly passed to this method.
	 * 
	 * @param Array
	 *            links An array (or array-like) list of anchor and/or area
	 *            elements to set up
	 * @param Object
	 *            opts Some options to use for the given links
	 * @return void
	 * @public
	 * @static
	 */
	Shadowbox.setup = function(links, opts) {
		// get links if none specified
		if (!links) {
			var links = [];
			var a = document.getElementsByTagName('a'), rel;
			for ( var i = 0, len = a.length; i < len; ++i) {
				rel = a[i].getAttribute('rel');
				if (rel && RE.rel.test(rel))
					links[links.length] = a[i];
			}
		} else if (!links.length) {
			links = [ links ]; // one link
		}

		var link;
		for (var i = 0, len = links.length; i < len; ++i) {
			link = links[i];
			if (typeof link.shadowboxCacheKey == 'undefined') {
				// assign cache key expando
				// use integer primitive to avoid memory leak in IE
				link.shadowboxCacheKey = cache.length;
				SL.addEvent(link, 'click', handleClick); // add listener
			}
			cache[link.shadowboxCacheKey] = this.buildCacheObj(link, opts);
		}
	};

	/**
	 * Builds an object from the original link element data to store in cache.
	 * These objects contain (most of) the following keys:
	 *  - el: the link element - title: the linked file title - player: the
	 * player to use for the linked file - content: the linked file's URL -
	 * gallery: the gallery the file belongs to (optional) - height: the height
	 * of the linked file (only necessary for movies) - width: the width of the
	 * linked file (only necessary for movies) - options: custom options to use
	 * (optional)
	 * 
	 * @param HTMLElement
	 *            link The link element to process
	 * @return Object An object representing the link
	 * @public
	 * @static
	 */
	Shadowbox.buildCacheObj = function(link, opts) {
		var href = link.href; // don't use getAttribute() here

		href = Shadowbox.convertVideoURL(href);

		var titleNode = link.nextSibling;
		var title = link.getAttribute('title');
		var description = link.getAttribute('title');
		/*---------- Recherche du titre pour chaque lien de type shadoxbox (pour le diaporama et les images) -------*/
		var altExist = false;
		var children = link.childNodes

		for ( var i = 0; i < children.length; i++) {
			var child = children[i];
			if (child.nodeType == 1
					&& (child.nodeName == 'img' || child.nodeName == 'IMG')) {
				description = child.getAttribute('alt');
				altExist = true;
			}
		}

		if (!altExist) {
			if (getPlayer(href) == 'img') {
				if (link.firstChild && link.firstChild.nodeType == 3)
					description = link.firstChild.nodeValue;

			} else {
				//Cas de la video
				if (link.firstChild && link.firstChild.nodeType == 3)
					title = link.firstChild.nodeValue;
			}
		}
		/*--------------------------------------------------------------------*/
		// Find Resource special links (HD and linked page link data) ghooghe
		var resourceSpecialLinks = {};
		resourceSpecialLinks.hd = null;
		resourceSpecialLinks.linkedPage = null;
		for (var i = 0; i < link.parentNode.childNodes.length; i++) {
			if (hasClass(link.parentNode.childNodes[i], 'resource-special-links')) {
				var specialLinksContainer = link.parentNode.childNodes[i];
				for (var j = 0; j < specialLinksContainer.childNodes.length; j++) {
					var childLink = specialLinksContainer.childNodes[j];
					if (hasClass(childLink, 'hd')) {
						resourceSpecialLinks.hd = '' + getAttribute(childLink, 'href');
					}
					
					if (hasClass(childLink, 'linked-page')) {
						resourceSpecialLinks.linkedPage = '' + getAttribute(childLink, 'href');
					}
				}
			}
		} 

		var o = {
			el : link,
			//title:      link.getAttribute('title'),
			title : title,
			player : getPlayer(href),
			options : apply( {}, opts || {}), // break the reference
			content : href,
			description : description,
			resourceSpecialLinks : resourceSpecialLinks
		};
		
		// remove link-level options from top-level options
		var opt, l_opts = [ 'player', 'title', 'height', 'width', 'gallery',
				'description' ];

		for ( var i = 0, len = l_opts.length; i < len; ++i) {
			opt = l_opts[i];
			if (typeof o.options[opt] != 'undefined') {
				o[opt] = o.options[opt];
				delete o.options[opt];
			}
		}

		// HTML options always trump JavaScript options, so do these last
		var rel = link.getAttribute('rel');
		if (rel) {
			// extract gallery name from shadowbox[name] format
			var match = rel.match(RE.gallery);
			if (match)
				o.gallery = escape(match[2]);

			// other parameters
			var params = rel.split(';');
			for ( var i = 0, len = params.length; i < len; ++i) {
				match = params[i].match(RE.param);
				if (match) {
					if (match[1] == 'options') {
						eval('apply(o.options, ' + match[2] + ')');
					} else {
						o[match[1]] = match[2];
					}
				}
			}
		}

		return o;
	};

	/**
	 * Applies the given set of options to those currently in use. Note: Options
	 * will be reset on Shadowbox.open() so this function is only useful after
	 * it has already been called (while Shadowbox is open).
	 * 
	 * @param Object
	 *            opts The options to apply
	 * @return void
	 * @public
	 * @static
	 */
	Shadowbox.applyOptions = function(opts) {
		if (opts) {
			// use apply here to break references
			default_options = apply( {}, options); // store default options
			options = apply(options, opts); // apply options
		}
	};

	/**
	 * Reverts Shadowbox' options to the last default set in use before
	 * Shadowbox.applyOptions() was called.
	 * 
	 * @return void
	 * @public
	 * @static
	 */
	Shadowbox.revertOptions = function() {
		if (default_options) {
			options = default_options; // revert to default options
			default_options = null; // erase for next time
		}
	};

	/**
	 * Opens the given object in Shadowbox. This object may be either an
	 * anchor/area element, or an object similar to the one created by
	 * Shadowbox.buildCacheObj().
	 * 
	 * @param mixed
	 *            obj The object or link element that defines what to display
	 * @return void
	 * @public
	 * @static
	 */
	Shadowbox.open = function(obj, opts) {
		// revert options
		this.revertOptions();
		// is it a link?
		if (isLink(obj)) {
			if (typeof obj.shadowboxCacheKey == 'undefined'
					|| typeof cache[obj.shadowboxCacheKey] == 'undefined') {
				// link element that hasn't been set up before
				// create on-the-fly object
				obj = this.buildCacheObj(obj, opts);
			} else {
				// link element that has been set up before, get from cache
				obj = cache[obj.shadowboxCacheKey];
			}
		}

		// is it already a gallery?
		if (obj.constructor == Array) {
			gallery = obj;
			current = 0;
		} else {
			// create a copy so it doesn't get modified later
			var copy = apply( {}, obj);

			// is it part of a gallery?
			if (!obj.gallery) { // single item, no gallery
				gallery = [ copy ];
				current = 0;
			} else {
				current = null; // reset current
				gallery = []; // clear the current gallery
				var ci;
				for ( var i = 0, len = cache.length; i < len; ++i) {
					ci = cache[i];
					if (ci.gallery) {
						if (ci.content == obj.content
								&& ci.gallery == obj.gallery
								&& ci.title == obj.title) { // compare content, gallery, & title
							current = gallery.length; // key element found
						}
						if (ci.gallery == obj.gallery) {
							gallery.push(apply( {}, ci));
						}
					}
				}
				// if not found in cache, prepend to front of gallery
				if (current == null) {
					gallery.unshift(copy);
					current = 0;
				}
			}
		}

		obj = gallery[current];

		// apply custom options
		if (obj.options || opts) {
			// use apply here to break references
			this.applyOptions(apply(apply( {}, obj.options || {}), opts || {}));
		}

		// filter gallery for unsupported elements
		var match, r;
		for ( var i = 0, len = gallery.length; i < len; ++i) {
			r = false; // remove the element?
			if (gallery[i].player == 'unsupported') { // don't support this at all
				r = true;
			} else if (match = RE.unsupported.exec(gallery[i].player)) { // handle unsupported elements
				if (options.handleUnsupported == 'link') {
					gallery[i].player = 'html';
					// generate a link to the appropriate plugin download
					// page(s)
					var s, a, oe = options.errors;
					switch (match[1]) {
					case 'qtwmp':
						s = 'either';
						a = [ oe.qt.url, oe.qt.name, oe.wmp.url, oe.wmp.name ];
						break;
					case 'qtf4m':
						s = 'shared';
						a = [ oe.qt.url, oe.qt.name, oe.f4m.url, oe.f4m.name ];
						break;
					default:
						s = 'single';
						if (match[1] == 'swf' || match[1] == 'flv')
							match[1] = 'fla';
						a = [ oe[match[1]].url, oe[match[1]].name ];
					}
					var msg = SB.LANG.errors[s].replace(/\{(\d+)\}/g, function(
							m, i) {
						return a[i];
					});
					gallery[i].content = '<div class="shadowbox_message">' + msg + '</div>';
				} else {
					r = true;
				}
			} else if (gallery[i].player == 'inline') { // handle inline elements
				// retrieve the innerHTML of the inline element
				var match = RE.inline.exec(gallery[i].content);
				if (match) {
					var el;
					if (el = SL.get(match[1])) {
						gallery[i].content = el.innerHTML;
					} else {
						SB.raise('Cannot find element with id ' + match[1]);
					}
				} else {
					SB.raise('Cannot find element id for inline content');
				}
			}
			if (r) {
				gallery.splice(i, 1); // remove the element from the gallery
				if (i < current) {
					--current;
				} else if (i == current) {
					// if current is unsupported, look for supported neighbor
					current = i > 0 ? current - 1 : i;
				}
				--i; // decrement to account for splice
				len = gallery.length; // gallery.length has changed!
			}
		}

		// anything left?
		if (gallery.length) {
			// fire onOpen hook
			if (options.onOpen && typeof options.onOpen == 'function') {
				options.onOpen(obj);
			}

			if (!activated) {
				// set initial dimensions & load
				setDimensions(options.initialHeight, options.initialWidth);
				adjustHeight(dims.inner_h, dims.top, false);
				adjustWidth(dims.width, false);
				toggleVisible(loadContent);
			} else {
				loadContent();
			}

			activated = true;
		}
	};

	/**
	 * Jumps to the piece in the current gallery with index num.
	 * 
	 * @param Number
	 *            num The gallery index to view
	 * @return void
	 * @public
	 * @static
	 */
	Shadowbox.change = function(num) {
		if (!gallery)
			return; // no current gallery
		if (!gallery[num]) { // index does not exist
			if (!options.continuous) {
				return;
			} else {
				num = num < 0 ? (gallery.length - 1) : 0; // loop
			}
		}

		if (typeof slide_timer == 'number') {
			clearTimeout(slide_timer);
			slide_timer = null;
			slide_delay = slide_start = 0; // reset slideshow variables
		}
		current = num; // update current

		if (options.onChange && typeof options.onChange == 'function') {
			options.onChange(gallery[current]); // fire onChange handler
		}

		loadContent();
	};

	/**
	 * Jumps to the next piece in the gallery.
	 * 
	 * @return void
	 * @public
	 * @static
	 */
	Shadowbox.next = function() {
		this.change(current + 1);
	};

	/**
	 * Jumps to the previous piece in the gallery.
	 * 
	 * @return void
	 * @public
	 * @static
	 */
	Shadowbox.previous = function() {
		this.change(current - 1);
	};

	/**
	 * Sets the timer for the next image in the slideshow to be displayed.
	 * 
	 * @return void
	 * @public
	 * @static
	 */
	Shadowbox.play = function() {
		if (!hasNext())
			return;
		if (!slide_delay)
			slide_delay = options.slideshowDelay * 1000;
		if (slide_delay) {
			slide_start = new Date().getTime();
			slide_timer = setTimeout( function() {
				slide_delay = slide_start = 0; // reset slideshow
					SB.next();
				}, slide_delay);

			// change play nav to pause
			toggleNav('play', false);
			toggleNav('pause', true);
		}
	};

	/**
	 * Pauses the current slideshow.
	 * 
	 * @return void
	 * @public
	 * @static
	 */
	Shadowbox.pause = function() {
		if (typeof slide_timer == 'number') {
			var time = new Date().getTime();
			slide_delay = Math.max(0, slide_delay - (time - slide_start));

			// any delay left on current slide? if so, stop the timer
			if (slide_delay) {
				clearTimeout(slide_timer);
				slide_timer = 'paused';
			}

			// change pause nav to play
			toggleNav('pause', false);
			toggleNav('play', true);
		}
	};

	/**
	 * Deactivates Shadowbox.
	 * 
	 * @return void
	 * @public
	 * @static
	 */
	Shadowbox.close = function() {
		if (!activated)
			return; // already closed

		// stop listening for keys
		listenKeys(false);
		// hide
		toggleVisible(false);
		// remove the content
		if (content) {
			content.remove();
			content = null;
		}

		// clear slideshow variables
		if (typeof slide_timer == 'number')
			clearTimeout(slide_timer);
		slide_timer = null;
		slide_delay = 0;

		// fire onClose handler
		if (options.onClose && typeof options.onClose == 'function') {
			options.onClose(gallery[current]);
		}

		activated = false;
	};

	/**
	 * Clears Shadowbox' cache and removes listeners and expandos from all
	 * cached link elements. May be used to completely reset Shadowbox in case
	 * links on a page change.
	 * 
	 * @return void
	 * @public
	 * @static
	 */
	Shadowbox.clearCache = function() {
		for ( var i = 0, len = cache.length; i < len; ++i) {
			if (cache[i].el) {
				SL.removeEvent(cache[i].el, 'click', handleClick);
				delete cache[i].el.shadowboxCacheKey; // remove expando
			}
		}
		cache = [];
	};

	/**
	 * Gets an object that lists which plugins are supported by the client. The
	 * keys of this object will be:
	 *  - fla: Adobe Flash Player - qt: QuickTime Player - wmp: Windows Media
	 * Player - f4m: Flip4Mac QuickTime Player
	 * 
	 * @return Object The plugins object
	 * @public
	 * @static
	 */
	Shadowbox.getPlugins = function() {
		return plugins;
	};

	/**
	 * Gets the current options object in use.
	 * 
	 * @return Object The options object
	 * @public
	 * @static
	 */
	Shadowbox.getOptions = function() {
		return options;
	};

	/**
	 * Gets the current gallery object.
	 * 
	 * @return Object The current gallery item
	 * @public
	 * @static
	 */
	Shadowbox.getCurrent = function() {
		return gallery[current];
	};

	/**
	 * Gets the current version number of Shadowbox.
	 * 
	 * @return String The current version
	 * @public
	 * @static
	 */
	Shadowbox.getVersion = function() {
		return version;
	};

	/**
	 * Returns an object containing information about the current client
	 * configuration.
	 * 
	 * @return Object The object containing client data
	 * @public
	 * @static
	 */
	Shadowbox.getClient = function() {
		return client;
	};

	/**
	 * Returns the current content object in use.
	 * 
	 * @return Object The current content object
	 * @public
	 * @static
	 */
	Shadowbox.getContent = function() {
		return content;
	};

	/**
	 * Gets the current dimensions of Shadowbox as calculated by
	 * setDimensions().
	 * 
	 * @return Object The current dimensions of Shadowbox
	 * @public
	 * @static
	 */
	Shadowbox.getDimensions = function() {
		return dims;
	};

	/**
	 * Gets the current dimensions of Shadowbox as calculated by
	 * setDimensions().
	 * 
	 * @return Object The current dimensions of Shadowbox
	 * @public
	 * @static
	 */
	Shadowbox.resizeHeightPE = function(height) {
		adjustHeight(height, dims.top, true, null);
	};

	/**
	 * Handles all Shadowbox exceptions (errors). Calls the exception handler
	 * callback if one is present (see handleException option) or throws a new
	 * exception.
	 * 
	 * @param String
	 *            e The error message
	 * @return void
	 * @public
	 * @static
	 */
	Shadowbox.raise = function(e) {
		if (typeof options.handleException == 'function') {
			options.handleException(e);
		} else {
			throw e;
		}
	};

	Shadowbox.convertVideoURL = function(URL) {
		var U = String(URL);
		switch (true) {
		case Boolean(U.match(/youtube\.com\/watch/i)):
			U = "http://www.youtube.com/v/" + U.split("=")[1];
			break;
		case Boolean(U.match(/dailymotion\.com/i)):
			U = "http://www.dailymotion.com/swf/"
					+ String(U.split("/video/")[1]).split("_")[0];
			break;
		case Boolean(U.match(/metacafe\.com\/watch/i)):
			U = "http://www.metacafe.com/fplayer/"
					+ String(U.split("watch/")[1]).split("/")[0] + "/"
					+ String(U.split("watch/")[1]).split("/")[1] + ".swf";
			break;
		case Boolean(U.match(/europarltv\.europa/i)):
			if (U.split("packageId=").length > 1){
				U = "http://europarltv.europa.eu/swf/EmbedPlayer.swf?vid="
						+ U.split("packageId=")[1]
						+ "&cid=0c8dedcf-1098-46c9-9b85-6f2b0f0b120d&lang="
						+ sLanguage
						+ "&bitrate=512&loop=off&autoplay=off&startVolume=medium&showTitle=on&showBottom=off";
			} else {
				U = "http://europarltv.europa.eu/swf/EmbedPlayer.swf?vid="
					+ U.split("PackageId=")[1]
					+ "&cid=0c8dedcf-1098-46c9-9b85-6f2b0f0b120d&lang="
					+ sLanguage
					+ "&bitrate=512&loop=off&autoplay=off&startVolume=medium&showTitle=on&showBottom=off";
			}
			break;
		}
		return U;
	}

})();;
