/*  iSite Technologies Corporation copyright, 2007 */
var $em = 0.0625;
var $mr = Math.round;
var $mc = Math.ceil;
var $u = function (u){return (typeof u!="undefined");};
var $ma = Math.abs;

function viMap(viewer, options)
{
	// listeners that are notified on a move (pan) event
	this.viewerMovedListeners=[];
	// listeners that are notified on a zoom event
	this.viewerZoomedListeners=[];
	if (typeof viewer=='string')
		this.viewer=document.getElementById(viewer);
	else
		this.viewer=viewer;

	if (typeof options=='undefined')
		options={};
		
	if (typeof options.tileUrlProvider!='undefined'&&viMap.isInstance(options.tileUrlProvider, viMap.TileUrlProvider))
		this.tileUrlProvider=options.tileUrlProvider;
	else
		this.tileUrlProvider=new viMap.TileUrlProvider(options.tileBaseUri?options.tileBaseUri : viMap.TILE_BASE_URI, options.tilePrefix?options.tilePrefix : viMap.TILE_PREFIX, options.tileExtension?options.tileExtension : viMap.TILE_EXTENSION);

	this.tipUrl = options.tipUrl;
	this.btnState = ((typeof this.viewer.btnState)=="undefined")?"HTML":this.viewer.btnState;
	this.tilePrefix = options.tilePrefix;
	this.tileSize=(options.tileSize?options.tileSize : viMap.TILE_SIZE);
	// assign and do some validation on the zoom levels to ensure sanity
	this.zoomLevel=(typeof options.initialZoom=='undefined'?-1: parseInt(options.initialZoom, 10));
	this.maxZoomLevel=(typeof options.maxZoom=='undefined'?0 : $ma(parseInt(options.maxZoom, 10)));
	this.minZoomLevel=(typeof options.minZoom=='undefined'?0 : $ma(parseInt(options.minZoom, 10)));   
	
	if (this.zoomLevel>this.maxZoomLevel)
		this.zoomLevel=this.maxZoomLevel;
	else
		if (this.zoomLevel<this.minZoomLevel) 
		  this.zoomLevel=this.minZoomLevel;
		
    this.layerMask = options.layerMask;
	this.initialPan=(options.initialPan?options.initialPan : viMap.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.mX = 0;
    this.mY = 0;
	this.border=-1;
	this.selLy = null; 
	this.mark={'x': 0, 'y': 0};
	this.pressed=false;
	this.tiles=[];
	this.cache={};
	this.tipOut = 0;
	var blankTile=options.blankTile?options.blankTile : viMap.BLANK_TILE_IMAGE;
	var loadingTile=options.loadingTile?options.loadingTile : viMap.LOADING_TILE_IMAGE;
	this.cache['blank']=new Image();
	this.cache['blank'].src=blankTile;
	if (blankTile!=loadingTile)
	{
		this.cache['loading']=new Image();
		this.cache['loading'].src=loadingTile;
	}
	else
	{
		this.cache['loading']=this.cache['blank'];
	}
	// employed to throttle the number of redraws that
	// happen while the mouse is moving
	this.moveCount=0;
	this.slideMonitor=0;
	this.slideAcceleration=0;
	if ($u(options.zones)) 
		this.zones = options.zones;
	else
		this.zones = null;
	// add to viewer registry
	viMap.VIEWERS[viMap.VIEWERS.length]=this;
	
    this.layers = [];
    this.layerCache = [];
    this.ei6 = navigator.userAgent.indexOf('MSIE 6')>=0;
    this.ei7 = navigator.userAgent.indexOf('MSIE 7')>=0;
    this.moz = navigator.userAgent.indexOf('firefox')>=0;
    this.wheelCount = 0;
}

// project specific variables
viMap.PROJECT_NAME='viMap';
viMap.PROJECT_VERSION='v1.0.0.0';
viMap.REVISION_FLAG='';

// CSS definition settings
viMap.SURFACE_STYLE_CLASS='surface';
viMap.WELL_STYLE_CLASS='well';
viMap.CONTROLS_STYLE_CLASS='controls';
viMap.TILE_STYLE_CLASS='tile';
viMap.TIP_STYLE_CLASS='tip';

// language settings
viMap.MSG_BEYOND_MIN_ZOOM='Cannot zoom out past the current level.';
viMap.MSG_BEYOND_MAX_ZOOM='Cannot zoom in beyond the current level.';

// defaults if not provided as constructor options
viMap.TILE_BASE_URI='tiles';
viMap.TILE_PREFIX='tile-';
viMap.TILE_EXTENSION='jpg';
viMap.TILE_SIZE=256;
viMap.BLANK_TILE_IMAGE='../apps/vimap/img/blank.gif';
viMap.LOADING_TILE_IMAGE='blank.gif';
viMap.INITIAL_PAN = {'x': 0.5, 'y': 0.5};
viMap.USE_LOADER_IMAGE=true;
viMap.USE_SLIDE=true;
viMap.USE_KEYBOARD=true;

// performance tuning variables
viMap.MOVE_THROTTLE=3;
viMap.SLIDE_DELAY=40;
viMap.SLIDE_ACCELERATION_FACTOR=5;

// the following are calculated settings
viMap.DOM_ONLOAD=(navigator.userAgent.indexOf('KHTML')>=0?false : true);
viMap.GRAB_MOUSE_CURSOR=(navigator.userAgent.search(/KHTML|Opera/i)>=0?'pointer' : (document.attachEvent?'../apps/vimap/img/grab.cur' : '-moz-grab'));
viMap.GRABBING_MOUSE_CURSOR=(navigator.userAgent.search(/KHTML|Opera/i)>=0?'move' : (document.attachEvent?'../apps/vimap/img/grabbing.cur' : '-moz-grabbing'));

// registry of all known viewers
viMap.VIEWERS=[];

// utility functions
viMap.isInstance=function(object, clazz)
{
	while (object!==null)
	{
		if (object==clazz.prototype)
			return true;
		object=object.__proto__;
	}
	return false;
};

///////////////////////////////////////////////////////////////////////////
//                                                                      ///
///////////////////////////////////////////////////////////////////////////
viMap.prototype=
{
    // add new layers
    /*
        structure
        [{x:, y:, w:, h:, img:, link:},{...}]}
    */
    addLayer : function(ly)
    {
        var layer = {img:[], lyInfo: ly, visible:false};
        var idx = this.layers.push(layer)-1;
        return idx;
    },
    
    addSelLayer: function(ly)
    {
        this.selLy  = this.addLayer(ly);
        this.selImg = null;
        return this.selLy;
    },
    
    showAllLayer: function(idx)
    {
        var ly = this.layers[idx];
        if (!ly.visible)
        {
            var z = this.zones[this.zoomLevel-this.minZoomLevel];
            var sx = z.sx;
            var sy = z.sy;
            for (var j=0; j<ly.lyInfo.length; j++)
            {
                var iinfo = ly.lyInfo[j];
                if (typeof iinfo=="undefined")
                  return;
                var img = null;
                if (typeof ly.img[j]=="undefined")
                {
                    if (this.ei6 && (iinfo.img.indexOf('.gif')<=1))
                    {
                        img = document.createElement('div');
                        img.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale,src="'+iinfo.img+'")';
                    }
                    else
                    {
				        img = new Image();
				        img.onload = function ()
				        {
				            img.onload = function(){};
				        };
				        img.src = iinfo.img;
				    }
				    
				    if (idx!=0) img.style.cursor = "pointer";
				    
                    if (typeof iinfo.w=="undefined")
                    {
                        iinfo.w = 40;
                        iinfo.h = 40;
                    }
                    
                    iinfo.dx = iinfo.w/2;
                    iinfo.dy = iinfo.h/2;
                    
				    img.className  = "layer";
				    
				    img.style.height = (iinfo.h)*$em + 'em';
				    img.style.width = (iinfo.w)*$em + 'em';
				    img.ltInfo = iinfo;
        			
				    img.ow = 0;
				    img.oh = 0;
				    img.title = iinfo.text_e;
				    ly.img.push(img);
                    
                    img.viMap = this;
				    img.onmouseover = function (e)
				    {
				        var ltInfo = this.ltInfo;
				        if (ltInfo)
				        {
				            var th = ltInfo["text_"+this.viMap.tileUrlProvider.lang];
				            if (($u(ltInfo.mire))|| ((typeof e== "boolean") && e))
				            {
				                this.title = th;
				                return false;
				            }   
				            if (th)
				            {   
				                
				                var vM = this.viMap;
				                if (vM.tipOut!=0)
				                {
				                    clearTimeout(vM.tipOut);
			                        vM.tipOut=0;
				                }
				                
	                            e = e?e : window.event;
	                            var x = this.offsetLeft;
	                            var y = this.offsetTop;
				                if (vM.showTip(x, y, th, this.ltInfo.sn))
				                    this.title = "";
				                else
				                    this.title = th;
				                    
				            }
				                
				        }
				    };
				    img.onmouseout = function ()
				    {
				        var vM = this.viMap;
				        vM.tipOut=setTimeout(function()
		                {
			                vM.hideTip(); 
		                }, 300);
				    };

				    img.onclick = function ()
				    {
				        var vM = img.viMap;
				        var isel = this;
				        if (vM.selImg==this)
				            return;
				        if ($u(isel.ltInfo))
				        {
				            if ($u(isel.ltInfo.link)&&(isel.ltInfo.link!=""))
				            {
				                var link;
				                if ($u(vM.what))
				                {
				                    var btSt = vM.btnState?vM.btnState:"";
				                    /*
				                    if (vM.what=="text")
				                        btSt = "3-D";
				                     else 
				                       if (vM.what=="site")
				                          btSt = "HTML";  
				                    */
				                    link = "3d_"+vM.tileUrlProvider.lang+".asp?what="+vM.what+'&'+isel.ltInfo.link+btSt;
				                }
				                else
				                    link = "3d_"+vM.tileUrlProvider.lang+".asp?what=site&"+isel.ltInfo.link+vM.btnState;
				                window.location.href = link;
				                // window.navigate(link);
				            }
				        }
				    };
				    
				    img.onMapFloat = function(x, y, sx, sy)
				    {
				        var ti = this.ltInfo;
				        var s = this.style;
				        if (ti)
				        {
				            if (ti && typeof ti.vi=="string")
                            {
                                if (ti.vi.indexOf("|"+this.viMap.zoomLevel)==-1)
                                    this.style.visibility = "hidden";
                                else
                                    this.style.visibility = "visible";
                            }

			                if (ti.scale)
			                {
                                s.left = $mr(x+((ti.x-ti.dx)*sx))*$em+'em';
			                    s.top  = $mr(y+((ti.y-ti.dy)*sy))*$em+'em';
			                    s.width  = $mr(ti.w*sx)*$em +'em';
			                    s.height = $mr(ti.h*sy)*$em +'em';
			                }
			                else
			                {
                                s.left = $mr(x+(ti.x*sx)-ti.dx)*$em+'em';
			                    s.top  = $mr(y+(ti.y*sy)-ti.dy)*$em+'em';
			                };
			                if ($u(ti.zi))
			                    s.zIndex = ti.zi;
			            }
				    };
			    }
			    else 
			        img = ly.img[j];
			        
			    //this.well.appendChild(img);
				img.onMapFloat.apply(img, [this.mX, this.mY, sx, sy]);
			    this.surface.appendChild(img);
		    }
        }
    },
    
    hideLayer : function (idx)
    {
        var ly = this.layers[idx];
        if (ly!=null)
        {
            for (var j=0; j<ly.img.length; j++)
            {
                var img = ly.img[j];
                if (this.selImg==img)
                {
                    this.hideLayer(this.selLy);
				    img.style.zIndex = 0;
				    this.selImg = null;
                }
                if (img.parentNode!=null)
                    img.parentNode.removeChild(img);
            }
        }
    },
    
    removeLayer: function (idx)
    {
        this.hideLayer(idx);     
        var ly = this.layers[idx];
        for (var j=0; j<ly.img.length; j++)
        {
            delete ly.img[j];
            ly.img[j] = null;
        }

        ly = null;
    },
    
    gotoSite: function (site)
    {
        this.selecte_site = site;
        for (var i=0; i<this.layers.length;i++)
        {
            var ly = this.layers[i];
            for (var j=0; j<ly.lyInfo.length; j++)
            {
                if (ly.lyInfo[j].sn==site)
                {
                    try
                    {
                        var img = ly.img[j];// select site 
                        img.className  = "sel-layer";
                        if (img)
                        {
							var self = this;
						  this.showCrossHair(img, true);
                        }
                    }
                    catch(e){}
                }
                else
                {
                    try
                    {
                        var img = ly.img[j];// select site 
                        if (img)
                            img.className  = "layer";

                        //sel-layer
                    }
                    catch(e){}
                }
            }
        }
    },
    
    checkCrossHair: function()
    {
		if (this.selLy!=null)
		{
            for (var i=0; i<this.layers.length;i++)
            {
                var ly = this.layers[i];
                for (var j=0; j<ly.lyInfo.length; j++)
                {
                    if (ly.lyInfo[j].sn==this.selecte_site)
                        try
                        {
                            var img = ly.img[j];// select site 
                            if (img && (img.style.visibility == "visible"))
                            this.showCrossHair(img, false);
                        }
                        catch(e){}
                }
            }
		}
    },
    
    showCrossHair : function(selImg, navegate)
    {
		if (this.selLy!=null)
		{
			var ly = this.layers[this.selLy];
			if (ly)
			{
                if (selImg.ltInfo.zi==40)
                  return;
                  
				this.hideLayer(this.selLy);
				if (this.selImg!=null)
				    this.selImg = null;
				
				this.selImg = selImg;

				lyr = ly.lyInfo[0];
				lyr.x = selImg.ltInfo.x;
				lyr.y = selImg.ltInfo.y;
				lyr.text_e = selImg.ltInfo.text_e;
				lyr.text_f = selImg.ltInfo.text_f;
				var self = this;
				var lsel = this.selLy;
				setTimeout(function(){self.showAllLayer(lsel);}, 200);
				//self.showAllLayer(lsel);
				if (navegate)
				{
				    this.resetSlideMotion();
				    var coord = 
				    {
				        x : $mr(selImg.offsetLeft+(selImg.offsetHeight/2)),
				        y : $mr(selImg.offsetTop+(selImg.offsetWidth/2))
				    };
				    var t = viMap.USE_SLIDE;
				    viMap.USE_SLIDE = false;
		            this.recenter(coord, false);
				    viMap.USE_SLIDE = t;
				}
			}
		}
			else
			{
				this.resetSlideMotion();
				var coord = 
				{
				    x : $mr(selImg.offsetLeft+(selImg.offsetHeight/2)),
				    y : $mr(selImg.offsetTop+(selImg.offsetWidth/2))
				    
				    //x : $mr(this.offsetLeft+(this.offsetHeight/2)),
				    //y : $mr(this.offsetTop+(this.offsetWidth/2))
				};
				var t = viMap.USE_SLIDE;
				viMap.USE_SLIDE = false;
		        this.recenter(coord, false);
				viMap.USE_SLIDE = t;
			}
		
    },
    
    moveFloater: function(motion)
    {
        var x = $mr(this.x+motion.x);
        var y = $mr(this.y+motion.y);
        this.mX = x;
        this.mY = y;
        var z = this.zones[this.zoomLevel-this.minZoomLevel];
        var sx = z.sx;
        var sy = z.sy;
        
		//for (var child=this.well.firstChild; child!=null; child=child.nextSibling)
        if (this.surface!=null)
        {
		    for (var child=this.surface.firstChild; child!=null; child=child.nextSibling)
		    {
		        if ($u(child.onMapFloat))
		            child.onMapFloat.apply(child, [x, y, sx, sy]);
		    }
		}
    },
    
	/**
	 * Resize the viewer to fit snug inside the browser window (or frame),
	 * spacing it from the edges by the specified border.
	 *
	 * This method should be called prior to init()
	 * FIXME: option to hide viewer to prevent scrollbar interference
	 */
	fitToWindow: function(border)
	{
		if (typeof border!='number'||border<0)
		{
			border=0;
		}
		this.border=border;
		var calcWidth=0;
		var calcHeight=0;
		if (window.innerWidth)
		{
			calcWidth=window.innerWidth;
			calcHeight=window.innerHeight;
		}
		else
		{
			calcWidth=(document.compatMode=='CSS1Compat'?document.documentElement.clientWidth : document.body.clientWidth);
			calcHeight=(document.compatMode=='CSS1Compat'?document.documentElement.clientHeight : document.body.clientHeight);
		}
		calcWidth=Math.max(calcWidth-2*border, 0);
		calcHeight=Math.max(calcHeight-2*border, 0);
		if (calcWidth%2)
			calcWidth--;
		if (calcHeight%2)
			calcHeight--;

		this.width=calcWidth;
		this.height=calcHeight;
		this.viewer.style.width=this.width*$em+'em';
		this.viewer.style.height=this.height*$em+'em';
		this.viewer.style.top=border*$em+'em';
		this.viewer.style.left=border*$em+'em';
	}, 
	
	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;
		}
		var fullSize=this.tileSize;
		// explicit set of zoom level
		/*
		if (this.zoomLevel>=0&&this.zoomLevel<=this.maxZoomLevel)
		{
			fullSize=this.tileSize*Math.pow(2, this.zoomLevel);
		}
		// calculate the zoom level based on what fits best in window
		else
		{
			this.zoomLevel=-1;
			fullSize=this.tileSize/2;
			do
			{
				this.zoomLevel+=1;
				fullSize*=2;
			}
			while (fullSize<Math.max(this.width, this.height));
		}
		*/
		var zb = this.zones[this.zoomLevel-1];
		if ($u(zb.fixX))
		{
		    this.x=$mr(zb.fixX);
		    this.y=$mr(zb.fixY);
		}
		else
		{ 
		    this.x=$mr(((fullSize*zb.x)-this.width)*-this.initialPan.x);
		    this.y=$mr(((fullSize*zb.y)-this.height)*-this.initialPan.y);
		}

		if (zb.url)
			this.tileUrlProvider.prefix = zb.url;
		else 
			this.tileUrlProvider.prefix = this.tilePrefix;

		// offset of viewer in the window
		for (var node=this.viewer; node; node=node.offsetParent)
		{
			this.top+=node.offsetTop;
			this.left+=node.offsetLeft;
		}
		for (var child=this.viewer.firstChild; child; child=child.nextSibling)
		{
			if (child.className==viMap.SURFACE_STYLE_CLASS)
			{
				this.surface=child;
				this.surface.className == "well";
				child.objMap=this;
			}
			else if (child.className==viMap.WELL_STYLE_CLASS)
			{
				this.well=child;
				child.objMap=this;
			}
			else if (child.className==viMap.TIP_STYLE_CLASS)
			{
			    this.tip = child;
				child.objMap = this;
			}
			else if (child.className==viMap.CONTROLS_STYLE_CLASS)
			{
			    this.ctrls = new Array();
				for (var control=child.firstChild; control; control=control.nextSibling)
				{
					if (control.className)
					{
						control.onclick=viMap[control.className+'Handler'];
						this.ctrls[control.className] = control;
					}
				}
			}
		}
		this.viewer.objMap=this;
		this.surface.style.cursor=viMap.GRAB_MOUSE_CURSOR;
		this.prepareTiles();
		this.initialized=true;
		if (this.surface)
		{
		  //this.surface.className == "surface";
		}
		
	}
	, 
	prepareTiles: function()
	{
		var rows=Math.ceil(this.height/this.tileSize)+1;
		var cols=Math.ceil(this.width/this.tileSize)+1;
		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
				};
				tileCol.push(tile);
			}
			this.tiles.push(tileCol);
		}
		this.surface.onmousedown=viMap.mousePressedHandler;
		this.surface.onmouseup=viMap.mouseReleasedHandler; // this.surface.onmouseout=
		this.surface.ondblclick=viMap.doubleClickHandler;
		if (viMap.USE_KEYBOARD)
		{
			window.onkeypress=viMap.keyboardMoveHandler;
			window.onkeydown=viMap.keyboardZoomHandler;
		}
		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.
	 */
	positionTiles: function(motion, reset)
	{
		// default to no motion, just setup tiles
		if (typeof motion=='undefined')
		{
			motion=
			{
				'x': 0, 'y': 0
			};
		}
		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)
				{
					// 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);
					
					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)
					{
						tile.xIndex+=this.tiles.length;
						tile.posx=(tile.xIndex*this.tileSize)+this.x+motion.x;
					}
					if (tile.posx>this.width)
						visible=false;
				}
				
				if (tile.posy>this.height)
				{
					// 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);
					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)
					{
						tile.yIndex+=this.tiles[c].length;
						tile.posy=(tile.yIndex*this.tileSize)+this.y+motion.y;
					}
					if (tile.posy>this.height)
					{
						visible=false;
					}
				}
				// initialize the image object for this quadrant
				visible = true;
				if (!this.initialized)
				{
					this.assignTileImage(tile, true);
					// tile.element.style.top=tile.posy*$em+'em';
					//tile.element.style.left=tile.posx*$em+'em';
				}
				else
				    if (visible)
					    this.assignTileImage(tile);
					    
				// seems to need this no matter what
				tile.element.style.top=tile.posy*$em+'em';
				tile.element.style.left=tile.posx*$em+'em';
			}
		}
		// reset the x, y coordinates of the viewer according to motion
		this.moveFloater(motion);
		if ($u(this.onPick))
		{
		    try
		    {
		        this.onPick(this, motion);
		    }
		    catch(e)
		    {
		    }
		}
		if (reset)
		{
			this.x+=$mr(motion.x);
			this.y+=$mr(motion.y);
		}
	}
	, 
	/**
	 * 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.
	 */
	assignTileImage: function(tile, forceBlankImage)
	{
		var tileImgId, src;
		var useBlankImage=(forceBlankImage?true : 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;
			var right = null;
			var i = this.zoomLevel-this.minZoomLevel;
			if (this.zones && this.zones.length>i) //.rag
			{
				var z = this.zones[i];
				right=tile.xIndex>=z.x;
				var low=tile.yIndex>=z.y;
			}
			else
			{
				right=tile.xIndex>=Math.pow(2, this.zoomLevel);
				var low=tile.yIndex>=Math.pow(2, this.zoomLevel);
			}
			if (high||left||low||right)
			{
				useBlankImage=true;
			}
		}
		if (useBlankImage)
		{
			tileImgId='blank:'+tile.qx+':'+tile.qy;
			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!=src)
		{
			this.well.removeChild(tile.element);
		}
		var tileImg=this.cache[tileImgId];
		// create cache if not exist
		if (tileImg==null)
		{
			tileImg=this.cache[tileImgId]=this.createPrototype(src);
		}
		if (useBlankImage||!viMap.USE_LOADER_IMAGE||tileImg.complete||(tileImg.image&&tileImg.image.complete))
		{
			tileImg.onload=function(){};
			if (tileImg.image)
				tileImg.image.onload=function(){};

			if (tileImg.parentNode==null)
				tile.element=this.well.appendChild(tileImg);
		}
		else
		{
			var loadingImgId='loading:'+tile.qx+':'+tile.qy;
			var loadingImg=this.cache[loadingImgId];
			if (loadingImg==null)
			{
				loadingImg=this.cache[loadingImgId]=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;
					well.replaceChild(tileImg, loadingImg);
					tile.element=tileImg;
				}
				tileImg.onload=function(){};
				return false;
			};
			// konqueror only recognizes the onload event on an Image
			// javascript object, so we must handle that case here
			if (!viMap.DOM_ONLOAD)
			{
				tileImg.image=new Image();
				tileImg.image.onload=tileImg.onload;
				tileImg.image.src=tileImg.src;
			}
		}
	}
	, 
	createPrototype: function(src)
	{
		var img=document.createElement('img');
		img.src=src;
		img.relativeSrc=src;
		img.className=viMap.TILE_STYLE_CLASS;
		img.style.width=this.tileSize*$em+'em';
		img.style.height=this.tileSize*$em+'em';
		return img;
	}
	, 
	addViewerMovedListener: function(listener)
	{
		this.viewerMovedListeners.push(listener);
	}
	, 
	addViewerZoomedListener: function(listener)
	{
		this.viewerZoomedListeners.push(listener);
	}
	, 
	/**
	 * Notify listeners of a zoom event on the viewer.
	 */
	notifyViewerZoomed: function()
	{
		var percentage=(100/(this.maxZoomLevel+1))*(this.zoomLevel+1);
		for (var i=0; i<this.viewerZoomedListeners.length; i++)
		{
			this.viewerZoomedListeners[i].viewerZoomed(new viMap.ZoomEvent(this.x, this.y, this.zoomLevel, percentage));
		}
	}
	, 
	/**
	 * Notify listeners of a move event on the viewer.
	 */
	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 viMap.MoveEvent(this.x+(coords.x-this.mark.x), this.y+(coords.y-this.mark.y)));
		}
	}
	, 
	
	zoom: function(direction)
	{
	    this.resetSlideMotion();

		if (this.zoomLevel+direction<this.minZoomLevel)
		{   
			return ;
	    }
		else 
		    if (this.zoomLevel+direction>this.maxZoomLevel)
		    {
			    return ;
		    }


		this.blank();
		var center = { 'x': $mr(this.x+(this.width/2)), 'y': $mr(this.y+(this.height/2)) };
	    var after = {'x': 0, 'y': 0};
	    var zb = null;
		if (this.zones) 
		{
			var z = this.zoomLevel-this.minZoomLevel;
			zb = this.zones[z+direction];

			if (zb.url)
			    this.tileUrlProvider.prefix = zb.url;
			else 
			   this.tileUrlProvider.prefix = this.tilePrefix;
			   
			if (zb.fixX)
			{
			    this.x = $mr(zb.fixX);
			    this.y = $mr(zb.fixY);
			}
			else
			{
			    var za = this.zones[z];
			    var sx = zb.sx/za.sx;
			    var sy = zb.sy/za.sy;
		        var halfx = this.width/2;
		        var halfy = this.height/2;
			    this.x = $mr(this.x*sx+(halfx-halfx*sx));
			    this.y = $mr(this.y*sy+(halfy-halfy*sx));
    	    }
		}
		this.zoomLevel+=direction;
		this.positionTiles();
		this.notifyViewerZoomed();
		
		if (zb && (!zb.fixX)&&(this.selImg!=null)&&(typeof this.selImg.onmouseover=='function'))
			 this.selImg.onmouseover(true);
        
        var ctrlIn = this.ctrls['zoomIn'];
		var ctrlOut = this.ctrls['zoomOut'];

        if (direction==0)
            return;
            
		if (this.zoomLevel+direction<this.minZoomLevel)
		    ctrlOut.style.visibility = "hidden";
		else 
		    if (this.zoomLevel+direction>this.maxZoomLevel)
		        ctrlIn.style.visibility = "hidden";
		    else
		    {
		        ctrlIn.style.visibility = "visible";
		        ctrlOut.style.visibility = "visible";
		    }
        
        // this.checkCrossHair();
	}, 
	/** 
	 * 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.
	 */
	clear: function()
	{
		this.blank();
		this.initialized=false;
		this.tiles=[];
	}
	, 
	/**
	 * Remove all tiles from the well, which effectively "hides"
	 * them for a repaint.
	 */
	blank: function()
	{
		for (imgId in this.cache)
		{
			var img=this.cache[imgId];
			img.onload=function(){};
			if (img.image)
				img.image.onload=function(){};
			if (img.parentNode!=null)
				this.well.removeChild(img);
		}
	}
	, 
	/**
	 * Method specifically for handling a mouse move event.  A direct
	 * movement of the viewer can be achieved by calling positionTiles() directly.
	 */
	moveViewer: function(coords)
	{
		this.positionTiles(
		{
			'x': (coords.x-this.mark.x), 'y': (coords.y-this.mark.y)
		}
		);
		this.notifyViewerMoved(coords);
	}
	, 

	recenter: function(coords, absolute)
	{
		if (absolute)
		{
			coords.x+=this.x;
			coords.y+=this.y;
		}
		var motion=
		{
			'x': $mr((this.width/2)-coords.x), 'y': $mr((this.height/2)-coords.y)
		};
		if (motion.x==0&&motion.y==0)
			return ;

		if (viMap.USE_SLIDE)
		{
			var target=motion;
			var x, y;
			// handle special case of vertical movement
			if (target.x==0)
			{
				x=0;
				y=this.slideAcceleration;
			}
			else
			{
				var slope=$ma(target.y/target.x);
				x=$mr(Math.pow(Math.pow(this.slideAcceleration, 2)/(1+Math.pow(slope, 2)), 0.5));
				y=$mr(slope*x);
			}
			motion=
			{
				'x': $mr(Math.min(x, $ma(target.x))*(target.x<0?-1: 1)), 'y': $mr(Math.min(y, $ma(target.y))*(target.y<0?-1: 1))
			};
		}
		this.positionTiles(motion, true);
		this.notifyViewerMoved();
		if (!viMap.USE_SLIDE)
		{
			return ;
		}
		var newcoords=
		{
			'x': coords.x+motion.x, 'y': coords.y+motion.y
		};
		var self=this;
		this.slideAcceleration+=viMap.SLIDE_ACCELERATION_FACTOR;
		this.slideMonitor=setTimeout(function()
		{
			self.recenter(newcoords); 
		}, viMap.SLIDE_DELAY);
	}, 
	
	autoMove: function(motion)
	{
        var cord = {x:this.x+motion.x, y:this.y+motion.y};
        if (this.pointExceedsBoundaries2(cord))
        {
            this.resetSlideMotion();
            return;
        }
        
		this.positionTiles(motion, true);
		this.notifyViewerMoved();
        
		var self=this;
		this.slideMonitor=setTimeout(function()
		{
			self.autoMove(motion); 
		}, viMap.SLIDE_DELAY);
	},
	
	resize: function()
	{
		// IE fires a premature resize event
		if (!this.initialized)
		{
			return ;
		}
		var newWidth=this.viewer.offsetWidth;
		var newHeight=this.viewer.offsetHeight;
		this.viewer.style.display='none';
		this.clear();
		var before=
		{
			'x': Math.ceil(this.width/2), 'y': Math.ceil(this.height/2)
		};
		if (this.border>=0)
		{
			this.fitToWindow(this.border);
		}
		else
		{
			this.width=newWidth;
			this.height=newHeight;
		}
		this.prepareTiles();
		var after=
		{
			'x': Math.ceil(this.width/2), 'y': Math.ceil(this.height/2)
		};
		if (this.border>=0)
		{
			this.x+=$mr(after.x-before.x);
			this.y+=$mr(after.y-before.y);
		}
		this.positionTiles();
		this.viewer.style.display='';
		this.initialized=true;
		this.notifyViewerMoved();
	},
	 
	/**
	 * 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.
	 */
	resolveCoordinates: function(e)
	{
		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
				};
	}, 
	
	press: function(coords)
	{
		this.activate(true);
		this.mark=coords;
	}
	, 
	release: function(coords)
	{
		this.activate(false);
		var motion=
		{
			'x': (coords.x-this.mark.x), 
			'y': (coords.y-this.mark.y)
		};
		this.x+=$mr(motion.x);
		this.y+=$mr(motion.y);
		this.mark=
		{
			'x': 0, 
			'y': 0
		};
	}, 
	/**
	 * 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.
	 */
	activate: function(pressed)
	{
		this.pressed=pressed;
		this.surface.style.cursor=(pressed?viMap.GRABBING_MOUSE_CURSOR : viMap.GRAB_MOUSE_CURSOR);
		this.surface.onmousemove=(pressed?viMap.mouseMovedHandler : function(){});
		var self = this;
		if (window.addEventListener)
            window.addEventListener('DOMMouseScroll', function (e){ viMap.ZoomWheel(e, self);}, false);
        else
            this.surface.onmousewheel = function (e){ viMap.ZoomWheel(e, self);};
	}
	, 
	/**
	 * Check whether the specified point exceeds the boundaries of
	 * the viewer's primary image.
	 */
	pointExceedsBoundaries: function(coords)
	{
	    var zb = this.zones[this.zoomLevel-1];
		return (coords.x<this.x
		        ||coords.y<this.y
		        ||coords.x>(zb.x*this.tileSize+this.x)
		        ||coords.y>(zb.y*this.tileSize+this.y));
	}, 
	
    pointExceedsBoundaries2: function(coords)
	{
	    var zb = this.zones[this.zoomLevel-1];
	    if ($u(zb.move))
	        return true;
	    else
	    {
	        var bsx = $u(zb.bsx)?-zb.bsx*zb.sx:0;
	        var bex = $u(zb.bex)?-zb.bex*zb.sx:(-(zb.x*this.tileSize-this.width));
    	    
	        var bsy = $u(zb.bsy)?-zb.bsy*zb.sy:0;
	        var bey = $u(zb.bey)?-zb.bey*zb.sy:(-(zb.y*this.tileSize-this.height));

		    return (coords.x>bsx || coords.x<bex || coords.y>bsy || coords.y<bey);
		}
	}, 
	

	boundary: function(coords)
    {
	    var zb = this.zones[this.zoomLevel-1];
        if ($u(zb.move))
            return false;

	    var newX = (coords.x-this.mark.x)+this.x;
	    var newY = (coords.y-this.mark.y)+this.y;
	    
	    var bsx = $u(zb.bsx)?-zb.bsx*zb.sx:0;
	    var bex = $u(zb.bex)?-zb.bex*zb.sx:(-(zb.x*this.tileSize-this.width));
	    
	    var bsy = $u(zb.bsy)?-zb.bsy*zb.sy:0;
	    var bey = $u(zb.bey)?-zb.bey*zb.sy:(-(zb.y*this.tileSize-this.height));
	    
	    if (newX>bsx)
	    {
	        this.x = $mr(bsx);
	        this.mark.x = coords.x;
	        //coords.x = bsx-this.x+this.mark.x;
	    }
	    else 
	        if (newX<bex)
	        {
	            this.x = $mr(bex);
	            this.mark.x = coords.x;
	            //coords.x = bex-this.x+this.mark.x;
	        }

	    if (newY>bsy)
	    {
	        this.y = bsy;
	        this.mark.y = coords.y;
	        //coords.y = bsy-this.y+this.mark.y;
	    }
	    else 
	        if (newY<bey)
	        {
	            this.y = bey;
	            this.mark.y = coords.y;
	            // coords.y = bey-this.y+this.mark.y;
	        }
        return true;
    },

	// QUESTION: where is the best place for this method to be invoked?
	resetSlideMotion: function()
	{
		if (this.slideMonitor!==0)
		{
			clearTimeout(this.slideMonitor);
			this.slideMonitor=0;
		}
		this.slideAcceleration=0;
	},

    setLang : function(langPre)
    {
        this.tileUrlProvider.lang = langPre;
        var ctrl = this.ctrls['zoomIn'];
        if (ctrl!=null)
        {
            ctrl.alt = (langPre=='f')?'Zoom avant':'Zoom In';
            ctrl.title = ctrl.alt;
        }
            
        var ctrl = this.ctrls['zoomOut'];
        if (ctrl!=null)
        {
            ctrl.alt = (langPre=='f')?'Zoom arrière':'Zoom Out';
            ctrl.title = ctrl.alt;
        }
        this.refresh();
        
        // this.initialized = true;
        // this.resize();
    },
    
    refresh: function()
    {
    
        this.zoom(0);
    },
    
    ///////////tips
    showTip: function (x, y, text, simg)
    {
        if ($u(this.tip))
        {
            var html = "<table class='tipShow'>";
            
            if ($u(this.tipUrl) && $u(simg))
            {
                html += '<tr><td><CENTER><img class="tipImg" src="'+this.tipUrl+simg+'.jpg" + ></img></CENTER></td></tr>';
            }
            html += '<tr><td><div class="tipText">'+text+'</div></td></tr>';
            html += '</table>';
            
            this.tip.innerHTML = html;
            var w = this.tip.parentNode.offsetWidth - this.tip.offsetWidth-20;
            
            if (x<w)
                x = (x+15)*$em;
            else
                x = (x-this.tip.offsetWidth)*$em;
            if (x<1)
                x = 1;
            this.tip.style.left = x +'em';
            var h = this.tip.parentNode.offsetHeight - this.tip.offsetHeight-20;
            if (y<h)
                y = (y*$em);
            else
                y = ((y-this.tip.offsetHeight)*$em);
                
            if (y<1) 
              y = 1;
                              
            this.tip.style.top = y+'em'
            //this.tip.style.pixelLeft=(e.x+15+document.body.scrollLeft);
            //this.tip.style.pixelTop=(e.y+document.body.scrollTop);
            this.tip.style.visibility="visible";
            
            writeToTextContainer(text);
            return true;
        }
        return false;
    },
    
    hideTip: function ()
    {
        if ($u(this.tip))
        {
            this.tip.style.visibility="hidden";
            writeToTextContainer('');
        }
    }
    
};

