(function($){
	$.fn.mouseZoom = function(options){
		options = $.extend({
			offsetX: 0, //Viewport offset x.
			offsetY: 0, //Viewport ofset y.
			glass: false, //Show zoomed area under cursor.
			closeDelay: 125,
			loadingDelay: 200,
			fadeDurration: 500,
			moveType: 'box' //'box' is more smooth in MSIE, especially with glass. 'mouse' also available.
		}, options);
		var elements = $(this);
		if (elements.length == 0) return;
		var images = [];
		var getImages = function(){
			images = [];
			elements.each(function(index){
				var element = $(this);
				images.push({
					full: {src: element.attr('href')},
					large: {src: element.attr('data-largeimg')},
					thumb: {src: element.attr('data-thumbnailimg')},
					position: index
				});
			});
		};
		getImages();
		var currentElement = 0;
		var viewport = $('<div class="mousezoom-viewport" />');
		viewport.bind('click', function(){
			var element = elements.eq(0);
			var f = function(){
				if (element.attr('data-opentype') != 'popup') window.location.href = element.attr('href');
			};
			element.bind('click', f).trigger('click').unbind('click', f);
		});
		var glass = $('<div class="mousezoom-glass" />');
		var glassOverlay = $('<div class="mousezoom-glassoverlay" />');
		var loading = $('<div class="mousezoom-loading" />');
		glassOverlay.append(glass);
		var loadingTimer;
		var isLoading = false;
		var showLoading = function(){
			loadingTimer = setTimeout(function(){
				var element = elements.eq(0);
				var img = element.find('img');
				var offset = img.offset();
				loading.css({
					left: offset.left + 'px',
					top: offset.top + 'px'
				});
				loading.width(img.width());
				loading.height(img.height());
				element.append(loading);
				isLoading = true;
			}, options.loadingDelay);
		};
		var hideLoading = function(){
			if (isLoading){
				loading.detach();
				isLoading = false;
			}
			else {
				clearTimeout(loadingTimer);
			}
		};
		/*
		jQuery does not always report border-width as a pixel value in all browsers (MSIE),
		so getOffsetBorder encapsulates a fix.
		*/
		var getBorderOffset = function(jElement){
			var element = jElement[0];
			var left = parseInt(jElement.css('borderLeftWidth'));
			if (isNaN(left)) left = (element.currentStyle) ? parseInt(element.currentStyle['borderLeftWidth']) : 0;
			var top = parseInt(jElement.css('borderTopWidth'));
			if (isNaN(top)) top = (element.currentStyle) ? parseInt(element.currentStyle['borderTopWidth']) : 0;
			return {left :left, top: top};
		};
		var onMousemove1 = function(event){
			var image = images[currentElement];
			var element = elements.eq(0);
			var offset = element.find('img').offset();
			var x = Math.round(((event.pageX - offset.left) / image.large.width) * 100);
			var y = Math.round(((event.pageY - offset.top) / image.large.height) * 100);
			viewport.css('backgroundPosition', x + '% ' + y + '%');
			if (options.glass){
				var x2 = (image.large.width - glass.width()) * (x / 100);
				var y2 = (image.large.height - glass.height()) * (y / 100);
				var border = getBorderOffset(glass);
				glass.css({
					'backgroundPosition': -x2 + 'px ' + -y2 + 'px',
					'left': x2 - border.left,
					'top': y2 - border.top
				});
			}
		}
		var onMousemove2 = function(event){
			var image = images[currentElement];
			var element = elements.eq(0);
			var offset = element.find('img').offset();
			var r = image.large.width / image.full.width;
			var glassWidth = Math.round(image.large.width * r);
			var glassHeight = Math.round(image.large.height * r);
			var x = (event.pageX - offset.left) - (glassWidth / 2);
			var y = (event.pageY - offset.top) - (glassHeight / 2);
			var right = image.large.width - glassWidth;
			var bottom = image.large.height - glassHeight;
			if (x < 0) x = 0;
			if (x > right) x = right;
			if (y < 0) y = 0;
			if (y > bottom) y = bottom;
			viewport.css('backgroundPosition', Math.round((x / right) * 100) + '% ' + Math.round((y / bottom) * 100) + '%');
			if (options.glass){
				var border = getBorderOffset(glass);
				x = Math.round(x);
				y = Math.round(y);
				glass.css({
					'backgroundPosition': -x + 'px ' + -y + 'px',
					'left': x - border.left,
					'top': y - border.top
				});
			}
		};
		var onMousemove = (options.moveType == 'box')? onMousemove2 : onMousemove1;
		var showZoom = function(){
			hideLoading();
			var image = images[currentElement];
			if (image.large.width >= image.full.width) return;
			viewport.stop(true, true).show();
			var element = elements.eq(0);
			var offset = element.find('img').offset();
			$(document.body).append(viewport);
			var border = getBorderOffset(viewport);
			viewport.css({
				left: (offset.left + options.offsetX - border.left) + 'px',
				top: (offset.top + options.offsetY - border.top) + 'px'
			});
			viewport.width(image.large.width);
			viewport.height(image.large.height);
			viewport.css('backgroundImage', 'url(' + image.full.src + ')');
			$(document).bind('mousemove', onMousemove);
			if (options.glass){
				element.append(glassOverlay);
				glassOverlay.width(image.large.width);
				glassOverlay.height(image.large.height);
				border = getBorderOffset(glassOverlay);
				glassOverlay.css({
					left: (offset.left - border.left) + 'px',
					top: (offset.top - border.top) + 'px'					 
				});
				var r = image.large.width / image.full.width;
				glass.width(image.large.width * r);
				glass.height(image.large.height * r);
				glass.css({
					'backgroundImage': 'url(' + image.large.src + ')',
					'left': (image.large.width * 2) + 'px',
					'top': (image.large.height * 2) + 'px'
				});
			}
		};
		var loadImage = function(index, size, fn){
			if (images[index][size].cached){
				if (typeof(fn) == 'function') fn();
				return;
			}
			var image = images[index][size].img = new Image();
			image.onload = function(){
				var obj = images[index][size];
				obj.width = image.width;
				obj.height = image.height;
				obj.cached = true;
				image.onload = function(){};
				if (typeof(fn) == 'function') fn();
			};
			image.src = images[index][size].src;
		};
		loadImage(0, 'large');
		elements.eq(0).data('refreshImages', function(){
			getImages();
			loadImage(0, 'large');
			currentElement = 0;
		});
		var assignAttributes = function(element, obj, size){
			element.attr('href', obj.full.src);
			var image = obj[size];
			element.find('img').attr({
				src: image.src,
				width: image.width,
				height: image.height
			});
		};
		var getImageByPosition = function(p){
			for (var i=0; i<images.length; i++){
				if (images[i].position == p) return i;
			}
		};
		var changeElement = function(index){
			var element1 = elements.eq(0);
			var index1 = getImageByPosition(index);
			var image1 = images[index1];
			if (!image1.full.src) return;
			loadImage(index1, 'large', function(){
				assignAttributes(element1, image1, 'large');
			});
			var element2 = elements.eq(index);
			var index2 = getImageByPosition(0);
			var image2 = images[index2];
			loadImage(index2, 'thumb', function(){
				assignAttributes(element2, image2, 'thumb');
			});
			images[index1].position = 0;
			images[index2].position = index;
			currentElement = index1;
		};
		var closeTimer;
		var closeTimerSet = false;
		var closeViewport = function(){
			closeTimerSet = false;
			viewport.fadeOut(options.fadeDurration, function(){
				viewport.detach();
			});
			$(document).unbind('mousemove', onMousemove);
			if (options.glass) glassOverlay.detach();
		};
		var isMouseOver = false;
		var onMouseleave = function(){
			if (!isMouseOver) return
			isMouseOver = false;
			if(!closeTimerSet) {
				closeTimer = setTimeout(closeViewport, options.closeDelay);
				closeTimerSet = true;
			}
		};
		var onMouseenter = function(){
			if (isMouseOver) return
			isMouseOver = true;
			if (closeTimerSet) {
				clearTimeout(closeTimer);
				closeTimerSet = false;
				return;
			}
			showLoading();
			loadImage(currentElement, 'full', showZoom);
		}
		elements.eq(0).bind({
			'mouseenter': onMouseenter,
			'mouseleave': onMouseleave
		});
		if (elements.eq(0).attr('data-opentype') == 'popup') elements.eq(0).bind('click', function(event){
			event.preventDefault();
		});
		viewport.bind({
			'mouseenter': onMouseenter,
			'mouseleave': onMouseleave
		});
		if (options.glass) glassOverlay.bind('mouseleave', onMouseleave);
		elements.slice(1).each(function(index){
			$(this).bind('click', function(event){
				event.preventDefault();
				event.stopImmediatePropagation();
				changeElement(index + 1);
			});
		});
		return this;
	}
})(ekm.$);
