(function() {
	if (typeof MooTools == 'undefined') throw "MooTools is required for this Plug-In";
	if (typeof Definiens == 'undefined') Definiens = new Hash();
	
	function getScrollBarWidth() {
		var inner, outer, w1, w2;
		
		inner = document.createElement('p');
		inner.style.width = '100%';
		inner.style.height = '200px';

		outer = document.createElement('div');
		outer.style.position = 'absolute';
		outer.style.top = '0px';
		outer.style.left = '0px';
		outer.style.visibility = 'hidden';
		outer.style.width = '200px';
		outer.style.height = '150px';
		outer.style.overflow = 'hidden';
		outer.appendChild (inner);

		document.body.appendChild (outer);
		w1 = inner.offsetWidth;
		outer.style.overflow = 'scroll';
		w2 = inner.offsetWidth;
		if (w1 == w2) w2 = outer.clientWidth;

		document.body.removeChild (outer);

		return (w1 - w2);
	}
	
	Definiens.extend({
		Ui: {
			_Layer: new Class({
				_getLayerStack: function() {
					return window.retrieve('definiens.ui._layer.'+this._layerObject+'::stack') || [];
				},
				
				_addToLayerStack: function() {
					window.store('definiens.ui._layer.'+this._layerObject+'::stack', this._getLayerStack().combine([this]));
				},
				
				_removeFromLayerStack: function() {
					window.store('definiens.ui._layer.'+this._layerObject+'::stack', this._getLayerStack().filter(function (item) { return item !== this; }.bind(this)));
				},
				
				_pushLayer: function() {
					var highestLayer = 0;
					
					this._getLayerStack().each(function(item) {
						var zIndex = parseInt(item[this._layerObject].getStyle('z-index'), 10);
						if (zIndex > highestLayer) highestLayer = zIndex;
					}, this);
					
					this[this._layerObject].setStyle('z-index', highestLayer+1);
				}
			})
		}
	});
	
	Definiens.extend({
		Ui: {
			
			Modalizer: new Class({
				
				Implements: [Events, Options, Definiens.Ui._Layer],
				
				options: {
					color: '#000000',
					opacity: 0.6
				},
				
				container: null,
				shadow: null,
				documentBody: null,
				locked: false,
				_layerObject: 'shadow',
				
				initialize: function(options) {
					this.setOptions(options);
					
					this.documentBody = $$('body')[0];
					if (!("container" in this.options)) this.options.container = this.documentBody;
					
					this._initializeDom();
					this._initializeEvents();
					this._addToLayerStack();
				},
				
				_initializeDom: function() {
					this.container = this.options.container;
					this.shadow = new Element('div', {
						'class': 'definiens-ui-modalizer-shadow',
						styles: {
							position: 'absolute',
							opacity: 0,
							display: 'none',
							'background-color': this.options.color,
							'z-index': 9999
						}
					});
					this.shadow.inject(this.container);
				},
				
				_initializeEvents: function() {
					var update = this.update.bind(this),
						container = this.container;
					if (this.container === this.documentBody) container = window;
					container.addEvents({
						scroll: update,
						resize: update,
						keypress: function(e) {
							if (!this.displayed || this.locked || !['esc'].contains(e.key)) return;
							this.fade('out');
						}.bind(this)
					});
					this.shadow.addEvent('click', function() {
						if (this.locked) return;
						this.fade('out');
					}.bind(this));
				},
				
				update: function() {
					if (!this.displayed) return;
					var size = this.container.getSize(),
						scroll = this.container.getScroll();
					this.shadow.setStyles({
						width: size.x,
						height: size.y,
						top: scroll.y,
						left: scroll.x
					});
				},
				
				show: function() {
					this.shadow.setStyles({
						opacity: this.options.opacity,
						display: 'block'
					});
					this.displayed = true;
					this.update();
					this._pushLayer();
					this.fireEvent('show');
				},
				
				hide: function() {
					this.shadow.setStyles({
						opacity: 0,
						display: 'none'
					});
					this.displayed = false;
					this.fireEvent('hide');
				},
				
				fade: function(/* String */ mode) {
					var shadow = this.shadow
					switch (mode.toLowerCase()) {
						case 'in':
							this.shadow.fade(0, this.options.opacity);
							this.shadow.setStyle('display', 'block');
							this.displayed = true;
							this.update();
							this._pushLayer();
							this.fireEvent('fade:in');
							break;
						case 'out':
							this.shadow.fade(this.options.opacity, 0);
							this.displayed = false;
							this.fireEvent('fade:out');
							break;
					}
				},
				
				lock: function() {
					this.locked = true;
				},
				
				unlock: function() {
					this.locked = false;
				}
			}),
			
			Lightbox: new Class({
				
				Implements: [Events, Options, Definiens.Ui._Layer],
				
				options: {
					linkMarker: 'lightbox',
					links: [],
					width: 650,
					height: 540,
					loadingClass: 'loading',
					printClass: 'printable',
					noControls: false,
					fx: {
						duration: 500,
						transition: 'sine:out'
					},
					noCache: true,
					padding: 20,
					titleAttribute: 'title'
				},
				
				l10n: {
					next: 'Next',
					previous: 'Previous',
					print: 'Print',
					of: 'of',
					close: 'Close'
				},
				
				_layerObject: 'container',
				
				displayed: false,
				links: [],
				documentBody: null,
				modalizer: null,
				container: null,
				requestPipe: null,
				currentIndex: 0,
				scrollBarWidth: 0,
				layoutCallbacks: [],
				
				/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
				  	Initializations
				 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
				initialize: function(/* Object */ options) {
					this.setOptions(options);
					
					this.scrollBarWidth = getScrollBarWidth();
					
					if (options && typeof options.l10n === 'object') $extend(this.l10n, options.l10n);
					
					this.documentBody = $$('body')[0];
					var overruleExistingInstances = true;
					if (this.options.links.length === 0) {
						this.scanPage();
						this.options.noControls = true;
						overruleExistingInstances = false;
					} else {
						this.links = this.options.links;
					}
					
					this.links = this.links.filter(function(link) {
						var existingInstance = link.retrieve('definiens:ui:lightbox');
						if (existingInstance == null) return true;
						if (overruleExistingInstances) existingInstance.unBind(link);
						return link.retrieve('definiens:ui:lightbox') == null;
					});
					
					//if (!this.links.length) return;
					
					this._initializeModalizer();
					this._initializeDom();
					this._initializeRequestPipe();
					
					this._initializeEvents();
					this.links.each(function(link) {
						link.store('definiens:ui:lightbox', this);
					});
					this._addToLayerStack();
				},
				
				_initializeModalizer: function() {
					this.modalizer = new Definiens.Ui.Modalizer();
				},
				
				_initializeDom: function() {
					this.container = new Element('div', {
						'class': 'definiens-ui-lightbox-container',
						'styles': {
							position: 'absolute',
							opacity: 0,
							'z-index': 10000
						}
					}).inject(this.documentBody);
					
					this.titleBar = new Element('div', {
						'class': 'definiens-ui-lightbox-titlebar'
					}).inject(this.container);
					
					['prepend', 'center', 'append'].each(function(pos) {
						this['title'+pos] = new Element('div', {
							'class': 'definiens-ui-lightbox-titlebar-'+pos
						}).inject(this.titleBar, pos == 'prepend' ? 'top' : 'bottom');
					}, this);
					
					this.title = new Element('span', {
						'class': 'definiens-ui-lightbox-title'
					}).inject(this.titlecenter);
					
					this.closeButton = new Element('a', {
						'class': 'definiens-ui-lightbox-button definiens-ui-lightbox-button-close',
						'href': '#',
						'title': this.l10n.close
					}).inject(this.titleBar);
					
					this.content = new Element('div', {
						'class': 'definiens-ui-lightbox-content',
						styles: {
							//overflow: 'hidden'
						}
					}).inject(this.container);
					
					/*
					this.contentInner = new Element('div', {
							styles: {
								'text-align': 'left'
							}
						}).inject(
						new Element('td', {
								align: 'center'
							}).inject(
							new Element('tr').inject(
								new Element('table', {
									cellpadding: 0,
									cellspacing: 0,
									border: 0,
									valign: 'center',
									height: '100%',
									width: '100%'
								}).inject(this.content)
							)
						)
					);
					*/
					
					this.contentInner = new Element('div', {
						styles: {
							'text-align': 'left'
						}
					}).inject(this.content);
					
					this.footerBar = new Element('div', {
						'class': 'definiens-ui-lightbox-footerbar',
						'styles': {
							clear: 'both',
							position: 'relative'
						}
					}).inject(this.container);
					
					['prepend', 'center', 'append'].each(function(pos) {
						this['footer'+pos] = new Element('div', {
							'class': 'definiens-ui-lightbox-footerbar-'+pos
						}).inject(this.footerBar, pos == 'prepend' ? 'top' : 'bottom');
					}, this);
					
					this.counter = new Element('span', {
						'class': 'definiens-ui-lightbox-counter',
						'styles': {
							opacity: this.options.noControls ? 0 : 1
						}
					}).inject(this.footercenter);
					
					this.controlWrapper = new Element('div', {
						'class': 'definiens-ui-lightbox-controls'
					}).inject(this.footercenter);
					
					this.printButton = new Element('a', {
						'class': 'definiens-ui-lightbox-button definiens-ui-lightbox-button-print',
						'href': '#',
						'html': this.l10n.print
					}).inject(this.controlWrapper);
					
					this.previousButton = new Element('a', {
						'class': 'definiens-ui-lightbox-button definiens-ui-lightbox-button-previous',
						'href': '#',
						'html': this.l10n.previous,
						'styles': {
							opacity: this.options.noControls ? 0 : 1
						}
					}).inject(this.controlWrapper);
					
					this.nextButton = new Element('a', {
						'class': 'definiens-ui-lightbox-button definiens-ui-lightbox-button-next',
						'href': '#',
						'html': this.l10n.next,
						'styles': {
							opacity: this.options.noControls ? 0 : 1
						}
					}).inject(this.controlWrapper);
				},
				
				_initializeEvents: function() {
					var positionCallback = this.position.bind(this);
					
					this.links.each(function(link, i) {
						var callback = function(e) {
							this._onLinkClick(e, link.get('href'), i, link);
						}.bind(this),
							attachedEvent = link.retrieve('definiens:lightbox:link:event:click');
						
						if (attachedEvent) link.removeEvent('click', attachedEvent);
						
						link.addEvent('click', callback);
						link.store('definiens:lightbox:link:event:click', callback);
					}, this);
					
					window.addEvents({
						resize: positionCallback,
						scroll: positionCallback
					});
					
					this.modalizer.addEvent('fade:out', this._onModalizerHide.bind(this));
					
					this.requestPipe.addEvents({
						request: this._onRequestPage.bind(this),
						cancel: this._onCancelPage.bind(this),
						complete: this._onCompletePage.bind(this)
					});
					
					this.closeButton.addEvent('click', function(e) {
						var e = new Event(e).stop();
						this.close();
					}.bind(this));
					
					this.nextButton.addEvent('click', function(e) {
						var e = new Event(e).stop();
						this.next();
					}.bind(this));
					
					this.previousButton.addEvent('click', function(e) {
						var e = new Event(e).stop();
						this.previous();
					}.bind(this));
					
					this.printButton.addEvent('click', function(e) {
						var e = new Event(e).stop();
						this.print();
					}.bind(this));
				},
				
				_initializeRequestPipe: function() {
					this.requestPipe = new Request.HTML({
						update: this.contentInner,
						evalScripts: false
					});
					// TYPO3 does some useless stuff here, like supporting old Netscape versions etc.
					// This means including scripts as:
					//	<script type="text/javascript">
					//		/*<![CDATA[*/
					//	<!--
					//		...more or less good code goes here...
					//	// -->
					//	</script>
					//	Its a pitty IE doesnt support evaluating this, so we need to parse
					//	a sane piece of script out of this mess 
					this.requestPipe.addEvent('success', function(tree, elements, html, javascript) {
						var saneScript = javascript.replace(/<!--([\s\S]*)\/\/\s-->/g, '$1');
						$exec(saneScript);
					});
				},
				
				_scaffold: function(width, height) {
					var docSize = this.documentBody.getSize();
					
					if (typeof width == 'undefined') width = this.options.width;
					if (typeof height == 'undefined') height = this.options.height;
					
					this.container.setStyles({
						height: height > docSize.y ? docSize.y : height,
						width: width > docSize.x ? docSize.x : width
					});
					
					var containerSize = this.container.getSize(),
						titleBarSize = this.titleBar.getSize(),
						footerBarSize = this.footerBar.getSize();
					
					this.content.setStyles({
						height: containerSize.y - titleBarSize.y - footerBarSize.y
					});
					
					this._adjustFloatingDimensions();
				},
				
				_adjustFloatingDimensions: function() {
					var headerWidth = this.titleBar.getSize().x,
						headerPrependWidth = this.titleprepend.getSize().x
							+ this.titleprepend.getStyle('padding-left').toInt()
							+ this.titleprepend.getStyle('padding-right').toInt()
							+ this.titleprepend.getStyle('margin-left').toInt()
							+ this.titleprepend.getStyle('margin-right').toInt(),
						headerAppendWidth = this.titleappend.getSize().x
							+ this.titleappend.getStyle('padding-left').toInt()
							+ this.titleappend.getStyle('padding-right').toInt()
							+ this.titleappend.getStyle('margin-left').toInt()
							+ this.titleappend.getStyle('margin-right').toInt(),
						footerWidth = this.footerBar.getSize().x,
						footerPrependWidth = this.footerprepend.getSize().x
							+ this.footerprepend.getStyle('padding-left').toInt()
							+ this.footerprepend.getStyle('padding-right').toInt()
							+ this.footerprepend.getStyle('margin-left').toInt()
							+ this.footerprepend.getStyle('margin-right').toInt(),
						footerAppendWidth = this.footerappend.getSize().x
							+ this.footerappend.getStyle('padding-left').toInt()
							+ this.footerappend.getStyle('padding-right').toInt()
							+ this.footerappend.getStyle('margin-left').toInt()
							+ this.footerappend.getStyle('margin-right').toInt();
					
					this.titlecenter.setStyle('width', (headerWidth - headerPrependWidth - headerAppendWidth)+'px');
					this.footercenter.setStyle('width', (footerWidth - footerPrependWidth - footerAppendWidth)+'px');
				},
				
				position: function() {
					if (!this.displayed) return;
					var bodySize = this.documentBody.getSize(),
						bodyScroll = this.documentBody.getScroll(),
						containerSize = this.container.getSize();
					
					this.container.setStyles({
						top: (bodySize.y / 2 - containerSize.y/2) + bodyScroll.y,
						left: (bodySize.x / 2 - containerSize.x/2) + bodyScroll.x
					});
					this.fireEvent('position');
				},
				
				updateCount: function() {
					this.counter.set('html', (this.currentIndex + 1)+' '+this.l10n.of+' '+this.links.length);
				},
				
				checkPreviousNext: function() {
					this.previousButton.setStyle('opacity', this.currentIndex === 0 ? 0 : 1);
					this.nextButton.setStyle('opacity', this.currentIndex >= this.links.length-1 ? 0 : 1);
				},
				
				/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
				  	Ajax / Page functions
				 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
				_onRequestPage: function() {
					this._clearForLoading();
					this.show();
					this.layoutForPage();
					
					var layoutCallback = this.layoutForPage.bind(this),
						attachedCallback = window.retrieve('definiens:lightbox:layoutCallback');
					
					if (attachedCallback) window.removeEvent('resize', attachedCallback);
					
					this.layoutCallbacks.push(layoutCallback);
					
					window.addEvent('resize', layoutCallback);
					window.store('definiens:lightbox:layoutCallback', layoutCallback);
				},
				
				_onCancelPage: function() {
					this.content.removeClass(this.options.loadingClass);
				},
				
				_onCompletePage: function() {
					this.fireEvent('load');
					this.content.removeClass(this.options.loadingClass);
					this.layoutForPage();
					new Definiens.Ui.Lightbox($merge(this.options, {links: this.contentInner.getElements('a[href$='+this.options.linkMarker+']')}));
				},
				
				layoutForPage: function() {
					this._scaffold();
					this.position();
				},
				
				/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
				  	Internal functions
				 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
				_onLinkClick: function(/* Event */ e, /* String */ href, /* Integer */ index, /* DomNode */ link) {
					var e = new Event(e).stop();
					this.currentIndex = index;
					this.open(href, link);
				},
				
				_onModalizerHide: function() {
					this.container.fade('out');
					this.displayed = false;
					this.container.removeClass(this.options.printClass);
					this.documentBody.addClass(this.options.printClass);
					this._clear();
					this.layoutCallbacks.each(function(callback) {
						window.removeEvent('resize', callback);
					});
				},
				
				_clear: function() {
					this.contentInner.empty();
					this.content.store('definiens.ui.lightbox:image.current', null);
				},
				
				_clearForLoading: function() {
					this.content.addClass(this.options.loadingClass);
					this._clear();
					this.updateCount();
					this.options.noControls || this.checkPreviousNext();
				},
				
				/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
				  	Image functions
				 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
				_scaleImage: function() {
					var image = this.content.retrieve('definiens.ui.lightbox:image.current'),
						bodySize = this.documentBody.getSize(),
						containerSize = this.container.getSize();
				},
				
				__mktmpCaption: function() {
					var obj = {
						container: new Element('div', {
							'class': 'definiens-ui-lightbox-container'
						}),
						content: new Element('div', {
							'class': 'definiens-ui-lightbox-content'
						}),
						caption: new Element('div', {
							'class': 'definiens-ui-lightbox-image-caption'
						})
					};
					
					obj.content.inject(obj.container);
					obj.caption.inject(obj.content);
					return obj;
				},
				
				layoutForImage: function() {
					var self = this,
						image = this.content.retrieve('definiens.ui.lightbox:image.current'),
						captionNode = image.retrieve('definiens.ui.lightbox:image.captionNode'),
						docSize = this.documentBody.getSize(),
						height, width,
						content = this.content,
						container = this.container,
						titleBar = this.titleBar,
						footerBar = this.footerBar,
						titleBarSize = this.titleBar.getSize(),
						footerBarSize = this.footerBar.getSize(),
						animations = [],
						fxOptions = this.options.fx,
						maxContentSize = {
							x: docSize.x - this.scrollBarWidth - this.options.padding,
							y: docSize.y - titleBarSize.y - footerBarSize.y - this.scrollBarWidth - this.options.padding
						},
						xQ,yQ,maxQ,
						imageFullDimensions = {
							width: image.retrieve('definiens.ui.lightbox:image.dimensions').width,
							height: image.retrieve('definiens.ui.lightbox:image.dimensions').height
						},
						imageTargetDimensions = imageFullDimensions,
						imageOuter = image.measure(function() {
							return {
								x: image.getStyle('padding-left').toInt()
									+ image.getStyle('padding-right').toInt()
									+ image.getStyle('margin-left').toInt()
									+ image.getStyle('margin-right').toInt(),
								y: image.getStyle('padding-top').toInt()
									+ image.getStyle('padding-bottom').toInt()
									+ image.getStyle('margin-top').toInt()
									+ image.getStyle('margin-bottom').toInt()
							}
						}),
						captionNodeHeight,
						captionNodeOuter,
						//tmpCaption = this.__mktmpCaption(),
						tmpCaptionHeight;
					
					height = imageTargetDimensions.height + imageOuter.y;
					width = imageTargetDimensions.width + imageOuter.x;
					
					if (width > maxContentSize.x || height > maxContentSize.y) {
						xQ = maxContentSize.x / width;
						yQ = maxContentSize.y / height;
						maxQ = xQ <= yQ ? xQ : yQ;
						
						imageTargetDimensions.width = image.retrieve('definiens.ui.lightbox:image.dimensions').width * maxQ;
						imageTargetDimensions.height = image.retrieve('definiens.ui.lightbox:image.dimensions').height * maxQ;
						
						height = imageTargetDimensions.height + imageOuter.y;
						width = imageTargetDimensions.width + imageOuter.x;
					}
					
					captionNodeOuter =  {
						x: captionNode.getStyle('padding-left').toInt()
							+ captionNode.getStyle('padding-right').toInt()
							+ captionNode.getStyle('margin-left').toInt()
							+ captionNode.getStyle('margin-right').toInt(),
						y: captionNode.getStyle('padding-top').toInt()
							+ captionNode.getStyle('padding-bottom').toInt()
							+ captionNode.getStyle('margin-top').toInt()
							+ captionNode.getStyle('margin-bottom').toInt()
					};
						
					captionNode.setStyle('width', width - captionNodeOuter.x);
					captionNodeHeight = captionNode.getSize().y + captionNodeOuter.y;
					
					/*
					tmpCaption.caption.set('html', captionNode.get('html'));
					tmpCaption.container.setStyles({
						height: height + titleBarSize.y + footerBarSize.y + captionNodeHeight,
						width: width
					});
					tmpCaption.container.inject($(document.body));
					tmpCaptionHeight = tmpCaption.caption.getDimensions().height;
					tmpCaption.container.destroy();
					tmpCaption = null;
					
					captionNodeHeight = tmpCaptionHeight;
					// */
					
					animations.push(function() {
						var morph = new Fx.Morph(image, fxOptions),
							tween = image.retrieve('definiens.ui.lightbox:tween'),
							currentContentSize = this.content.getSize();
						
						tween && tween.cancel();
						
						morph.start({
							display: '',
							height: [currentContentSize.y - imageOuter.y - captionNodeHeight, imageTargetDimensions.height - captionNodeHeight],
							width: [currentContentSize.x - imageOuter.x, imageTargetDimensions.width - captionNodeHeight],
							opacity: [0, 1]
						});
						
						image.store('definiens.ui.lightbox:tween', morph);
					}.bind(this));
					
					animations.push(function() {
						var morph = new Fx.Morph(container, fxOptions),
							_set = morph.set
							tween = container.retrieve('definiens.ui.lightbox:tween');
						
						tween && tween.cancel();
						
						morph.set = function() { // hook callback
							_set.apply(this, arguments);
							self.position();
							self._adjustFloatingDimensions();
						};
						
						morph.start({
							height: height + titleBarSize.y + footerBarSize.y/* + captionNodeHeight*/,
							width: width
						});
						
						container.store('definiens.ui.lightbox:tween', morph);
					});
					
					animations.push(function() {
						var morph = new Fx.Morph(content, fxOptions),
							tween = content.retrieve('definiens.ui.lightbox:tween'),
							overFlowHandling = content.getStyle('overflow');
						
						tween && tween.cancel();
						
						morph.addEvents({ // this might look dumb, but actually it fixes a chrome issue
							start: function(){
								content.setStyle('overflow', 'hidden');
							},
							complete: function(){
								content.setStyle('overflow', overFlowHandling);
								this.fireEvent('layoutComplete', {});
							}.bind(this)
						});
						
						morph.start({
							height: height// + captionNodeHeight
						});
						
						content.store('definiens.ui.lightbox:tween', morph);
					}.bind(this));
					
					animations.each(function(animation) { return animation(); });
				},
				
				/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
				  	API
				 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
				scanPage: function() {
					this.links = $$('a[href$='+this.options.linkMarker+']');
				},
				
				unBind: function(link) {
					link.removeEvent('click', link.retrieve('definiens:lightbox:link:event:click'));
				},
				
				open: function(/* String */ url, /* DomNode */ link) {
					var uri = url.toURI(),
						file = uri.get('file'),
						extension = 'html';
					
					uri.setData({}, false, 'fragment'); // clear fragment
					
					if (file.indexOf('.') !== -1) extension = file.substr(file.lastIndexOf('.') + 1);
					
					if (this.options.noCache) uri.setData({ noCache: new Date().getTime() }, true);
					
					this.fireEvent('open', [uri, file, extension]);
					
					this._pushLayer();
					
					switch (true) {
						case ['jpg', 'jpeg', 'png', 'gif'].contains(extension):
							return this.openImage(uri.toString(), link);
						default:
							return this.openUrl(uri.setData({
								type: 200
							}, true).toString());
					}
				},
				
				openImage: function(/* String */ url, /* DomNode */ link) {
					this.fireEvent('openImage', url);
					this._clearForLoading();
					
					if (!this.displayed) {
						this._scaffold(400, 100);
					}
					
					var image = $(new Image()),
						imageContainer = new Element('div', {
							'styles': {
								'text-align': 'center'
							}
						});
					
					image.store('onload:fired', false);
					
					this._tmpImageToOpen = image;
					image.onload = function() {
						if (this._tmpImageToOpen !== image) return;
						if (image.retrieve('onload:fired')) return;
						image.store('onload:fired', true);
						
						var layoutCallback = this.layoutForImage.bind(this),
							attachedCallback = window.retrieve('definiens:lightbox:layoutCallback'),
							caption = new Element('div', {
								'class': 'definiens-ui-lightbox-image-caption'
							}),
							captionText = '';
						
						
						if (link) captionText = link.get('title');
						if (attachedCallback) window.removeEvent('resize', attachedCallback);
						
						image = $(image);
						image.store('definiens.ui.lightbox:image.dimensions', {width: image.width, height: image.height});
						image.store('definiens.ui.lightbox:image.captionNode', caption);
						this.content.removeClass(this.options.loadingClass);
						this.content.store('definiens.ui.lightbox:image.current', image);
						caption.set('html', captionText);
						image.setStyle('display', 'none');
						image.inject(imageContainer);
						imageContainer.inject(this.contentInner);
						caption.inject(this.contentInner);
						
						this.layoutForImage.delay(10, this);
						
						this.layoutCallbacks.push(layoutCallback);
						
						this.addEvent('layoutComplete', function layoutCompleteCallback(e) {
							if (attachedCallback) window.removeEvent('resize', attachedCallback);
							window.addEvent('resize', layoutCallback);
							this.removeEvent('layoutComplete', layoutCompleteCallback);
						}.bind(this));
					}.bind(this);
					
					image.src = url;
					
					$(image).addClass('definiens-ui-lightbox-currentImage');
					this.show();
				},
				
				openUrl: function(/* String */ url) {
					this.fireEvent('openUrl', url);
					this.requestPipe.cancel();
					this.requestPipe.get(url);
					this.links.length && this.title.set('html', this.links[this.currentIndex].get(this.options.titleAttribute) || this.links[this.currentIndex].get('text'));
				},
				
				show: function() {
					this.fireEvent('show');
					if (!this.displayed) {
						this.displayed = true;
						this.modalizer.fade('in');
						this.container.fade('in');
						this.container.addClass(this.options.printClass);
						this.documentBody.removeClass(this.options.printClass);
					}
					this.position();
				},
				
				close: function() {
					this.fireEvent('close');
					this.modalizer.fade('out'); // "this" will visually close via event binding to modalizer
				},
				
				next: function() {
					if (this.currentIndex + 1 === this.links.length) this.currentIndex = 0; 
					var link = this.links[++this.currentIndex];
					this.open(link.get('href'), link);
				},
				
				previous: function() {
					if (this.currentIndex === 0) this.currentIndex = this.links.length - 1;
					var link = this.links[--this.currentIndex];
					this.open(link.get('href'), link);
				},
				
				print: function() {
					this.fireEvent('print');
					this.modalizer.lock();
					window.print();
					this.modalizer.unlock();
				}
			})
		}
	});
})();
