/*******************************************************************************
  ThumbnailControl - creates thumbnail navigator and listens to the viewer for
					 view transformations
  
  GSV 3.0 : PanoJS3
  @author Dmitry Fedorov  <fedorov@ece.ucsb.edu>   
  
  Copyright (c) 2010 Dmitry Fedorov, Center for Bio-Image Informatics
  
  using: isClientTouch() and isClientPhone() from utils.js

*******************************************************************************/

PanoJS.CONTROL_THUMBNAIL_SHOW_MINIMIZE = false;
PanoJS.CONTROL_THUMBNAIL_STYLE = "position: absolute; z-index: 60; opacity:0.5; filter:alpha(opacity=50); ";
PanoJS.CONTROL_IMAGE_PLUS	  = "images/16px_plus.png";
PanoJS.CONTROL_IMAGE_MINUS	 = "images/16px_minus.png";
PanoJS.CONTROL_IMAGE_PROGRESS  = "images/progress.gif";

function trim(v, l, h) {
	if (v<l) return l;
	else
	if (v>h) return h;
	else
	return v;	
}  

function ThumbnailControl(viewer) {
	// queuing causes issues in IE - turn off, not needed anyway
	this.move_delay_ms = 0;  // Delay before moving the viewer
  
	this.viewer = viewer;
	this.initControls();   
	this.createDOMElements();

	this.scale = 1;
	this.x = 0;
	this.y = 0;  
	this.width = 128;  
	this.height = 128; 
  
	this.viewer.addViewerMovedListener(this);
	this.viewer.addViewerZoomedListener(this);
	this.viewer.addViewerResizedListener(this);	
	
	// load thumbnail image
	this.update();
}

ThumbnailControl.prototype.initControls = function() {
	if (PanoJS.CONTROL_THUMBNAIL_UPDATED_URLS) return;
	PanoJS.CONTROL_IMAGE_PLUS	 = PanoJS.STATIC_BASE_URL + PanoJS.CONTROL_IMAGE_PLUS;
	PanoJS.CONTROL_IMAGE_MINUS	= PanoJS.STATIC_BASE_URL + PanoJS.CONTROL_IMAGE_MINUS;
	PanoJS.CONTROL_IMAGE_PROGRESS = PanoJS.STATIC_BASE_URL + PanoJS.CONTROL_IMAGE_PROGRESS;  
	PanoJS.CONTROL_THUMBNAIL_UPDATED_URLS = true;
}

ThumbnailControl.prototype.update = function() {
	this.dom_image.onload = callback(this, this.init);
	this.dom_image.src = this.viewer.thumbnailURL();
}

ThumbnailControl.prototype.init = function() {
	this.dom_image.onload = null;
	if (this.dom_image_progress && this.dom_image_progress.parentNode)
		this.dom_element.removeChild(this.dom_image_progress);   

	// the thumbnail image may be larger that the space allocated for the thumbnail view
	// resize control accordingly to the larger side
	if (this.dom_image.width >= this.dom_image.height) {
		this.dom_image.width = PanoJS.CONTROL_THUMBNAIL_SIZE;
		this.dom_element.style.height = this.dom_image.height+'px';
	} else { // if (this.dom_image.width < this.dom_image.height)
		this.dom_image.height = PanoJS.CONTROL_THUMBNAIL_SIZE;	
		this.dom_element.style.width = this.dom_image.width+'px';	
	}
  
	// store thumbnmail control size for maximizing
	this.dom_width = this.dom_element.style.width;
	this.dom_height = this.dom_element.style.height;  
  
	this.tw = this.dom_image.width;
	this.th = this.dom_image.height;  
	this.thumbscale = this.tw / this.viewer.imageSize().width;

	this.viewer.notifyViewerZoomed();
}

ThumbnailControl.prototype.createDOMElements = function() {
	var de = this.viewer.viewerDomElement();

	this.dom_element = document.createElement('div');
	this.dom_element.className = 'thumbnail';
	de.appendChild(this.dom_element); 
	PanoJS.CONTROL_THUMBNAIL_SIZE = Math.max(this.dom_element.clientWidth, this.dom_element.clientHeight);
	PanoJS.CONTROL_THUMBNAIL_BORDER = (this.dom_element.offsetWidth - this.dom_element.clientWidth) / 2;
	  
	this.dom_surface = document.createElement('div');
	this.dom_surface.className = 'thumbnail_surface';
	this.dom_element.appendChild(this.dom_surface); 

	this.dom_roi = document.createElement('div');
	this.dom_roi.className = 'thumbnail_roi';
	this.dom_element.appendChild(this.dom_roi); 

	this.dom_roi_prev = document.createElement('div');
	this.dom_roi_prev.className = 'thumbnail_roi_preview';
	this.dom_element.appendChild(this.dom_roi_prev); 
	
	this.dom_scale = document.createElement('span');
	this.dom_scale.className = 'thumbnail_scale';
	this.dom_element.appendChild(this.dom_scale); 

//	this loads forever in IE for some reason - we don't need it though
//	this.dom_image_progress = document.createElement('img');
//	this.dom_element.appendChild(this.dom_image_progress);
//	this.dom_image_progress.width = '128';
//	this.dom_image_progress.height = '128';
//	this.dom_image_progress.src = PanoJS.CONTROL_IMAGE_PROGRESS;
	
	this.dom_image = document.createElement('img');
	this.dom_element.appendChild(this.dom_image); 

	this.dom_surface.onmousedown = callback (this, this.onmousedown );
	this.dom_surface.onmouseup   = callback (this, this.onmouseup );
	this.dom_surface.onmousemove = callback (this, this.onmousemove );
	this.dom_surface.onmouseout  = callback (this, this.onmouseout ); 
	
	if (PanoJS.CONTROL_THUMBNAIL_SHOW_MINIMIZE) {
		var style = PanoJS.CONTROL_THUMBNAIL_STYLE + " bottom: 16px; right: 1px; width: 16px;"
		this.btn = document.createElement('span');
		this.dom_element.appendChild(this.btn);
		this.btn.setAttribute("style", style);
		this.btn.style.cssText = style;   
		
		this.img = document.createElement('img');
		this.img.src = PanoJS.CONTROL_IMAGE_MINUS;
		if (this.btn.style.width) this.img.style.width = this.btn.style.width;
		this.btn.appendChild(this.img);	
		
		this.btn.onclick = callback(this, this.toggleMinimize); 
	}
}

ThumbnailControl.prototype.toggleMinimize = function(e) {
	if (!this.minimized) this.minimized=false;
	this.minimized = !this.minimized;

	if (this.minimized) {
		this.img.src = PanoJS.CONTROL_IMAGE_PLUS;
		this.dom_element.style.width = '17px';
		this.dom_element.style.height = '17px'; 

		this.dom_surface.style.display  = 'none';
		this.dom_roi.style.display	  = 'none';
		this.dom_roi_prev.style.display = 'none';		
		this.dom_scale.style.display	= 'none';
		this.dom_image.style.display	= 'none';
		
	} else {
		this.img.src = PanoJS.CONTROL_IMAGE_MINUS;
		this.dom_element.style.width = this.dom_width;
		this.dom_element.style.height = this.dom_height;
		
		this.dom_surface.style.display  = '';
		this.dom_roi.style.display	  = '';
		this.dom_roi_prev.style.display = '';			   
		this.dom_scale.style.display	= '';
		this.dom_image.style.display	= '';
	}
}

ThumbnailControl.prototype.viewerMoved = function(e) {
	if (!this.dom_roi || typeof this.dom_roi == 'undefined') return;
	if (!this.thumbscale)
		return;
	var img_x = -1.0 * (e.x / this.scale);
	var img_y = -1.0 * (e.y / this.scale);  
	var tx = trim( img_x * this.thumbscale, 0, this.tw);
	var ty = trim( img_y * this.thumbscale, 0, this.th);
	var w = trim(this.width, 0, this.viewer.imageSize().width-img_x);
	var h = trim(this.height, 0, this.viewer.imageSize().height-img_y);
	if (img_x<0) w += img_x;
	if (img_y<0) h += img_y;  

	this.dom_roi.style.left = tx + 'px';
	this.dom_roi.style.top  = ty + 'px';   
	this.dom_roi.style.width = Math.min(w*this.thumbscale-2, this.tw-tx-PanoJS.CONTROL_THUMBNAIL_BORDER) + 'px';
	this.dom_roi.style.height = Math.min(h*this.thumbscale-2, this.th-ty-PanoJS.CONTROL_THUMBNAIL_BORDER) + 'px';
	
	this.dom_roi_prev.style.left = tx + 'px';
	this.dom_roi_prev.style.top  = ty + 'px';   
	this.dom_roi_prev.style.width = Math.min(w*this.thumbscale-2, this.tw-tx-PanoJS.CONTROL_THUMBNAIL_BORDER) + 'px';
	this.dom_roi_prev.style.height = Math.min(h*this.thumbscale-2, this.th-ty-PanoJS.CONTROL_THUMBNAIL_BORDER) + 'px';	
}

