(function ($, iTim) {

	var asyncContent = iTim.AsyncContent;

	var options = {
		getDescription: function (link) {
			return $(link).next('.linkDescription').eq(0);
		}
	};

	var extractors = asyncContent.baseController.create().init();

	extractors.set('linkDefault', asyncContent.basePlugin.create({
		extract: function (context, cb) {
			var link = $(context.link);
			$.extend(true, context.extracted, {
				content: {
					title: link.attr('title'),
					href: link.attr('href')
				}
			});
			$.extend(true, context.extracted, $(link).metadata());
			cb();
		}
	}));

	var linkDescriptions = [null];
	extractors.set('description', asyncContent.basePlugin.create({
		extract: function (context, cb) {
			var link = $(context.link);
			var description = context.getDescription(link);
			if (description) {
				context.extracted.content.descriptionCacheKey = linkDescriptions.push(description) - 1;
			}
			cb();
		}
	}));


	$.extend({
		contextual: {
			addExtractor: function (name, extractor) {
				extractor.extractorName = name;
				extractors.set(name, extractor);
			},
			extractors: extractors,
			options: options,
			linkDescriptions: linkDescriptions
		}
	});


	$.extend($.fn, {
		contextual: function (opt) {
			if (typeof opt == 'function') {
				opt = {
					onComplete: opt
				};
			}
			if (!opt) {
				opt = {};
			}
			var context = $.extend({}, opt, options, {
				link: this.get(0),
				extracted: {}
			});
			if (!this.length) {
				if (opt.onComplete) {
					opt.onComplete();
				}
				return {};
			}
			extractors.run('extract', context, function () {
				if (opt.onComplete) {
					opt.onComplete(context.extracted);
				}
			});
			return context.extracted;
		}
	});

})(jQuery, iTim);/**
 * modalOverlay jQuery plugin
 *
 */
