function TileCache( container, viewportWidthPixels, viewportHeightPixels, zoomLevel, name, isSafari ) {

  //	YAHOO.log( "new tile cache created for '" + name + "'" );
  
  this._name = name;
  this._zoomLevel = zoomLevel;
  this._isSafari = isSafari;   // used to work around a bug where seting an image to a new URL causes the image object to freeze up in Safari


  this._tileCacheColumns = Math.ceil( viewportWidthPixels / TileCache.TILE_SIZE_PIXELS ) + 1;
  this._tileCacheRows = Math.ceil( viewportHeightPixels / TileCache.TILE_SIZE_PIXELS ) + 1;

  this._tileCache = this._createTileCache( this._tileCacheColumns, this._tileCacheRows );
  this._elMapContainer = container;

  this._mapLeftPixel = null;
  this._mapTopPixel = null;
  this._mapLeftTile = null;
  this._mapTopTile = null;

  this._noActionZoneRight = null;
  this._noActionZoneBottom = null;

  this._tileUrlSuffix = "_" + this._zoomLevel + ".jpg)";
  this._tileIdSuffix = "_zoom_" + this._zoomLevel;

  // calculate difference between map size and viewport size. Assume map is bigger
  this._mapExtraWidth = (this._tileCacheColumns * TileCache.TILE_SIZE_PIXELS) - viewportWidthPixels;
  this._mapExtraHeight = (this._tileCacheRows * TileCache.TILE_SIZE_PIXELS) - viewportHeightPixels;

	// compute the max tile coords based on the map size and zoom level
  this._tileUrlPrefix = "url(" + gStaticServerRoot + "/img/bn/maptiles/jpeg2/maptile_";

  
  // dynamically create a map boundary coordinate test for this zoom level so that the edge is hard coded for this zoom level.  (Makes it fast)
  var boundaryCoord =  _proxy_jslib_handle(TileCache.MAP_BOUNDARY_BY_ZOOM, (zoomLevel), 0, 0);
  this._mapBoundaryTest = _proxy_jslib_new_function(["zoomTileX","zoomTileY"],
                                       "return (zoomTileX > " + boundaryCoord + " || zoomTileY > " + boundaryCoord + ");");
  this._mapBoundaryTestNum =  _proxy_jslib_handle(TileCache.MAP_BOUNDARY_BY_ZOOM, (zoomLevel), 0, 0);

};



  // **********************
  //    STATIC FUNCTIONS
  // **********************


TileCache.SQUARE_SIZE_PIXELS = 10;
TileCache.TILE_SIZE_SQUARES = 25;
TileCache.TILE_SIZE_PIXELS = TileCache.TILE_SIZE_SQUARES * TileCache.SQUARE_SIZE_PIXELS;
TileCache.MAP_BOUNDARY_BY_ZOOM = [39, // zoom 0
                                  19, // zoom 1
                                  9, // zoom 2
                                  4, // zoom 3
                                  2, // zoom 4
                                  1, // zoom 5
                                  0, // zoom 6
                                  0, // zoom 7
                                  0]  




TileCache.getTileAtPixel = function( pixelX, pixelY ) {
  
  var tileSizePixels = TileCache.TILE_SIZE_PIXELS;
  var tileX = Math.floor( pixelX / tileSizePixels );
  var tileY = Math.floor( pixelY / tileSizePixels );

  return [ tileX , tileY ];
};

TileCache.getZoomTileAtTile = function( tileX, tileY, zoomLevel ) {
  // figure out which zoom tile this tile is part of
  var tilesPerZoomTile = TileCache.getTilesPerZoomTile( zoomLevel );
  var zoomTileX = Math.floor( tileX / tilesPerZoomTile );
  var zoomTileY = Math.floor( tileY / tilesPerZoomTile );
  
  return [ zoomTileX, zoomTileY ];
};

TileCache.getZoomTileAtPixel = function( mapPixelX, mapPixelY, zoomLevel ) {
  var tileAtPixel = TileCache.getTileAtPixel( mapPixelX, mapPixelY );
  return TileCache.getZoomTileAtTile( tileAtPixel[0], tileAtPixel[1], zoomLevel );
};