viMap.TileUrlProvider=function(baseUri, prefix, extension)
{
	this.baseUri=baseUri;
	this.prefix=prefix;
	this.extension=extension;
	this.lang = 'e';
};

///////////////////////////////////////////////////////////////////////////
//                                                                      ///
///////////////////////////////////////////////////////////////////////////
viMap.TileUrlProvider.prototype=
{
	assembleUrl: function(xIndex, yIndex, zoom)
	{
		zoom--;
		var s = this.prefix;
		s = s.replace(/{x}/gi, xIndex);
		s = s.replace(/{y}/gi, yIndex);
		s = s.replace(/{l}/gi, this.lang);
		return s.replace(/{z}/gi, zoom);
	}
};

///////////////////////////////////////////////////////////////////////////
//                                                                      ///
///////////////////////////////////////////////////////////////////////////
viMap.mousePressedHandler=function(e)
{
	e=e?e : window.event;
	// only grab on left-click
	if (e.button<2)
	{
		var self=this.objMap;
		
		if ($u(this.setCapture))
		    this.setCapture();
		else
		    if ($u(document.addEventListener))
		    {
                document.addEventListener('mousemove', viMap.mouseMovedHandler, false);
                document.addEventListener('mouseup', viMap.mouseReleasedHandler, false);
                document.addEventListener('mouseout', viMap.mouseOutHandler, false);
                document.vmapMove = self;
		    }
		var coords=self.resolveCoordinates(e);
		if (self.pointExceedsBoundaries(coords))
			e.cancelBubble=true;
		else
			self.press(coords);
			
	    self.resetSlideMotion();
        self.mouseTrack = new Array();
        self.mouseTrack.push(coords);
	}
	return false;
};