ThumbnailControl.prototype.viewerZoomed = function(e) {
	this.scale  = e.scale;
	this.width  = e.width;
	this.height = e.height;
	//if (this.dom_scale) this.dom_scale.innerHTML = this.scale*100 + '%';   
	this.viewerMoved(e);
}

ThumbnailControl.prototype.viewerResized = function(e) {
	this.width  = e.width;
	this.height = e.height;
	this.viewerMoved(e);
}

ThumbnailControl.prototype.moveViewer = function (e) {
	if (!this.viewer) return;
	var mx = e.offsetX != undefined ? e.offsetX : e.layerX;
	var my = e.offsetY != undefined ? e.offsetY : e.layerY; 
	var x = (mx / this.thumbscale);
	var y = (my / this.thumbscale);   
	
	this.viewer.resetSlideMotion();
	PanoJS.USE_SLIDE = false;
	this.viewer.recenter( this.viewer.toViewerFromImage({'x': x, 'y': y}), true, true );
	PanoJS.USE_SLIDE = true;	
}

ThumbnailControl.prototype.movePreview = function (e) {
	var mx = e.offsetX != undefined ? e.offsetX : e.layerX;
	var my = e.offsetY != undefined ? e.offsetY : e.layerY; 
	mx -= this.dom_roi_prev.offsetWidth/2;
	my -= this.dom_roi_prev.offsetHeight/2;	
	this.dom_roi_prev.style.left = mx + 'px';
	this.dom_roi_prev.style.top  = my + 'px'; 
}

ThumbnailControl.prototype.moveViewerNow = function (e) {
	if (this.move_timeout) clearTimeout (this.move_timeout);
	this.move_timeout = null;
	this.moveViewer(e);
}

ThumbnailControl.prototype.queueMove = function (e) {
	this.movePreview(e);
	if (this.move_timeout) clearTimeout (this.move_timeout);

	this.move_timeout = setTimeout(callback(this, 'moveViewerNow', e), this.move_delay_ms);
}

ThumbnailControl.prototype.blockPropagation = function (e) {
	if (e.stopPropagation) e.stopPropagation(); // DOM Level 2
	else e.cancelBubble = true;				 // IE	
	if (e.preventDefault) e.preventDefault(); // prevent image dragging
	else e.returnValue = false;		
}

ThumbnailControl.prototype.onmousedown = function (e) {
	if (!e) e = window.event;  // IE event model
	if (e == null) return false;
	this.blockPropagation(e);

	this.mouse_pressed = true;  
	if (this.dom_surface) this.dom_surface.style.cursor = 'move';
	//this.moveViewer(e);
	return false;
}

ThumbnailControl.prototype.onmouseup = function (e) {
	if (!e) e = window.event;  // IE event model  
	if (e == null) return false; 
	this.blockPropagation(e);

	this.mouse_pressed = false; 
	if (this.dom_surface) this.dom_surface.style.cursor = 'default';
	this.moveViewer(e);
	return false;	
}

ThumbnailControl.prototype.onmousemove = function (e) {
	if (!e) e = window.event;  // IE event model  
	if (e == null) return false;
	this.blockPropagation(e);		

	// don't require a drag to move thumbnail
	//if (!this.mouse_pressed) return false;

	if (this.move_delay_ms<=0)
	  this.moveViewer(e);
	else	  
	  this.queueMove(e);
	return false;	  
}

ThumbnailControl.prototype.onmouseout = function (e) {
	if (!e) e = window.event;  // IE event model  
	if (e == null) return false;
	// this.blockPropagation(e);	

	this.mouse_pressed = false;
	if (this.dom_surface) this.dom_surface.style.cursor = 'default';

	// add a slight delay before hiding to ensure mouse has moved away 
	// from area, so it won't get re-shown
	jQuery(".viewer").hide(200, completeHideThumbnailControl);

	return false;
}

function completeHideThumbnailControl() {
	jQuery(".viewer").css('visibility', 'hidden');
	jQuery(".viewer").css('display', 'block');
}
  
/**
 * MtZoomViewer: A Javascript-only viewer for Zoomify-tiled images.
 *
 * Derived from PanoJS (http://code.google.com/p/panojs/), and extended
 * for a product-display use case.
 *
 * Also depends upon jQuery.
 *
 * @author tom.schwenk
 *
 */

/**
 * options we need:
 *  -image base name
 *  -image base URL
 *  -z-index
 *  -thumbnail size
 *  -thumbmail location (small, medium, large)
 *  -zoom window size
 *  -thumbnail coordinates
 *
 * @param viewer
 * @param options
 */
function MtZoomViewer(viewer, options) {
	PanoJS.MSG_BEYOND_MIN_ZOOM = null;
	PanoJS.MSG_BEYOND_MAX_ZOOM = null;
	PanoJS.CREATE_CONTROLS = false;
	PanoJS.CREATE_INFO_CONTROLS = false;
	PanoJS.CREATE_OSD_CONTROLS = false;
	PanoJS.CREATE_THUMBNAIL_CONTROLS = true;

	if (arguments.length)
		this._preInit(viewer, options)
}

MtZoomViewer.prototype = new PanoJS();
MtZoomViewer.prototype.constructor = MtZoomViewer;


MtZoomViewer.prototype._preInit = function(viewer, options) {
	var MY_URL	  = options.imageUrlBase + 'product_zoomify/' + options.imageNameBase ;
	var MY_TILESIZE = 256;
	var myPyramid = new ZoomifyPyramid( options.imageWidth, options.imageHeight, MY_TILESIZE);

	var myProvider = new PanoJS.TileUrlProvider('','','');
	myProvider.assembleUrl = function(xIndex, yIndex, zoom) {
		return MY_URL + '/' + myPyramid.tile_filename( zoom, xIndex, yIndex );
	}

	var panoJSOptions = {
		tileUrlProvider : myProvider,
		tileSize		: myPyramid.tilesize,
		maxZoom		 : myPyramid.getMaxLevel(),
		initialZoom	 : 3,
		imageWidth	  : myPyramid.width,
		imageHeight	 : myPyramid.height,
		blankTile	   : 'js/images/blank.gif',
		loadingTile	 : 'js/images/progress.gif'
	};

	// invoke base class _preInit() method
	this.tmp_preInit = PanoJS.prototype._preInit;
	this.tmp_preInit(viewer, panoJSOptions);

	MtZoomViewer.prototype.thumbnailURL = function() {
		return options.imageUrlBase + 'product_medium/' + options.imageNameBase + '.jpg';
	};

	this.init(options);

	//display the zoom icon and text if product has zoomable image
	jQuery("#ZoomDisplay" + options.imageNameBase).css('display', 'block');
}

MtZoomViewer.prototype.init = function (options) {
	// call base class init first
	this.tmp_init = PanoJS.prototype.init;
	this.tmp_init();

	// then set some additional size and position params once it's initialized
	this.viewerDomElement().style.width = options.zoomRegionWidth;
	this.viewerDomElement().style.height = options.zoomRegionHeight;
	this.thumbnail_control.dom_element.style.left = options.thumbOffsetLeft;
	this.thumbnail_control.dom_element.style.top = options.thumbOffsetTop;
	this.thumbnail_control.dom_element.style.zIndex = options.zIndex;
	this.thumbnail_control.dom_roi.style.zIndex = options.zIndex + 1;
}

function _createViewer(viewer, options, width, height) {
	if (viewer) return;

	viewer = new MtZoomViewer(options.viewerDomName, {
		imageUrlBase		: options.imageUrlBase,
		imageNameBase	   : options.imageNameBase,
		zIndex			  : options.zIndex,
		zoomRegionWidth	 : options.zoomRegionWidth,
		zoomRegionHeight	: options.zoomRegionHeight,
		thumbOffsetLeft	 : options.thumbOffsetLeft,
		thumbOffsetTop	  : options.thumbOffsetTop,
		imageWidth		  : width,
		imageHeight		 : height
	});

}