(function ($, iTim) {

	/**
	 * Import namespace that handles basic asynchronous processing and implements
	 * basic plugin concept.
	 */
	var asyncContent = iTim.AsyncContent;



	/**
	 * Default configuration for modal overlays.
	 * Every plugin added will append entries in .lang and .plugins properties.
	 * Plugins may define default content properties, too.
	 * Examine during runtime to determine default config as a whole or see
	 * jqueryModalPlugins.js for builtin plugin examples.
	 */
	var options = {

		// whether calls to $.fn.modalOverlay should return first instance instead of jquery result set
		api: false,

		// used to namespace events, data, etc.
		namespace: '.modalOverlay',

		// will be set to the index of the lightbox instance using this options hash
		modalId: null,

		// skin to be used, usually results in a class of the container like "overlayDefault
		skin: 'default',

		// determines whether clicks on the dimmed background should close the overlay
		modal: false,

		// shortcuts to css classes being used by this instance
		classes: {
			container: iTim.Ui.classOverlay,
			bg: 'overlayBg',
			logo: 'overlayLogo',
			loader: 'overlayLoading',
			layer: 'overlayContent',
			content: 'overlayContentInner',
			player: 'overlayPlayer',
			hasModal: iTim.Ui.classOverlayActive,
			preloading: 'overlayPreloading',
			visible: 'overlayVisible',
			inactive: 'overlayItemInactive'
		},

		templates: {
			container: iTim.format('<div class="{0}">{1}</div>', iTim.Ui.classOverlay, iTim.Ui.templateOverlay),
			layer: '<div class="overlayContent"><div class="overlayContentInner">' +
				'<div class="overlayPlayer"><div class="overlayPlayerInner"></div></div></div></div>'
		},

		// augmented by plugins during runtime
		content: {

			// actual content to be displayed
			href: null,

			// name of the player to be used (e.g. "image")
			player: null,
			width: null,
			height: null,

			// if this content is part of a gallery
			gallery: null

		},

		// configurations per plugin
		plugins: {},

		// configuration for this instance's player
		player: {},

		// key-value-pairs of language dependent strings
		// each plugin's .pluginLang property will be merged into this when added
		// example: "buttonClose.text": "Close"
		lang: {}

	};


	var instances = [];
	var currentInstance = null;
	var instanceBefore = null;


	/**
	 * Plugins are stateless in respect to the overlays they augment.
	 * This controller will call a method for each display phase on each plugin.
	 */
	var plugins = asyncContent.baseController.create().init();


	/**
	 * Hash of known players.
	 * Use $.modalOverlay.addPlayer to append players.
	 */
	var players = {};


	/**
	 * Extend this base object via .create({...})
	 */
	var basePlayer = asyncContent.basePlugin.create({
		regexFileExtension: null,
		playerName: null,
		canPlay: function (opt) {
			if (!this.regexFileExtension || !opt.content.href) {
				return false;
			}
			var url = iTim.url(opt.content.href);
			return url.extension.match(this.regexFileExtension);
		},
		options: null,
		opt: function (context) {
			return $.extend(true, this.options, context.opt.player);
		},
		insertHtml: function (context, cb) {
			context.select('player').addClass(iTim.ccase('overlayPlayer-' + this.playerName));
			cb();
		}
	});


	/**
	 * Enables the retrieval of mroe default options from
	 * options hashes.
	 */
	var extractors = asyncContent.baseController.create().init();



	/**
	 * Base object of modalOverlay plugin.
	 */
	var baseModalOverlay = asyncContent.basePlugin.create({

		el: null,
		containerEl: null,
		linkEl: null,
		opt: null,
		initialized: false,
		isVisible: false,


		bindLink: function (linkEl) {
			this.linkEl = $(linkEl).bind('click' + this.opt.namespace, iTim.bind(function (e) {
				this.sendShow(function () {});
				e.preventDefault();
			}, this));
		},


		/**
		 * Lazily create the surrounding container of all overlays and
		 * this instance's overlay.
		 * Binds the click handler to the overlay bg.
		 */
		ensureElements: function () {
			this.containerEl = this.containerEl || $('.' + this.opt.classes.container);
			if (!this.containerEl.length) {
				this.containerEl = $(this.opt.templates.container).appendTo(document.body);
				this.plugins.run('initContainer', this, function () {});
			}
			this.containerEl.find('.' + this.opt.classes.bg).unbind(this.opt.namespace).bind('click' + this.opt.namespace, function (e) {
				if (!currentInstance.opt.modal) {
					currentInstance.sendHide(function () {});
				}
			});
			if (!this.el || !this.el.length) {
				this.el = $(this.opt.templates.layer).appendTo(this.containerEl).data('modalOverlay' + this.opt.namespace, this);
			}
		},


		/**
		 * If this.opt.classes contains the key given in sel, the class specified there is used, else
		 * sel is treated as a full selector.
		 * Searches this overlay (including itself and travelling upwards) for the first occurrance of that selector
		 * and returns it.
		 */
		select: function (sel) {
			return this.opt.classes[sel] ? $(this.el).find('.' + this.opt.classes[sel]).andSelf().closest('.' + this.opt.classes[sel]) : $(this.el).find(sel).andSelf().closest(sel);
		},


		/**
		 * Calls this.select(sel) and toggles its this.opt.classes.inactive on it.
		 */
		toggleItem: function (sel, active) {
			if (active) {
				return this.activateItem(sel);
			} else {
				return this.deactivateItem(sel);
			}
		},

		activateItem: function (sel) {
			return this.select(sel).removeClass(this.opt.classes.inactive);
		},

		deactivateItem: function (sel) {
			return this.select(sel).addClass(this.opt.classes.inactive);
		},



		insertHtml: function (who, cb) {
			// insert basic html
			this.ensureElements();
			$(document.body).addClass(this.opt.classes.hasModal);
			this.containerEl.addClass(iTim.ccase('overlay-' + this.opt.skin));
			cb();
		},

		preload: function (who, cb) {
			// preload and asynchronously return with calling cb() if needed
			this.el.addClass(this.opt.classes.preloading);
			cb();
		},

		insertContent: function (who, cb) {
			// insert actual content; this should result in final layer dimensions
			this.el.removeClass(this.opt.classes.preloading);
			cb();
		},

		beforeTransitionIn: function (who, cb) {
			// plugins: last chance to set visual properties without being seen (position)
			cb();
		},

		transitionIn: function (who, cb) {
			// plugins: animate showing
			this.el.addClass(this.opt.classes.visible);
			cb();
		},

		afterTransitionIn: function (who, cb) {
			// show this
			cb();
		},

		beforeTransitionOut: function (who, cb) {
			cb();
		},

		transitionOut: function (who, cb) {
			// plugins: animate hiding
			cb();
		},

		afterTransitionOut: function (who, cb) {
			// hide this
			this.el.hide();
			if (!currentInstance || (currentInstance && currentInstance.opt.skin != this.opt.skin)) {
				this.containerEl.removeClass(iTim.ccase('overlay_' + this.opt.skin));
			}
			this.removeHtml();
			cb();
		},

		removeHtml: function () {
			// remove all html
			this.el.remove();
			this.el = null;
		},

		hideContainer: function (who, cb) {
			$(document.body).removeClass(this.opt.classes.hasModal);
			cb();
		},

		sendHideForChange: function (cb) {
			this.isVisible = false;
			currentInstance = null;
			this.plugins.runSequence([
				'beforeTransitionOut',
				'transitionOut',
				'afterTransitionOut'
			], this, cb);
		},

		sendHide: function (cb) {
			this.isVisible = false;
			currentInstance = null;
			this.plugins.runSequence([
				'beforeTransitionOut',
				'transitionOut',
				'afterTransitionOut',
				'hideContainer'
			], this, cb);
		},

		sendShow: function (cb) {

			// this layer is already visible, so do nothing
			if (this.isVisible) {
				cb();
				return;
			}

			// another layer is currently visible, hide it, then show this layer
			if (currentInstance && currentInstance != this) {
				lastInstance = currentInstance;
				this.isVisible = true;
				this.plugins.runSequence([
					'insertHtml',
					'preload',
					'insertContent'
				], this, iTim.bind(function () {
					lastInstance.sendHideForChange();
					currentInstance = this;
					this.plugins.runSequence([
						'beforeTransitionIn',
						'transitionIn',
						'afterTransitionIn'
					], this, cb);
				}, this));

			// no other layer is visible, show this layer
			} else if (!currentInstance) {
				currentInstance = this;
				this.plugins.runSequence([
					'insertHtml',
					'preload',
					'insertContent',
					'beforeTransitionIn',
					'transitionIn',
					'afterTransitionIn'
				], this, cb);
			}
		},


		/**
		 * Send an event on this instance's dom container.
		 */
		trigger: function (name, args) {
			args = [$.extend({
				modalOverlay: this
			}, args || {})];
			return this.el.trigger(name, args);
		},


		init: function () {

			if (this.initialized) {
				return;
			}

			// create a new plugin controller from global plugins controller
			this.plugins = plugins.create();

			var player = this.player = players[this.opt.content.player];

			// insert the overlay and its player as first elements into the plugin controller
			this.plugins.before('player', player);
			this.plugins.before('container', this);

			// disable all plugins, that have been removed from the options hash
			iTim.each(this.opt.plugins, function (i, plugin) {
				if (!plugin || plugin.disabled) {
					this.plugins.set(i, false);
				}
			}, this);

			this.initialized = true;

		}

	});





	/**
	 * Make plugin configuration and base objects public in $.modalOverlay
	 */
	var common = {

		baseModalOverlay: baseModalOverlay,
		basePlayer: basePlayer,
		players: players,
		instances: instances,
		options: options,
		plugins: plugins,
		extractors: extractors,

		show: function (opt, cb) {
			return this.setup(opt, function (modal) {
				modal.sendShow(cb);
			});
		},

		setup: function (opt, cb) {
			var modal = null;
			if (!isNaN(+opt)) {
				modal = instances[opt];
			} else if (opt && !isNaN(+opt.modalId)) {
				modal = instances[opt.modalId];
			} else if (opt.opt && !isNaN(+opt.opt.modalId)) {
				modal = opt;
			}
			if (!modal) {
				modal = iTim.object(baseModalOverlay);
				opt.modalId = instances.push(modal) - 1;
				this.extract(opt, function (opt) {
					modal.opt = $.extend(true, {}, options, opt);
					modal.init();
					if (cb) {
						cb(modal);
					}
				});
			}
			return modal;
		},

		addPlayer: function (name, player) {
			player.playerName = name;
			this.players[name] = player;
		},

		addPlugin: function (name, plugin) {
			this.plugins.set(name, plugin);
			$.extend(true, this.options, plugin.contextOptions || {});
			if (plugin.pluginLang) {
				iTim.each(plugin.pluginLang, function (key, value) {
					key = iTim.format('{0}.{1}', name, key);
					this.options.lang[key] = value;
				}, this);
			}
			this.options.plugins[name] = plugin.pluginOptions || {};
		},


		/**
		 * Add an extractor to be run on the option hashes when an overlay
		 * is being instantiated.
		 */
		addExtractor: function (name, extractor) {
			extractor.extractorName = name;
			extractors.set(name, extractor);
		},


		/**
		 * Runs the extractors on an options hash given by context.
		 * Pass an onComplete callback to be run on completion of
		 * possible asynchronous tasks.
		 */
		extract: function (context, onComplete) {
			if (typeof context == 'string') {
				context = {
					opt: {
						content: {
							href: context
						}
					}
				};
			}
			if (!context.opt) {
				context = {
					opt: context
				};
			}
			extractors.run('extract', context, function () {
				onComplete(context.opt);
			});
		},

		hide: function (cb) {
			if (currentInstance) {
				currentInstance.sendHide(cb);
			}
		},

		get: function (opt) {
			return currentInstance;
		}

	};
	$.extend({
		modalOverlay: common
	});


	/**
	 * Adds $(...).modalOverlay plugin method to be called on links.
	 */
	$.extend($.fn, {

		modalOverlay: function (opt) {

			opt = opt || {};
			var jq = this;
			var firstInstance = null;

			// iterate over result set
			this.each(function (i) {
				var el = $(this);
				var setup = $.extend(true, {}, options, opt);
				var instance = el.data('modalOverlay' + setup.namespace);
				if (!instance) {
					el.contextual(function (linkOpt) {
						setup = $.extend(true, {}, opt, linkOpt);
						instance = common.setup(setup);
						el.data('modalOverlay' + setup.namespace, instance);
						instance.bindLink(el);
					});
				}
				if (i == 0) {
					firstInstance = instance;
				}
			});

			// if control object should be returned
			if (opt.api) {
				return firstInstance;
			} else {
				return jq;
			}
		}

	});



})(jQuery, iTim);(function ($, iTim) {

	var asyncContent = iTim.AsyncContent;
	var modal = $.modalOverlay;

	$.modalOverlay.addPlayer('ajax', modal.basePlayer.create({
		regexFileExtension: /\.(html|jsp|php|shtml|htm|do)$/,
		options: {
			ajax: {

			}
		},
		preload: function (context, cb) {
			if (context.opt.content.ajaxContent) {
				cb();
				return;
			}
			var opt = $.extend({}, this.opt(context).ajax, {
				url: context.opt.content.href,
				complete: function (xhr) {
					context.opt.content.ajaxContent = xhr.responseText;
					cb();
				}
			});
			$.ajax(opt);
		},
		insertHtml: function (context, cb) {
			context.select('player').html('<div class="overlayPlayerContent"/>');
			this.__prototype.insertHtml.apply(this, arguments);
		},
		insertContent: function (context, cb) {
			context.select('.overlayPlayerContent').html(context.opt.content.ajaxContent);
			cb();
		}
	}));


})(jQuery, iTim);(function ($, iTim) {

	var asyncContent = iTim.AsyncContent;
	var modal = $.modalOverlay;

	$.modalOverlay.addPlayer('image', modal.basePlayer.create({
		regexFileExtension: /\.(jpe?g|png|gif)$/,
		preload: function (context, cb) {
			var image = new Image();
			var content = context.opt.content;
			image.onload = function () {
				content.width = content.width || this.width;
				content.height = content.height || this.height;
				cb();
			};
			image.src = context.opt.content.href;
		},
		insertContent: function (context, cb) {
			var content = context.opt.content;
			var alt = content.alt || content.title || '';
			context.select('player').append(iTim.format('<img class="overlayPlayerContent" alt="{0}" src="{1}" />', alt, content.href));
			cb();
		}
	}));


})(jQuery, iTim);(function ($, iTim) {

	var asyncContent = iTim.AsyncContent;
	var modal = $.modalOverlay;

	$.modalOverlay.addPlayer('inline', modal.basePlayer.create({
		canPlay: function (opt) {
			return opt.content.href && iTim.url(opt.content.href).hash == opt.content.href && $(opt.content.href).length;
		},
		options: {
		},
		preload: function (context, cb) {
			var content = $(context.opt.content.href);
			if (content.is(':visible')) {
				context.opt.content.width = context.opt.content.width || content.width();
				context.opt.content.height = context.opt.content.height || content.height();
			}
			cb();
		},
		insertHtml: function (context, cb) {
			context.select('player').html('<div class="overlayPlayerContent"/>');
			this.__prototype.insertHtml.apply(this, arguments);
		},
		insertContent: function (context, cb) {
			$(context.opt.content.href).clone(true).appendTo(context.select('.overlayPlayerContent'));
			cb();
		}
	}));


})(jQuery, iTim);(function ($, iTim) {



	var asyncContent = iTim.AsyncContent;
	var basePlugin = asyncContent.basePlugin;
	var modal = $.modalOverlay;


	/**
	 * Adds an extractor that tries to determine basic properties from
	 * the content url.
	 * Both Query String and Hash are searched for the parameters:
	 * - modalWidth and -Height
	 * - modalContent, if the base file should not be used as target content
	 * - modalParent, if another page should be used as base for the overlay
	 * - modalOptions for stored options to be used
	 */
	modal.addExtractor('modalHrefFromDeeplink', basePlugin.create({
		extract: function (context, cb) {
			var content = context.opt.content;
			var url = iTim.url(content.href);
			var inherits = url.params.modalOptions || context.opt.inherits;
			if (url.params.modalContent) {
				content.deepLinkParent = url.pathname;
				content.deepLinkHref = content.href;
				content.href = url.params.modalContent;
			}
			if (url.params.modalParent) {
				content.deepLinkParent = url.params.modalParent;
			}
			content.width = +url.params.modalWidth || content.width;
			content.height = +url.params.modalHeight || content.height;
			if (!content.width) {
				delete content.width;
			}
			if (!content.height) {
				delete content.height;
			}
			if (inherits) {
				context.opt[iTim.StoredOptions.keyInherits] = inherits;
			}
			cb();
		}
	}));


	/**
	 * Resolve all references to stored options if applicable.
	 */
	modal.addExtractor('storedOptions', basePlugin.create({
		extract: function (context, cb) {
			context.opt = iTim.StoredOptions.get(context.opt);
			cb();
		}
	}));


	/**
	 * Build galleries for all contents, that contain a "gallery" property.
	 */
	var galleries = modal.galleries = {};
	modal.addExtractor('gallery', basePlugin.create({
		extract: function (context, cb) {
			var content = context.opt.content;
			if (!content.gallery) {
				cb();
				return;
			}
			var gallery = galleries[content.gallery] = galleries[content.gallery] || [];
			gallery.push(context.opt.modalId);
			cb();
		}
	}));


	/**
	 * If no player is given by the content, test each player if it applies and
	 * use the first one matching.
	 */
	modal.addExtractor('defaultPlayer', basePlugin.create({
		extract: function (context, cb) {
			var content = context.opt.content;
			if (content.player) {
				cb();
				return;
			}
			var url = iTim.url(content.href);
			$.each(modal.players, function (name) {
				if (this.canPlay(context.opt)) {
					content.player = name;
					return false;
				}
			});
			cb();
		}
	}));


	/**
	 * The basic plugin to use for navigation targets. These can be toolbars
	 * of overlays.
	 * Navigation buttons will be appended to these.
	 */
	modal.baseNavigationPlugin = basePlugin.create({
		navigationName: 'default',
		where: 'append',
		select: 'content',
		isToolbar: true,
		initContainer: function (context, cb) {
			cb();
		},
		insertHtml: function (context, cb) {
			context.select(this.select)[this.where](iTim.format('<div class="overlayNavigation {0} {1}"><div class="overlayNavigationInner"></div></div>', iTim.ccase('overlayNavigation_' + this.navigationName), this.isToolbar ? 'overlayToolbar' : ''));
			cb();
		}
	});


	/**
	 * Toolbar to be shown above of the player.
	 */
	modal.addPlugin('toolbarTop', modal.baseNavigationPlugin.create({
		navigationName: 'top',
		where: 'prepend'
	}));


	/**
	 * Toolbar to be shown below the player.
	 */
	modal.addPlugin('toolbarBottom', modal.baseNavigationPlugin.create({
		navigationName: 'bottom'
	}));


	/**
	 * A navigation to be shown overlaying the whole content in most cases.
	 * As this is no toolbar, no print buttons, etc. will be appended.
	 */
	modal.addPlugin('navigationAbove', modal.baseNavigationPlugin.create({
		select: 'player',
		navigationName: 'above',
		isToolbar: false
	}));


	/**
	 * Appends shadow elements to the layer consisting of images that scale
	 * with the layer content. Disable this, if not needed.
	 */
	modal.addPlugin('shadow', basePlugin.create({
		pluginOptions: {
			t: '/swp/media/images_dokumente/00-layout/imagejquerymodal/shadow/shadowTop.png',
			rt: '/swp/media/images_dokumente/00-layout/imagejquerymodal/shadow/shadowRightTop.png',
			r: '/swp/media/images_dokumente/00-layout/imagejquerymodal/shadow/shadowRight.png',
			rb: '/swp/media/images_dokumente/00-layout/imagejquerymodal/shadow/shadowRightBottom.png',
			b: '/swp/media/images_dokumente/00-layout/imagejquerymodal/shadow/shadowBottom.png',
			lb: '/swp/media/images_dokumente/00-layout/imagejquerymodal/shadow/shadowLeftBottom.png',
			l: '/swp/media/images_dokumente/00-layout/imagejquerymodal/shadow/shadowLeft.png',
			lt: '/swp/media/images_dokumente/00-layout/imagejquerymodal/shadow/shadowLeftTop.png'
		},
		insertHtml: function (context, cb) {
			var html = '';
			$.each(this.opt(context), function (key) {
				html += iTim.format('<img alt="" src="{1}" class="shadow {0}" />', 'shadow-' + key, this);
			});
			context.el.prepend(iTim.format('<div class="overlayShadow">{0}</div>', html));
			cb();
		}
	}));


	/**
	 * Appends the title container to each toolbar.
	 * Disabled, if content object does not contain a title property.
	 */
	modal.addPlugin('title', basePlugin.create({
		insertHtml: function (context, cb) {
			context.select('.overlayToolbar > .overlayNavigationInner').prepend('<div class="overlayTitle"></div>');
			cb();
		},
		insertContent: function (context, cb) {
			var content = context.opt.content;
			context.toggleItem('.overlayTitle', content.title).html(content.title || '');
			cb();
		}
	}));


	/**
	 * Adds a simple textual pager to to each toolbar.
	 */
	modal.addPlugin('galleryPaging', basePlugin.create({
		pluginOptions: {},
		pluginLang: {
			pageOf: 'Page <span class="overlayPage overlayPageActive">{0}</span> of <span class="overlayPageNumber">{1}</span>'
		},
		insertHtml: function (context, cb) {
			var nav = context.select('.overlayToolbar > .overlayNavigationInner');
			var paging = $('<div class="overlayPaging"></div>').appendTo(nav);
			cb();
		},
		insertContent: function (context, cb) {
			var content = context.opt.content;
			var gallery = galleries[content.gallery];
			var galleryEl = context.toggleItem('.overlayPaging', !!content.gallery);
			if (gallery) {
				galleryEl.html(iTim.format(this.lang(context, 'pageOf'), $.inArray(context.opt.modalId, gallery) + 1, galleries[content.gallery].length));
			}
			cb();
		}
	}));


	/**
	 * Base plugin for buttons, that should be appended to all toolbars.
	 */
	var baseButton = modal.baseButton = basePlugin.create({
		buttonName: 'default',
		onClick: null,
		pluginLang: {},
		insertHtml: function (context, cb) {
			var button = $(iTim.format('<div class="overlayButton {0}"><div class="overlayIcon" title="{1}"></div><div class="overlayText">{1}</div></div>', iTim.ccase('overlayButton_' + this.buttonName), this.lang(context, 'text'))).appendTo(context.select('.overlayToolbar > .overlayNavigationInner'));
			button.bind('click', iTim.bind(function (e) {
				this.onClick(context);
			}, this));
			cb();
		}
	});


	/**
	 * Adds a print button to all toolbars.
	 * Calls player's print() method on click if exists, else window's print() function.
	 */
	modal.addPlugin('buttonPrint', baseButton.create({
		buttonName: 'print',
		contextOptions: {
			content: {
				printable: true
			}
		},
		pluginLang: {
			text: 'Print'
		},
		onClick: function (context) {
			if (context.player.print) {
				context.player.print();
			} else {
				print();
			}
		},
		insertContent: function (context, cb) {
			context.toggleItem('.overlayButtonPrint', context.opt.content.printable);
			cb();
		}
	}));


	/**
	 * If content specifies a download property, that url will be opened in
	 * the current window.
	 */
	modal.addPlugin('buttonDownload', baseButton.create({
		buttonName: 'download',
		pluginLang: {
			text: 'Download'
		},
		onClick: function (context) {
			location = context.opt.content.downloadHref;
		},
		contextOptions: function () {
			content: {
				downloadHref: null
			}
		},
		insertContent: function (context, cb) {
			context.toggleItem('.overlayButtonDownload', !!context.opt.content.downloadHref);
			cb();
		}
	}));


	/**
	 * If a gallery is specfied, buttons to move between that galleries elements are
	 * added to all navigations inside the given container.
	 */
	modal.addPlugin('galleryNav', basePlugin.create({
		pluginOptions: {
			hotKeys: true,
			isCircular: true
		},
		pluginLang: {
			next: 'Next',
			previous: 'Previous'
		},
		insertHtml: function (context, cb) {
			var nav = context.select('.overlayNavigationInner');
			var gallery = galleries[context.opt.content.gallery];
			$(iTim.format('<div class="overlayButton overlayButtonNext" title="{0}"><div class="overlayIcon"></div><div class="overlayText">{0}</div></div>', this.lang(context, 'next'))).appendTo(nav).bind('click', function (e) {
				var index = $.inArray(context.opt.modalId, gallery);
				modal.setup(gallery[(index + 1) % gallery.length]).sendShow();
			});
			$(iTim.format('<div class="overlayButton overlayButtonPrevious" title="{0}"><div class="overlayIcon"></div><div class="overlayText">{0}</div></div>', this.lang(context, 'previous'))).appendTo(nav).bind('click', function (e) {
				var index = $.inArray(context.opt.modalId, gallery);
				modal.setup(gallery[(index + gallery.length - 1) % gallery.length]).sendShow();
			});
			cb();
		},
		insertContent: function (context, cb) {
			context.toggleItem('.overlayButtonNext, .overlayButtonPrevious', !!context.opt.content.gallery);
			cb();
		}
	}));


	/**
	 * Appends a close button to each navigation.
	 */
	modal.addPlugin('buttonClose', baseButton.create({
		buttonName: 'close',
		pluginLang: {
			text: 'Close'
		},
		onClick: function (context) {
			context.sendHide();
		}
	}));


	/**
	 * Adds the description container
	 */
	modal.addPlugin('description', basePlugin.create({
		where: 'append',
		method: 'clone',
		insertHtml: function (context, cb) {
			context.select('content')[this.where]('<div class="overlayDescription"></div>');
			cb();
		},
		insertContent: function (context, cb) {
			var description = $.contextual.linkDescriptions[context.opt.content.descriptionCacheKey];
			context.toggleItem('.overlayDescription', !!(description && description.length));
			if (!description) {
				cb();
				return;
			}
			if (this.method == 'clone') {
				description = description.clone(true);
			}
			context.select('.overlayDescription')[this.where](description);
			cb();
		}
	}));


	/**
	 * TODO: Shows a loader
	 */
	modal.addPlugin('loader', basePlugin.create({
		insertHtml: function (context, cb) {
			cb();
		},
		insertContent: function (context, cb) {
			cb();
		}
	}));


	/**
	 * Constrains all content inside overlayContentInner to the width of the content if defined.
	 */
	modal.addPlugin('constrainToContentWidth', basePlugin.create({
		beforeTransitionIn: function (context, cb) {
			var content = context.select('content');
			if (context.opt.content.width) {
				$('<div class="overlayConstrainToContentWidth"/>').css({
					width: context.opt.content.width + 'px'
				}).append(content.children()).appendTo(content);
			}
			cb();
		}
	}));



	/**
	 * TODO: implement
	 * Resizes the content if possible.
	 */
	modal.addPlugin('resize', basePlugin.create({
		pluginOptions: {

			// possible values: aspect, drag, force
			method: 'resize'

		},
		pluginLang: {
			drag: 'Drag to pan.'
		},
		insertHtml: function (context, cb) {
			cb();
		},
		beforeTransitionIn: function (context, cb) {
			cb();
		}
	}));




	/**
	 * Positions the layer inside the given constraints
	 */
	modal.addPlugin('position', basePlugin.create({
		pluginOptions: {

			// possible values: screen, else this is treated as a selector
			constrainTo: 'screen',

			// possible values: center, else a left-/top-based position in px
			position: 'center',

			// override for vertical direction
			verticalConstrainTo: null,
			verticalPosition: null,

			// override for horizontal direction
			horizontalConstrainTo: null,
			horizontalPosition: null

		},
		beforeTransitionIn: function (context, cb) {

			var width = context.el.outerWidth();
			var height = context.el.outerHeight();

			var verticalConstrainTo = this.opt(context).constrainTo;
			var horizontalConstrainTo = verticalConstrainTo;

			var verticalPosition = this.opt(context).position;
			var horizontalPosition = verticalPosition;

			if (this.opt(context).verticalConstrainTo != null) {
				verticalConstrainTo = this.opt(context).verticalConstrainTo;
			}
			if (this.opt(context).verticalPosition != null) {
				verticalPosition = this.opt(context).verticalPosition;
			}
			if (this.opt(context).horizontalConstrainTo != null) {
				horizontalConstrainTo = this.opt(context).horizontalConstrainTo;
			}
			if (this.opt(context).horizontalPosition != null) {
				horizontalPosition = this.opt(context).horizontalPosition;
			}

			var left = 0;
			var constrainWidth = $(window).width();

			if (horizontalConstrainTo == 'screen') {
				left += $(document).scrollLeft();
			} else {
				constrainWidth = $(horizontalConstrainTo).width();
			}

			if (horizontalPosition == 'center') {
				left += Math.max(0, (constrainWidth - width) / 2) >>> 0;
			} else {
				left += horizontalPosition;
			}

			var top = 0;
			var constrainHeight = $(window).height();

			if (verticalConstrainTo == 'screen') {
				top += $(document).scrollTop();
			} else {
				constrainHeight = $(verticalConstrainTo).height();
			}

			if (verticalPosition == 'center') {
				top += Math.max(0, (constrainHeight - height) / 2) >>> 0;
			} else {
				top += verticalPosition;
			}

			context.el.css({
				top: top + 'px',
				left: left + 'px'
			});

			cb();
		}
	}));


	/**
	 * Fades in and out.
	 * Resets opacity styles on complete.
	 */
	modal.addPlugin('transitions', basePlugin.create({
		pluginOptions: {
			duration: 0.4 * 1000,
			easing: 'swing'
		},
		beforeTransitionIn: function (context, cb) {
			context.el.addClass('overlayTransitioning').css({
				opacity: 0,
				visibility: 'visible'
			});
			cb();
		},
		transitionIn: function (context, cb) {
			context.el.animate({
				opacity: 1
			}, {
				duration: this.opt(context).duration,
				easing: this.opt(context).easing,
				complete: cb
			});
		},
		afterTransitionIn: function (context, cb) {
			context.el.removeClass('overlayTransitioning').css({
				opacity: '',
				visibility: ''
			});
			cb();
		},
		beforeTransitionOut: function (context, cb) {
			context.el.addClass('overlayTransitioning');
			cb();
		},
		transitionOut: function (context, cb) {
			context.el.animate({
				opacity: 0
			}, {
				duration: this.opt(context).duration,
				easing: this.opt(context).easing,
				complete: cb
			});
		}
	}));

})(jQuery, iTim);