viMap.mouseReleasedHandler=function(e)
{
	e=e?e : window.event;
	var self= this.objMap ? this.objMap: document.vmapMove;
	if ($u(this.releaseCapture))
	    this.releaseCapture();
	else
	  if ($u(document.removeEventListener))
	  {
            document.removeEventListener('mousemove', viMap.mouseMovedHandler, false);
            document.removeEventListener('mouseup', viMap.mouseReleasedHandler, false);
            document.removeEventListener('mouseout', viMap.mouseOutHandler, false);
            document.vmapMove = null;
	  }
	    
	var coords = self.resolveCoordinates(e);
    if (self.pressed)
		self.release(coords);
		
	if ($u(self.mouseTrack))
	{
	    var l=self.mouseTrack.length;
	    if (l>4)
	    {
	        var c1 = self.mouseTrack[l-3];
	        var c2 = self.mouseTrack[l-1];
            var dt = $ma(c1.s-c2.s);
	        var s = {x: $mr((c2.x-c1.x)/2), y:$mr((c2.y-c1.y)/2)};
            if ((dt<20)&&(($ma(s.x)>1)||($ma(s.y)>1)))
                self.autoMove(s);
        }
    }
};

viMap.mouseMovedHandler = function(e)
{
	e=e?e : window.event;
	var self= this.objMap ? this.objMap: document.vmapMove;
	self.moveCount++;
	var coords = self.resolveCoordinates(e);
	if (self.moveCount%viMap.MOVE_THROTTLE==0)
	{
	    if (self.boundary(coords))
		    self.moveViewer(coords);
		else
		{
	        self.mark.x = coords.x;
	        self.mark.y = coords.y;
		}
	}
	
	if ($u(self.mouseTrack))
	{
	    if (self.mouseTrack.length>8)
	        self.mouseTrack.shift();
    	    
    	var d = new Date();
    	coords.s = d.getTime();
	    self.mouseTrack.push(coords);
	}

	e.cancelBubble=true;
	return false;
};