function displayZoomViewer(viewer, options) {
	var urlPath = options.imageUrlBase + 'product_zoomify/' + options.imageNameBase;
	jQuery.getJSON('ClientAction.aspx?action=getZoomifyImageSize&imageLocation=' + urlPath,
			function (data) {
				var width = data.width;
				var height = data.height;

				// actual available size has to be at least 600x600, otherwise don't show
				if (width >= 600 && height >= 600)
					_createViewer(viewer, options, width, height);
			}
	);
};/*******************************************************************************
 Panoramic JavaScript Image Viewer (PanoJS) 2.0.0
 aka GSV 3.0 aka Giant-Ass Image Viewer 3

 Generates a draggable and zoomable viewer for images that would
 be otherwise too large for a browser window.  Examples would include
 maps or high resolution document scans.

 History:
   GSV 1.0 : Giant-Ass Image Viewer : http://mike.teczno.com/giant/pan/
   @author Michal Migurski <mike-gsv@teczno.com>

   GSV 2.0 : PanoJS : http://code.google.com/p/panojs/
   @author Dan Allen	   <dan.allen@mojavelinux.com>
	 
   GSV 3.0 : PanoJS3
   @author Dmitry Fedorov  <fedorov@ece.ucsb.edu> 

 Images must be precut into tiles: 
   a) tilemaker.py python library shipped with GSV 2.0
   b) Zoomify
   c) imagcnv 
   d) Bisque system
   e) dynamically served by websystems (requires writing TileProvider)

 
  var viewerBean = new PanoJS(element, 'tiles', 256, 3, 1);

 Copyright (c) 2005 Michal Migurski <mike-gsv@teczno.com>
					Dan Allen <dan.allen@mojavelinux.com>
			   2010 Dmitry Fedorov, Center for Bio-Image Informatics <fedorov@ece.ucsb.edu>
  
 Redistribution and use in source form, with or without modification,
 are permitted provided that the following conditions are met:
 1. Redistributions of source code must retain the above copyright
	notice, this list of conditions and the following disclaimer.
 2. The name of the author may not be used to endorse or promote products
	derived from this software without specific prior written permission.
  
 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*******************************************************************************/
 
 
function PanoJS(viewer, options) {
	if (arguments.length)
		this._preInit(viewer, options)
}

// project specific variables
PanoJS.PROJECT_NAME = 'PanoJS';
PanoJS.PROJECT_VERSION = '2.0.0';
PanoJS.REVISION_FLAG = '';

// CSS definition settings
PanoJS.SURFACE_STYLE_CLASS  = 'surface';
PanoJS.SURFACE_ID		   = 'viewer_contorls_surface';
PanoJS.SURFACE_STYLE_ZINDEX = 20;
PanoJS.WELL_STYLE_CLASS	 = 'well';
PanoJS.CONTROLS_STYLE_CLASS = 'controls'
PanoJS.TILE_STYLE_CLASS	 = 'tile';

// language settings
PanoJS.MSG_BEYOND_MIN_ZOOM = 'Cannot zoom out past the current level.';
PanoJS.MSG_BEYOND_MAX_ZOOM = 'Cannot zoom in beyond the current level.';

// defaults if not provided as constructor options
PanoJS.TILE_BASE_URI = 'tiles';
PanoJS.TILE_PREFIX = 'tile-';
PanoJS.TILE_EXTENSION = 'jpg';
PanoJS.TILE_SIZE = 256;
PanoJS.BLANK_TILE_IMAGE = 'blank.gif';
PanoJS.LOADING_TILE_IMAGE = 'blank.gif';
PanoJS.INITIAL_PAN = { 'x' : .5, 'y' : .5 };
PanoJS.USE_LOADER_IMAGE = true;
PanoJS.USE_SLIDE = true;

// dima
if (!PanoJS.STATIC_BASE_URL) PanoJS.STATIC_BASE_URL = '';
PanoJS.CREATE_CONTROLS = true;
PanoJS.CREATE_INFO_CONTROLS = true;
PanoJS.CREATE_OSD_CONTROLS = true;
PanoJS.CREATE_THUMBNAIL_CONTROLS = (isClientPhone() ? false : true);

PanoJS.MAX_OVER_ZOOM = 2;
PanoJS.PRE_CACHE_AMOUNT = 3; // 1 - only visible, 2 - more, 3 - even more

// dima
// The dafault is to pan with wheel events on a mac and zoom on other systems
PanoJS.USE_WHEEL_FOR_ZOOM = (navigator.userAgent.indexOf("Mac OS X")>0 ? false: true);
// the deltas on Firefox and Chrome are 40 times smaller than on Safari or IE
PanoJS.WHEEL_SCALE = (navigator.userAgent.toLowerCase().indexOf('chrome')>-1 ? 1 : 40);

// dima: keys used by keyboard handlers
// right now event is attached to 'document', can't make sure which element is current, skip for now
PanoJS.USE_KEYBOARD = false;
PanoJS.KEY_MOVE_THROTTLE = 15;
PanoJS.KEY_UP	= 38;
PanoJS.KEY_DOWN  = 40;
PanoJS.KEY_RIGHT = 39;
PanoJS.KEY_LEFT  = 37;
PanoJS.KEY_MINUS = {109:0, 189:0};
PanoJS.KEY_PLUS  = {107:0, 187:0};

// performance tuning variables
PanoJS.MOVE_THROTTLE = 3;
PanoJS.SLIDE_DELAY = 40;
PanoJS.SLIDE_ACCELERATION_FACTOR = 5;

// the following are calculated settings
PanoJS.DOM_ONLOAD = (navigator.userAgent.indexOf('KHTML') >= 0 ? false : true);
PanoJS.GRAB_MOUSE_CURSOR = (navigator.userAgent.search(/KHTML|Opera/i) >= 0 ? 'pointer' : (document.attachEvent ? 'url(grab.cur)' : '-moz-grab'));
PanoJS.GRABBING_MOUSE_CURSOR = (navigator.userAgent.search(/KHTML|Opera/i) >= 0 ? 'move' : (document.attachEvent ? 'url(grabbing.cur)' : '-moz-grabbing'));

/**
 * stuff that happens in constructor
 * @param viewer
 * @param options
 */
PanoJS.prototype._preInit = function(viewer, options) {
	// listeners that are notified on a move (pan) event
	this.viewerMovedListeners = [];
	// listeners that are notified on a zoom event
	this.viewerZoomedListeners = [];
	// listeners that are notified on a resize event
	this.viewerResizedListeners = [];


	if (typeof viewer == 'string')
		this.viewer = document.getElementById(viewer);
	else
		this.viewer = viewer;

	if (typeof options == 'undefined') options = {};

	if (typeof options.tileUrlProvider != 'undefined' && (options.tileUrlProvider instanceof PanoJS.TileUrlProvider) )
		this.tileUrlProvider = options.tileUrlProvider;
	else
		this.tileUrlProvider = new PanoJS.TileUrlProvider( options.tileBaseUri ? options.tileBaseUri : PanoJS.TILE_BASE_URI,
														   options.tilePrefix ? options.tilePrefix : PanoJS.TILE_PREFIX,
														   options.tileExtension ? options.tileExtension : PanoJS.TILE_EXTENSION
		);

	this.tileSize = (options.tileSize ? options.tileSize : PanoJS.TILE_SIZE);
	this.realTileSize = this.tileSize;

	if (options.staticBaseURL) PanoJS.STATIC_BASE_URL = options.staticBaseURL;

	// assign and do some validation on the zoom levels to ensure sanity
	this.zoomLevel = (typeof options.initialZoom == 'undefined' ? -1 : parseInt(options.initialZoom));
	this.maxZoomLevel = (typeof options.maxZoom == 'undefined' ? 0 : Math.abs(parseInt(options.maxZoom)));
	if (this.zoomLevel > this.maxZoomLevel) this.zoomLevel = this.maxZoomLevel;

	this.initialPan = (options.initialPan ? options.initialPan : PanoJS.INITIAL_PAN);

	this.initialized = false;
	this.surface = null;
	this.well = null;
	this.width = 0;
	this.height = 0;
	this.top = 0;
	this.left = 0;
	this.x = 0;
	this.y = 0;
	this.mark = { 'x' : 0, 'y' : 0 };
	this.pressed = false;
	this.tiles = [];

	this.cache = {};
	this.blankTile = options.blankTile ? options.blankTile : PanoJS.BLANK_TILE_IMAGE;
	this.loadingTile = options.loadingTile ? options.loadingTile : PanoJS.LOADING_TILE_IMAGE;
	this.resetCache();
	this.image_size = { width: options.imageWidth, height: options.imageHeight };

	// employed to throttle the number of redraws that
	// happen while the mouse is moving
	this.moveCount = 0;
	this.slideMonitor = 0;
	this.slideAcceleration = 0;
}

