 

var gMap = function(){
	
  var MAP_CONTAINER_ID = "mapContainer";
	
  var HIGHLIGHT_BORDER_SIZE = 5;
  var ZOOM_TICK_SIZE_PIXELS = 20;
	
  var _mapDragOffsetX = null;
  var _mapDragOffsetY = null;

	var _elMapContainer = null;
  var _elMapContainerStyle = null;
  var _elMiniMapViewport = null;
	
  var _elMapTilesContainer = null;
  var _elMapUILayer = null;
	
  var _oTileCache = null;
  var _oMiniMap = null;
	var _oViewport = null;
  var _oMapSelect = null;
	
  var _zoomLevel = null;
  var _zoomMagnification = null;
  var _mapZoomLevels = 5;
	var _miniMapZoomLevels = [6,6,6,7,8];
  var _zoomSlider = null;  // YUI slider
	
	var _elDeleteHighlight = null;
	var _highlightAnim = null;
  var _highlightInfo = null;   // used to preserve highlight across zooms

	
  // boundary detection
  var _xTicks = null;
  var _minYTicks = null;
  var _maxYTicks = null;

  var _lockMap = null;
  
	
	function init(){

    _xTicks = gXTicks;
    _minYTicks = gMinYTicks;
    _maxYTicks = gMaxYTicks;
		
    _elMapTilesContainer = $( "mapTilesContainer" );
    _elMapUILayer = $( "mapUILayer" );
    _elDeleteHighlight = $("deleteHighlight");
		
    // cache refs to dom elements and objects
    _elMapContainer = $( MAP_CONTAINER_ID );
    _elMapContainerStyle = _elMapContainer.style;
		_oViewport = gViewport;
    _oMapSelect = gMapSelect;
		
		
    // add click handers to the zoom buttons
    var btnZoomIn = $("btnZoomIn");
    var btnZoomOut = $("btnZoomOut");
    var btnPanUp = $("btnPanUp");
    var btnPanLeft = $("btnPanLeft");
    var btnPanRight = $("btnPanRight");
    var btnPanDown = $("btnPanDown");
    var btnCenterMap = $("btnCenterMap");
		
    YUE.addListener( btnZoomIn, "click", zoomIn );
    YUE.addListener( btnZoomOut, "click", zoomOut );
    YUE.addListener( btnPanUp, "click", _panUp );
    YUE.addListener( btnPanLeft, "click", _panLeft );
    YUE.addListener( btnPanRight, "click", _panRight );
    YUE.addListener( btnPanDown, "click", _panDown );
    YUE.addListener( btnCenterMap, "click", _centerMap );


    // fix some zoom slider rendering quirks in the different browsers
		if( gIsIE ){
      YAHOO.log("applying IE slider thumb hack");
      var thumb = $("sliderThumbImage");
      thumb.style.position = "relative";
			thumb.style.left = "-11px";
      thumb.style.cursor = "url(/img/bn/openhand.cur)";      
		} else if( gIsSafari ){
      YAHOO.log("applying safari slider thumb hack");
      $("sliderbg").style.marginLeft = "19px";
      $("sliderThumbImage").style.marginLeft = "-1px";
		}
    $("sliderthumb").style.visibility = "visible";

    setMapPositionFromString( gMapInitPosition, true );

    // zoom stuff
		YAHOO.log("gMapZoomLevels: " + gMapZoomLevels);
    _zoomSlider = YAHOO.widget.Slider.getVertSlider("sliderbg", "sliderthumb", 0, ZOOM_TICK_SIZE_PIXELS * 4, ZOOM_TICK_SIZE_PIXELS);  
    _zoomSlider.setValue( _zoomLevel * ZOOM_TICK_SIZE_PIXELS, true, true );
    _zoomSlider.subscribe("change", _zoomSliderChange );


    // initialize and draw the minimap
		_elMiniMapViewport = $( "miniMapViewport" );

		_makeNewMiniMap();

	};
	
	
  function resize(){
		
    // looks like the window can fire a resize event at startup.  this if should prevent it
    if( _oTileCache ){
      // destroy the tile cache and make a new one
      _oTileCache.destroy();
    }
		
    // redraw
    draw();
  };
	
	
	
  function draw(){
		
    YAHOO.log( "drawing map" );
		
		// create the tile cache
    var viewportSize = _oViewport.getSize();
		_oTileCache = new TileCache( _elMapTilesContainer, viewportSize[0], viewportSize[1], _zoomLevel, "map", gIsSafari );
		
    // determine the top left visible tile to start drawing
    var mapPosition = _getMapPosition();
    var topLeftTile = TileCache.getZoomTileAtPixel( mapPosition[0], mapPosition[1], _zoomLevel );
		
		_oTileCache.createGridImages( topLeftTile[0], topLeftTile[1] );

		if( gSoldImage ){
      showSoldImage(gSoldImage);
		}

// 		if( _zoomLevel == 0 ) {
// 			showUploadableArea();    
// 		} else {
//       // hide the uploadable area
//       hideUploadableArea();
//     }


    if( _highlightInfo ){
      highlightSquares( _highlightInfo.minGridX,
                        _highlightInfo.maxGridX,
                        _highlightInfo.minGridY,
                        _highlightInfo.maxGridY,
                        _highlightInfo.hideDeleteButton,
                        _highlightInfo.highlightColor );
    }
		
    if( _oMiniMap ){
      // did this to prevent an error when page loads because oMiniMap has not been instanciated yet
      _oMiniMap.draw();
    }
  };
	
	
	function redraw(){
		
		// erase the grid
    try {
      _oMapSelect.clear();
      _oTileCache.reloadChangedImages();
    } catch( e ) {}
  };

  function beginMapSelectDisplay(){
		_lockZoomSlider();
    _makeMapTransparent();
    _showUploadableArea();
  };

  function endMapSelectDisplay(){
		_unlockZoomSlider();
    _makeMapOpaque();
    _hideUploadableArea();
  };
   

	
  function _makeMapTransparent(){
		// setting the tiles to transparent instead of the container div because of IE6. 
		//  in IE6, the images on the map would not show up when the container was transparent.
    _oTileCache.makeTilesTransparent();
  };
	
  function _makeMapOpaque(){ 
		// setting the tiles to transparent instead of the container div because of IE6. 
		//  in IE6, the images on the map would not show up when the container was transparent.
    _oTileCache.makeTilesOpaque();
  };
	
	
// 	function setMapZoomLevels( newMapZoomLevels ){
// 		_mapZoomLevels = newMapZoomLevels - MINI_MAP_EXTRA_ZOOM;
//   };
	

  function showSoldImage( imgInfo ){
    // instant gratification display of image just bought

    var elImg = $("sold");
    if( !elImg ){
      elImg = document.createElement("IMG");
      elImg.src = imgInfo.url;
      elImg.id = "sold";
    }

    var squareSize = TileCache.SQUARE_SIZE_PIXELS;
    var gridPixelsPerViewportPixel = TileCache.getTilesPerZoomTile( _zoomLevel );

    var style = elImg.style;
    var left = (imgInfo.gridX * squareSize) / gridPixelsPerViewportPixel;
    var top = (imgInfo.gridY * squareSize) / gridPixelsPerViewportPixel;
    var width = imgInfo.width / gridPixelsPerViewportPixel;
    var height = imgInfo.height / gridPixelsPerViewportPixel;

    style.position = "absolute";
    style.top = top + "px";
    style.left = left + "px";
    style.width = width + "px";
    style.height = height + "px";

    addOverlayElement(elImg);
  }

	
  function isAreaInBounds( left, top, widthPixels, heightPixels ){
    var bottom = top + (heightPixels / TileCache.SQUARE_SIZE_PIXELS) - 1;
    var right = left + (widthPixels / TileCache.SQUARE_SIZE_PIXELS) - 1;

		var xTicks = _xTicks;
		var minYTicks = _minYTicks;
		var maxYTicks = _maxYTicks;
    
		var numXTicks = xTicks.length;
		var minX = xTicks[0];
		var maxX = xTicks[xTicks.length - 1];
    
		if( left >= minX && right < maxX ){
			var xStop = numXTicks - 1;
      
			// figure out the xTick that the left side is in
      var xTickLeft = null;
      for(var i=0; i < xStop; i++){
        if( left < xTicks[i+1] ){
          xTickLeft = i;
          break;
        }
      }

      // figure out the xTick that the right side is in
      var xTickRight = null;
      for(var i=xTickLeft; i < xStop; i++){  // we can save a few loops by starting with the left side tick
        if( right < xTicks[i+1] ){
          xTickRight = i;
          break;
        }
      }

      // find the highest minimum Y between the x tick marks
      var highestMinY = minYTicks[xTickLeft];
      for( var x=xTickLeft + 1; x <= xTickRight; x++){
        if( minYTicks[x] > highestMinY ){
           highestMinY = minYTicks[x];
        }
      }

      // find the maximum Y between the x tick marks
      var lowestMaxY = maxYTicks[xTickLeft];
      for( var x=xTickLeft + 1; x <= xTickRight; x++){
        if( maxYTicks[x] < lowestMaxY ){
           lowestMaxY = maxYTicks[x];
        }
      }

   

      // final test:  see if top is less more than or equal to minY and bottom is less than max y
      if( top >= highestMinY && bottom < lowestMaxY ){
        return true;
      } else {
        return false;
      }
		}
		
		return false;
  };
	
  
//   function _hideOutlinedUploadArea(){
//     var old = YUD.getElementsByClassName( "uploadAreaOutline", "DIV", _elMapUILayer);
//     for( var i=0; i < old.length; i++){
//       old[i].parentNode.removeChild(old[i]);
//     }
//   }

//   function _showOutlinedUploadableArea(){
//     _hideOutlinedUploadArea();

// 		var xTicks = _xTicks;
// 		var minYTicks = _minYTicks;
// 		var maxYTicks = _maxYTicks;
// 		var outlineWidth = 2;

//     // draw the left border
//     var left = xTicks[0];
//     var top = minYTicks[0];
//     var width = outlineWidth;
//     var height = maxYTicks[0] - top;
//     _createGridDiv( left, top, width, height, "uploadAreaOutline", _elMapUILayer);


//     // trace the top middle region
//     for( var i=0; i < xTicks.length - 1; i++ ){

//       // draw the top line
//       left = xTicks[i];
//       top = minYTicks[i];
//       width = xTicks[i+1] - xTicks[i];
//       height = outlineWidth;
//       _createGridDiv( left, top, width, height, "uploadAreaOutline", _elMapUILayer);

//       // draw the y difference line
// 			if( i+1 < minYTicks.length ){
// 				var curY = top;
// 				var nextY = minYTicks[i+1];
// 				if( nextY < curY ){
// 					top = nextY;
// 					height = curY - nextY;
// 				} else {
// 					top = curY;
// 					height = nextY - curY;
// 				}
// 				left = left + width;
// 				width = outlineWidth;
// 				_createGridDiv( left, top, width, height, "uploadAreaOutline", _elMapUILayer);
// 			}
//     }


//     // draw the right border
// 		left = xTicks[xTicks.length-1];
// 		top = minYTicks[minYTicks.length-1];
// 		width = outlineWidth;
// 		height = maxYTicks[maxYTicks.length-1] - top;
// 		_createGridDiv( left, top, width, height, "uploadAreaOutline", _elMapUILayer);

//     // trace the bottom middle region
//     for( var i=0; i < xTicks.length; i++){
// 			left = xTicks[i];
// 			top = maxYTicks[i];
// 			width = xTicks[i+1] - left;
// 			height = outlineWidth;
// 			_createGridDiv( left, top, width, height, "uploadAreaOutline", _elMapUILayer);
			
			
//       // draw the y difference line
// 			if( i+1 < maxYTicks.length ){
// 				var curY = top;
// 				var nextY = maxYTicks[i+1];
// 				if( nextY < curY ){
// 					top = nextY;
// 					height = curY - nextY;
// 				} else {
// 					top = curY;
// 					height = nextY - curY;
// 				}
// 				left = left + width;
// 				width = outlineWidth;
// 				_createGridDiv( left, top, width, height, "uploadAreaOutline", _elMapUILayer);
// 			}
//     }
//   };


//   Used to make divs that show the outline of the uploadable area
//   function _createGridDiv( left, top, width, height, className, parentNode ){

//     var squareSize = TileCache.SQUARE_SIZE_PIXELS;
//     var gridPixelsPerViewportPixel = TileCache.getTilesPerZoomTile( _zoomLevel );      
//     var squaresPerViewportPixel = squareSize / gridPixelsPerViewportPixel;  // compute once for efficency

//     var el = document.createElement("DIV");
//     if(className){
//       el.className = className;
//     }

//     var elStyle = el.style;
//     elStyle.left = Math.round( left * squaresPerViewportPixel ) + "px";
//     elStyle.top = Math.round( top * squaresPerViewportPixel ) + "px";
//     elStyle.width = Math.round( width * squaresPerViewportPixel) + "px";
//     elStyle.height = Math.round( height * squaresPerViewportPixel ) + "px";
    
//     if( parentNode ){
//       parentNode.appendChild( el );
//     }
    
//     return el;
//   };



  function _hideUploadableArea(){
    var old = YUD.getElementsByClassName( "uploadArea", "DIV", _elMapUILayer);
    for( var i=0; i < old.length; i++){
      old[i].parentNode.removeChild(old[i]);
    }
  };

  function _showUploadableArea(){

    _hideUploadableArea();

    var squareSize = TileCache.SQUARE_SIZE_PIXELS;
		var xTicks = _xTicks;
		var minYTicks = _minYTicks;
		var maxYTicks = _maxYTicks;
    var totalSquares = 0;
    var gridPixelsPerViewportPixel = TileCache.getTilesPerZoomTile( _zoomLevel );      
    var nextLeft = null;  // prevents spaces and overlap of regions

    for( var i=0; i < xTicks.length - 1; i++){

      var gridLeft = xTicks[i];
      var gridTop = minYTicks[i];
      var gridWidth = xTicks[i+1] - xTicks[i];
      var gridHeight = maxYTicks[i] - minYTicks[i];
      
      var zoomLeft = nextLeft || (gridLeft * squareSize / gridPixelsPerViewportPixel);
      var zoomTop =  gridTop * squareSize / gridPixelsPerViewportPixel;
      var zoomWidth = gridWidth * squareSize / gridPixelsPerViewportPixel;
      var zoomHeight = gridHeight *squareSize / gridPixelsPerViewportPixel;

      var el = document.createElement("DIV");
      el.className = "uploadArea";
      el.id = "xTick_" + xTicks[i];

      var elStyle = el.style;
      elStyle.left = Math.round(zoomLeft) + "px";
      elStyle.top = Math.round(zoomTop) + "px";
      elStyle.width = Math.round(zoomWidth) + "px";
      elStyle.height = Math.round(zoomHeight) + "px";

      _elMapUILayer.appendChild(el);

      totalSquares += gridWidth * gridHeight;
      nextLeft = Math.round(zoomLeft) + Math.round(zoomWidth);
    }

    var totalPrice = totalSquares * gPricePerSquare;

    YAHOO.log( "TOTAL SQUARES: " + totalSquares );
    YAHOO.log( "TOTAL PRICE: $ " + totalPrice );

  }




  function addOverlayElement( elem ){
    // $$$$$ might want to keep track of the elements appended so we can be sure they are cleared later.
    // $$$$$ might need to require a callback that gurantees all references to the element are freed if we destroy the map.
    _elMapContainer.appendChild(elem);
  };
	
  function getOverlayAreaInfo( el ){

		// $$$$$ this might be the width of the select area including the resizing dots.  we just want the width of the image
		
		var style = el.style;
    
    var left = parseInt( style.left );
    var top = parseInt( style.top );
    var width = parseInt( YUD.getStyle( el, "width" ) );
    var height = parseInt( YUD.getStyle( el, "height" ) );
    
    return { 
      // x and y are on the grid square coordinate plane.  left and top are CSS position values
      x : left / TileCache.SQUARE_SIZE_PIXELS,
        y : top / TileCache.SQUARE_SIZE_PIXELS,
        left : left,
        top : top,
        
        // storing right and bottom values for fast lookup when showing resize cursors
        right: left + width - 1,
        bottom: top + height - 1,
        
        width : width,
        height : height
        };
		
  };
	
	
	
  // *************************************
  //    HIGHLIGHT SQUARES FOR DELEITION
  // *************************************

	function _roundDownToTen( num ){
		return Math.floor( num / 10 ) * 10;
	}
	
	
  function highlightSquares( minGridX, maxGridX, minGridY, maxGridY, hideDeleteButton, highlightColor ){
    // currently used to highlight a group of squares on the map.  can also be used for admins to delete content

    endHighlightSquares();

    var highlightInfo = {};
    highlightInfo.minGridX = minGridX;
    highlightInfo.maxGridX = maxGridX;
    highlightInfo.minGridY = minGridY;
    highlightInfo.maxGridY = maxGridY;
    highlightInfo.hideDeleteButton = hideDeleteButton;
    highlightInfo.highlightColor = highlightColor;
    _highlightInfo = highlightInfo;
		
    // calculate the position and dimenisons of the highlight element


    var squareSize = TileCache.SQUARE_SIZE_PIXELS;
    var gridPixelsPerViewportPixel = TileCache.getTilesPerZoomTile( _zoomLevel );
		var viewportPixelsPerSquare = squareSize / gridPixelsPerViewportPixel;


    // create and position the highlight element on the map
		
    if( hideDeleteButton ){
      $("deleteImageButton").style.display = "none";
    } else {
      $("deleteImageButton").style.display = "inline";
    }

		
    var left = Math.floor( minGridX * viewportPixelsPerSquare );
    var top =  Math.floor( minGridY * viewportPixelsPerSquare );
		var width = Math.round( (maxGridX - minGridX + 1) * viewportPixelsPerSquare );  // 2x for both sides of border
		var height = Math.round( (maxGridY - minGridY + 1) * viewportPixelsPerSquare );

    // correct the highlight size to account for the highlight border size
    var borderWidth = 5;  // $$$$$ should base this on CSS value.  I couldnt get it to work.
    var borderSize = 2 * borderWidth;
    if( borderSize < width ){
      width -= borderSize;
    } else {
      // $$$$$ could make the left and right borders be 1/2 the width so that the highlight is the right size.
      // right now the highlight can be bigger than the area of the image
      width = 0;
    }
    if( borderSize < height ){
      height -= borderSize;
    } else {
      // $$$$$ could make the left and right borders be 1/2 the width so that the highlight is the right size.
      // right now the highlight can be bigger than the area of the image
      height = 0;
    }


    // hack to make the outline fit the image properly at higher zoom levels

    var highlightStyle = _elDeleteHighlight.style;
    highlightStyle.left = left + "px";
    highlightStyle.top = top + "px";
    highlightStyle.width = width + "px";
    highlightStyle.height = height + "px"; 
    highlightStyle.display = "block";
		
    // add an animation to highlight the image to be deleted
    var anim = new YAHOO.util.ColorAnim( _elDeleteHighlight, { borderColor: {from: highlightColor, to: "#000000" } }, 1 );
    anim.onComplete.subscribe( function(){ anim.animate(); } );
    anim.animate();
    _highlightAnim = anim;   // store ref to animation object so we can turn it off later

    setTimeout( function(){ gMap.endHighlightSquares(); }, 5000 );

  };
	
	function endHighlightSquares(){

    // remove the highlight element from the DOM
    if( _highlightAnim ){
      stopHighlightAnimation();
    }
    _elDeleteHighlight.style.display = "none";
    _highlightInfo = null;

	};

  function stopHighlightAnimation(){
    if( _highlightAnim ){
      // stop the animation and unsubscribe from the onComplete event
      _highlightAnim.onComplete.unsubscribe();
      _highlightAnim.stop( true );

      _highlightAnim = null;
    }    
  }

	
  // *****************************************
  //    END HIGHLIGHT SQUARES FOR DELEITION
  // *****************************************
	
	
	
  function _updateMiniMap(){
		
 		var mapPos = _getMapPosition();
		_oMiniMap.update( mapPos[0], mapPos[1] );
  };
	
  function _makeNewMiniMap(){
		
    if( _oMiniMap ){
      _oMiniMap.destroy();
    }
		
    // $$$$$ could make this more efficient by making a mini map zoomable

    _oMiniMap = new MiniMap( _elMiniMapViewport, _miniMapZoomLevels[_zoomLevel], gMap );
  };
	
	
  function _destroyMap(){

		// delete the existing tile cache
    if( _oTileCache ){
      _oTileCache.destroy();
    }
    _oTileCache = null;
		
		// remove the existing image tiles
		_elMapTilesContainer.innerHTML = "";   
		
		// destroy the mini map
    if( _oMiniMap ){
      _oMiniMap.destroy();
    }
		_oMiniMap = null;
  };
	
	
	// **********************************************
	//                 MAP PANNING
	// **********************************************
	
  var PAN_SIZE_PIXELS = 250;
	
  function _panUp(){
    _animateBy( 0, PAN_SIZE_PIXELS);
  };
	
  function _panDown(){
    _animateBy(0, -PAN_SIZE_PIXELS);
  };
	
  function _panLeft(){
    _animateBy(PAN_SIZE_PIXELS, 0);
  };
	
  function _panRight(){
    _animateBy(-PAN_SIZE_PIXELS, 0);
  };
	
  function _centerMap(){
    centerAt(5000,5000);
  }
	
	// **********************************************
	//                 END MAP PANNING
	// **********************************************
	
	
	// **********************************************
	//                    ZOOM
	// **********************************************
	
	function _lockZoomSlider(){
		_zoomSlider.lock();
	};
	
	function _unlockZoomSlider(){
		_zoomSlider.unlock();
	};
	

  function lockMap(){
		YAHOO.log("locking map");
    _lockMap = true;
  };

  function unlockMap(){
		YAHOO.log("unlocking map");
    _lockMap = false;
  };


	
  function _zoomSliderChange(offsetFromStart){
		var newZoomLevel = offsetFromStart / ZOOM_TICK_SIZE_PIXELS;
		setZoomLevel( newZoomLevel, true );
  };
	
	
  function zoomIn(){
    if( _lockMap ){
      YAHOO.log("Map is locked.");
			//			debugger;
      return;
    }

    if( _zoomLevel > 0 ){
      setZoomLevel( _zoomLevel - 1, true );
    }
  };
	
  
  function zoomOut(){
    if( _lockMap ){
      YAHOO.log("Map is locked.");
			//			debugger;
      return;
    }

		if( _oMapSelect.isSelected() ){
			alert( "Sorry, you cant zoom out while you are placing an image.");  // i18n
			return;
		} else if( _zoomLevel < (_mapZoomLevels - 1) ){
      setZoomLevel( _zoomLevel + 1, true );
    }
  };
	
	
  function setZoomLevel( newZoomLevel, redrawMap ){
    
    // $$$$$ hack!  Uploading an image from an iframe causes the history to increment, which causes
    //  this history manager to reload an old view string.  This hack prevents it from happening
    if( _lockMap ){
      YAHOO.log("History manager change aborted because map is locked.");
			//			debugger;
      return;
    }

    YAHOO.log( "moving to zoom level: " + newZoomLevel );

    // _zoomLevel can be null.  to be safe i'll use the !== operator which doesnt type cast
    if( _zoomLevel !== newZoomLevel ) {

      gApp.hideLinkUI();
			
      if( redrawMap ){
        var mapCenter = getVisibleMapCenter();
        _destroyMap();
      }
			
      _zoomLevel = newZoomLevel;
      _zoomMagnification = TileCache.getZoomMagnification( newZoomLevel );
			
      if( redrawMap ){
				
        // restore the original center 
        centerAt( mapCenter[0], mapCenter[1] );
				
        // make a new mini map for the new zoom level
        _makeNewMiniMap();
        
        // draw the map at the new zoom level
        draw();
				
        _zoomSlider.setValue( newZoomLevel * ZOOM_TICK_SIZE_PIXELS, true, true );
			}
    }
  };
	
  function getZoomLevel(){
    // returns current map zoom level
    return _zoomLevel;
  };
	
  
	// **********************************************
	//                   END ZOOM
	// **********************************************
	
	
  // ***********************************
  //    BROWSER HISTORY MANAGER STUFF 
  // ***********************************
	
	
  function setMapPositionWithHistory( mapX, mapY ){
    YAHOO.util.History.navigate( "mapPos", mapX + "," + mapY + "," + getZoomLevel());
  }
	
  function setMapPositionFromString( positionStr ){
    // positionStr is "5,6" to position the map's visible center at coordinate (5,6)
		if( _lockMap ){
			YAHOO.log("Map is locked.");
			return;
		}

		
    positionStr = decodeURIComponent( positionStr );
		
    var xy = positionStr.split( "," );
    var mapLeft = parseInt( xy[0] );
    var mapTop = parseInt( xy[1] );
    var zoomLevel = parseInt( xy[2] );

    setZoomLevel( zoomLevel );
    centerAt( mapLeft, mapTop );
  };
	
  function getMapPositionAsString(){
    // returns a string specifing the center coordinates of the map
    var mapPosition = getVisibleMapCenter();
    return "" + mapPosition[0] + "," + mapPosition[1] + "," + _zoomLevel;
  };
	
	function addCurrentMapPositionToBrowserHistory(){
		var mapPosStr = getMapPositionAsString();
		YAHOO.util.History.navigate( "mapPos", mapPosStr );
	};
	
	
  // ***************************************
  //    END BROWSER HISTORY MANAGER STUFF 
  // ***************************************
	
  function animateToCenterAt( mapX, mapY ){
    var mapPos = _getMapPositionForCenterAt( mapX, mapY );
    animateMapToPosition( mapPos[0], mapPos[1] );
  };
	
  function centerAt( centerMapX, centerMapY ){
    var mapPos = _getMapPositionForCenterAt( centerMapX, centerMapY );
    setMapPosition( mapPos[0], mapPos[1] );
  };
	
  function _getMapPositionForCenterAt( centerMapX, centerMapY ){
    var zoomedSize = _getViewportZoomedSize();
    var zoomedCenterX = Math.round( zoomedSize[0] / 2 );
    var zoomedCenterY = Math.round( zoomedSize[1] / 2);
		
    return [ centerMapX - zoomedCenterX,
             centerMapY - zoomedCenterY ];
  };
	
	
	
  function getVisibleMapCenter(){
		
    // first, find the center pixel of the viewport withotu worring about the map position
    var viewportZoomedSize = _getViewportZoomedSize();
    var viewportZoomedCenterX = Math.round( viewportZoomedSize[0] / 2 );
    var viewportZoomedCenterY = Math.round( viewportZoomedSize[1] / 2 );
		
    // now, use the map position to figure out what map pixel is at the center of the viewport
    var mapPosition = _getMapPosition();
    var visibleMapCenterX = mapPosition[0] + viewportZoomedCenterX;
    var visibleMapCenterY = mapPosition[1] + viewportZoomedCenterY;
    
    return [ visibleMapCenterX, visibleMapCenterY ];
  };
	
  function _getViewportZoomedSize(){
    // returns the zoomed map visible in the viewport at the current zoom setting
    var viewportSize = _oViewport.getSize();
    var zoomedMapWidth = viewportSize[0] * _zoomMagnification;
    var zoomedMapHeight = viewportSize[1] * _zoomMagnification;
    
    return [ zoomedMapWidth, zoomedMapHeight ];
  };
  
	
  function getVisibleMapInfo(){
    // returns information about the area of the map currently visible in the viewport
    var viewportZoomedSize = _getViewportZoomedSize();
    var mapPositionInViewport = _getMapPosition();
		
    return {
      width : viewportZoomedSize[0],
				height : viewportZoomedSize[1],
				left : mapPositionInViewport[0],
				top : mapPositionInViewport[1]
				};
  };
	
	
  function _getMapPosition(){
		
    return [ -_elMapContainer.offsetLeft * _zoomMagnification, 
						 -_elMapContainer.offsetTop * _zoomMagnification];
		
  };
	
  function setMapPosition( mapX, mapY ){
    // takes the desired map X,Y position for the top left corner of the map's visible area
    //  and convertes it into style top and left values that account for the current zoom level
		
    var leftPixel = -mapX / _zoomMagnification;
    var topPixel = -mapY / _zoomMagnification;
		
    _elMapContainer.style.left = leftPixel + "px";
    _elMapContainer.style.top = topPixel + "px";
		
    if( _oTileCache ) {
			_destroyMap();
      _makeNewMiniMap();
			draw();
    }
		
		_oMapSelect.centerSelectAreaInMapViewport();
		
  };
	
	
	
	function animateMapToPosition( endMapX, endMapY ){ 
    var endLeftPixel = -endMapX / _zoomMagnification;
    var endTopPixel = -endMapY / _zoomMagnification;
		
		var attributes = {
			points: {to: [endLeftPixel, endTopPixel]}
		}
		var anim = new YAHOO.util.Motion( _elMapContainer, attributes, .5 );
		anim.onTween.subscribe( _whileAnimateMapToPosition );
		anim.onComplete.subscribe( _endAnimateMapToPosition );
		
		anim.animate();
		
	};
	
  function _animateBy( deltaMapX, deltaMapY ){
		
		var attributes = {
			points: {by: [deltaMapX, deltaMapY]}
		}
		var anim = new YAHOO.util.Motion( _elMapContainer, attributes, .25 );
		anim.onTween.subscribe( _whileAnimateMapToPosition );
		anim.onComplete.subscribe( _endAnimateMapToPosition );
		
		anim.animate();
  };
	
	
	
	function _whileAnimateMapToPosition(){
    //		YAHOO.log( "tween animiation" );
		
    // tile cache uses raw container position, NOT ZOOMED MAP POSITION
    _oTileCache.reactToMapDrag( -_elMapContainer.offsetLeft, -_elMapContainer.offsetTop );
		
    var mapPos = _getMapPosition();
		_oMiniMap.setLensMoverPositionWithMapCoordinates( mapPos[0], mapPos[1] );
		
		//_updateMiniMap();
		
	};
	
	function _endAnimateMapToPosition(){
		_updateMiniMap();
	};
	
	
	
	
	
  function getGridXY( e ){
    // pass in an event and get the grid coordiates where it occurred
		
		var gridPixelsPerViewportPixel = TileCache.getTilesPerZoomTile( _zoomLevel );

    var elTarget = YUE.getTarget( e );
    if( elTarget.id == "highlightArea"){
      // if the user clicked on an image that is being highlighted, we need to change the
      //   target to be the div that contains the highlight area
      elTarget = $("deleteHighlight");
    }
		
		// top left map pixel of tile that was clicked
    // (use YUI to get the styles because it will replace "" styles with "0px" automatically)
		var tileTop = parseInt( YUD.getStyle( elTarget, "top" ) );
		var tileLeft = parseInt( YUD.getStyle( elTarget, "left" ) );

		// position clicked on tile relative to top left corner of tile
		var clickTop = getLayerY( e );    
		var clickLeft = getLayerX( e );
		
		var clickedMapPixelX = tileLeft + clickLeft;
		var clickedMapPixelY = tileTop + clickTop;
		
    var gridSquaresPerViewportPixel = gridPixelsPerViewportPixel / TileCache.SQUARE_SIZE_PIXELS;
		var gridX = Math.floor( clickedMapPixelX * gridSquaresPerViewportPixel );
		var gridY = Math.floor( clickedMapPixelY * gridSquaresPerViewportPixel );
		
		return [gridX,gridY];
  };
	
	
  function getLayerX(ev){
    // returns the position where an event occured relative to it's container element.  (not totally reliable)
		
    var x = ev.layerX;
    if( !x ) {
      x = ev.offsetX;   // IE property
    }
    return x;
  };
  
  function getLayerY(ev){
    // returns the position where an event occured relative to it's container element.  (not totally reliable)
		
    var y = ev.layerY;
    if( !y ) {
      y = ev.offsetY;  // IE property
    }
    return y;
  };
	
	
	
	
	
  // **************************************
  //             EVENT ACTIONS
  // **************************************
	
  function startMapDrag( e ){
    YAHOO.log("startMapDrag");

    if( gIsMozilla ){
      YUD.setStyle( _elMapContainer, "cursor", "-moz-grabbing" );
    }
		
    var xy = YUE.getXY( e );
    var mouseDragStartX = xy[0];
    var mouseDragStartY = xy[1];
		
    var mapDragStartX = parseInt( YUD.getStyle( _elMapContainer, "left" ) );
    var mapDragStartY = parseInt( YUD.getStyle( _elMapContainer, "top" ) );
		
    if( isNaN(mapDragStartX) ) { mapDragStartX = 0 }
    if( isNaN(mapDragStartY) ) { mapDragStartY = 0 }

    _mouseDragOffsetX = mapDragStartX - mouseDragStartX;
    _mouseDragOffsetY = mapDragStartY - mouseDragStartY;

		
  };
	

	
  function whileMapDrag( e ){
		
    _elMapContainerStyle.left = (_mouseDragOffsetX + e.clientX ) + "px";
    _elMapContainerStyle.top = (_mouseDragOffsetY + e.clientY) + "px";
		

    var mapPosX = -_elMapContainer.offsetLeft;
    var mapPosY = -_elMapContainer.offsetTop;

    // pass the raw position of the map container to the tile cache, NOT THE MAP POSITION ACCOUNTING FOR ZOOM
    _oTileCache.reactToMapDrag( mapPosX, mapPosY );
		
		_oMiniMap.setLensMoverPositionWithMapCoordinates( mapPosX * _zoomMagnification, mapPosY * _zoomMagnification );
  };
	
	
  function endMapDrag( e ){
		
    YUD.setStyle( _elMapContainer, "cursor", "" );   // $$$$$ DEBUGGGING $$$$$
		
    _mapDragOffsetX = null;
    _mapDragOffsetY = null;
    
		
    _updateMiniMap();
  };
	
  function mapClick( e ){
		
    var target = YUE.getTarget(e);
		
    //    YAHOO.log( "click target id: " + target.id );
		
    // a delete image button appears when an admin user shift clicks an image on the map.  This is the click handler
    if( target.id == "deleteImageButton" ){
      YAHOO.log( "map: delete button clicked" );
			gAdmin.showAdminDeletePanel();
      return;
			
    } else {
			
      var gridXY = getGridXY( e );
      //YAHOO.log( "clicked grid (" + gridXY[0] + "," + gridXY[1] + ")" );

      if( gIsAdminUser && e.shiftKey ){
        // admin users can shift click an image to delete it
        gAdmin.confirmDeleteContent( gridXY[0], gridXY[1] );
      } else {
        gApp.getSquareOwner( gridXY[0], gridXY[1] );
      }
    }
  };
	

	// *****************************************
	//             END EVENT ACTIONS
	// *****************************************

	
	return {
    centerAt : centerAt,
      animateToCenterAt : animateToCenterAt,
      init: init,
      getVisibleMapCenter : getVisibleMapCenter,
			setMapPosition : setMapPosition,
			animateMapToPosition : animateMapToPosition,
      getVisibleMapInfo : getVisibleMapInfo,
      draw : draw,
      redraw : redraw,
      resize : resize,
			//      setMapZoomLevels : setMapZoomLevels,
      getLayerX : getLayerX,
      getLayerY : getLayerY,
      getGridXY : getGridXY,
      whileMapDrag : whileMapDrag,
      startMapDrag : startMapDrag,
      endMapDrag : endMapDrag,
      getZoomLevel : getZoomLevel,
      mapClick : mapClick,
      setMapPositionFromString : setMapPositionFromString,    // used by history manager in BeboNation.jsp
      setMapPositionWithHistory : setMapPositionWithHistory,
			addCurrentMapPositionToBrowserHistory : addCurrentMapPositionToBrowserHistory,
      highlightSquares : highlightSquares,
      endHighlightSquares : endHighlightSquares,
      stopHighlightAnimation : stopHighlightAnimation,
      addOverlayElement : addOverlayElement,
			getOverlayAreaInfo : getOverlayAreaInfo,
      isAreaInBounds : isAreaInBounds,
			setZoomLevel : setZoomLevel,
      beginMapSelectDisplay : beginMapSelectDisplay,
      endMapSelectDisplay : endMapSelectDisplay,
      getMapPositionAsString : getMapPositionAsString,
      lockMap : lockMap,
      unlockMap : unlockMap
	};

}();