viMap.mouseOutHandler = function(e)
{
    if ($u(e.target))
    {
        window.status = "Test"+e.target.nodeName;
        
        if (e.target.nodeName=="HTML")
        {
        }
            //viMap.mouseReleasedHandler(e);
    }
};


viMap.zoomInHandler=function(e)
{
	e=e?e : window.event;
	var self=this.parentNode.parentNode.objMap;
	self.zoom(1);
	return false;
};

viMap.zoomOutHandler=function(e)
{
	e=e?e : window.event;
	var self=this.parentNode.parentNode.objMap;
	self.zoom(-1);
	return false;
};

viMap.doubleClickHandler=function(e)
{
	e=e?e : window.event;

	var self=this.objMap;
	if (e.button==0)
	    self.zoom(1);
    else
	    if (e.button==2)
	        self.zoom(-1);

	coords=self.resolveCoordinates(e);
	if (!self.pointExceedsBoundaries(coords))
	{
		self.resetSlideMotion();
		self.recenter(coords);
	}
};

viMap.keyboardMoveHandler=function(e)
{
	e=e?e : window.event;
	for (var i=0; i<viMap.VIEWERS.length; i++)
	{
		var viewer=viMap.VIEWERS[i];
		if (e.keyCode==38)
		viewer.positionTiles(
		{
			'x': 0, 'y': -viMap.MOVE_THROTTLE
		}
		, true);
		if (e.keyCode==39)
		viewer.positionTiles(
		{
			'x': -viMap.MOVE_THROTTLE, 'y': 0
		}
		, true);
		if (e.keyCode==40)
		viewer.positionTiles(
		{
			'x': 0, 'y': viMap.MOVE_THROTTLE
		}
		, true);
		if (e.keyCode==37)
		viewer.positionTiles(
		{
			'x': viMap.MOVE_THROTTLE, 'y': 0
		}
		, true);
	}
};