PanoJS.prototype.init = function() {

	if (document.attachEvent)
	  document.body.ondragstart = function() { return false; }
 
	if (this.width == 0 && this.height == 0) {
	  this.width = this.viewer.offsetWidth;
	  this.height = this.viewer.offsetHeight;
	}
   
	// calculate the zoom level based on what fits best in window
	if (this.zoomLevel < 0 || this.zoomLevel > this.maxZoomLevel) {
			var new_level = 0;
			// here MAX defines partial fit and MIN would use full fit
			while (this.tileSize * Math.pow(2, new_level) <= Math.max(this.width, this.height) && 
				   new_level<=this.maxZoomLevel) {
				this.zoomLevel = new_level;
				new_level += 1;   
			}
	}
	  
	// move top level up and to the left so that the image is centered
	var fullWidth = this.tileSize * Math.pow(2, this.zoomLevel);
	var fullHeight = this.tileSize * Math.pow(2, this.zoomLevel);
	if (this.image_size) {
	  var cur_size = this.currentImageSize();  
	  fullWidth = cur_size.width;
	  fullHeight = cur_size.height;	
	}
	this.x = Math.floor((fullWidth - this.width) * -this.initialPan.x);
	this.y = Math.floor((fullHeight - this.height) * -this.initialPan.y);

	   
	// offset of viewer in the window
	for (var node = this.viewer; node; node = node.offsetParent) {
	  this.top += node.offsetTop;
	  this.left += node.offsetLeft;
	}
		
	// Create viewer elements
	if (!this.surface) {
	  this.surface = document.createElement('div');
	  this.surface.className = PanoJS.SURFACE_STYLE_CLASS;
	  this.surface.id = PanoJS.SURFACE_ID;
	  this.viewer.appendChild(this.surface); 
	  this.surface.style.cursor = PanoJS.GRAB_MOUSE_CURSOR;
	  this.surface.style.zIndex = PanoJS.SURFACE_STYLE_ZINDEX;
	}
	 
	if (!this.well) {
	  this.well = document.createElement('div');
	  this.well.className = PanoJS.WELL_STYLE_CLASS;
	  this.viewer.appendChild(this.well);
	}


	// set event handlers for controls buttons
	if (PanoJS.CREATE_CONTROLS && !this.controls)
	  this.controls = new PanoControls(this);
		 
	if (PanoJS.CREATE_INFO_CONTROLS && !this.info_control) {
	  this.info_control = new InfoControl(this);
	}		  

	if (PanoJS.CREATE_OSD_CONTROLS && !this.osd_control) {
	  this.osd_control = new OsdControl(this);
	}	 
  
	if (PanoJS.CREATE_THUMBNAIL_CONTROLS && !this.thumbnail_control) {
	  this.thumbnail_control = new ThumbnailControl(this);
	}	 
		
	this.prepareTiles();
	this.initialized = true;

	// dima: Setup UI events
	this.ui_listener = this.surface;
	if (isBrowserIE()) this.ui_listener = this.viewer; // issues with IE, hack it
	
	this.ui_listener.onmousedown   = callback(this, this.mousePressedHandler);
	this.ui_listener.onmouseup	 = callback(this, this.mouseReleasedHandler);
	this.ui_listener.onmouseout	= callback(this, this.mouseReleasedHandler);
	this.ui_listener.oncontextmenu = function() {return false;}; 
	this.ui_listener.ondblclick	= callback(this, this.doubleClickHandler);
	if (PanoJS.USE_KEYBOARD)
	  document.onkeydown  = callback(this, this.keyboardHandler);

	this.ui_listener.onmousewheel = callback(this, this.mouseWheelHandler);
	// dima: Firefox standard
	if (!('onmousewheel' in document.documentElement))
	  this.surface.addEventListener ("DOMMouseScroll", callback(this, this.mouseScrollHandler), false);
		
	// dima: support for HTML5 touch interfaces like iphone and android
	this.ui_listener.ontouchstart	= callback(this, this.touchStartHandler);
	this.ui_listener.ontouchmove	 = callback(this, this.touchMoveHandler);
	this.ui_listener.ongesturestart  = callback(this, this.gestureStartHandler);
	this.ui_listener.ongesturechange = callback(this, this.gestureChangeHandler);
	this.ui_listener.ongestureend	= callback(this, this.gestureEndHandler);		
		
	// notify listners
	this.notifyViewerZoomed();	
	this.notifyViewerMoved();  
};

PanoJS.prototype.viewerDomElement = function() { 
	return this.viewer;
};

PanoJS.prototype.thumbnailURL = function() { 
	return this.tileUrlProvider.assembleUrl(0, 0, 0);
};

PanoJS.prototype.imageSize = function() {  
	return this.image_size;
};	 

PanoJS.prototype.currentImageSize = function() { 
	var scale = this.currentScale();
	return { width: this.image_size.width * scale, height: this.image_size.height * scale };	   
};	
	
PanoJS.prototype.prepareTiles = function() {  
	var rows = Math.ceil(this.height / this.tileSize)+ PanoJS.PRE_CACHE_AMOUNT;
	var cols = Math.ceil(this.width / this.tileSize)+ PanoJS.PRE_CACHE_AMOUNT;
		   
	for (var c = 0; c < cols; c++) {
	  var tileCol = [];
			
	  for (var r = 0; r < rows; r++) {
		/**
		 * element is the DOM element associated with this tile
		 * posx/posy are the pixel offsets of the tile
		 * xIndex/yIndex are the index numbers of the tile segment
		 * qx/qy represents the quadrant location of the tile
		 */
		/*
		var tile = {
		  'element' : null,
		  'posx' : 0,
		  'posy' : 0,
		  'xIndex' : c,
		  'yIndex' : r,
		  'qx' : c,
		  'qy' : r
		};*/
		
		var tile = new Tile(this, c, r);
		
		tileCol.push(tile);
	  }
	  this.tiles.push(tileCol);
	}
		
	this.positionTiles();
};
	
/**
 * Position the tiles based on the x, y coordinates of the
 * viewer, taking into account the motion offsets, which
 * are calculated by a motion event handler.
 */
PanoJS.prototype.positionTiles = function(motion, reset) { 
	// default to no motion, just setup tiles
	if (typeof motion == 'undefined') {
	  motion = { 'x' : 0, 'y' : 0 };
	}

	var cur_size = this.currentImageSize();
	   
	for (var c = 0; c < this.tiles.length; c++) {
	  for (var r = 0; r < this.tiles[c].length; r++) {
		var tile = this.tiles[c][r];
				
		tile.posx = (tile.xIndex * this.tileSize) + this.x + motion.x;
		tile.posy = (tile.yIndex * this.tileSize) + this.y + motion.y;
				
		var visible = true;
				
		if (tile.posx > this.width  +this.tileSize ) {
		  // tile moved out of view to the right
		  // consider the tile coming into view from the left
		  do {
			tile.xIndex -= this.tiles.length;
			tile.posx = (tile.xIndex * this.tileSize) + this.x + motion.x;
		  } while (tile.posx > this.width +this.tileSize  );
					
		  if (tile.posx + this.tileSize < 0) {
			visible = false;
		  }
					
		} else {
		  // tile may have moved out of view from the left
		  // if so, consider the tile coming into view from the right
		  while (tile.posx < -this.tileSize  *2) {
			tile.xIndex += this.tiles.length;
			tile.posx = (tile.xIndex * this.tileSize) + this.x + motion.x;
		  }
					
		  if (tile.posx > this.width  +this.tileSize) {
			visible = false;
		  }
		}
				
		if (tile.posy > this.height   +this.tileSize) {
		  // tile moved out of view to the bottom
		  // consider the tile coming into view from the top
		  do {
			tile.yIndex -= this.tiles[c].length;
			tile.posy = (tile.yIndex * this.tileSize) + this.y + motion.y;
		  } while (tile.posy > this.height   +this.tileSize);
					
		  if (tile.posy + this.tileSize < 0) {
			visible = false;
		  }
					
		} else {
		  // tile may have moved out of view to the top
		  // if so, consider the tile coming into view from the bottom
		  while (tile.posy < -this.tileSize  *2) {
			tile.yIndex += this.tiles[c].length;
			tile.posy = (tile.yIndex * this.tileSize) + this.y + motion.y;
		  }
					
		  if (tile.posy > this.height   +this.tileSize) {
			visible = false;
		  }
		}
				
		// additional constraint				
		if (tile.xIndex*this.tileSize >= cur_size.width) visible = false;
		if (tile.yIndex*this.tileSize >= cur_size.height) visible = false;					
				
		// display the image if visible
		if (visible)
			this.assignTileImage(tile);
		else
			this.removeTileFromWell(tile);
	  }
	}

	// reset the x, y coordinates of the viewer according to motion
	if (reset) {
	  this.x += motion.x;
	  this.y += motion.y;
	}
};
	
