/* */
(function(ns){
	
	// bool indicating if event binding happened on the document
	var documentBound = false;
	
	// lightbox contructor
	var lightbox = function() {
		
		/* ---------------------------------------------------------------------------
		instance
		--------------------------------------------------------------------------- */
		var self = {};
		
		/* ---------------------------------------------------------------------------
		privcate properties
		--------------------------------------------------------------------------- */
		var POSITION_SPEED_BUSY = 10; // positioning interval speed when animating
		var POSITION_SPEED_IDLE = 500; // positioning interval speed when done
		
		var element; // root element
		var contentElement; // element where content is placed (descendant of element)
		var titleElement; // element where title can be stored (descendant of element)
		var hiddenElement; // (hidden) element used to temporarely put content for measuring.
		var mask; // mask/background element
		var positionInterval; // interval for positioning
		var positionSpeed = POSITION_SPEED_IDLE; // default speed
		var onCloseCallback; // current onclose callback (only supports one!)
		var onBeforeCloseCallback; // current onbefore callback (only supports one!)
		
		/* ---------------------------------------------------------------------------
		private methods
		--------------------------------------------------------------------------- */
		
		/*
		(private) updateMaskSize. Updates the mask width and height.
		*/
		var updateMaskSize = function() {
			if (!mask) return;
			im.css(mask, {
				width : im.width(document) + 'px',
				height : im.height(document) + 'px'
			});
		};
		
		/*
		(private) setPosition. Sets the position of the lightbox in the window.
		*/
		var setPosition = function() {
			if (!element || self.status == 'closed') return;
			
			var vw = im.width(window);
			var vh = im.height(window);
			var st = document.body.scrollTop;
			
			//console.info('vh: ' + vh + ', ' + document.body.clientHeight);
			//console.info('st: ' + st);
			
			if (element.style.display != 'none') {
				
				var w = im.width(element);
				var h = im.height(element);
				
				if (h < vh) {
					var top = (vh / 2) - (h / 2);
					if (top < 10) top = 10;
				} else {
					var top =  20;
				}
				
				top += st;
				
				var left = (vw / 2) - (w / 2);
				if (left < 20) left = 20;
				
				im.css(element, {
					left : left + 'px',
					top : top + 'px'
				});
			}
		};
		
		/*
		(private) startPositioning. Starts positioning interval.
		*/
		var startPositioning = function(speed) {
			stopPositioning();
			positionInterval = window.setInterval(function(){
				setPosition();
			}, speed);
			setPosition();
		};
		
		/*
		(private) stopPositioning. Stops positioning interval.
		*/
		var stopPositioning = function() {
			if (positionInterval) window.clearInterval(positionInterval);
		};
		
		/*
		(private) imageReady. Checks if image is loaded.
		*/
		// marcelb: taken from http://snippets.dzone.com/posts/show/89
		var imageReady = function(img) {
		    if (!img.complete) return false;
		    if (typeof img.naturalWidth != "undefined" && img.naturalWidth == 0) return false;
		    return true;
		};
		
		/*
		(private) resizeTo. Resizes the content element to w/h.
		*/
		var resizeTo = function(w, h, callback) {
			im(contentElement).animate({width : w + 'px'}, 200, function(){
				im(contentElement).animate({height : h + 'px'}, 200, function(){
					callback();
				});
			});
		};
				
		/*
		(private) setContent. Sets content, animates the box and places the content inside.
		*/
		var setContent = function(content, callback) {
			
			var w = content.offsetWidth || content.width;
			var h = content.offsetHeight || content.height;
			//console.dir('el w: ' + w);
			//console.dir('el h: ' + h);
			
			resizeTo(w, h, function(){
				setOpeningState();
				contentElement.appendChild(content);
				callback();
			});
		};
		
		/*
		(private) resetDimensions. Resets sizing of content to 'auto'.
		*/
		var resetDimensions = function() {
			im(contentElement).css({
				width : '',
				height : ''
			});
		};
		
		/*
		(private) setStatusClasses. Sets appropriate CSS classes based on current state.
		*/
		var setStatusClasses = function(status) {
			if (status == 'closed') {
				im(element).removeClass('lightbox-loading').removeClass('lightbox-opening').removeClass('lightbox-opened');
			} else if (status == 'opening') {
				im(element).removeClass('lightbox-loading').addClass('lightbox-opening').removeClass('lightbox-opened');
			} else if (status == 'open') {
				im(element).removeClass('lightbox-loading').removeClass('lightbox-opening').addClass('lightbox-opened');
			} else if (status == 'loading') {
				im(element).addClass('lightbox-loading').removeClass('lightbox-opening').removeClass('lightbox-opened');
			}
		};
		
		/*
		(private) setOpenState. Sets to open state.
		*/
		var setOpenState = function() {
			setStatusClasses('open');
			self.status = 'open';
			
			if (self.config.positionOnIdle) {
				startPositioning(POSITION_SPEED_IDLE);
			} else {
				stopPositioning(POSITION_SPEED_IDLE);
				setPosition();
			}
			
			im.show(titleElement);
		};
		
		/*
		(private) setOpeningState. Sets to opening state.
		*/
		var setOpeningState = function(callback) {
			setStatusClasses('opening');
			
			// don't do this twice (the setLoadingState might also call this function)
			var animates = false;
			if (self.status != 'opening') {
				animates = true;
				im(mask).stop().show().animate({opacity : '0.8'}, 300 , function(){
					setPosition(); // make sure we position before we show!
					im(element).show();//.css({opacity : 0}).animate('opacity:1');
					if (callback) callback();
				});
			}
			
			self.status = 'opening';
			
			im.hide(titleElement);
			
			resetDimensions();
			updateMaskSize();
			startPositioning(POSITION_SPEED_BUSY);
			
			if (!animates && callback) callback();
		};
		
		/*
		(private) setLoadingState. Sets to loading state.
		*/
		var setLoadingState = function() {
			setOpeningState();
			setStatusClasses('loading');
			im(element).addClass('lightbox-loading').removeClass('lightbox-opening');
		};
		
		/*
		(private) setClosedState. Sets to closed state.
		*/
		var setClosedState = function(callback) {
			setStatusClasses('closed');
			self.status = 'closed';
			
			stopPositioning();
			
			im.hide(titleElement);
			im(element).stop().hide();
			contentElement.innerHTML = '';
			
			im(mask).stop().animate({opacity : '0.1'}, null, function(){
				im.hide(this);
				if (callback) callback();
			});
		};
		
		/*
		(private) initElements. Creates and initializes lightbox elements.
		*/
		var initElements = function() {
			element = im.create('\
				<div class="lightbox"><table><tr><td>\
					<div class="rounded whitebox">\
			            <div class="w"><div class="ww"><div class="t"></div>\
			                <div class="rounded-content">\
								<div class="lightbox-title"></div>\
								<div class="lightbox-closebutton"></div>\
								<div class="lightbox-content"></div>\
			                </div>\
			            </div><div class="b"><div></div></div></div>\
			        </div>\
				</td></tr></table></div>');
			
			if (im.browser.msie && im.browser.version == '6.0') {
				im.addClass(element, 'lightbox-ie6');
			}
			
			contentElement = im.selectNodes('.lightbox-content', element)[0];
			hiddenElement = im.create('<div class="lightbox-hidden"></div>');
			titleElement = im.selectNodes('.lightbox-title', element)[0];
			
			mask = im.create('<div class="lightbox-mask"></div>');
			im([element, mask]).hide();
			
			document.body.appendChild(hiddenElement);
			document.body.appendChild(mask);
			document.body.appendChild(element);
			
			im(element).live('.lightbox-closebutton', 'click', function(){
				self.close();
			});
		};
		
		/* ---------------------------------------------------------------------------
		public methods
		--------------------------------------------------------------------------- */
		
		/*
		(public) getChildren. Returns all descendant lightboxes in an array.
		*/
		self.getChildren = function() {
			var children = [];
			var c = self;
			while (c.child) {
				c = c.child;
				children.push(c);
			}
			return children;
		};
		
		/*
		(public) getDeepestChild. Returns deepest descendant.
		
		param open:
			bool indicates if it should get the deepest open descendant.
		*/
		self.getDeepestChild = function(open) {
			var c = self.child;
			var match;
			while (c) {
				match = open ? (c.status == 'open' ? c : match) : c;
				c = c.child;
			}
			return match;
		};
		
		/*
		(public) cleanupChildren. Removes all descendant lightboxes that are not open.
		
		param _destroy:
			Used internally.
		*/
		self.cleanupChildren = function(_destroy) {
			if (self.child) {
				var c = self.child;
				if (_destroy || c.status == 'closed') {
					self.child = null;
					c._destroyElements();
					c.cleanupChildren(true);
				}
			}
		};
		
		/*
		(protected) _destroyElements. Destroys DOM elements.
		Used internally when parents clean up children.
		*/
		self._destroyElements = function() {
			if (element) im.remove(element);
			if (hiddenElement) im.remove(hiddenElement);
			if (mask) im.remove(mask);
		};
		
		/*
		(public) open. Opens the lightbox.
		Will re-open the lightbox if already open.
		
		Param hrefOrElement:
			An URL (string) of an image or DOM element.
			
		Param callback (optional):
			Callback when the lightbox is opened (after animation)
			
		Param options (optional):
			Object with additional optional params. Example:
			{
				titleElement : domElement, // element that should be put in as title.
				onClose : onCloseHandler, // callback triggered when closed
				onBeforeClose : onBeforeCloseHandler, // callback trigger before the lightbox is closed.
				isImage : true, // boolean to tell if the URL or content element is about a (single) image.
			}
			
			The isImage boolean is used when:
				- 	the URL (string) passed does not have a image extension (and therefore can't be detected)
				- 	an element is passed that wraps an image. With isImage the lightbox will search for 
					an image tag inside the structure.
			
			When the onBeforeClose handler returns false (=== false), then the closing of the lightbox
			will be canceled.
		*/
		self.open = function(hrefOrElement, callback, options) {
			if (!element) initElements();
						
			options = options || {};
			
			onCloseCallback = options.onClose;
			onBeforeCloseCallback = options.onBeforeClose;
			
			contentElement.innerHTML = '';
			titleElement.innerHTML = '';
			if (options.titleElement) {
				titleElement.appendChild(options.titleElement);
			}
			
			var isImage = options.isImage || false;
			var isURL = options.isURL || false;
			
			// check type of the hrefOrElement
			if (im.isString(hrefOrElement)) {
				isURL = true;
				if (hrefOrElement.match(/\.(?:jpg|gif|png|jpeg)/)) isImage = true;
			} else {
				if ((hrefOrElement.nodeName + '').toLowerCase() == 'img') isImage = true;
			}
			
			if (isImage) {
				
				var content, img;
				if (isURL) {
					content = img = im.create('<img src="' + hrefOrElement + '"/>');
				} else {
					content = hrefOrElement;
					if (content.nodeName.toLowerCase() == 'img') {
						img = content;
					} else {
						img = im(content).find('img').el(0);
					}
				}
				
				hiddenElement.appendChild(content);
				
				setLoadingState();
				
				var interval;
				interval = window.setInterval(function(){
					//alert(self.status);
					if (self.status == 'closed') {
						if (interval) clearInterval(interval);
						return;
					}
					
					if (!img || imageReady(img)) {
						
						if (interval) clearInterval(interval);
						//alert('x');
						setOpeningState(function(){
							setContent(content, function(){
								setOpenState();
								if (callback) callback.apply(self);
							});
						});
						
					}
				}, 20);
				
			} else if (!isURL) {
				hiddenElement.appendChild(hrefOrElement);
				setOpeningState(function(){
					setContent(hrefOrElement, function(){
						setOpenState();
						if (callback) callback.apply(self);
					});
				});
				
			} else {
				// !isImage && isURL not implemented
			}
		};
		
		/*
		(public) close. Closes the lightbox (and all its descendants)
		
		Param callback:
			Callback to execute when closing is finished (including animation)
		*/
		self.close = function(callback) {
			if (self.status == 'closed' || !element) return;
			
			if (onBeforeCloseCallback && im.isFunction(onBeforeCloseCallback)) {
				var result = onBeforeCloseCallback.apply(self);
				if (result === false) return;
			}
			
			if (self.child) self.child.close();
			
			setClosedState(function(){
				if (onCloseCallback && im.isFunction(onCloseCallback)) {
					onCloseCallback.apply(self);
				}
				if (callback) callback.apply(self);
			});
		};
		
		/*
		(public) add. Adds a new lightbox and returns it (initialized, in closed state).
		Returns the 'root' lightbox if there are no open lightboxes. In that sense it is
		always safe.
		
		Example of usage:
		lightbox.add().open('image.jpg')
		*/
		self.add = function() {
			self.cleanupChildren();
			if (self.status == 'closed') return self;
			var c = self.getDeepestChild();
			if (!c) c = this;
			var l = new lightbox();
			c.child = l;
			return l;
		};
		
		self.child; 
		self.status = 'closed';
		self.config = {
			positionOnIdle : false,
			positionOnResize : true,
			positionOnScroll : false
		};
		
		/* ---------------------------------------------------------------------------
		initialize
		--------------------------------------------------------------------------- */
		(function(){
			var update = function() {
				setPosition();
				updateMaskSize();
			};
			if (self.config.positionOnResize) im.bind(window, 'resize', update);
			if (self.config.positionOnScroll) im.bind(window, 'scroll', update);
			
			// we only do the bind on document once
			if (!documentBound) {
				documentBound = true;
				im.bind(document, 'keyup', function(e){
					var key = e.which ? e.which : e.keyCode;
					if (key == 27) {
						var c = self.getDeepestChild(true);
						if (!c) c = self;
						c.close();
					}
				});
			}
		})();
		
		/* ---------------------------------------------------------------------------
		return instance
		--------------------------------------------------------------------------- */
		return self;
	};
	
	/* ---------------------------------------------------------------------------
	exposure and initialization 
	--------------------------------------------------------------------------- */
	
	// create the root lightbox on ns
	ns.lightbox = lightbox();
	
})(window);
