jQuery.fn.imagePreview = function(options){ 
	this.windowFrame;
	this.previewPanel;
	this.titlePanel;
	this.loadingProgress;
	this.windowFrameEmptyHeight;
	this.windowFrameEmptyWidth;
	this.triggedBy;
	
	this.fadeInTimer;
	this.fadeOutTimer;
	this.isFirstTime = true;
 
	var imagePreview = this;
	
	this.getWindowPosition = function(thumbnail, largeImage, options, outerWrapper) {
		var pos = {
			left: 0,
			top:  0
		};
		var outerWrapperPos = {
			top:   window.parseInt(outerWrapper.offset().top),
			left:  window.parseInt(outerWrapper.offset().left)
		};
		var outerWrapperSize = {
			width:  parseInt(outerWrapper.width()),
			height: parseInt(outerWrapper.height())
		};
		var predictedSize = {
			width:  imagePreview.windowFrameEmptyWidth + largeImage.width,
			height: imagePreview.windowFrameEmptyHeight + largeImage.height
		};
		var windowSize = {
			width:  window.innerWidth,
			height: window.innerHeight
		};
		var documentSize = {
			width:  document.width,
			height: document.height
		};
		var boundaries = {
			top:    (window.pageYOffset || document.body.scrollTop) + options.margin,
			bottom: (window.pageYOffset || document.body.scrollTop) + windowSize.height - options.margin,
			left:   (window.pageXOffset || document.body.scrollLeft) + options.margin,
			right:  (window.pageXOffset || document.body.scrollLeft) + windowSize.width - options.margin		
		};
		
		var onLeft    = outerWrapperPos.left - options.outerWrapperMargin - predictedSize.width;
		var onRight   = outerWrapperPos.left + outerWrapperSize.width + options.outerWrapperMargin;
		var leftOOTR  = Math.max(0, boundaries.left - onLeft);
		var rightOOTR = Math.max(0, onRight + predictedSize.width - boundaries.right);
		pos.left      = leftOOTR < rightOOTR ? onLeft : onRight;
	
		var topMiddle  = outerWrapperPos.top + parseInt(outerWrapperSize.height / 2)
		pos.top        = topMiddle - parseInt(largeImage.height / 2) - parseInt(imagePreview.windowFrameEmptyHeight / 2);
		
		var topOOTR    = Math.max(0, boundaries.top - pos.top); 
		var bottomOOTR = Math.max(0, pos.top + predictedSize.height - boundaries.bottom);
		if (topOOTR > 0) {
			pos.top += topOOTR;
		} else if (bottomOOTR > 0) {
			pos.top -= bottomOOTR;
		}
		
		return pos;
	};
	
	this.displayImage = function(thumbnail, largeImage) {
		var options      = thumbnail.get(0).options;
		var outerWrapper = thumbnail.parentsUntil(options.outerWrapper).parent();
		
		var pos = imagePreview.getWindowPosition(thumbnail, largeImage, options, outerWrapper);
		
		if (imagePreview.isFirstTime) {
			imagePreview.isFirstTime = false;
			
			imagePreview.windowFrame.css({
				top:  pos.top,
				left: pos.left
			});
		}
		
		imagePreview.titlePanel.empty();
		imagePreview.previewPanel.empty();				
		imagePreview.windowFrame.animate({
			top:	pos.top,
			left:   pos.left,
			width:  largeImage.width,
			height: largeImage.height + 25
		}, "normal", function() {
			imagePreview.previewPanel.html("<img src='%%src%%' />".replace("%%src%%", largeImage.src));
			imagePreview.titlePanel.text(largeImage.title);
		});
	};
 
	this.loadImage = function(thumbnail) {
		if ("undefined" === typeof thumbnail.data("large-image")) {
			var largeImage = new Image();
			$(largeImage).bind("load", function() {
				imagePreview.displayImage(thumbnail, largeImage);
			});
			largeImage.src = thumbnail.attr("longdesc").substring(0, thumbnail.attr("longdesc").indexOf("#"));			
			largeImage.title = thumbnail.attr("longdesc").substring(thumbnail.attr("longdesc").indexOf("#") + 1);
			thumbnail.data("large-image", largeImage);
			
			imagePreview.previewPanel.html(imagePreview.loadingProgress);
		} else if (0 === thumbnail.data("large-image").width) {
			imagePreview.previewPanel.html(imagePreview.loadingProgress);
		} else {
			imagePreview.displayImage(thumbnail, thumbnail.data("large-image"));
		}
	};
 
	this.showWindowFrame = function(thumbnail) {
		imagePreview.loadImage(thumbnail);
		imagePreview.windowFrame.fadeIn("fast");		
	};
	
	this.hideWindowFrame = function() {
		imagePreview.windowFrame.fadeOut("fast");
	};
	
	this.setupMarkup = function() {
		this.windowFrame = $("<div id='image-preview-window' />");
		this.previewPanel = $("<div id='image-preview-image' />");
		this.titlePanel = $("<p id='image-preview-title' />");
		this.loadingProgress = $("<div id='image-preview-loading' />");
		
		this.windowFrame.appendTo("body");
		this.previewPanel.appendTo(this.windowFrame);
		this.titlePanel.appendTo(this.windowFrame);
		
		this.windowFrameEmptyHeight = parseInt(this.windowFrame.outerHeight());
		this.windowFrameEmptyWidth  = parseInt(this.windowFrame.outerWidth());
	};
	
	this.setupMarkup();
 
	return this.each(function() {
		this.options = $.extend({
			fadeInDelay:        500,
			fadeOutDelay:       1000,
			outerWrapper:       "div",
			outerWrapperMargin: 10,
			margin:             10
		}, options);
				
		$(this).hover(function() {
			var self = this;
		
			window.clearTimeout(imagePreview.fadeOutTimer);	
			
			if ("undefined" === typeof imagePreview.triggedBy || imagePreview.triggedBy !== this || imagePreview.css || "none" === imagePreview.windowFrame.css("display")) {
				imagePreview.triggedBy = this;
				
				imagePreview.fadeInTimer = window.setTimeout(function() {
					imagePreview.showWindowFrame($(self));
				}, this.options.fadeInDelay);	
			}					   
		}, function() {
			window.clearTimeout(imagePreview.fadeInTimer);		  
			imagePreview.fadeOutTimer = window.setTimeout(imagePreview.hideWindowFrame, this.options.fadeOutDelay); 
		});
	});
};