PanoJS.prototype.removeTileFromWell = function(tile) {  
	if (!tile || !tile.element || !tile.element.parentNode) return;
	this.well.removeChild(tile.element);   
	tile.element = null;	  
};
	
   
/**
 * Determine the source image of the specified tile based
 * on the zoom level and position of the tile.  If forceBlankImage
 * is specified, the source should be automatically set to the
 * null tile image.  This method will also setup an onload
 * routine, delaying the appearance of the tile until it is fully
 * loaded, if configured to do so.
 */
PanoJS.prototype.assignTileImage = function(tile) { 
	var tileImgId, src;
	var useBlankImage = false;
		
	// check if image has been scrolled too far in any particular direction
	// and if so, use the null tile image
	if (!useBlankImage) {
	  var left = tile.xIndex < 0;
	  var high = tile.yIndex < 0;
	  
	  // dima: allow zooming in more than 100%
	  var cur_size = this.currentImageSize();	  
	  var right = tile.xIndex*this.tileSize >= cur_size.width;
	  var low   = tile.yIndex*this.tileSize >= cur_size.height;			  
			
	  if (high || left || low || right) {
		useBlankImage = true;
	  }
	}

	if (useBlankImage) {
	  tileImgId = 'blank';
	  src = this.cache['blank'].src;
	}
	else {
	  tileImgId = src = this.tileUrlProvider.assembleUrl(tile.xIndex, tile.yIndex, this.zoomLevel);
	}

	// only remove tile if identity is changing
	if (tile.element != null &&
	  tile.element.parentNode != null &&
	  tile.element.relativeSrc != null &&	  
	  tile.element.relativeSrc != src) {
	  delete this.cache[tile.element.relativeSrc];
	  this.well.removeChild(tile.element);
	}

	var scale = Math.max(this.tileSize / this.realTileSize, 1.0);		 
	var tileImg = this.cache[tileImgId];

	//window.localStorage (details)
	//var available = navigator.mozIsLocallyAvailable("my-image-file.png", true);

	// create cache if not exist
	if (tileImg == null)
	  //tileImg = this.cache[tileImgId] = this.createPrototype('', src); // delayed loading
	  tileImg = this.cache[tileImgId] = this.createPrototype(src);
	else
	  tileImg.done = true;

	//if (tileImg.done)  
	if (tileImg.naturalWidth && tileImg.naturalHeight && tileImg.naturalWidth>0 && tileImg.naturalHeight>0) {
	  tileImg.style.width = tileImg.naturalWidth*scale + 'px';
	  tileImg.style.height = tileImg.naturalHeight*scale + 'px';   
	} else 
	if (isBrowserIE() && tileImg.offsetWidth>0 && tileImg.offsetHeight>0) { // damn IE does not have naturalWidth ...
	  tileImg.style.width = tileImg.offsetWidth*scale + 'px';
	  tileImg.style.height = tileImg.offsetHeight*scale + 'px';		 
	}

	if ( tileImg.done || !tileImg.delayed_loading &&
		 (useBlankImage || !PanoJS.USE_LOADER_IMAGE || tileImg.complete || (tileImg.image && tileImg.image.complete))  ) {
	  tileImg.onload = null;
	  if (tileImg.image) tileImg.image.onload = null;
			
	  if (tileImg.parentNode == null) {
		tile.element = this.well.appendChild(tileImg);
	  }  
	  tileImg.done = true;	  
	} else {
	  var loadingImg = this.createPrototype(this.cache['loading'].src);
	  loadingImg.targetSrc = tileImgId;
			
	  var well = this.well;
	  tile.element = well.appendChild(loadingImg);
	  tileImg.onload = function() {
		// make sure our destination is still present
		if (loadingImg.parentNode && loadingImg.targetSrc == tileImgId) {
		  tileImg.style.top = loadingImg.style.top;
		  tileImg.style.left = loadingImg.style.left;
		  if (tileImg.naturalWidth && tileImg.naturalHeight && tileImg.naturalWidth>0 && tileImg.naturalHeight>0) {
			tileImg.style.width = tileImg.naturalWidth*scale + 'px';
			tileImg.style.height = tileImg.naturalHeight*scale + 'px'; 
		  } else 
		  if (isBrowserIE() && tileImg.offsetWidth>0 && tileImg.offsetHeight>0) { // damn IE does not have naturalWidth ...
			tileImg.style.width = tileImg.offsetWidth*scale + 'px';
			tileImg.style.height = tileImg.offsetHeight*scale + 'px';		 
		  }		  
		  well.replaceChild(tileImg, loadingImg);
		  tile.element = tileImg;
		} else {
		  // delete a tile if the destination is not present anymore
		  if (loadingImg.parentNode) {
			well.removeChild(loadingImg);   
			tile.element = null;	  
		  }		   
		}
				
		tileImg.onload = function() {};
		return false;
	  }

	  // dima, fetch image after onload method is set-up
	  if (!tileImg.done) {// && tileImg.delayed_loading) {
		tileImg.src = tileImg.relativeSrc;
	  }
			
	  // konqueror only recognizes the onload event on an Image
	  // javascript object, so we must handle that case here
	  if (!PanoJS.DOM_ONLOAD) {
		tileImg.image = new Image();
		tileImg.image.onload = tileImg.onload;
		tileImg.image.src = tileImg.src;
	  }
	}
	
	if (tile.element) {
	  tile.element.style.top = tile.posy + 'px';
	  tile.element.style.left = tile.posx + 'px';	
	}
	
};

PanoJS.prototype.createPrototype = function(src, src_to_load) {  
	var img = document.createElement('img');
	img.src = src;
	if (!src_to_load)
	  img.relativeSrc = src;
	else {
	  img.relativeSrc = src_to_load;
	  img.delayed_loading = true;
	}
	img.className = PanoJS.TILE_STYLE_CLASS;
	//img.style.width = this.tileSize + 'px';
	//img.style.height = this.tileSize + 'px';
	return img;
};
	
PanoJS.prototype.currentScale = function() {
	var scale = 1.0;
	if (this.zoomLevel<this.maxZoomLevel)
	  scale = 1.0 / Math.pow(2, Math.abs(this.zoomLevel-this.maxZoomLevel));
	else
	if (this.zoomLevel>this.maxZoomLevel)
	  scale = Math.pow(2, Math.abs(this.zoomLevel-this.maxZoomLevel));
	return scale;
};
  
PanoJS.prototype.toImageFromViewer = function(p) {
	var scale = this.currentScale();
	p.x = (p.x / scale);
	p.y = (p.y / scale);	
	return p;
};  
	
PanoJS.prototype.toViewerFromImage = function(p) { 
	var scale = this.currentScale();
	p.x = (p.x * scale);
	p.y = (p.y * scale);	
	return p;
};  

PanoJS.prototype.addViewerMovedListener = function(listener) {	
	this.viewerMovedListeners.push(listener);
};
	
PanoJS.prototype.addViewerZoomedListener = function(listener) {  
	this.viewerZoomedListeners.push(listener);
};

PanoJS.prototype.addViewerResizedListener = function(listener) {   
	this.viewerResizedListeners.push(listener);
};  
	
// Notify listeners of a zoom event on the viewer.
PanoJS.prototype.notifyViewerZoomed = function() {	  
	var scale = this.currentScale();
	var w = this.surface.clientWidth / scale;
	var h = this.surface.clientHeight / scale;  
	
	for (var i = 0; i < this.viewerZoomedListeners.length; i++)
	  this.viewerZoomedListeners[i].viewerZoomed( new PanoJS.ZoomEvent(this.x, this.y, this.zoomLevel, scale, w, h) );
};
  
// dima : Notify listeners of a zoom event on the viewer
PanoJS.prototype.notifyViewerResized = function() {   
	var scale = this.currentScale();
	var w = this.surface.clientWidth / scale;
	var h = this.surface.clientHeight / scale;  
	for (var i = 0; i < this.viewerResizedListeners.length; i++)
	  this.viewerResizedListeners[i].viewerResized( new PanoJS.ResizeEvent(this.x, this.y, w, h) );
};
	
// Notify listeners of a move event on the viewer.
PanoJS.prototype.notifyViewerMoved = function(coords) {	  
	if (typeof coords == 'undefined') {
		coords = { 'x' : 0, 'y' : 0 };
	}
		
	for (var i = 0; i < this.viewerMovedListeners.length; i++) {
		this.viewerMovedListeners[i].viewerMoved( new PanoJS.MoveEvent( this.x + (coords.x - this.mark.x), this.y + (coords.y - this.mark.y))
		);
	}
};