TileCache.getTilesPerZoomTile = function( zoomLevel ) {
  return Math.pow( 2, zoomLevel );
};



TileCache.getZoomTileSizePixels = function( zoomLevel ) {
  return TileCache.TILE_SIZE_PIXELS * TileCache.getZoomMagnification( zoomLevel );
};

TileCache.getZoomMagnification = function( zoomLevel ) {
		if( zoomLevel == 0 ){
			return 1;
		} else {
      //			return 2 * zoomLevel;
      return Math.pow( 2, zoomLevel );
		}
	};


  // **************************
  //    END STATIC FUNCTIONS
  // **************************



TileCache.prototype = {

  // ****************************************
  //            PUBLIC METHODS
  // ****************************************

  _applyToAllTiles : function( func ) {
    var numRows = this._tileCacheRows;
    var numColumns = this._tileCacheColumns;
    for( var x=0; x < numColumns; x++){
      var column =  _proxy_jslib_handle(this._tileCache, (x), 0, 0);
      for( var y=0; y < numRows; y++ ){
        func(  _proxy_jslib_handle(column, (y), 0, 0) );
      }
    }
  },


  makeTilesTransparent: function() {
    this._applyToAllTiles( function(elem) {
      YUD.addClass(elem, "halfAlpha");
    });
  },

  makeTilesOpaque: function() {
    this._applyToAllTiles( function(elem) {
      YUD.removeClass(elem, "halfAlpha");
    });
  },


  createGridImages: function( mapLeftTile, mapTopTile ) {

    var tileSizePixels = TileCache.TILE_SIZE_PIXELS;

    // cache position data inside this object
    this._mapLeftTile = mapLeftTile;
    this._mapTopTile = mapTopTile;
    this._mapLeftPixel = mapLeftTile * tileSizePixels;
    this._mapTopPixel = mapTopTile * tileSizePixels;

    this._noActionZoneBottom = this._mapTopPixel + this._mapExtraHeight;
    this._noActionZoneRight = this._mapLeftPixel + this._mapExtraWidth;


    function tileImageOnloadHandler() {
      this.style.visibility = "visible";
    };
    
    function onloadEventToPreventImageDrag() {
      // prevents images from dragging in IE
      return false;
    };
    
    function onerrorEventForImages( e ) {
      //      debugger;
      YAHOO.log( "img onerror fired" );
    };


    // keep positive integer indexes on the rows and columns for the 2D tile cache array
		var tileCacheColumn = 0;
		var tileCacheRow = 0;

    // cache some data for better performance

		var mapRightTile = mapLeftTile + this._tileCacheColumns;
		var mapBottomTile = mapTopTile + this._tileCacheRows;

    if( gMapSelect.isSelected()){
      // keep the background greyed out if a select is in progress
      var className = "mapTile halfAlpha";
    } else {
      var className = "mapTile";
    }



		for( var column=mapLeftTile; column < mapRightTile; column++ ){

      var urlPrefix = this._tileUrlPrefix + column + "_";

      var proto = document.createElement( "IMG" );
      proto.style.left = (column * tileSizePixels) + "px";
      proto.className = className;

			tileCacheRow = 0;
			for( var row=mapTopTile; row < mapBottomTile; row++ ){
        
        elTile = proto.cloneNode(true);
         _proxy_jslib_assign('', elTile.style, 'top', '=', ( (row * tileSizePixels) + "px"));
         _proxy_jslib_assign('', elTile.style, 'backgroundImage', '=', ( urlPrefix + row + this._tileUrlSuffix));
        
        YUE.addListener( elTile, "load", tileImageOnloadHandler );
        YUE.addListener( elTile, "error", onerrorEventForImages );
        YUE.addListener( elTile, "drag", onloadEventToPreventImageDrag );   // ie temp fix to prevent images from dragging

				//elTile.id = column + "_" + row + this._tileIdSuffix;
         _proxy_jslib_assign('', elTile, 'src', '=', ( this._getTileUrl( column, row )));
				
				this._cacheTile( elTile, tileCacheColumn, tileCacheRow );
				tileCacheRow++;
        
        this._elMapContainer.appendChild( elTile );
      }
			tileCacheColumn++;
    }
  },



  reloadChangedImages: function() {
		// called after user uploads an image so that the image appears on the map right away

    var mapTopTile = this._mapTopTile;
    var mapLeftTile = this._mapLeftTile;

    var columns = this._tileCache;
    var numColumns = this._tileCacheColumns;  // should be the same as columns.length
    var numRows = this._tileCacheRows;
    for( var columnIndex=0; columnIndex < numColumns; columnIndex++ ){
      var tileX = columnIndex + mapLeftTile;

      var row =  _proxy_jslib_handle(columns, ( columnIndex ), 0, 0);
      for( var rowIndex =0; rowIndex < numRows; rowIndex++ ){
        var tileY = mapTopTile + rowIndex;
        var newUrl = this._getTileUrl( tileX, tileY );
        
        var elTile =  _proxy_jslib_handle(row, ( rowIndex ), 0, 0);
        if( newUrl !=  _proxy_jslib_handle(elTile, 'src', '', 0, 0) ){
           _proxy_jslib_assign('', elTile, 'src', '=', ( newUrl));
        }
      }
    }
  },


  destroy: function() {
    // walk through this._tileCache and remove the images from the DOM

    var elMapContainer = this._elMapContainer;
    var tileCache = this._tileCache;

    var numColumns = tileCache.length;
    for( var i=0; i < numColumns; i++ ){
      var column =  _proxy_jslib_handle(tileCache, (i), 0, 0);
      var numRows = column.length;

      for( var j=0; j < numRows; j++ ){
        var img = elMapContainer.removeChild(  _proxy_jslib_handle(column, (j), 0, 0) );
        YUE.purgeElement( img );   // remove events
      }
    }

    _tileCache = null;   // should be good enough
  },




	reactToMapDrag : function( viewportLeftPixel, viewportTopPixel ) {

		if( viewportLeftPixel < this._mapLeftPixel ){
      //			YAHOO.log( "move right tiles to the left side" );
      var numTilesToMove = Math.ceil( (this._mapLeftPixel - viewportLeftPixel) / TileCache.TILE_SIZE_PIXELS ) ;

      for( var i=0; i < numTilesToMove; i++ ){
        var columnLeftTile = this._mapLeftTile - 1;
        var columnTopTile = this._mapTopTile;
        this._moveRightColumnToLeft( columnLeftTile, columnTopTile );
      }

		} else if ( viewportLeftPixel > this._noActionZoneRight ){
      //      YAHOO.log( "move left tiles to the right side" );
      var numTilesToMove = Math.ceil( ( viewportLeftPixel - this._noActionZoneRight ) / TileCache.TILE_SIZE_PIXELS ) ;

      for( var i=0; i < numTilesToMove; i++ ){
        var columnLeftTile = this._mapLeftTile + this._tileCacheColumns;
        var columnTopTile = this._mapTopTile;
        this._moveLeftColumnToRight( columnLeftTile, columnTopTile );
      }
		}

		if( viewportTopPixel < this._mapTopPixel ){
      //      YAHOO.log( "move bottom tiles to top" );
      var numTilesToMove = Math.ceil( (this._mapTopPixel - viewportTopPixel) / TileCache.TILE_SIZE_PIXELS ) ;
      
      for( var i=0; i < numTilesToMove; i++ ){
        var rowLeftTile = this._mapLeftTile;
        var rowTopTile = this._mapTopTile - 1;
        this._moveBottomRowToTop( rowLeftTile, rowTopTile );
      }
		} else if ( viewportTopPixel > this._noActionZoneBottom ){
      //      YAHOO.log( "move top tiles to bottom" );
      var numTilesToMove = Math.ceil( ( viewportTopPixel - this._noActionZoneBottom ) / TileCache.TILE_SIZE_PIXELS ) ;

      for( var i=0; i < numTilesToMove; i++ ){
        var rowLeftTile = this._mapLeftTile;
        var rowTopTile = this._mapTopTile + this._tileCacheRows;
        this._moveTopRowToBottom( rowLeftTile, rowTopTile );
      }
		}

	},


  
  // ****************************************
  //            PRIVATE METHODS
  // ****************************************


  _getTileUrl : function( tileX, tileY ) {

    var key = tileX + "_" + tileY;
    
    if( typeof( gTiles ) == "undefined" ) return "/img/bn/maptiles/transparentTile.gif";
    
    var zoomTileUrls =  _proxy_jslib_handle(gTiles, ( this._zoomLevel ), 0, 0);
    var url = null;
    if( typeof( zoomTileUrls ) != "undefined" ){
      url =  _proxy_jslib_handle(zoomTileUrls, ( key ), 0, 0);
    }
    
    if( url ){
      return url;
    } else {
			return "/img/bn/maptiles/transparentTile.gif";
    }

  },

  _getBackground : function( tileX, tileY ) {
    
  },

 
	_cacheTile: function( elTile, column, row ) {
     _proxy_jslib_assign('',  _proxy_jslib_handle(this._tileCache, (column), 0, 0), (row), '=', ( elTile));
	},



  _createTileCache: function( tileColumns, tileRows ) {
    var cache = [];
    for( var i=0; i < tileColumns; i++ ){
       _proxy_jslib_assign('', cache, (i), '=', ( []));
    }
		return cache;
  },


  _moveLeftColumnToRight : function( newColumnLeftTile, newColumnTopTile ) { 

    var tiles = this._getLeftTiles();
    this._moveTileColumn( tiles, newColumnLeftTile, newColumnTopTile );
    this._setRightTiles( tiles );
    this._saveVisiblePixelsHorizontal();
  },

  _moveRightColumnToLeft : function( newColumnLeftTile, newColumnTopTile ) {
    var tiles = this._getRightTiles();
    this._moveTileColumn( tiles, newColumnLeftTile, newColumnTopTile );
    this._setLeftTiles( tiles );
    this._saveVisiblePixelsHorizontal();
  },

  _moveTopRowToBottom : function( newRowLeftTile, newRowTopTile ) {
    var tiles = this._getTopTiles();
    this._moveTileRow( tiles, newRowLeftTile, newRowTopTile );
    this._setBottomTiles( tiles );
    this._saveVisiblePixelsVertical();
  },

  _moveBottomRowToTop : function( newRowLeftTile, newRowTopTile ) {

    var tiles = this._getBottomTiles();
    this._moveTileRow( tiles, newRowLeftTile, newRowTopTile );
    this._setTopTiles( tiles );
    this._saveVisiblePixelsVertical();
  },







  _moveTileColumn : function( tiles, newColumnLeftTile, newColumnTopTile ) {
    
    var isSafari = this._isSafari;  // cache to make loop as fast as possible
    
		// figure out the pixel x coordinate for the column when it is moved to the right
		var newLeftStyle = (newColumnLeftTile * TileCache.TILE_SIZE_PIXELS) + "px";

    var urlPrefix = this._tileUrlPrefix + newColumnLeftTile + "_";

//     var idPrefix = newColumnLeftTile + "_"; 
//     var idSuffix = this._tileIdSuffix;

    var inMapBounds = (newColumnLeftTile >= 0 && newColumnLeftTile <= this._mapBoundaryTestNum);

		var numTiles = tiles.length;
		for( var i=0; i < numTiles; i++ ) {
			var elTile =  _proxy_jslib_handle(tiles, (i), 0, 0);
			var rowTile = newColumnTopTile + i;

      // update the id and title
      var elStyle = elTile.style;
      elStyle.visibility = "hidden";
			elStyle.left = newLeftStyle;

      if( inMapBounds ){
         _proxy_jslib_assign('', elStyle, 'backgroundImage', '=', ( urlPrefix + rowTile + this._tileUrlSuffix));
      } else {
         _proxy_jslib_assign('', elStyle, 'backgroundImage', '=', ( ""));
      }

      //elTile.id = idPrefix + rowTile + idSuffix;

      if( isSafari ){
         _proxy_jslib_assign('', elTile, 'src', '=', ( ""));   // prevents safari image tiles from locking up
      }
			 _proxy_jslib_assign('', elTile, 'src', '=', ( this._getTileUrl( newColumnLeftTile, rowTile )));
		}
  },



  _moveTileRow : function( tiles, newRowLeftTile, newRowTopTile ) {

    var isSafari = this._isSafari;  // cache to make loop as fast as possible
    
    var newTopStyle = (newRowTopTile * TileCache.TILE_SIZE_PIXELS) + "px";
		var numTiles = tiles.length;

    var urlPrefix = this._tileUrlPrefix;
    var urlSuffix = "_" + newRowTopTile + this._tileUrlSuffix;

    //    var idSuffix = "_" + newRowTopTile + this._tileIdSuffix;

    var inMapBounds = (newRowTopTile >=0 && newRowTopTile <= this._mapBoundaryTestNum);
    
		for( var i=0; i < numTiles; i++ ) {
			var elTile =  _proxy_jslib_handle(tiles, (i), 0, 0);
      var columnTile = newRowLeftTile + i;

      var elStyle = elTile.style;
      elStyle.visibility = "hidden";
			 _proxy_jslib_assign('', elStyle, 'top', '=', ( newTopStyle));

      if( inMapBounds ){
         _proxy_jslib_assign('', elStyle, 'backgroundImage', '=', ( urlPrefix + columnTile + urlSuffix));
      } else {
         _proxy_jslib_assign('', elStyle, 'backgroundImage', '=', ( ""));
      }

      //			elTile.id = columnTile + idSuffix

      if( isSafari ){
         _proxy_jslib_assign('', elTile, 'src', '=', ( ""));   // prevents safari image tiles from locking up
      }
			 _proxy_jslib_assign('', elTile, 'src', '=', ( this._getTileUrl( columnTile, newRowTopTile )));
    }
  },





  _saveVisiblePixelsHorizontal : function() {
    // get the left style for a tile in the leftmost column
    this._mapLeftPixel = parseInt( this._tileCache[0][0].style.left );
    this._mapLeftTile = this._mapLeftPixel / TileCache.TILE_SIZE_PIXELS;

    this._noActionZoneRight = this._mapLeftPixel + this._mapExtraWidth;
  },

  _saveVisiblePixelsVertical : function() {
    // use the top style for a tile in the top row
    this._mapTopPixel = parseInt(  _proxy_jslib_handle(this._tileCache[0][0].style, 'top', '', 0, 0) );
    this._mapTopTile = this._mapTopPixel / TileCache.TILE_SIZE_PIXELS;

    this._noActionZoneBottom = this._mapTopPixel + this._mapExtraHeight;
  },




	_getLeftTiles : function() {
    return this._tileCache.shift();
	},

	_getRightTiles : function () {
    return this._tileCache.pop();
	},

	_getTopTiles : function () {
    var tiles = [];
    var numColumns = this._tileCacheColumns;
    for( var i=numColumns -1; i >= 0; i-- ){  // count down for speed
      // get the first tile in each column list
       _proxy_jslib_assign('', tiles, (i), '=', (  _proxy_jslib_handle(this._tileCache, (i), 0, 0).shift()));
    }
    return tiles;
	},

	_getBottomTiles : function() {
    var tiles = [];
    var numColumns = this._tileCacheColumns;
    for( var i=numColumns -1; i >= 0; i-- ){  // count down for speed
      // get the last tile in each column list
       _proxy_jslib_assign('', tiles, (i), '=', (  _proxy_jslib_handle(this._tileCache, (i), 0, 0).pop()));
    }
    return tiles;
	},

	_setLeftTiles : function( tiles ) {
    this._tileCache.unshift( tiles );
  },

	_setRightTiles : function( tiles ) {
    this._tileCache.push( tiles );
	},

	_setTopTiles : function( tiles ) {
    var numTiles = tiles.length;
    for( var i=0; i < numTiles; i++ ){
      var curTile =  _proxy_jslib_handle(tiles, (i), 0, 0);
       _proxy_jslib_handle(this._tileCache, (i), 0, 0).unshift( curTile );
    }

	},

	_setBottomTiles : function( tiles, topPixel ) {
    var numTiles = tiles.length;
    for( var i=0; i < numTiles; i++ ){
      var curTile =  _proxy_jslib_handle(tiles, (i), 0, 0);
       _proxy_jslib_handle(this._tileCache, (i), 0, 0).push( curTile );
    }
	}



};
  
	
	// ****************************************
	//             TILE CACHE END
	// ****************************************
 ;
_proxy_jslib_flush_write_buffers() ;