///////////////////////////////////////////////////////////////////////////
//                                                                      ///
///////////////////////////////////////////////////////////////////////////
viMap.keyboardZoomHandler=function(e)
{
	e=e?e : window.event;
	for (var i=0; i<viMap.VIEWERS.length; i++)
	{
		var viewer=viMap.VIEWERS[i];
		if (e.keyCode==109)
			viewer.zoom(-1);
		if (e.keyCode==107)
			viewer.zoom(1);
	}
};

///////////////////////////////////////////////////////////////////////////
//                                                                      ///
///////////////////////////////////////////////////////////////////////////
viMap.MoveEvent=function(x, y)
{
	this.x=$mr(x);
	this.y=$mr(y);
};

viMap.ZoomEvent=function(x, y, level, percentage)
{
	this.x=$mr(x);
	this.y=$mr(y);
	this.percentage=percentage;
	this.level=level;
};

viMap.ZoomWheel =function(e, self)
{
	e=e?e : window.event;
	if (e)
	{
	    var delta = 0;
        if (e.wheelDelta) 
        { /* IE/Opera. */
            delta = e.wheelDelta;
            /** In Opera 9, delta differs in sign as compared to IE.
            */
            if (window.opera)
                delta = -delta;
        } 
        else 
            if (e.detail) /** Mozilla case. */
                delta = -e.detail*20;

        if (self.wheelCount>119)
        {
            self.zoom(1);
            self.wheelCount = 0;
        }
        else
          if (self.wheelCount<-119)
          {
            self.zoom(-1);
            self.wheelCount = 0;
          }
          else
            self.wheelCount+=delta;
	}
};