PanoJS.prototype.zoom = function(direction) {	
	// ensure we are not zooming out of range
	if (this.zoomLevel + direction < 0) {
		if (PanoJS.MSG_BEYOND_MIN_ZOOM) {
			alert(PanoJS.MSG_BEYOND_MIN_ZOOM);
		}
		return;
	}
	if (this.zoomLevel+direction > this.maxZoomLevel+PanoJS.MAX_OVER_ZOOM) return;
	
	this.blank();
	this.resetCache();	   
		
	if (this.zoomLevel+direction > this.maxZoomLevel) {
	  //dima
	  var scale_dif = (this.zoomLevel+direction - this.maxZoomLevel) * 2;
		this.tileSize = this.realTileSize*scale_dif;	  
	} else {
		this.tileSize = this.realTileSize;
	}
		
	var coords = { 'x' : Math.floor(this.width / 2), 'y' : Math.floor(this.height / 2) };
		
	var before = {
	  'x' : (coords.x - this.x),
	  'y' : (coords.y - this.y)
	};
		
	var after = {
	  'x' : Math.floor(before.x * Math.pow(2, direction)),
	  'y' : Math.floor(before.y * Math.pow(2, direction))
	};
		
	this.x = coords.x - after.x;
	this.y = coords.y - after.y;
	this.zoomLevel += direction;
		
	this.positionTiles();
	this.notifyViewerZoomed();
};

PanoJS.prototype.update = function() {	 
	this.blank();
	this.resetCache();
	this.positionTiles();
	if (this.thumbnail_control) this.thumbnail_control.update();
};	
	
// Clear all the tiles from the well for a complete reinitialization of the
// viewer. At this point the viewer is not considered to be initialized.
PanoJS.prototype.clear = function() {	  
	this.blank();
	this.initialized = false;
	this.tiles = [];
	this.resetCache();
};
	
PanoJS.prototype.resetCache = function() {	 
	this.cache = {};
	this.cache['blank'] = new Image();
	this.cache['blank'].src = this.blankTile;
	if (this.blankTile != this.loadingTile) {
	  this.cache['loading'] = new Image();
	  this.cache['loading'].src = this.loadingTile;
	} else {
	  this.cache['loading'] = this.cache['blank'];
	}	
};	
	
// Remove all tiles from the well, which effectively "hides"
// them for a repaint.
PanoJS.prototype.blank = function() {   
	for (imgId in this.cache) {
	  var img = this.cache[imgId];
	  if (!img) continue;
	  img.onload = function() {};
	  if (img.image) {
		img.image.onload = function() {};
	  }
			
	  if (img.parentNode != null) {
		this.well.removeChild(img);
	  }
	}
	this.resetCache();
};
	
// Method specifically for handling a mouse move event.  A direct
// movement of the viewer can be achieved by calling positionTiles() directly.
PanoJS.prototype.moveViewer = function(coords) {
  if (coords.x == this.x && coords.y == this.y) return;
  this.positionTiles({ 'x' : (coords.x - this.mark.x), 'y' : (coords.y - this.mark.y) });
  this.notifyViewerMoved(coords);
};
	
// dima: Event that works for any input, expects DeltaX and DeltaY
PanoJS.prototype.moveViewerBy = function(coords) {  
	  this.positionTiles(coords, true);
	  //this.notifyViewerMoved(coords);
	  this.notifyViewerMoved();
},
  
  
/**
 * Make the specified coords the new center of the image placement.
 * This method is typically triggered as the result of a double-click
 * event.  The calculation considers the distance between the center
 * of the viewable area and the specified (viewer-relative) coordinates.
 * If absolute is specified, treat the point as relative to the entire
 * image, rather than only the viewable portion.
 */
PanoJS.prototype.recenter = function(coords, absolute, skip_motion) {
  skip_motion = typeof(skip_motion) != 'undefined' ? skip_motion : false; 
  if (absolute) {
	coords.x += this.x;
	coords.y += this.y;
  }
  if (coords.x == this.x && coords.y == this.y) return;	  
	  
  var motion = {
	'x' : Math.floor((this.width / 2) - coords.x),
	'y' : Math.floor((this.height / 2) - coords.y)
  };
	  
  if (motion.x == 0 && motion.y == 0) {
	return;
  }
	  
  if (PanoJS.USE_SLIDE && !skip_motion) {
	var target = motion;
	var x, y;
	// handle special case of vertical movement
	if (target.x == 0) {
	  x = 0;
	  y = this.slideAcceleration;
	}
	else {
	  var slope = Math.abs(target.y / target.x);
	  x = Math.round(Math.pow(Math.pow(this.slideAcceleration, 2) / (1 + Math.pow(slope, 2)), .5));
	  y = Math.round(slope * x);
	}
	
	motion = {
	  'x' : Math.min(x, Math.abs(target.x)) * (target.x < 0 ? -1 : 1),
	  'y' : Math.min(y, Math.abs(target.y)) * (target.y < 0 ? -1 : 1)
	}
  }

  // reject any motion that pushes the image beyond the visible area
  if (this.x + motion.x > 0)
	motion.x = -1*this.x;
  if (this.y + motion.y > 0)
	motion.y = -1*this.y;
  if (this.x + motion.x +(this.image_size.width - this.width) < 0) // 1200 - 480
	motion.x = -1*(this.image_size.width - this.width) - this.x;
  if (this.y + motion.y +(this.image_size.height - this.height) < 0) // 1200 - 480
	motion.y = -1*(this.image_size.height - this.height) - this.y;

  this.positionTiles(motion, true);
  this.notifyViewerMoved();
	  
  if (!PanoJS.USE_SLIDE && !skip_motion) {
	return;
  }
	  
  var newcoords = {
	'x' : coords.x + motion.x,
	'y' : coords.y + motion.y
  };
	  
  var self = this;
  // TODO: use an exponential growth rather than linear (should also depend on how far we are going)
  // FIXME: this could be optimized by calling positionTiles directly perhaps
  this.slideAcceleration += PanoJS.SLIDE_ACCELERATION_FACTOR;
  this.slideMonitor = setTimeout(function() { self.recenter(newcoords); }, PanoJS.SLIDE_DELAY );
};

PanoJS.prototype.resize = function() {  
  // IE fires a premature resize event
  if (!this.initialized) return;
  if (this.width == this.viewer.offsetWidth && this.height == this.viewer.offsetHeight) return;
	  
  var newWidth = this.viewer.offsetWidth;
  var newHeight = this.viewer.offsetHeight;
  this.viewer.style.display = 'none';
  this.clear();
  this.width = newWidth;
  this.height = newHeight;
	  
  this.prepareTiles();
  this.positionTiles();
  this.viewer.style.display = '';
  this.initialized = true;
  this.notifyViewerMoved();
  this.notifyViewerResized();
};

PanoJS.prototype.toggleMaximize = function() {  
  if (!this.maximized) this.maximized = false;
  this.maximized = !this.maximized;
  
  var vd = this.viewer;
  if (this.maximized) {
	  this.viewer_style = { 'width': vd.style.width, 'height': vd.style.height,
		  'position': vd.style.position, 'zIndex': vd.style.zIndex,
		  'left': vd.style.left, 'top': vd.style.top };
	  this.document_style = { 'padding': document.body.style.padding, 'overflow': document.body.style.overflow };
	  
	  vd.style.position = 'fixed';
	  //vd.style.position = 'absolute';			
	  vd.style.zIndex   = '14999';
	  //vd.style.left	 = window.scrollX + 'px';
	  //vd.style.top	  = window.scrollY + 'px';
	  vd.style.left	 = '0px';
	  vd.style.top	  = '0px';
	  vd.style.width	= '100%';
	  vd.style.height   = '100%'; 
	  document.body.style.overflow = 'hidden';
	  document.body.style.padding = '0';
	  if (isMobileSafari()) {
		vd.style.left = window.scrollX + 'px';
		vd.style.top  = window.scrollY + 'px';
		vd.style.width	= window.innerWidth + 'px';
		vd.style.height   = window.innerHeight + 'px';		
	  }
  } else {
	  document.body.style.padding = this.document_style.padding;
	  document.body.style.overflow = this.document_style.overflow;		  
	  vd.style.width	= this.viewer_style.width;
	  vd.style.height   = this.viewer_style.height;
	  vd.style.position = this.viewer_style.position;
	  vd.style.zIndex   = this.viewer_style.zIndex;
	  vd.style.left	 = this.viewer_style.left;
	  vd.style.top	  = this.viewer_style.top;
  }
  
  this.resize();
};
  
/**
 * Resolve the coordinates from this mouse event by subtracting the
 * offset of the viewer in the browser window (or frame).  This does
 * take into account the scroll offset of the page.
 */
PanoJS.prototype.resolveCoordinates = function(e) { 
  if (this.maximized)
	return { 'x' : e.clientX, 'y' : e.clientY };

  return {
	'x' : (e.pageX || (e.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft))) - this.left,
	'y' : (e.pageY || (e.clientY + (document.documentElement.scrollTop || document.body.scrollTop))) - this.top
  };
};

