// jQuery Sonic Zoom Plugin
//
// Version 1.3.0
//
// JavaScript Library: jQuery
// 
// Copyright (c) 2009 John Resig, http://jquery.com/
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
(function($) {
	$.soniczoom = {
		setupCallback : function(data) {
			var zoomer = $.data($(data.id).get(0), 'zoomer');
			if (zoomer == null)
				return;
			zoomer.setupCallback(data);
		},
		changeImageCallback : function(data) {
			var zoomer = $.data($(data.id).get(0), 'zoomer');
			if (zoomer == null)
				return;
			zoomer.changeImageCallback(data);
		}
	};
	
	szFunctions = {
		"zoomin" : function(zoomer) {
			zoomer.zoomin();
		},
		"zoomout" : function(zoomer) {
			zoomer.zoomout();
		},
		"reset" : function(zoomer) {
			zoomer.reset();
		},
		"moving" : function(zoomer, args) {
			zoomer.moving(args[0], args[1]);
		},
		"moved" : function(zoomer) {
			zoomer.moved();
		},
		"moveToPosition" : function(zoomer, args) {
			zoomer.moveToPosition(args[0]);
		},
		"switchNavigation" : function(zoomer) {
			zoomer.switchNavigation();
		},
		"visibleNavigation" : function(zoomer) {
			zoomer.visibleNavigation();
		},
		"hiddenNavigation" : function(zoomer) {
			zoomer.hiddenNavigation();
		},
		"changeImage" : function(zoomer, args) {
			zoomer.changeImage(args[0], args[1], args[2]);
		},
		"changeSize" : function(zoomer, args) {
			zoomer.changeSize(args[0], args[1]);
		},
		"appendOverlap" : function(zoomer, args) {
			zoomer.appendOverlap(args[0]);
		},
		"removeOverlap" : function(zoomer, args) {
			zoomer.removeOverlap();
		},
		"disable" : function(zoomer) {
			zoomer.disable();
		},
		"enable" : function(zoomer) {
			zoomer.enable();
		}
	};
	
	$.fn.soniczoom = function(options)
	{
		if (typeof options === "string")
		{
			var args = Array.prototype.slice.call(arguments, 1);
			
			return this.each(function()
			{
				var zoomer = $.data(this, 'zoomer');
				if (zoomer == null)
					return;
				szFunctions[options](zoomer, args);
			});
		} else {
			var settings = {
				maxZoomLevels:		4,
				zoomPerLevel:		2,
				zIndex:				0,
				grid:				100,
				animationSpeed:		320,
				control:			{mousewheel:'fz',contextmenu:'reset',shiftclick:'zoomout',ctrlclick:'swnavi'},
				requestOption:		'',
				imageDirectory:		'images',
				navigationDisplay:	'off',
				navigationSize:		'0.25',
				navigationImage:	'thumbNail',
				navigationCss:		null,
				dragviewCss:		null,
				startPosition:		null,
				noImage:			null,
				jsonpgis:			null,
				mrl:				null
			};
			
			options = options || {};
			
			$.extend(settings, options);
			
			return this.each(function()
			{
				var zoomer = new Zoomer(this, settings);
				
				$.data(this, 'zoomer', zoomer);
				
				zoomer.setup(zoomer.mrl, zoomer.path);
			});
		}
	};
	
	//Zoomer define
	Zoomer.PREFIX = 'sz_';
	Zoomer.MAX_ANIMATION_FRAME = 8;
	Zoomer.UPDATE_FADEIN = 'normal';
	Zoomer.MAX_ZOOM_WAIT = 10000;
	Zoomer.MAX_LOAD_WAIT = 10000;
	Zoomer.UPDATE_CHECK_TIME = 100;
	
	//Zoomer variable
	Zoomer.counter = 0;
	
	//Zoomer class
	function Zoomer(fullZoomImage, settings)
	{
		// initialize the member variables for this instance
		var src = settings.mrl;
		if (src == null)
			src = $(fullZoomImage).attr('src');
		var index = src.indexOf('?');
		if (index == -1)
			return;
		var image = src.substring(0, index);
		var params = src.substring(index+1);
		index = params.indexOf('is=');
		if (index == -1)
			return;
		var isParams = params.substring(index+3).split(',');
		if (isParams.length != 3)
			return;
		
		index = image.toLowerCase().lastIndexOf('/mgen');
		this.mrl = image.substring(0, index+6);
		this.path = image.substring(index+6);
		this.zoomWidth = eval(isParams[0]);
		this.zoomHeight = eval(isParams[1]);
		this.padColor = isParams[2];
		
		this.maxZoomLevels = settings.maxZoomLevels;
		this.zoomPerLevel = settings.zoomPerLevel;
		this.zIndex = settings.zIndex;
		this.grid = settings.grid;
		this.animationUpdataTime = Math.floor(settings.animationSpeed / Zoomer.MAX_ANIMATION_FRAME);
		this.control = settings.control;
		this.requestOption = settings.requestOption;
		this.imageDirectory = settings.imageDirectory;
		this.navigationDisplay = settings.navigationDisplay
		this.navigationSize = settings.navigationSize;
		this.navigationImage = settings.navigationImage;
		this.navigationCss = settings.navigationCss;
		this.dragviewCss = settings.dragviewCss;
		this.startPosition = settings.startPosition;
		this.noImage = settings.noImage;
		this.jsonpgis = settings.jsonpgis;
		this.settings = settings;
		
		// create id
		var id = $(fullZoomImage).attr('id');
		if (id == null || id == '')
		{
			id = $(fullZoomImage).attr('class');
			if (id == null || id == '')
				id = Zoomer.PREFIX + id + '_' + (Zoomer.counter++);
			else
				id = Zoomer.PREFIX + id + '_' + $.inArray(fullZoomImage, $('.'+id));
			
			$(fullZoomImage).attr('id', id);
		}
		this.id = '#' + id;
		
		// set MRL request option
		if (this.requestOption != '')
			this.requestOption = '&' + this.requestOption;
		
		// contains current preview window zoom level and position
		this.currentPreviewPosition = new Object;
		this.currentPreviewPosition.x = 0;
		this.currentPreviewPosition.y = 0;
		this.currentPreviewPosition.zoomLevel = 0;
		
		// contains current new position to animate to
		this.newPosition = new Object;
		this.newPosition.x = 0;
		this.newPosition.y = 0;
		this.newPosition.zoomLevel = 0;
		
		// contains amount to animate between frames
		this.changePosition = new Object;
		this.changePosition.x = 0;
		this.changePosition.y = 0;
		this.changePosition.zoomLevel = 0;
		
		// conteins original image size.
		this.image = new Object;
		this.image.width = -1;
		this.image.height = -1;
		this.image.scale = 1.0;
		this.image.adjWidth = 0;
		this.image.adjHeight = 0;
		this.image.padWidth = 0;
		this.image.padHeight = 0;
		
		// contains true zoom adjuster.
		this.trueZoomAdjuster = new Object;
		this.trueZoomAdjuster.x = 0;
		this.trueZoomAdjuster.y = 0;
		
		// contains true zoom padding layer.
		this.trueZoomPadding = new Object;
		this.trueZoomPadding.left = 0;
		this.trueZoomPadding.top = 0;
		this.trueZoomPadding.right = 0;
		this.trueZoomPadding.bottom = 0;
		
		// create container
		var container = document.createElement('div');
		$(container).css({
			margin : '0',
			padding : '0',
			overflow : 'visible',
			left : '0',
			top : '0',
			zIndex : this.zIndex,
			borderStyle : 'none',
			position : 'relative'
		});
		$(container).width(this.zoomWidth).height(this.zoomHeight);
		
		// create frame
		var frame = document.createElement('div');
		$(frame).css({
			margin : '0',
			padding : '0',
			overflow : 'hidden',
			left : '0',
			top : '0',
			zIndex : this.zIndex,
			borderStyle : 'none',
			position : 'absolute'
		});
		$(frame).width(this.zoomWidth).height(this.zoomHeight);
		$(frame).css('cursor', 'crosshair');
		
		// create preview zoom layer
		var previewZoom = document.createElement('div');
		$(previewZoom).css({
			margin : '0',
			padding : '0',
			overflow : 'hidden',
			left : '0',
			top : '0',
			zIndex : this.zIndex,
			borderStyle : 'none',
			position : 'absolute'
		});
		$(previewZoom).width(this.zoomWidth).height(this.zoomHeight);
		
		// fullZoomImage wrap around layer
		$(fullZoomImage).css({margin:'0',padding:'0',borderStyle:'none'});
		$(fullZoomImage).wrap(container);
		$(fullZoomImage).wrap(frame);
		$(fullZoomImage).wrap(previewZoom);
		
		// save element to this instance
		this.fullZoomImage = fullZoomImage;
		this.previewZoom = fullZoomImage.parentNode;
		this.frame = this.previewZoom.parentNode;
		this.container = this.frame.parentNode;
		
		// set true zoom size
		this.trueZoomWidth = this.zoomWidth;
		this.trueZoomHeight = this.zoomHeight;
		this.trueZoom = null;
		this.trueZoomClone = null;
		
		// set visibility of zoom layers
		$(previewZoom).css('visibility', 'visible');
		
		// create loader icon
		var loader = document.createElement('div');
		$(loader).append($('<img>').attr('src', this.imageDirectory + '/loader.gif'));
		$(loader).addClass('soniczoom-loader');
		$(loader).css('z-index', this.zIndex);
		$(loader).css('border-style', 'none');
		$(loader).css('visibility','hidden');
		$(this.container).append(loader);
		this.loader = loader;
		
		// create loading image object
		this.loadImage = new Object;
		this.loadImage.action = 'switch';
		this.loadImage.actionArgs = null;
		this.loadImage.disableLoadAction = false;
		this.loadImage.fullZoomImage = null;
		this.loadImage.thumbNail = null;
		this.loadImage.ready = {fullZoomImage: false, thumbNail: false};
		
		// updateZoom timer id
		this.updateZoomTimerId = null;
		
		// overlap
		this.overlap = new Object;
		this.overlap.elem = null;
		this.overlap.left = 0;
		this.overlap.top = 0;
		
		// set zoom action
		this.currentFrame = 0;
		this.disableZoomAction = true;
		
		// setup control
		if (typeof this.control == 'function')
			Zoomer.prototype.setupControl = this.control;
		
		this.disabled = false;
	}
	
	Zoomer.prototype.setup = function(mrl, path)
	{
		this.preparation = false;
		
		// setup ajax params
		if (this.jsonpgis == null)
		{
			var zoomer = this;
			this.ajaxParams = {
				type: 'GET',
				url: null,
				data: null,
				success: null,
				dataType: 'text'
			};
			this.ajaxParams.url = mrl + '?gis=' + path;
			this.ajaxParams.success = function(data){
				try {
					var size = data.split(',');
					if (size.length == 2)
						$.soniczoom.setupCallback({id:zoomer.id, width:eval(size[0]), height:eval(size[1])});
					else
						$.soniczoom.setupCallback({id:zoomer.id, width:-1, height:-1});
				} catch(e) {
					$.soniczoom.setupCallback({id:zoomer.id, width:-1, height:-1});
				}
			};
		}
		else
		{
			this.ajaxParams = {
				type: 'GET',
				url: null,
				data: null,
				success: null,
				dataType: 'jsonp'
			}
			this.ajaxParams.url = mrl + this.jsonpgis + "?args='" + path + "','$.soniczoom.setupCallback','" + encodeURIComponent(this.id) + "'";
		}
		$.ajax(this.ajaxParams);
	}
	
	Zoomer.prototype.setupCallback = function(data)
	{
		if (data.width <= 0 || data.height <= 0)
		{
			this.disable();
			return;
		}
		
		// set source image size
		this.setSourceImageSize(data);
		
		// setup Zoom per level
		this.setupZoomPerLevel();
		
		// setup navigation
		this.setupNavigation();
		
		// set start position
		if (this.hasStartPosition())
		{
			this.setPosition(this.startPosition);
		}
		
		// setup control
		this.setupControl(this);
		
		// setup end
		this.disableZoomAction = false;
		this.preparation = true;
		
		// event hook
		$(this.fullZoomImage).triggerHandler('sz_image_info', [this, data]);
	}
	
	Zoomer.prototype.setSourceImageSize = function(size)
	{
		this.image.width = size.width;
		this.image.height = size.height;
		
		var scaleX = this.image.width / this.zoomWidth;
		var scaleY = this.image.height / this.zoomHeight;
		if (scaleX > scaleY)
		{
			this.image.scale = scaleX;
			this.image.adjWidth = this.image.width;
			this.image.adjHeight = Math.floor(this.zoomHeight * this.image.scale);
			this.image.padWidth = 0;
			this.image.padHeight = Math.floor((this.image.adjHeight - this.image.height) / 2);
		}
		else
		{
			this.image.scale = scaleY;
			this.image.adjWidth = Math.floor(this.zoomWidth * this.image.scale);
			this.image.adjHeight = this.image.height;
			this.image.padWidth = Math.floor((this.image.adjWidth - this.image.width) / 2);
			this.image.padHeight = 0;
		}
	}
	
	Zoomer.prototype.setupZoomPerLevel = function()
	{
		if (typeof this.settings.zoomPerLevel != 'number' && this.settings.zoomPerLevel == "upToOriginalSize")
		{
			this.zoomPerLevel = Math.pow(this.image.adjWidth / this.zoomWidth,(1.0/this.maxZoomLevels));
		}
		else
		{
			this.zoomPerLevel = this.settings.zoomPerLevel;
		}
	}
	
	Zoomer.prototype.setupNavigation = function()
	{
		if (this.navigationDisplay == 'none')
		{
			// create dummy navigationView
			this.navigationView = new Object;
			this.navigationView.mainLayer = null;
			this.navigationView.dragView = null;
			this.navigationView.updateZoomBox = function(){}
			
			// clear navigation method
			this.updateNavigationBox = function(){}
			this.navigationZoomin = function(){}
			this.navigationMoving = function(){}
			this.navigationMoved = function(){}
			this.switchNavigation = function(){}
			this.visibleNavigation = function(){}
			this.hiddenNavigation = function(){}
		}
		else
		{
			// navigation elements size information
			this.setupNavigationSize();
			
			// create zoom navigation window
			var thumbNailImg = null;
			if (this.navigationImage == 'thumbNail')
			{
				var thumbNailMRL = this.createThumbNailMRL(this.mrl, this.path, this.navWidth, this.navHeight, this.padColor, this.requestOption);
				thumbNailImg = $('<img>').attr('src', thumbNailMRL).css({margin:'0',padding:'0',borderStyle:'none'});
			}
			this.navigationView = new NavigationView(this.navWidth, this.navHeight, thumbNailImg, this.imageDirectory, this.navigationCss, this.dragviewCss);
			
			$(this.navigationView.mainLayer).css('z-index', this.zIndex);
			$(this.navigationView.dragView).css('z-index', this.zIndex);
			if (this.navigationDisplay == 'on')
				$(this.navigationView.mainLayer).css('visibility', 'visible');
			else
				$(this.navigationView.mainLayer).css('visibility', 'hidden');
			$(this.container).append(this.navigationView.mainLayer);
		}
	}
	
	Zoomer.prototype.setupNavigationSize = function()
	{
		if (this.navigationSize.indexOf('width ') > -1)
		{
			this.navWidth = eval($.trim(this.navigationSize.substring(this.navigationSize.indexOf('width ')+'width '.length)));
			this.thumbNailRatio = this.zoomWidth / this.navWidth;
			this.navHeight = Math.floor(this.zoomHeight / this.thumbNailRatio);
		}
		else if (this.navigationSize.indexOf('height ') > -1)
		{
			this.navHeight = eval($.trim(this.navigationSize.substring(this.navigationSize.indexOf('height ')+'height '.length)));
			this.thumbNailRatio = this.zoomHeight / this.navHeight;
			this.navWidth = Math.floor(this.zoomWidth / this.thumbNailRatio);
		}
		else
		{
			this.thumbNailRatio = this.zoomWidth / (this.zoomWidth * eval(this.navigationSize));
			this.navWidth = Math.floor(this.zoomWidth / this.thumbNailRatio);
			this.navHeight = Math.floor(this.zoomHeight / this.thumbNailRatio);
		}
	}
	
	Zoomer.prototype.setupControl = function()
	{
		// Mouse
		this.mouse = new Mouse();
		
		// set delegate action
		if (this.control.contextmenu != undefined)
		{
			var act = this.control.contextmenu;
			this.contextmenuDelegate = ('reset' == act) ? this.reset : ('zoomout' == act) ? this.zoomout : ('swnavi' == act) ? this.switchNavigation : null;
		}
		if (this.control.shiftclick != undefined)
		{
			var act = this.control.shiftclick;
			this.shiftClickDelegate = ('reset' == act) ? this.reset : ('zoomout' == act) ? this.zoomout : ('swnavi' == act) ? this.switchNavigation : null;
		}
		if (this.control.ctrlclick != undefined)
		{
			var act = this.control.ctrlclick;
			this.ctrlClickDelegate = ('reset' == act) ? this.reset : ('zoomout' == act) ? this.zoomout : ('swnavi' == act) ? this.switchNavigation : null;
		}
		
		// set event trigger
		if (this.navigationDisplay == 'none')
		{
			this.clickTrigger = this.previewZoom;
			this.dragTrigger = this.previewZoom;
		}
		else
		{
			this.clickTrigger = [this.previewZoom, this.navigationView.mainLayer];
			this.dragTrigger = [this.previewZoom, this.navigationView.mainLayer, this.navigationView.dragView];
		}
		
		var zoomer = this;
		
		if (this.control.mousewheel != undefined)
		{
			zoomer.mouse.mousewheelEvent = function(e)
			{
				var rollCount = zoomer.mouse.wheel(e);
				
				if (zoomer.control.mousewheel == 'bz')
					rollCount *= -1;
				
				if (rollCount < 0)
				{
					if (this == zoomer.navigationView.mainLayer)
					{
						zoomer.navigationZoomin(zoomer.getNavigationEventPosition(e));
						zoomer.mouse.preventEvent(e);
					}
					else
						zoomer.zoomin(zoomer.getEventPosition(e));
				}
				else if (rollCount > 0)
					zoomer.zoomout();
			}
			
			var types = ['DOMMouseScroll', 'mousewheel'];
			if (this.previewZoom.addEventListener)
			{
				for (var i=types.length-1; i >=0; i--)
				{
					this.previewZoom.addEventListener(types[i], zoomer.mouse.mousewheelEvent, false);
					if (this.navigationDisplay != 'none')
						this.navigationView.mainLayer.addEventListener(types[i], zoomer.mouse.mousewheelEvent, false);
				}
			}
			else
				$(this.clickTrigger).bind('mousewheel', zoomer.mouse.mousewheelEvent);
		}
		
		if (this.control.contextmenu != undefined)
		{
			$(this.clickTrigger).bind('contextmenu', function(e)
			{
				zoomer.mouse.preventEvent(e);
				zoomer.contextmenuDelegate();
			});
		}
		
		if (this.control.shiftclick != undefined && this.control.ctrlclick != undefined)
			$(this.clickTrigger).click(function(e)
			{
				if (zoomer.mouse.preventClickEvent)
					zoomer.mouse.preventClickEvent = false;
				else if (e.shiftKey)
					zoomer.shiftClickDelegate();
				else if (e.ctrlKey)
					zoomer.ctrlClickDelegate();
				else
				{
					if (this == zoomer.navigationView.mainLayer)
					{
						zoomer.navigationZoomin(zoomer.getNavigationEventPosition(e));
						zoomer.mouse.preventEvent(e);
					}
					else
						zoomer.zoomin(zoomer.getEventPosition(e));
				}
			});
		else if (this.control.shiftclick != undefined)
			$(this.clickTrigger).click(function(e)
			{
				if (zoomer.mouse.preventClickEvent)
					zoomer.mouse.preventClickEvent = false;
				else if (e.shiftKey)
					zoomer.shiftClickDelegate();
				else
				{
					if (this == zoomer.navigationView.mainLayer)
					{
						zoomer.navigationZoomin(zoomer.getNavigationEventPosition(e));
						zoomer.mouse.preventEvent(e);
					}
					else
						zoomer.zoomin(zoomer.getEventPosition(e));
				}
			});
		else if (this.control.ctrlclick != undefined)
			$(this.clickTrigger).click(function(e)
			{
				if (zoomer.mouse.preventClickEvent)
					zoomer.mouse.preventClickEvent = false;
				else if (e.ctrlKey)
					zoomer.ctrlClickDelegate();
				else
				{
					if (this == zoomer.navigationView.mainLayer)
					{
						zoomer.navigationZoomin(zoomer.getNavigationEventPosition(e));
						zoomer.mouse.preventEvent(e);
					}
					else
						zoomer.zoomin(zoomer.getEventPosition(e));
				}
			});
		else
			$(this.clickTrigger).click(function(e)
			{
				if (zoomer.mouse.preventClickEvent)
					zoomer.mouse.preventClickEvent = false;
				else
				{
					if (this == zoomer.navigationView.mainLayer)
					{
						zoomer.navigationZoomin(zoomer.getNavigationEventPosition(e));
						zoomer.mouse.preventEvent(e);
					}
					else
						zoomer.zoomin(zoomer.getEventPosition(e));
				}
			});
		
		$(this.dragTrigger).mousedown(function(e)
		{
			if (this != zoomer.navigationView.mainLayer)
				zoomer.mouse.start(e);
			else
				zoomer.mouse.preventEvent(e);
		});
		
		$(this.clickTrigger).mousemove(function(e)
		{
			if (zoomer.mouse.move(e) == false)
				return;
			
			if (this == zoomer.navigationView.mainLayer || this == zoomer.navigationView.dragView)
				isMoved = zoomer.navigationMoving(zoomer.mouse.shiftX, zoomer.mouse.shiftY);
			else
				isMoved = zoomer.moving(zoomer.mouse.shiftX, zoomer.mouse.shiftY);
			
			if (isMoved == false)
				zoomer.mouse.cancelDrag();
			else
				zoomer.mouse.preventEvent(e);
		});
		
		$(this.clickTrigger).bind('mouseup mouseleave', function(e)
		{
			if (zoomer.mouse.end(e))
			{
				zoomer.mouse.preventClickEvent = true;
				if (this == zoomer.navigationView.mainLayer || this == zoomer.navigationView.dragView)
					zoomer.navigationMoved();
				else
					zoomer.moved();
			}
		});
	}
	
	Zoomer.prototype.beginAnimation = function(newPosition)
	{
		// check to see if zoom is past max
		if (newPosition.zoomLevel > this.maxZoomLevels) 
		{
			//alert('Maximum zoom levels reached');
			$(this.fullZoomImage).triggerHandler('sz_max_zoom_levels', [this]);
			return;
		}
		
		// set newPosition
		this.newPosition = newPosition;
		
		// set animation frame counter and parameters
		this.currentFrame = 1;
		
		// constrain image
		this.constrainZoom();
		
		// remove trueZoom clone
		this.removeTrueZoomClone();
		
		// load zoom image in background
		this.loadNewZoom();
		
		// set intervals between frames
		// determine increment for x,y, and  zoom
		var oldCenterPoint = this.getCenterPoint(this.currentPreviewPosition);
		var newCenterPoint = this.getCenterPoint(this.newPosition);
		this.changePosition.x = (newCenterPoint.x - oldCenterPoint.x) / Zoomer.MAX_ANIMATION_FRAME;
		this.changePosition.y = (newCenterPoint.y - oldCenterPoint.y) / Zoomer.MAX_ANIMATION_FRAME;
		
		this.changePosition.zoomLevel = (this.newPosition.zoomLevel - this.currentPreviewPosition.zoomLevel) / Zoomer.MAX_ANIMATION_FRAME;
		
		//alert('Change Position for zoom x,y,zoomlevel ' + this.changePosition.x + ',' + this.changePosition.y + ',' + this.changePosition.zoomLevel);
		
		// start animation
		var zoomer = this;
		setTimeout(function(){ zoomer.animateZoom(); }, this.animationUpdataTime);
	}
	
	Zoomer.prototype.getCenterPoint = function(position)
	{
		var centerPoint = new Object();
		var zoomLevel = this.calculateZoomLevel(position.zoomLevel);
		centerPoint.x = position.x + this.zoomWidth / (2 * zoomLevel);
		centerPoint.y = position.y + this.zoomHeight / (2 * zoomLevel);
		return centerPoint;
	}
	
	Zoomer.prototype.loadNewZoom = function()
	{
		// load new zoom image in seperate thread
		if (this.newPosition.zoomLevel != 0)
		{
			// show loader icon
			$(this.loader).css('visibility', 'visible');
			this.disableZoomAction = true;
			this.loadImageInBackground(Zoomer.MAX_LOAD_WAIT);
		}
		else
		{
			if (this.trueZoom != null)
			{
				$(this.trueZoom).remove();
				this.trueZoom = null;
			}
			this.disableZoomAction = true;
		}
	}
	
	Zoomer.prototype.loadImageInBackground = function(waitTime)
	{
		// load MediaServer zoom image
		var zoomMRL = this.zoomMediaServerImage(this.newPosition);
		var zoomImage = $('<img>').attr('src', zoomMRL).css({margin:'0',padding:'0',borderStyle:'none'});
		
		// remove true zoom layer
		if (this.trueZoom != null)
			$(this.trueZoom).remove();
		
		// set true zoom layer
		var trueZoom = $('<div>').css({
			margin : '0',
			padding : '0',
			overflow : 'hidden',
			left : '0',
			top : '0',
			zIndex : $(this.previewZoom).css('z-index'),
			borderStyle : 'none',
			position : 'absolute',
			display : 'none'
		});
		$(trueZoom).append(zoomImage);
		
		this.trueZoomImage = zoomImage.get(0);
		this.trueZoom = trueZoom.get(0);
	}
	
	Zoomer.prototype.constrainZoom = function()
	{
		if (this.newPosition.zoomLevel != 0)
		{
			var zoomLevel = this.calculateZoomLevel(this.newPosition.zoomLevel);
			var maxHeight = this.zoomHeight - this.zoomHeight / zoomLevel;
			var maxWidth  = this.zoomWidth - this.zoomWidth / zoomLevel;
			
			if (this.newPosition.x < 0)
				this.newPosition.x = 0;
			else if (this.newPosition.x > maxWidth)
				this.newPosition.x = maxWidth;
			
			if (this.newPosition.y < 0)
			    this.newPosition.y = 0;
			else if (this.newPosition.y > maxHeight)
				this.newPosition.y = maxHeight;
		} 
	}
	
	Zoomer.prototype.animateZoom = function()
	{
		// animate current frame
		this.zoomPreviewImage();
		
		this.currentFrame++;
		
		if (this.currentFrame > Zoomer.MAX_ANIMATION_FRAME)
		{
			// now display zoom image
			this.currentFrame = 0;
			this.updateZoom(Zoomer.MAX_ZOOM_WAIT);
			return;
		}
		else
		{
			var zoomer = this;
			setTimeout(function(){ zoomer.animateZoom(); }, this.animationUpdataTime);
		}
	}
	
	Zoomer.prototype.updateZoom = function(waitTime)
	{	
		// if reseting image than preview is good enough
		if (this.newPosition.zoomLevel == 0)
		{
			// clear loader
			$(this.loader).css('visibility', 'hidden');
			this.disableZoomAction = false;
			
			// event hook
			$(this.fullZoomImage).triggerHandler('sz_update_zoom', [this]);
			return;
		}
		
		var loaded = this.trueZoomImage != null && this.trueZoomImage.complete;
		
		// if not call us again and wait 
		if(waitTime >= 0 && !loaded)
		{
			var zoomer = this;
			var nextWaitTime = waitTime-Zoomer.UPDATE_CHECK_TIME;
			this.updateZoomTimerId = setTimeout(function(){ zoomer.updateZoom(nextWaitTime); }, Zoomer.UPDATE_CHECK_TIME);
			return;
		}
		else
		{
			// clear loader
			$(this.loader).css('visibility', 'hidden');
			
			// create new true zoom layer
			$(this.trueZoom).width(this.trueZoomWidth);
			$(this.trueZoom).height(this.trueZoomHeight);
			var zoomLevel = this.calculateZoomLevel(this.currentPreviewPosition.zoomLevel);
			var x = this.currentPreviewPosition.x * zoomLevel - this.trueZoomAdjuster.x;
			var y = this.currentPreviewPosition.y * zoomLevel - this.trueZoomAdjuster.y;
			$(this.trueZoom).css({ 'left': x + 'px', 'top': y + 'px' });
			
			// create new true zoom padding layer
			this.createTrueZoomPaddingLayer();
			
			// add true zoom layer
			if (this.overlap.elem == null)
				$(this.previewZoom).append(this.trueZoom);
			else
				$(this.overlap.elem).before(this.trueZoom);
			
			var zoomer = this;
			$(this.trueZoom).fadeIn(Zoomer.UPDATE_FADEIN, function () {
				zoomer.removeTrueZoomClone();
			});
			
			this.disableZoomAction = false;
			this.updateZoomTimerId = null;
			
			// event hook
			$(this.fullZoomImage).triggerHandler('sz_update_zoom', [this]);
		}
	}
	
	Zoomer.prototype.createTrueZoomPaddingLayer = function()
	{
		if (this.image.padWidth > 0)
		{
			if (this.trueZoomPadding.left > 0)
			{
				var left = $('<div>').css({
					margin : '0',
					padding : '0',
					zIndex : $(this.previewZoom).css('z-index'),
					borderStyle : 'none',
					position : 'absolute',
					top: '0'
				});
				left.width(this.trueZoomPadding.left).height(this.trueZoomHeight);
				left.css('left', '0');
				left.css('background-color', '#'+this.padColor.substring(2));
				$(this.trueZoom).append(left);
			}
			if (this.trueZoomPadding.right > 0)
			{
				var right = $('<div>').css({
					margin : '0',
					padding : '0',
					zIndex : $(this.previewZoom).css('z-index'),
					borderStyle : 'none',
					position : 'absolute',
					top: '0'
				});
				right.width(this.trueZoomPadding.right).height(this.trueZoomHeight);
				right.css('left', (this.trueZoomWidth - this.trueZoomPadding.right) + 'px');
				right.css('background-color', '#'+this.padColor.substring(2));
				$(this.trueZoom).append(right);
			}
		}
		else if (this.image.padHeight > 0)
		{
			if (this.trueZoomPadding.top > 0)
			{
				var top = $('<div>').css({
					margin : '0',
					padding : '0',
					zIndex : $(this.previewZoom).css('z-index'),
					borderStyle : 'none',
					position : 'absolute',
					left: '0'
				});
				top.width(this.trueZoomWidth).height(this.trueZoomPadding.top);
				top.css('top', '0');
				top.css('background-color', '#'+this.padColor.substring(2));
				$(this.trueZoom).append(top);
			}
			if (this.trueZoomPadding.bottom > 0)
			{
				var bottom = $('<div>').css({
					margin : '0',
					padding : '0',
					zIndex : $(this.previewZoom).css('z-index'),
					borderStyle : 'none',
					position : 'absolute',
					left: '0'
				});
				bottom.width(this.trueZoomWidth).height(this.trueZoomPadding.bottom);
				bottom.css('top', (this.trueZoomHeight - this.trueZoomPadding.bottom) + 'px');
				bottom.css('background-color', '#'+this.padColor.substring(2));
				$(this.trueZoom).append(bottom);
			}
		}
	}
	
	Zoomer.prototype.calculateZoomLevel = function(zoomLevel)
	{
		// calculate scale value
		return Math.pow(this.zoomPerLevel, zoomLevel);
	}
	
	Zoomer.prototype.zoomPreviewImage = function()
	{
		// update preview window
		
		// update center point
		var newCenter = this.getCenterPoint(this.currentPreviewPosition);
		
		newCenter.x += this.changePosition.x;
		newCenter.y += this.changePosition.y;
		
		this.currentPreviewPosition.zoomLevel += this.changePosition.zoomLevel;
		
		// update newX and newY based on new zoom level
		var zoomLevel = this.calculateZoomLevel(this.currentPreviewPosition.zoomLevel);
		newCenter.x -= this.zoomWidth / (2 * zoomLevel);
		newCenter.y -= this.zoomHeight / (2 * zoomLevel);
		
		// assign new position
		this.currentPreviewPosition.x = newCenter.x;
		this.currentPreviewPosition.y = newCenter.y;
		
		//alert('preview X,Y,Zoom ' + this.currentPreviewPosition.x + ',' + this.currentPreviewPosition.y + ',' + this.currentPreviewPosition.zoomLevel);
		
		// redraw preview using absolute coordinates
		
		// check to see if we are near zero
		if (this.currentPreviewPosition.zoomLevel < .1)
		{
			this.currentPreviewPosition.zoomLevel = 0;
			this.currentPreviewPosition.x = 0;
			this.currentPreviewPosition.y = 0;
		}
		
		// calculate new height and width
		var zoomLevel = this.calculateZoomLevel(this.currentPreviewPosition.zoomLevel);
		var newWidth = this.zoomWidth * zoomLevel;
		var newHeight = this.zoomHeight * zoomLevel;
		
		// calculate offset 
		var layerXOffset = - this.currentPreviewPosition.x * zoomLevel;
		var layerYOffset = - this.currentPreviewPosition.y * zoomLevel;
		
		//alert('layer offset ' + layerXOffset + ',' + layerYOffset);
		//alert('new width,height ' + newWidth + ',' + newHeight);
		
		// set image size
		$(this.previewZoom).width(newWidth).height(newHeight);
		$(this.fullZoomImage).width(newWidth).height(newHeight);
		
		// move and show
		$(this.previewZoom).css({ left: layerXOffset + 'px', top: layerYOffset + 'px' });
		
		//alert('actual width,height ' + $(this.previewZoom).width() + ',' + $(this.previewZoom).height());
		//alert('actual x,y ' + $(this.previewZoom).css('left') + ',' + $(this.previewZoom).css('top'));
		
		// update Navigation Box
		this.updateNavigationBox();
		
		// update overlap position
		this.updateOverlapPosition();
	}
	
	Zoomer.prototype.updateNavigationBox = function()
	{
		// update navigation box
		var zbX = this.currentPreviewPosition.x / this.thumbNailRatio;
		var zbY = this.currentPreviewPosition.y / this.thumbNailRatio;
		var zoomLevel = this.calculateZoomLevel(this.currentPreviewPosition.zoomLevel);
		var zbW = this.zoomWidth / this.thumbNailRatio / zoomLevel;
		var zbH = this.zoomHeight / this.thumbNailRatio / zoomLevel;
		
		this.navigationView.updateZoomBox(this.currentPreviewPosition.zoomLevel,zbX,zbY,zbW,zbH);
	}
	
	Zoomer.prototype.zoomMediaServerImage = function(newPosition)
	{
		var zoomLevel = this.calculateZoomLevel(newPosition.zoomLevel);
		var nextWidth = this.zoomWidth * zoomLevel;
		var nextHeight = this.zoomHeight * zoomLevel;
		
		// create zoom MRL
		var x, y, w, h;
		if (this.zoomWidth + this.grid > nextWidth && this.zoomHeight + this.grid > nextHeight)
		{
			this.trueZoomWidth = nextWidth;
			this.trueZoomHeight = nextHeight;
			
			x = y = 0;
			w = this.image.adjWidth;
			h = this.image.adjHeight;
		} else {
			this.trueZoomWidth = this.zoomWidth + this.grid;
			this.trueZoomHeight = this.zoomHeight + this.grid;
			
			var gridZoomLevel = this.grid / zoomLevel;
			if (gridZoomLevel < 1)
				gridZoomLevel = 1;
			
			x = Math.floor(newPosition.x / gridZoomLevel) * gridZoomLevel;
			y = Math.floor(newPosition.y / gridZoomLevel) * gridZoomLevel;
			w = (this.image.adjWidth / zoomLevel) + (gridZoomLevel * this.image.scale);
			h = (this.image.adjHeight / zoomLevel) + (gridZoomLevel * this.image.scale);
		}
		// alert('x,y ' + x + ',' + y + ' w,h ' + w + ',' + h);
		
		var crLeft = x * this.image.scale - this.image.padWidth;
		var crTop = y * this.image.scale - this.image.padHeight;
		var crRight = crLeft + w;
		var crBottom = crTop + h;
		// alert('crLeft,crTop,crRight,crBottom '+crLeft+','+crTop+','+crRight+','+crBottom);
		
		// set true zoom adjusted position properties
		this.trueZoomAdjuster.x = (newPosition.x - x) * zoomLevel;
		this.trueZoomAdjuster.y = (newPosition.y - y) * zoomLevel;
		
		// set new trueZoom padding layer properties
		this.setTrueZoomPaddingProperties(crLeft, crTop, crRight, crBottom, zoomLevel);
		
		crLeft = Math.floor(crLeft);
		crTop = Math.floor(crTop);
		crRight = Math.ceil(crRight);
		crBottom = Math.ceil(crBottom);
		var zoomMRL = this.createZoomMRL(this.mrl, this.path, this.trueZoomWidth, this.trueZoomHeight, crLeft, crTop, crRight, crBottom, this.requestOption);
		// alert(zoomMRL);
		
		return zoomMRL;
	}
	
	Zoomer.prototype.setTrueZoomPaddingProperties = function(crLeft, crTop, crRight, crBottom, zoomLevel)
	{
		this.trueZoomPadding.left = 0;
		this.trueZoomPadding.top = 0;
		this.trueZoomPadding.right = 0;
		this.trueZoomPadding.bottom = 0;
		
		if (this.image.padWidth > 0)
		{
			if (crLeft < 0)
			{
				this.trueZoomPadding.left = Math.ceil(crLeft / this.image.scale * zoomLevel * -1);
			}
			if (crRight > this.image.width)
			{
				this.trueZoomPadding.right = Math.ceil((crRight - this.image.width) / this.image.scale * zoomLevel);
			}
		}
		else if (this.image.padHeight > 0)
		{
			if (crTop < 0)
			{
				this.trueZoomPadding.top = Math.ceil(crTop / this.image.scale * zoomLevel * -1);
			}
			if (crBottom > this.image.height)
			{
				this.trueZoomPadding.bottom = Math.ceil((crBottom - this.image.height) / this.image.scale * zoomLevel);
			}
		}
	}
	
	Zoomer.prototype.createZoomMRL = function(mrl, path, width, height, crLeft, crTop, crRight, crBottom, option)
	{
		return mrl + path + '?is=' + width +',' + height
						  + '&cr=' + crLeft + ',' + crTop + ',' + crRight + ',' + crBottom
						  + '&cvt=jpeg'
						  + option;
	}
	
	Zoomer.prototype.createThumbNailMRL = function(mrl, path, width, height, background, option)
	{
		return mrl + path + '?is=' + width +',' + height + ',' + background + '&cvt=jpeg' + option;
	}
	
	Zoomer.prototype.getEventPosition = function(e)
	{
		var offset = $(this.previewZoom).offset();
		return {x:(e.pageX - offset.left), y:(e.pageY - offset.top) };
	}
	
	Zoomer.prototype.getNavigationEventPosition = function(e)
	{
		var offset = $(this.navigationView.mainLayer).offset();
		return {x:(e.pageX - offset.left), y:(e.pageY - offset.top) };
	}
	
	// zoom in
	Zoomer.prototype.zoomin = function(position)
	{
		if (this.disabled)
			return;
		
		// event hook
		var result = $(this.fullZoomImage).triggerHandler('sz_zoomin', [this, position]);
		if (result == 'end')
			return;
		
		// do not handle if updating main window
		if (this.disableZoomAction)
			return;
		
		// create new Position object
		var newPosition = new Object();
		
		if (position == undefined)
		{
			var zoomLevel = this.calculateZoomLevel(this.currentPreviewPosition.zoomLevel);
			newPosition.x = this.currentPreviewPosition.x + (this.zoomWidth / (2 * zoomLevel));
			newPosition.y = this.currentPreviewPosition.y + (this.zoomHeight / (2 * zoomLevel));
		}
		else
		{
			// translate click to absolute coordintates
			var zoomLevel = this.calculateZoomLevel(this.currentPreviewPosition.zoomLevel);
			newPosition.x = position.x / zoomLevel;
			newPosition.y = position.y / zoomLevel;
			
			// alert('x,y new x,y ' + position.x + ',' + position.y + ' ' + newPosition.x + ',' + newPosition.y);
		}
		
		// calculate new Zoom
		newPosition.zoomLevel = this.currentPreviewPosition.zoomLevel + 1;
		
		// offset for new zoom level
		zoomLevel = this.calculateZoomLevel(newPosition.zoomLevel);
		newPosition.x -= this.zoomWidth / (2 * zoomLevel);
		newPosition.y -= this.zoomHeight / (2 * zoomLevel);
		
		this.beginAnimation(newPosition);
	}
	
	// zoom out
	Zoomer.prototype.zoomout = function()
	{
		if (this.disabled)
			return;
		
		// event hook
		var result = $(this.fullZoomImage).triggerHandler('sz_zoomout', [this]);
		if (result == 'end')
			return;
		
		// do not handle if updating main window
		if (this.disableZoomAction)
			return;
		
		// create new Position object
		var newPosition = new Object();
		newPosition.x = this.currentPreviewPosition.x;
		newPosition.y = this.currentPreviewPosition.y;
		newPosition.zoomLevel = this.currentPreviewPosition.zoomLevel;
		
		if (newPosition.zoomLevel >= .2)
		{
			newPosition.zoomLevel -= 1;
			
			if (newPosition.zoomLevel <= .2)
			{
				newPosition.zoomLevel = 0;
				newPosition.x = 0;
				newPosition.y = 0;
			}
			else
			{
				// adjust newPosition based on new zoom level
				var zoomLevel = this.calculateZoomLevel(newPosition.zoomLevel + 1);
				newPosition.x -= this.zoomWidth / ( 2 * zoomLevel);
				newPosition.y -= this.zoomHeight / (2 * zoomLevel);
			}
			
			// fire off animation
			this.beginAnimation(newPosition);
		}
	}
	
	// reset
	Zoomer.prototype.reset = function()
	{
		if (this.disabled)
			return;
		
		// event hook
		var result = $(this.fullZoomImage).triggerHandler('sz_reset', [this]);
		if (result == 'end')
			return;
		
		if (this.hasStartPosition())
		{
			this.beginAnimation(this.startPosition);
		}
		else
		{
			if (this.currentPreviewPosition.zoomLevel >= .2)
			{
				var newPosition = new Object();
				newPosition.zoomLevel = 0;
				newPosition.x = 0;
				newPosition.y = 0;
				
				// fire off animation
				this.beginAnimation(newPosition);
			}
		}
	}
	
	// moving
	Zoomer.prototype.moving = function(shiftX, shiftY)
	{
		if (this.disabled)
			return;
		
		// event hook
		var result = $(this.fullZoomImage).triggerHandler('sz_moving', [this, shiftX, shiftY]);
		if (result == 'end')
			return true;
		if (result == 'cancel')
			return false;
		
		if (this.disableZoomAction)
			return false;
		
		if (this.currentPreviewPosition.zoomLevel < 1)
			return false;
		
		$(this.frame).css('cursor', 'move');
		
		var position = $(this.previewZoom).position();
		var x = position.left + shiftX;
		if (x + $(this.previewZoom).width() < $(this.frame).width())
			x = $(this.frame).width() - $(this.previewZoom).width();
		else if (0 < x)
			x = 0;
		$(this.previewZoom).css('left', x+'px');
		
		var y = position.top + shiftY;
		if (y + $(this.previewZoom).height() < $(this.frame).height())
			y = $(this.frame).height() - $(this.previewZoom).height();
		else if (0 < y)
			y = 0;
		$(this.previewZoom).css('top', y+'px');
		
		// update zoom box during drag
		var zoomLevel = this.calculateZoomLevel(this.currentPreviewPosition.zoomLevel);
		var posX = - x / zoomLevel / this.thumbNailRatio;
		var posY = - y / zoomLevel / this.thumbNailRatio;
		this.navigationView.updateZoomBox(this.currentPreviewPosition.zoomLevel,posX,posY);
		
		// update overlap position
		this.updateOverlapPosition();
		
		// translate layer position to absolute coordinates
		var position = $(this.previewZoom).position();
		var zoomLevel = this.calculateZoomLevel(this.currentPreviewPosition.zoomLevel);
		this.currentPreviewPosition.x = this.newPosition.x = - position.left / zoomLevel;
		this.currentPreviewPosition.y = this.newPosition.y = - position.top / zoomLevel;
		
		return true;
	}
	
	// moved
	Zoomer.prototype.moved = function()
	{
		if (this.disabled)
			return;
		
		// event hook
		var result = $(this.fullZoomImage).triggerHandler('sz_moved', [this]);
		if (result == 'end')
			return;
		
		$(this.frame).css('cursor', 'crosshair');
		
		if (this.currentPreviewPosition.zoomLevel < 1)
			return;
		
		// translate layer position to absolute coordinates
		var position = $(this.previewZoom).position();
		var zoomLevel = this.calculateZoomLevel(this.currentPreviewPosition.zoomLevel);
		this.currentPreviewPosition.x = this.newPosition.x = - position.left / zoomLevel;
		this.currentPreviewPosition.y = this.newPosition.y = - position.top / zoomLevel;
		
		// update Zoom Display
		this.setTrueZoomClone();
		this.update();
	}
	
	Zoomer.prototype.setTrueZoomClone = function()
	{
		this.removeTrueZoomClone();
		
		if (this.trueZoom != null)
		{
			this.trueZoomClone = this.trueZoom;
			this.trueZoom = null;
		}
	}
	
	Zoomer.prototype.removeTrueZoomClone = function()
	{
		if (this.trueZoomClone != null)
		{
			$(this.trueZoomClone).remove();
			this.trueZoomClone = null;
		}
	}
	
	Zoomer.prototype.update = function()
	{
		// update Zoom
		this.loadNewZoom();
		this.updateZoom(Zoomer.MAX_ZOOM_WAIT);
		
		// update Navigation Box
		this.updateNavigationBox();
		
		// update overlap position
		this.updateOverlapPosition();
	}
	
	Zoomer.prototype.navigationZoomin = function(position)
	{
		if (this.disabled)
			return;
		
		// event hook
		var result = $(this.fullZoomImage).triggerHandler('sz_navigation_zoomin', [this, position]);
		if (result == 'end')
			return;
		
		// do not handle if updating main window
		if (this.disableZoomAction)
			return;
		
		// create new Position object
		var newPosition = new Object();
		
		// translate click to absolute coordintates
		var offset = $(this.navigationView.mainLayer).offset();
		newPosition.x = (position.x * this.thumbNailRatio);
		newPosition.y = (position.y * this.thumbNailRatio);
		// alert('x,y new x,y ' + position.x + ',' + position.y + ' ' + newPosition.x + ',' + newPosition.y);
		
		// calculate new Zoom
		newPosition.zoomLevel = this.currentPreviewPosition.zoomLevel + 1;
		
		// offset for new zoom  level
		var zoomLevel = this.calculateZoomLevel(newPosition.zoomLevel);
		newPosition.x -= this.zoomWidth / (2 * zoomLevel);
		newPosition.y -= this.zoomHeight / (2 * zoomLevel);
		
		this.beginAnimation(newPosition);
	}
	
	Zoomer.prototype.navigationMoving = function(shiftX, shiftY)
	{
		if (this.disabled)
			return;
		
		// event hook
		var result = $(this.fullZoomImage).triggerHandler('sz_navigation_moving', [this, shiftX, shiftY]);
		if (result == 'end')
			return true;
		if (result == 'cancel')
			return false;
		
		// do not handle if updating main window
		if (this.disableZoomAction)
			return false;
		
		$(this.navigationView.mainLayer).css('cursor', 'pointer');
		
		// update zoom box during drag
		var position = $(this.navigationView.dragView).position();
		var x = position.left+shiftX;
		var y = position.top+shiftY;
		
		if (x < 0)
			x = 0;
		else if (x + this.navigationView.zoomboxWidth > $(this.navigationView.mainLayer).width())
			x = $(this.navigationView.mainLayer).width() - this.navigationView.zoomboxWidth;
		
		if (y < 0)
			y = 0;
		else if (y + this.navigationView.zoomboxHeight > $(this.navigationView.mainLayer).height())
			y = $(this.navigationView.mainLayer).height() - this.navigationView.zoomboxHeight;
		
		this.navigationView.updateZoomBox(this.currentPreviewPosition.zoomLevel,x,y);
		
		// update main window during drag
		// calculate offset
		var zoomLevel = this.calculateZoomLevel(this.currentPreviewPosition.zoomLevel);
		var layerXOffset = -x * zoomLevel * this.thumbNailRatio;
		var layerYOffset = -y * zoomLevel * this.thumbNailRatio;
		
		// move and show
		$(this.previewZoom).css({ left: layerXOffset + 'px', top: layerYOffset + 'px' });
		
		// update overlap position
		this.updateOverlapPosition();
		
		// translate layer position to absolute coordinates
		var position = $(this.previewZoom).position();
		var zoomLevel = this.calculateZoomLevel(this.currentPreviewPosition.zoomLevel);
		this.currentPreviewPosition.x = this.newPosition.x = - position.left / zoomLevel;
		this.currentPreviewPosition.y = this.newPosition.y = - position.top / zoomLevel;
		
		return true;
	}
	
	Zoomer.prototype.navigationMoved = function()
	{
		if (this.disabled)
			return;
		
		// event hook
		var result = $(this.fullZoomImage).triggerHandler('sz_navigation_moved', [this]);
		if (result == 'end')
			return;
		
		$(this.navigationView.mainLayer).css('cursor', 'crosshair');
		
		if (this.currentPreviewPosition.zoomLevel < 1)
			return;
		
		// translate click to absolute coordintates
		var position = $(this.navigationView.dragView).position();
		this.currentPreviewPosition.x = this.newPosition.x = position.left * this.thumbNailRatio;
		this.currentPreviewPosition.y = this.newPosition.y = position.top * this.thumbNailRatio;
		
		// update Zoom
		this.setTrueZoomClone();
		this.loadNewZoom();
		this.updateZoom(Zoomer.MAX_ZOOM_WAIT);
	}
	
	// move to position
	Zoomer.prototype.moveToPosition = function(position)
	{
		if (this.disabled)
			return;
		
		// do not handle if updating main window
		if (this.disableZoomAction)
			return;
		
		this.beginAnimation(position);
	}
	
	// switch navigation
	Zoomer.prototype.switchNavigation = function()
	{
		if (this.disabled)
			return;
		
		if ($(this.navigationView.mainLayer).css('visibility') == 'visible')
		{
			$(this.navigationView.mainLayer).css('visibility', 'hidden');
			$(this.navigationView.dragView).css('visibility', 'hidden');
		}
		else
		{
			$(this.navigationView.mainLayer).css('visibility', 'visible');
			if (this.currentPreviewPosition.zoomLevel > .2)
				$(this.navigationView.dragView).css('visibility', 'visible');
		}
	}
	
	// visible navigation
	Zoomer.prototype.visibleNavigation = function()
	{
		if (this.disabled)
			return;
		
		if ($(this.navigationView.mainLayer).css('visibility') == 'hidden')
		{
			$(this.navigationView.mainLayer).css('visibility', 'visible');
			if (this.currentPreviewPosition.zoomLevel > .2)
				$(this.navigationView.dragView).css('visibility', 'visible');
		}
	}
	
	// hidden navigation
	Zoomer.prototype.hiddenNavigation = function()
	{
		if (this.disabled)
			return;
		
		if ($(this.navigationView.mainLayer).css('visibility') == 'visible')
		{
			$(this.navigationView.mainLayer).css('visibility', 'hidden');
			$(this.navigationView.dragView).css('visibility', 'hidden');
		}
	}
	
	// change image
	Zoomer.prototype.changeImage = function(path, action, args)
	{
		if (this.disabled)
		{
			this.loadImage.disableLoadAction = false;
			this.disableZoomAction = false;
		}
		
		// do not handle if updating image
		if (this.loadImage.disableLoadAction)
			return;
		
		this.loadImage.disableLoadAction = true;
		
		// cancel zoom action
		if (this.disableZoomAction)
		{
			clearTimeout(this.updateZoomTimerId);
			this.updateZoomTimerId = null;
		}
		this.disableZoomAction = true;
		
		// change image path
		this.path = path;
		
		// set action
		if (action == undefined)
		{
			this.loadImage.action = 'switch';
			this.loadImage.actionArgs = null;
		}
		else
		{
			this.loadImage.action = action;
			this.loadImage.actionArgs = args;
		}
		
		if (this.jsonpgis == null)
		{
			var zoomer = this;
			this.ajaxParams.url = this.mrl + '?gis=' + this.path;
			this.ajaxParams.success = function(data){
				try {
					var size = data.split(',');
					if (size.length == 2)
						$.soniczoom.changeImageCallback({id:zoomer.id, width:eval(size[0]), height:eval(size[1])});
					else
						$.soniczoom.changeImageCallback({id:zoomer.id, width:-1, height:-1});
				} catch(e) {
					$.soniczoom.changeImageCallback({id:zoomer.id, width:-1, height:-1});
				}
			};
		}
		else
		{
			this.ajaxParams.url = this.mrl + this.jsonpgis + "?args='" + path + "','$.soniczoom.changeImageCallback','" + encodeURIComponent(this.id) + "'";
		}
		$.ajax(this.ajaxParams);
	}
	
	Zoomer.prototype.changeImageCallback = function(data)
	{
		if (data.width <= 0 || data.height <= 0)
		{
			this.disable();
			if (this.noImage != null)
				$(this.fullZoomImage).attr('src', this.noImage);
			return;
		}
		
		if (this.disabled)
		{
			this.enable();
			this.disableZoomAction = false;
			this.loadImage.disableLoadAction = false;
		}
		
		if (this.preparation == false)
			this.setupCallback(data);
		else
		{
			// set new source image size
			this.setSourceImageSize(data);
			
			// setup Zoom per level
			this.setupZoomPerLevel();
			
			// event hook
			$(this.fullZoomImage).triggerHandler('sz_image_info', [this, data]);
		}
		
		// get new fullZoomImage image
		var thumbNailMRL = this.createThumbNailMRL(this.mrl, this.path, this.zoomWidth, this.zoomHeight, this.padColor, this.requestOption)
		this.loadImage.fullZoomImage = $('<img>').attr('src', thumbNailMRL).css({margin:'0',padding:'0',borderStyle:'none'}).get(0);
		
		// get new thumbNail image
		if (this.navigationDisplay != 'none' && this.navigationImage == 'thumbNail')
		{
			thumbNailMRL = this.createThumbNailMRL(this.mrl, this.path, this.navWidth, this.navHeight, this.padColor, this.requestOption);
			this.loadImage.thumbNail = $('<img>').attr('src', thumbNailMRL).css({margin:'0',padding:'0',borderStyle:'none'}).get(0);
		}
		
		// update image
		this.updateImage(Zoomer.MAX_LOAD_WAIT);
	}
	
	Zoomer.prototype.updateImage = function(waitTime)
	{
		var loaded = this.loadImage.fullZoomImage.complete && ((this.navigationDisplay != 'none' && this.navigationImage == 'thumbNail') ? this.loadImage.thumbNail.complete : true);
		if(waitTime >= 0 && !loaded)
		{
			// show loader icon
			$(this.loader).css('visibility', 'visible');
			
			var zoomer = this;
			var nextWaitTime = waitTime-Zoomer.UPDATE_CHECK_TIME;
			setTimeout(function(){ zoomer.updateImage(nextWaitTime); }, Zoomer.UPDATE_CHECK_TIME);
			return;
		}
		else
		{
			// clear loader
			$(this.loader).css('visibility', 'hidden');
			
			// remove true zoom layer
			if (this.trueZoom != null)
			{
				$(this.trueZoom).remove();
				this.trueZoom = null;
			}
			
			// navigation drag view is non-displayed.
			$(this.navigationView.dragView).css('visibility', 'hidden');
			
			// event hook
			$(this.fullZoomImage).triggerHandler('sz_update_image', [this]);
			
			// action
			if (this.loadImage.action == 'fadeIn')
			{
				$(this.loadImage.fullZoomImage).css({
					margin : '0',
					padding : '0',
					overflow : 'hidden',
					left : '0',
					top : '0',
					zIndex : $(this.previewZoom).css('z-index'),
					borderStyle : 'none',
					position : 'absolute'
				});
				var position = $(this.previewZoom).position();
				$(this.loadImage.fullZoomImage).css('left', Math.abs(position.left) + 'px');
				$(this.loadImage.fullZoomImage).css('top', Math.abs(position.top) + 'px');
				$(this.loadImage.fullZoomImage).width(this.zoomWidth).height(this.zoomHeight);
				if (this.overlap.elem == null)
					$(this.previewZoom).append(this.loadImage.fullZoomImage);
				else
					$(this.overlap.elem).before(this.loadImage.fullZoomImage);
				
				var zoomer = this;
				$(this.loadImage.fullZoomImage).css('display', 'none');
				$(this.loadImage.fullZoomImage).fadeIn(Zoomer.UPDATE_FADEIN, function()
				{
					zoomer.setInitialPosition();
					zoomer.setLoadedImage();
				});
				
				if (this.navigationDisplay != 'none' && this.navigationImage == 'thumbNail')
				{
					$(this.loadImage.thumbNail).css({
						margin : '0',
						padding : '0',
						overflow : 'hidden',
						left : '0',
						top : '0',
						zIndex : $(this.navigationView.mainLayer).css('z-index'),
						borderStyle : 'none',
						position : 'absolute'
					});
					$(this.loadImage.thumbNail).width(this.navWidth).height(this.navHeight);
					$(this.navigationView.mainLayer).append(this.loadImage.thumbNail);
					
					$(this.loadImage.thumbNail).css('display', 'none');
					$(this.loadImage.thumbNail).fadeIn(Zoomer.UPDATE_FADEIN);
				}
			}
			else if (this.loadImage.action == 'keepPosition')
			{
				this.setLoadedImage();
				if (this.currentPreviewPosition.zoomLevel > 0)
					this.update();
			}
			else if (this.loadImage.action == 'setPosition')
			{
				this.setInitialPosition();
				this.setLoadedImage();
				var position = this.loadImage.actionArgs;
				if (position.zoomLevel > 0)
					this.setPosition(position);
			}
			else
			{
				this.setInitialPosition();
				this.setLoadedImage();
			}
		}
	}
	
	Zoomer.prototype.setInitialPosition = function()
	{
		// set initial position
		this.currentPreviewPosition.zoomLevel = 0;
		this.currentPreviewPosition.x = 0;
		this.currentPreviewPosition.y = 0;
		
		// set zoom image size
		$(this.previewZoom).width(this.zoomWidth).height(this.zoomHeight);
		$(this.fullZoomImage).width(this.zoomWidth).height(this.zoomHeight);
		
		// set initial position
		$(this.previewZoom).css({ left: '0', top: '0' });
		
		// update overlap position
		this.updateOverlapPosition();
	}
	
	Zoomer.prototype.setLoadedImage = function()
	{
		var zoomer = this;
		var useNavigationImage = (this.navigationDisplay != 'none' && this.navigationImage == 'thumbNail');
		
		if (useNavigationImage)
		{
			$(this.navigationView.thumbNail).attr('src', $(this.loadImage.thumbNail).attr('src'));
			if (this.navigationView.thumbNail.complete == false)
			{
				this.loadImage.ready.thumbNail = false;
				$(this.navigationView.thumbNail).bind('load', function(e)
				{
					$(zoomer.navigationView.thumbNail).unbind('load');
					zoomer.loadImage.ready.thumbNail = true;
					if (zoomer.loadImage.ready.fullZoomImage)
						zoomer.clearLoadedImage(true);
				});
			}
			else
				this.loadImage.ready.thumbNail = true;
		}
		
		$(this.fullZoomImage).attr('src', $(this.loadImage.fullZoomImage).attr('src'));
		if (this.fullZoomImage.complete == false)
		{
			this.loadImage.ready.fullZoomImage = false;
			$(this.fullZoomImage).bind('load', function()
			{
				$(zoomer.fullZoomImage).unbind('load');
				if (useNavigationImage)
				{
					zoomer.loadImage.ready.fullZoomImage = true;
					if (zoomer.loadImage.ready.thumbNail)
						zoomer.clearLoadedImage(true);
				}
				else
					zoomer.clearLoadedImage(false);
			});
		}
		else {
			if (useNavigationImage)
			{
				if (this.loadImage.ready.thumbNail)
					this.clearLoadedImage(true);
				else
					this.loadImage.ready.fullZoomImage = true;
			}
			else
				this.clearLoadedImage(false);
		}
	}
	
	Zoomer.prototype.clearLoadedImage = function(useNavigationImage)
	{
		if (useNavigationImage)
		{
			$(this.loadImage.thumbNail).remove();
			this.loadImage.thumbNail = null;
			this.loadImage.ready.thumbNail = false;
		}
		
		$(this.loadImage.fullZoomImage).remove();
		this.loadImage.fullZoomImage = null;
		this.loadImage.ready.fullZoomImage = false;
		
		this.disableZoomAction = false;
		this.loadImage.disableLoadAction = false;
	}
	
	// change size
	Zoomer.prototype.changeSize = function(size, type)
	{
		// do not handle if updating image
		if (this.loadImage.disableLoadAction)
			return;
		
		this.loadImage.disableLoadAction = true;
		
		// cancel zoom action
		if (this.disableZoomAction)
		{
			clearTimeout(this.updateZoomTimerId);
			this.updateZoomTimerId = null;
		}
		this.disableZoomAction = true;
		
		// set action
		this.loadImage.action = 'switch';
		this.loadImage.actionArgs = null;
		
		if (type == 'keepZoom')
		{
			// set load image action
			if (this.currentPreviewPosition.zoomLevel > 0)
			{
				// set new new zoom level
				var currentWidth, currentHeight, newWidth, newHeight;
				var scaleX = this.zoomWidth / this.image.width;
				var scaleY = this.zoomHeight / this.image.height;
				if (scaleX < scaleY)
				{
					currentWidth = this.zoomWidth;
					currentHeight = this.image.height * scaleX;
				}
				else
				{
					currentWidth = this.image.width * scaleY;
					currentHeight = this.zoomHeight;
				}
				
				scaleX = size.width / this.image.width;
				scaleY = size.height / this.image.height;
				if (scaleX < scaleY)
				{
					newWidth = size.width;
					newHeight = this.image.height * scaleX;
				}
				else
				{
					newWidth = this.image.width * scaleY;
					newHeight = size.height;
				}
				var zoomLevel = this.calculateZoomLevel(this.currentPreviewPosition.zoomLevel);
				var newZoomLevel = Math.sqrt((currentWidth * zoomLevel * currentHeight * zoomLevel) / (newWidth * newHeight));
				var newZoomPerLevel = Math.pow(newZoomLevel,(1.0/this.currentPreviewPosition.zoomLevel));
				if (newZoomPerLevel > 1.0)
				{
					this.zoomPerLevel = newZoomPerLevel;
					
					// set new size position
					this.newPosition.x = (this.currentPreviewPosition.x + this.zoomWidth / (2 * zoomLevel)) * size.width / this.zoomWidth;
					this.newPosition.y = (this.currentPreviewPosition.y + this.zoomHeight / (2 * zoomLevel)) * size.height / this.zoomHeight;
					this.newPosition.x -= size.width / (2 * newZoomLevel);
					this.newPosition.y -= size.height / (2 * newZoomLevel);
					this.newPosition.zoomLevel = this.currentPreviewPosition.zoomLevel;
					
					this.constrainZoom();
					
					this.currentPreviewPosition.x = this.newPosition.x;
					this.currentPreviewPosition.y = this.newPosition.y;
					
					this.loadImage.action = 'setPosition';
					this.loadImage.actionArgs = this.newPosition;
				}
			}
		}
		
		// change zoom size
		this.zoomWidth = size.width;
		this.zoomHeight = size.height;
		
		// update frame size
		$(this.previewZoom).width(this.zoomWidth).height(this.zoomHeight);
		$(this.frame).width(this.zoomWidth).height(this.zoomHeight);
		$(this.container).width(this.zoomWidth).height(this.zoomHeight);
		
		// set true zoom size
		this.trueZoomWidth = this.zoomWidth;
		this.trueZoomHeight = this.zoomHeight;
		
		// set new source image size
		this.setSourceImageSize({width:this.image.width, height:this.image.height});
		
		// get new fullZoomImage image
		var thumbNailMRL = this.createThumbNailMRL(this.mrl, this.path, this.zoomWidth, this.zoomHeight, this.padColor, this.requestOption)
		this.loadImage.fullZoomImage = $('<img>').attr('src', thumbNailMRL).css({margin:'0',padding:'0',borderStyle:'none'}).get(0);
		
		// get new thumbNail image
		if (this.navigationDisplay != 'none')
		{
			this.setupNavigationSize();
			this.navigationView.changeSize(this.navWidth, this.navHeight);
			
			if (this.navigationImage == 'thumbNail')
			{
				thumbNailMRL = this.createThumbNailMRL(this.mrl, this.path, this.navWidth, this.navHeight, this.padColor, this.requestOption);
				this.loadImage.thumbNail = $('<img>').attr('src', thumbNailMRL).css({margin:'0',padding:'0',borderStyle:'none'}).get(0);
			}
		}
		
		// update image
		this.updateImage(Zoomer.MAX_LOAD_WAIT);
	}
	
	// append overlap
	Zoomer.prototype.appendOverlap = function(content)
	{
		$(this.previewZoom).append(content);
		this.overlap.elem = this.previewZoom.lastChild;
		var position = $(this.overlap.elem).position();
		this.overlap.left = position.left;
		this.overlap.top = position.top;
	}
	
	// remove overlap
	Zoomer.prototype.removeOverlap = function()
	{
		if (this.overlap.elem)
		{
			$(this.overlap.elem).remove();
			this.overlap.elem = null;
			this.overlap.left = 0;
			this.overlap.top = 0;
		}
	}
	
	Zoomer.prototype.updateOverlapPosition = function()
	{
		if (this.overlap.elem != null)
		{
			var position = $(this.previewZoom).position();
			$(this.overlap.elem).css('left', (Math.abs(position.left) + this.overlap.left) + 'px');
			$(this.overlap.elem).css('top', (Math.abs(position.top) + this.overlap.top) + 'px');
		}
	}
	
	Zoomer.prototype.hasStartPosition = function()
	{
		if (this.startPosition != null
		 && this.startPosition.x != undefined
		 && this.startPosition.y != undefined
		 && this.startPosition.zoomLevel != undefined)
		{
			return true;
		}
		return false;
	}
	
	Zoomer.prototype.setPosition = function(newPosition)
	{
		// check to see if zoom is past max
		if (newPosition.zoomLevel > this.maxZoomLevels) 
		{
			//alert('Maximum zoom levels reached');
			$(this.fullZoomImage).triggerHandler('sz_max_zoom_levels', [this]);
			return;
		}
		
		// set newPosition
		this.newPosition = newPosition;
		
		// constrain image
		this.constrainZoom();
		
		// load zoom image in background
		this.loadNewZoom();
		$(this.loader).css('visibility', 'hidden');
		
		// set intervals between frames
		// determine increment for x,y, and  zoom
		var oldCenterPoint = this.getCenterPoint(this.currentPreviewPosition);
		var newCenterPoint = this.getCenterPoint(this.newPosition);
		this.changePosition.x = (newCenterPoint.x - oldCenterPoint.x);
		this.changePosition.y = (newCenterPoint.y - oldCenterPoint.y);
		
		this.changePosition.zoomLevel = (this.newPosition.zoomLevel - this.currentPreviewPosition.zoomLevel);
		
		// zoom preview image
		this.zoomPreviewImage();
		
		// now display zoom image
		this.updateZoom(Zoomer.MAX_ZOOM_WAIT);
		
		// show full zoom image
		$(this.fullZoomImage).css('visibility', 'visible');
	}
	
	Zoomer.prototype.disable = function()
	{
		this.disabled = true;
		$(this.frame).css('cursor', 'default');
		$(this.loader).css('visibility', 'hidden');
		
		if (this.disableZoomAction)
		{
			clearTimeout(this.updateZoomTimerId);
			this.updateZoomTimerId = null;
		}
		
		this.disableZoomAction = true;
		this.loadImage.disableLoadAction = true;
		
		this.setInitialPosition();
		if (this.trueZoom != null)
		{
			$(this.trueZoom).remove();
			this.trueZoom = null;
		}
		
		$(this.fullZoomImage).triggerHandler('sz_disable', [this]);
	}
	
	Zoomer.prototype.enable = function()
	{
		this.disabled = false;
		$(this.frame).css('cursor', 'crosshair');
		
		if (this.disableZoomAction)
		{
			clearTimeout(this.updateZoomTimerId);
			this.updateZoomTimerId = null;
		}
		
		this.disableZoomAction = false;
		this.loadImage.disableLoadAction = false;
		
		$(this.fullZoomImage).triggerHandler('sz_enable', [this]);
	}
	
	function NavigationView(width, height, image, imageDirectory, navigationCss, dragviewCss)
	{
		// save thumbNail image
		if (image == null)
		{
			image = $('<img>').attr('src', imageDirectory + '/transparent.gif').css({margin:'0',padding:'0',borderStyle:'none'});
		}
		this.thumbNail = image;
		
		// create base layer
		this.mainLayer = document.createElement('div');
		if (navigationCss != null)
		{
			$(this.mainLayer).css(navigationCss);
		}
		else
		{
			$(this.mainLayer).addClass('soniczoom-navigation');
		}
		$(this.mainLayer).css({
			margin : '0',
			padding : '0',
			overflow : 'hidden',
			position : 'absolute',
			'cursor' : 'crosshair'
		});
		$(this.mainLayer).width(width).height(height);
		$(this.mainLayer).append(image);
		
		// set up zoom box
		this.zoomboxWidth = width;
		this.zoomboxHeight = height;
		
		// create drag view box
		this.dragViewImage = $('<img>').attr('src', imageDirectory + '/transparent.gif').css({margin:'0',padding:'0',borderStyle:'none'});
		$(this.dragViewImage).width(width-2).height(height-2);
		
		this.dragView = document.createElement('div');
		$(this.dragView).addClass('soniczoom-dragview');
		if (dragviewCss != null)
		{
			$(this.dragView).css(dragviewCss);
		}
		else
		{
			$(this.dragView).addClass('soniczoom-dragview');
		}
		$(this.dragView).css({
			margin : '0',
			padding : '0',
			overflow : 'hidden',
			left : '0',
			top : '0',
			position : 'absolute'
		});
		$(this.dragView).width(width-2).height(height-2);
		$(this.dragView).css('visibility', 'hidden');
		$(this.dragView).append(this.dragViewImage);
		$(this.mainLayer).append(this.dragView);
	}
	
	NavigationView.prototype.updateZoomBox = function(zoomLevel,posX,posY,width,height)
	{
		if (zoomLevel <= .2)
		{
			$(this.dragView).css('visibility', 'hidden');
		}
		else
		{
			// if new width is passed then use it
			if (width != null)
				this.zoomboxWidth = width;
			
			if (height != null)
				this.zoomboxHeight = height;
			
			$(this.dragViewImage).width(this.zoomboxWidth-2).height(this.zoomboxHeight-2);
			$(this.dragView).width(this.zoomboxWidth-2).height(this.zoomboxHeight-2);
			$(this.dragView).css({ left: posX + 'px', top: posY + 'px' });
			
			if ($(this.mainLayer).css('visibility') == 'visible')
				$(this.dragView).css('visibility', 'visible');
		}
	}
	
	NavigationView.prototype.changeSize = function(width, height)
	{
		// set base layer size
		$(this.mainLayer).width(width).height(height);
		
		// set up zoom box
		this.zoomboxWidth = width;
		this.zoomboxHeight = height;
		
		// set drag view box size
		$(this.dragViewImage).width(width-2).height(height-2);
		$(this.dragView).width(width-2).height(height-2);
	}
	
	Mouse.ACTIONCOUNT = 2;
	
	function Mouse() {
		this.on = false;
		this.x = 0;
		this.y = 0;
		this.shiftX = 0;
		this.shiftY = 0;
		this.drag = false;
		
		this.actionCount = Mouse.ACTIONCOUNT;
		this.rollCount = 0;
		
		this.preventClickEvent = false;
	}
	
	Mouse.prototype.start = function(e)
	{
		if (this.on)
			return false;
		this.on = true;
		this.x = e.pageX;
		this.y = e.pageY;
		this.shiftX = 0;
		this.shiftY = 0;
		this.drag = false;
		this.preventEvent(e);
		
		return true;
	}
	
	Mouse.prototype.move = function(e)
	{
		if (this.on == false)
			return false;
		this.shiftX = (e.pageX - this.x);
		this.shiftY = (e.pageY - this.y);
		this.x = e.pageX;
		this.y = e.pageY;
		this.drag = true;
		this.preventEvent(e);
		
		return true;
	}
	
	Mouse.prototype.wheel = function(e)
	{
		var roll = 0;
		if (e.wheelDelta)
			roll = e.wheelDelta / -120;
		else if (e.detail)
			roll = e.detail / 3;
		
		if ((this.rollCount > 0 && roll < 0) || (this.rollCount < 0 && roll > 0))
			this.rollCount = 0;
		
		this.rollCount += roll;
		
		this.preventEvent(e);
		
		if (this.rollCount <= -this.actionCount)
		{
			this.rollCount = 0;
			return -1;
		}
		else if (this.rollCount >= this.actionCount)
		{
			this.rollCount = 0;
			return 1;
		}
		
		return 0;
	}
	
	Mouse.prototype.end = function(e)
	{
		if (this.on == false)
			return false;
		var result = this.drag;
		this.on = false;
		this.x = 0;
		this.y = 0;
		this.shiftX = 0;
		this.shiftY = 0;
		this.drag = false;
		this.preventEvent(e);
		
		return result;
	}
	
	Mouse.prototype.preventEvent = function(e)
	{
		e.preventDefault();
		e.returnValue = false;
		e.stopPropagation();
		e.cancelBubble = false;
	}
	
	Mouse.prototype.cancelDrag = function(e)
	{
		this.on = false;
		this.x = 0;
		this.y = 0;
		this.shiftX = 0;
		this.shiftY = 0;
		this.drag = false;
	}
})(jQuery);