PanoJS.prototype.press = function(coords) {  
  this.activate(true);
  this.mark = coords;
  this.mouse_have_moved = false;
};

PanoJS.prototype.release = function(coords) {  
  this.activate(false);
  var motion = {
	'x' : (coords.x - this.mark.x),
	'y' : (coords.y - this.mark.y)
  };
	  
  this.x += motion.x;
  this.y += motion.y;
  this.mark = { 'x' : 0, 'y' : 0 };
  this.mouse_have_moved = false;		
};
  
/**
 * Activate the viewer into motion depending on whether the mouse is pressed or
 * not pressed.  This method localizes the changes that must be made to the
 * layers.
 */
PanoJS.prototype.activate = function(pressed) {
  this.pressed = pressed;
  this.surface.style.cursor = (pressed ? PanoJS.GRABBING_MOUSE_CURSOR : PanoJS.GRAB_MOUSE_CURSOR);
  this.ui_listener.onmousemove = (pressed ? callback(this, this.mouseMovedHandler) : function() {});
};
  
/**
 * Check whether the specified point exceeds the boundaries of
 * the viewer's primary image.
 */
PanoJS.prototype.pointExceedsBoundaries = function(coords) {  
  return (coords.x < this.x ||
		  coords.y < this.y ||
		  coords.x > (this.tileSize * Math.pow(2, this.zoomLevel) + this.x) ||
		  coords.y > (this.tileSize * Math.pow(2, this.zoomLevel) + this.y));
};
  
// QUESTION: where is the best place for this method to be invoked?
PanoJS.prototype.resetSlideMotion = function() {  
  // QUESTION: should this be > 0 ? 
  if (this.slideMonitor != 0) {
	clearTimeout(this.slideMonitor);
	this.slideMonitor = 0;
  }
	  
  this.slideAcceleration = 0;
};



//-------------------------------------------------------
// Mouse Events
//-------------------------------------------------------

PanoJS.prototype.blockPropagation = function (e) {
	if (e.stopPropagation) e.stopPropagation(); // DOM Level 2
	else e.cancelBubble = true;				 // IE	
	if (e.preventDefault) e.preventDefault(); // prevent image dragging
	else e.returnValue=false;	
}

PanoJS.prototype.mousePressedHandler = function(e) {
  e = e ? e : window.event;
  this.blockPropagation(e);
	
  // only grab on left-click
  var coords = this.resolveCoordinates(e);
  //if (this.pointExceedsBoundaries(coords))
	this.press(coords);
	
  // NOTE: MANDATORY! must return false so event does not propagate to well!
  return false;
};

PanoJS.prototype.mouseReleasedHandler = function(e) {
  e = e ? e : window.event;
  if (!this.pressed) return false;
  var coords = this.resolveCoordinates(e);	
  var motion = {
		'x' : (coords.x - this.mark.x),
		'y' : (coords.y - this.mark.y)
  };		
  var moved = this.mouse_have_moved;
  this.release(coords);
	
  // only if there was little movement
  if (moved || motion.x>5 || motion.y>5) return false;
	
  if (e.button == 2) {
	this.blockPropagation(e);	  
	this.zoom(-1);	
  } else
  // move on one click
  if (e.button < 2) {
	//if (!this.pointExceedsBoundaries(coords)) {
		 this.resetSlideMotion();
		 this.recenter(coords);
	//}		
  }
	
  return false;	
};

PanoJS.prototype.mouseMovedHandler = function(e) {
  e = e ? e : window.event;
	
  // only move on left-click
  if (e.button < 2) {
	this.mouse_have_moved = true;
	this.moveCount++;
	if (this.moveCount % PanoJS.MOVE_THROTTLE == 0)
	  this.moveViewer(this.resolveCoordinates(e));
  }
  return false;		
};

PanoJS.prototype.doubleClickHandler = function(e) {
  e = e ? e : window.event;
  //var coords = this.resolveCoordinates(e);
  //if (!this.pointExceedsBoundaries(coords)) {
	//this.resetSlideMotion();
	//this.recenter(coords);		
	this.zoom(1);
  //}
  return false;  
};

PanoJS.prototype.mouseWheelHandler = function(e) {
  e = e ? e : window.event;
  this.blockPropagation(e);	 
  
  if (PanoJS.USE_WHEEL_FOR_ZOOM) {
	  if (e.wheelDelta<0) this.zoom(-1);
	  else				
	  if (e.wheelDelta>0) this.zoom(1);  
  } else {
	  var dx = e.wheelDeltaX/PanoJS.WHEEL_SCALE;
	  var dy = e.wheelDeltaY/PanoJS.WHEEL_SCALE;
	  this.moveViewerBy({'x': dx,'y': dy});
  }  
  return false;	  
};

PanoJS.prototype.mouseScrollHandler = function(e) {
  e = e ? e : window.event;
  this.blockPropagation(e); 
  
  // Here we only have delta Y, so for firefox only Zoom will be implemented
  //var wheelData = e.detail * -1 * PanoJS.WHEEL_SCALE; // adjust delta value in sync with Webkit	
  if (e.detail<0) this.zoom(1);
  else				
  if (e.detail>0) this.zoom(-1);
  
  return false;  
};

//----------------------------------------------------------------------
// keyboard events
//----------------------------------------------------------------------

PanoJS.prototype.keyboardHandler = function(e) {
  if (!PanoJS.USE_KEYBOARD) return;  
  e = e ? e : window.event;
  var key = e.keyCode ? e.keyCode : e.which;
  
  if (key in PanoJS.KEY_MINUS) {
	  this.blockPropagation(e); 
	  this.zoom(-1);
	  return false;	  
  } else 
  if (key in PanoJS.KEY_PLUS) {
	  this.blockPropagation(e); 
	  this.zoom(1);
	  return false;
  } else
  if (key == PanoJS.KEY_UP) {
	  this.blockPropagation(e); 
	  this.moveViewerBy({'x': 0,'y': -PanoJS.KEY_MOVE_THROTTLE});
	  return false;	  
  } else 
  if (key == PanoJS.KEY_RIGHT) {
	  this.blockPropagation(e); 
	  this.moveViewerBy({'x': PanoJS.KEY_MOVE_THROTTLE,'y': 0});	  
	  return false;	  
  } else 
  if (key == PanoJS.KEY_DOWN) {
	  this.blockPropagation(e); 
	  this.moveViewerBy({'x': 0,'y': PanoJS.KEY_MOVE_THROTTLE});	  
	  return false;	  
  } else 
  if (key == PanoJS.KEY_LEFT) {
	  this.blockPropagation(e); 
	   this.moveViewerBy({'x': -PanoJS.KEY_MOVE_THROTTLE,'y': 0});	  
	  return false;
  }  
  
}

//----------------------------------------------------------------------
// touch events
//----------------------------------------------------------------------

PanoJS.prototype.touchStartHandler = function(e) {
  e = e ? e : window.event;
  if (e == null) return false;
	
  if (e.touches.length == 1) { // Only deal with one finger
	  // prevent anything else happening for this event further
	  this.blockPropagation(e);   
	  
	  // actully store the initial touch move position
	  var touch = e.touches[0]; // Get the information for finger #1
	  this.touch_start = {'x': touch.clientX,'y': touch.clientY}; 
  }
  return false;	   
}

PanoJS.prototype.touchMoveHandler = function(e) {
  e = e ? e : window.event;
  if (e == null) return false;
  
  if (e.touches.length==1 && this.touch_start) { // Only deal with one finger
	  // prevent anything else happening for this event further
	  this.blockPropagation(e);		  
	  
	  // move
	  var touch = e.touches[0]; // Get the information for finger #1	
	  var p = {'x': touch.clientX-this.touch_start.x,'y': touch.clientY-this.touch_start.y};
	  this.moveViewerBy(p); 
	  this.touch_start = {'x': touch.clientX,'y': touch.clientY}; 
  }
  return false;	   
}


//----------------------------------------------------------------------
// gesture events
//----------------------------------------------------------------------

PanoJS.prototype.gestureStartHandler = function(e) {
  e = e ? e : window.event;
  if (e == null) return false;  
  this.blockPropagation(e);
  this.gesture_current_scale = 1.0;
  this.gesture_image_scale = this.currentScale();  
  return false;			  
}

PanoJS.prototype.gestureChangeHandler = function(e) {
  e = e ? e : window.event;
  if (e == null) return false;  
  this.blockPropagation(e);	  
  
  if (e.scale/this.gesture_current_scale>2.0) {
	this.gesture_current_scale = e.scale;
	this.zoom(1);
  } else 
  if (e.scale/this.gesture_current_scale<0.5) {
	this.gesture_current_scale = e.scale;
	this.zoom(-1);
  }
  
  if (this.osd_control) {
	e.image_scale = this.gesture_image_scale;
	e.gesture_current_scale = this.gesture_current_scale;
	this.osd_control.viewerZooming(e); 
  }
  
  return false;	   
}

PanoJS.prototype.gestureEndHandler = function(e) {
  e = e ? e : window.event;
  if (e == null) return false;  
  this.blockPropagation(e);	  
  if (this.osd_control) this.osd_control.show(false);
  
  // e.scale e.rotation
  //if (e.scale>1) this.zoom(1);
  //else
  //if (e.scale<1) this.zoom(-1);  
  return false;	   
}


//-------------------------------------------------------
// Control Events
//-------------------------------------------------------

PanoJS.prototype.zoomInHandler = function(e) {
  this.zoom(1);
};

PanoJS.prototype.zoomOutHandler = function(e) {
  this.zoom(-1);
};

PanoJS.prototype.zoom11Handler = function(e) {
  this.zoom(this.maxZoomLevel-this.zoomLevel);
};

PanoJS.prototype.maximizeHandler = function(e) {
  this.toggleMaximize();  
};


//-------------------------------------------------------
// PanoJS Events
//-------------------------------------------------------

PanoJS.MoveEvent = function(x, y) {
  this.x = x;
  this.y = y;
};

PanoJS.ZoomEvent = function(x, y, level, scale, width, height) {
  this.x = x;
  this.y = y;
  this.level = level;
  this.scale = scale;
  this.width = width;
  this.height = height;   
};

PanoJS.ResizeEvent = function(x, y, width, height) {
  this.x = x;
  this.y = y;
  this.width = width;
  this.height = height;
};




//-------------------------------------------------------
// Tile
//-------------------------------------------------------
	
function Tile(viewer, x, y) {
	this.viewer = viewer;  
	this.element = null;
	this.posx = 0;
	this.posy = 0;
	this.xIndex = x;
	this.yIndex = y;
	this.qx = x;
	this.qy = y;
};

Tile.prototype.createDOMElements = function() {
	//this.dom_info.innerHTML = "";
};

//-------------------------------------------------------
// TileUrlProvider
//-------------------------------------------------------

PanoJS.TileUrlProvider = function(baseUri, prefix, extension) {
  this.baseUri = baseUri;
  this.prefix = prefix;
  this.extension = extension;
}

PanoJS.TileUrlProvider.prototype = {
assembleUrl: function(xIndex, yIndex, zoom) {
	return this.baseUri + '/' +
	this.prefix + zoom + '-' + xIndex + '-' + yIndex + '.' + this.extension +
	(PanoJS.REVISION_FLAG ? '?r=' + PanoJS.REVISION_FLAG : '');
}
}

;/*******************************************************************************
  ZoomifyAJAX - creates an image URL pyramid based on Zoomify tiles
  <http://www.staremapy.cz/zoomifyjs/>
  <http://www.zoomify.com/>
  
  GSV 3.0 : PanoJS3
  @author Klokan Petr Pridal
  
  Copyright (c) Klokan Petr Pridal

*******************************************************************************/

function ZoomifyLevel( width, height, tilesize ) {
	this.width = width;
	this.height = height;
	this.xtiles = Math.ceil( width / tilesize );
	this.ytiles = Math.ceil( height / tilesize );
}
ZoomifyLevel.prototype.tiles = function() {
	return this.xtiles * this.ytiles;
}

function ZoomifyPyramid( width, height, tilesize ) {
	this.width = width;
	this.height = height;
	this.tilesize = tilesize;
	this._pyramid = Array();
	var level = new ZoomifyLevel( width, height, tilesize );
	while (level.width > tilesize | level.height > tilesize ) {
		this._pyramid.push( level );
		level = new ZoomifyLevel( Math.floor( level.width / 2 ), Math.floor( level.height / 2 ), tilesize )
	}
	this._pyramid.push( level );
	this._pyramid.reverse();

	this.length = this._pyramid.length;
	this.levels = this._pyramid.length;
	// tiles() is needed
	//this.tiles = this.tiles_upto_level( this.levels );
}

ZoomifyPyramid.prototype.getMaxLevel = function() {
	return this.levels - 1;	
}

ZoomifyPyramid.prototype.tiles_upto_level = function( level ) {
	var tiles = 0;
	for (var i = 0; i < level; i++) {
		tiles = tiles + this._pyramid[i].tiles();
	}
	return tiles;
}
ZoomifyPyramid.prototype.tiles = function() {
	return this.tiles_upto_level( this.levels );
}
ZoomifyPyramid.prototype.tile_index = function( level, x_coordinate, y_coordinate ) {
	return x_coordinate + y_coordinate * this._pyramid[ level ].xtiles + this.tiles_upto_level( level );
}
ZoomifyPyramid.prototype.tile_filename = function( level, x_coordinate, y_coordinate ) {
	return "TileGroup" + Math.floor( this.tile_index( level, x_coordinate, y_coordinate ) / this.tilesize ) + "/" + level + "-" + x_coordinate + "-" + y_coordinate + ".jpg";
}

;// <![CDATA[

// Create a method callback on a javascript objects.
// Used for event handlers binding an object instance
// to a method invocation.  
// Usage:
//  on_event =  callback (m, 'some_method' [, arg1, ... ])
// When the event fires the callback will be called with 
// both the static arguments and the dynamic arguments provided
// by the event 
// Example:
//   m.some_method([arg1, arg,..., evt_arg1, evt_arg2, ...])
//

function callback (obj, method) {
	var thisobj = obj;
	var thismeth = (typeof method == "string")?thisobj[method]:method;
	var thisextra = Array.prototype.slice.call(arguments,2);
	
	return function () {
		var args = Array.prototype.slice.call(arguments);
		return thismeth.apply (thisobj, thisextra.concat(args));
	};
}

function isClientPhone () {
	// Apple
	if (navigator.userAgent.indexOf("iPhone")>=0) return true;
	if (navigator.userAgent.indexOf("iPod")>=0) return true;	

	// Google
	if (navigator.userAgent.toLowerCase().indexOf("android")>=0) return true;	  
	
	// Nokia
	if (navigator.userAgent.toLowerCase().indexOf("series60")>=0 &&
		navigator.userAgent.toLowerCase().indexOf("webkit")>=0) return true;   
	if (navigator.userAgent.toLowerCase().indexOf("symbian")>=0 &&
		navigator.userAgent.toLowerCase().indexOf("webkit")>=0) return true; 
	
	// RIM
	if (navigator.userAgent.toLowerCase().indexOf("blackberry")>=0) return true;	  
	
	// Palm/HP
	if (navigator.userAgent.toLowerCase().indexOf("palm")>=0) return true;	  
	if (navigator.userAgent.toLowerCase().indexOf("webos")>=0) return true;	  

	// Mcrosoft
	if (navigator.userAgent.indexOf("Windows Phone OS")>=0) return true;  
	if (navigator.userAgent.indexOf("IEMobile")>=0) return true;	  
	
	return false;
}

function isClientTouch () {
	// Apple
	if (navigator.userAgent.indexOf("iPad")>=0) return true;
	if (navigator.userAgent.indexOf("iPhone")>=0) return true;
	if (navigator.userAgent.indexOf("iPod")>=0) return true;   
	
	// Google
	if (navigator.userAgent.toLowerCase().indexOf("android")>=0) return true;	

	// Nokia
	if (navigator.userAgent.toLowerCase().indexOf("series60")>=0 &&
		navigator.userAgent.toLowerCase().indexOf("webkit")>=0) return true;   
	if (navigator.userAgent.toLowerCase().indexOf("symbian")>=0 &&
		navigator.userAgent.toLowerCase().indexOf("webkit")>=0) return true; 
	
	// RIM
	if (navigator.userAgent.toLowerCase().indexOf("blackberry")>=0 &&
		navigator.userAgent.toLowerCase().indexOf("webkit")>=0) return true;   
	if (navigator.userAgent.toLowerCase().indexOf("playbook")>=0 &&
		navigator.userAgent.toLowerCase().indexOf("webkit")>=0) return true;   
		
	// Palm/HP	
	if (navigator.userAgent.toLowerCase().indexOf("webos")>=0) return true;	  
	
	// Mcrosoft
	if (navigator.userAgent.indexOf("Windows Phone OS")>=0) return true;		 
	
	return false;
}

function isBrowserIE () {
	if (navigator.appName == 'Microsoft Internet Explorer') return true;  
	return false;
}

function isMobileSafari () {
  if (navigator.userAgent.toLowerCase().indexOf("mobile")>=0 &&
	  navigator.userAgent.toLowerCase().indexOf("safari")>=0
	  ) return true; 
}

// ]]>
;
