/**
 * LabelOverlay
 *
 * This is a custom Google Map overlay that is drawn as a simple text label on
 * the map.
 *
 *
 *
 * Creates a new LabelOverlay with the specified center, text label, and color.
 *
 * @param pCenter GlatLng - the center of the label
 * @param pLabel string - the text label
 * @param pColor string - the hex code for the color (include the '#')
 * @param pFontSize - the size of the font (in px)
 **/
function LabelOverlay ( pCenter, pLabel, pColor, pFontSize ) {
  this.mCenter = pCenter;
  this.mLabel = pLabel;
  this.mColor = pColor || "#fffa73";
  this.mWidth = 200;
  this.mFontSize = parseInt( pFontSize || 12 );
  this.mHeight = this.mFontSize + 4;
}

// set the prototype to the GOverlay class
LabelOverlay.prototype = new GOverlay( );

/**
 * Initialize the custom overlay
 *
 * @param pMap GMap2 - the map this overlay is on
 **/
LabelOverlay.prototype.initialize = function ( pMap ) {

  // create the div representing the label
  var div = document.createElement( 'div' );
  div.style.position = 'absolute';
  div.style.overflow = 'visible';
  div.style.textAlign = 'center';
  div.style.fontWeight = 'bold';
  div.style.whiteSpace = 'nowrap';
  div.style.width = this.mWidth + 'px';
  div.style.height = this.mHeight + 'px';

  // create a div for the drop shadow
  var shadowDiv = document.createElement( 'div' );
  shadowDiv.className = 'mapShadow';
  shadowDiv.style.width = this.mWidth + 'px';
  shadowDiv.style.height = this.mHeight + 'px';

  pMap.getPane( G_MAP_MARKER_PANE ).appendChild( div );

  this.mMap = pMap;
  this.mDiv = div;
  this.mShadow = shadowDiv;
}

/**
 * Removes the overlay from the map.
 **/
LabelOverlay.prototype.remove = function ( ) {
  this.mDiv.parentNode.removeChild( this.mDiv );
}

/**
 * Copies the label to a new label
 **/
LabelOverlay.prototype.copy = function ( ) {
  return new LabelOverlay( this.mCenter, this.mLabel, this.mColor );
}

/**
 * Redraws the label.
 *
 * @param pForce boolean - whether or not to force redrawing
 **/
LabelOverlay.prototype.redraw = function ( pForce ) {

  // We only need to redraw if the coordinate system has changed
  if( !pForce ) {
    return;
  }

  this.mDiv.style.height = this.mHeight + 'px';
  this.mShadow.style.height = this.mHeight + 'px';

  // set the text and color
  this.mDiv.innerHTML = '<span style="font-size: ' + this.mFontSize + 'px;">' + this.mLabel + '</span>';
  this.mShadow.innerHTML = '<span style="font-size: ' + this.mFontSize + 'px;">' + this.mLabel + '</span>';
  this.mDiv.appendChild( this.mShadow );
  this.mDiv.style.color = this.mColor;

  // recalculate the center of the label
  var c1 = this.mMap.fromLatLngToDivPixel( this.mCenter );

  // position the div based on the calculated center
  this.mDiv.style.left = ( c1.x - this.mWidth / 2 ) + 'px';
  this.mDiv.style.top = ( c1.y - this.mHeight / 2 ) + 'px';
}

/**
 * Makes this label draggable.
 **/
LabelOverlay.prototype.makeDraggable = function ( ) {

  // create a new draggable object for the div
  this.mDragObject = new GDraggableObject( this.mDiv );
  this.mDragObject.mParent = this;

  // listen for the dragstart and set the left/top  
  GEvent.addListener( this.mDragObject, 'dragstart', function ( ) {
    this.mParent.left = this.left;
    this.mParent.top = this.top;
  });

  // listen for the dragend and redraw the label
  GEvent.addListener( this.mDragObject, 'dragend', function ( ) {
    var newpixels = new GPoint( this.left + this.mParent.mWidth / 2, this.top + this.mParent.mHeight / 2 );
    this.mParent.mCenter = this.mParent.mMap.fromDivPixelToLatLng( newpixels );
    this.mParent.redraw( true );
    GEvent.trigger( this.mParent, 'dragend', this.mParent.mCenter );
  });

}

/**
 * Changes the text label.
 *
 * @param pLabel string - the new label
 **/
LabelOverlay.prototype.setLabel = function ( pLabel ) {
  this.mLabel = pLabel;
  this.redraw( true );
}

/**
 * Changes the color of the label.
 *
 * @param pColor string - the new color hex code (include the '#')
 **/
LabelOverlay.prototype.setColor = function ( pColor ) {
  this.mColor = pColor;
  this.redraw( true );
}

/**
 * Changes the color of the label.
 *
 * @param pColor string - the new color hex code (include the '#')
 **/
LabelOverlay.prototype.setFontSize = function ( pFontSize ) {
  this.mFontSize = parseInt( pFontSize );
  this.mHeight = this.mFontSize + 4;
  this.redraw( true );
}


/**
 * EuclideanProjection
 *
 * This class does tranformation between latitude/longitude coordinates to a
 * flat x/y coordinate system.  It contains a set of discreet flat image height
 * and width values for each zoom level to determine the pixel locations of a
 * given coordinate.
 *
 *
 *
 * Creates a new EuclideanProjection with width and height values matching the
 * LOTRO game map image set.
 *
 * @param pMaxZoom int - the maximum zoom level
 **/
function EuclideanProjection ( a ) {

  // remember the max zoom
  this.maxZoom = a;

  // the list of zoom values we've calculated data for
  this.calculatedZooms = [];

  // arrays of overall map widths and heights for the LOTRO world map
  this.mapWidths = [444,888,1776,3552,7104,14208,28416,56832];
  this.mapHeights = [240,480,960,1920,3840,7680,15360,30720];

  // arrays containing the amount of shift from center of the visible image from
  // the possible full 256x256 landblock images
  this.mapShiftsX = [-23,-46,-92,-184,-368,-736,-1472,-2944];
  this.mapShiftsY = [2,3,7,13,26,53,106,211];

  // these are the sizes of the images at different zooms that would encompass
  // the entire 256 landblock grid
  this.mapExtents = [512,1024,2048,4096,8192,16384,32768,65536];

  // arrays containing the number of tiles in the x and y direction for each
  // zoom level as GPoint objects
  this.tileBounds = [];

  // array containing points that hold the x/y padding on the edge of the
  // visible image for each zoom level
  this.padding = [];

}

// Attach it to the GProjection() class
EuclideanProjection.prototype=new GProjection();

/**
 * Gets the tile bounds for a given zoom level.
 *
 * @param zoom int - the zoom level
 *
 * @return GPoint - a point with the x and y tile bounds
 **/
EuclideanProjection.prototype.getTileBounds = function(zoom) {
  // if it's not set for this zoom, calculate it
  if( !this.tileBounds[zoom] ) {
    var boundX = Math.ceil(this.getMapWidth(zoom)/256);
    var boundY = Math.ceil(this.getMapHeight(zoom)/256);
    this.tileBounds[zoom] = new GPoint( boundX, boundY );
  }
  return this.tileBounds[zoom];
};

/**
 * Gets the map padding for the given zoom level.
 *
 * @param zoom int - the zoom level
 *
 * @return GPoint - a point with the x and y padding
 **/
EuclideanProjection.prototype.getPadding = function(zoom) {
  // if it's not set for this zoom, calculate it
  if( !this.padding[zoom] ) {
    var padX = (Math.ceil(this.mapWidths[zoom] / 256)*256 - this.mapWidths[zoom]) / 2;
    var padY = (Math.ceil(this.mapHeights[zoom] / 256)*256 - this.mapHeights[zoom]) / 2;
    this.padding[zoom] = new GPoint( padX, padY );
  }
  return this.padding[zoom];
};

/**
 * Gets the map height for the given zoom level.
 *
 * @param zoom int - the zoom level
 *
 * @return int - the map height
 **/
EuclideanProjection.prototype.getMapHeight = function(zoom) {
  // if it's not set for this zoom, extrapolate it from the known values
  if( !this.mapHeights[zoom] ) {
    var mapHeightStep = ( this.mapHeights[this.maxZoom-1] - this.mapHeights[0] ) / ( this.maxZoom - 1 );
    var exHeight = this.mapHeights[this.maxZoom-1] + ( mapHeightStep * ( zoom - this.maxZoom ) );
    this.mapHeights[zoom] = exHeight;
  }
  return this.mapHeights[zoom];
};

/**
 * Gets the map width for the given zoom level.
 *
 * @param zoom int - the zoom level
 *
 * @return int - the map width
 **/
EuclideanProjection.prototype.getMapWidth = function(zoom) {
  // if it's not set for this zoom, extrapolate it from the known values
  if( !this.mapWidths[zoom] ) {
    var mapWidthStep = ( this.mapWidths[this.maxZoom-1] - this.mapWidths[0] ) / ( this.maxZoom - 1 );
    var exWidth = this.mapWidths[this.maxZoom-1] + ( mapWidthStep * ( zoom - this.maxZoom ) );
    this.mapWidths[zoom] = exWidth;
  }
  return this.mapWidths[zoom];
};

/**
 * Gets the map extent for the given zoom level.
 *
 * @param zoom int - the zoom level
 *
 * @return int - the map extent
 **/
EuclideanProjection.prototype.getMapExtent = function(zoom) {
  // if it's not set for this zoom, extrapolate it from the known values
  if( !this.mapExtents[zoom] ) {
    var maxExtentStep = ( this.mapExtents[this.maxZoom-1] - this.mapExtents[0] ) / ( this.maxZoom - 1 );
    var exExtent = this.mapExtents[this.maxZoom-1] + ( maxExtentStep * ( zoom - this.maxZoom ) );
    this.mapExtents[zoom] = exExtent;
  }
  return this.mapExtents[zoom];
};

/**
 * Gets the map padding for the given zoom level.
 *
 * @param zoom int - the zoom level
 *
 * @return int - the map padding
 **/
EuclideanProjection.prototype.getPadding = function(zoom) {
  // if it's not set for this zoom, extrapolate it from the known values
  if( !this.padding[zoom] ) {
    var padX = (Math.ceil(this.getMapWidth(zoom) / 256)*256 - this.getMapWidth(zoom)) / 2;
    var padY = (Math.ceil(this.getMapHeight(zoom) / 256)*256 - this.getMapHeight(zoom)) / 2;
    this.padding[zoom] = new GPoint( padX, padY );
  }
  return this.padding[zoom];
};

/**
 * Gets the map shift for the given zoom level.
 *
 * @param zoom int - the zoom level
 *
 * @return Point - the map shift
 **/
EuclideanProjection.prototype.getShift = function(zoom) {
  // if it's not set for this zoom, extrapolate it from the known values
  if( !this.mapShiftsX[zoom] || !this.mapShiftsY[zoom] ) {
    var shiftXStep = ( this.mapShiftsX[this.maxZoom-1] - this.mapShiftsX[0] ) / ( this.maxZoom - 1 );
    var shiftYStep = ( this.mapShiftsY[this.maxZoom-1] - this.mapShiftsY[0] ) / ( this.maxZoom - 1 );

    var exShiftX = this.mapShiftsX[this.maxZoom-1] + ( shiftXStep * ( zoom - this.maxZoom ) );
    var exShiftY = this.mapShiftsY[this.maxZoom-1] + ( shiftYStep * ( zoom - this.maxZoom ) );

    this.mapShiftsX[zoom] = exShiftX;
    this.mapShiftsY[zoom] = exShiftY;
  }
  return new GPoint( this.mapShiftsX[zoom], this.mapShiftsY[zoom] );
};

/**
 * Converts latitude/longitude coordinates to pixel x/y coordinates within the
 * given tileset.
 *
 * @param pCoord GLatLng - the latitude/longitude object
 * @param pZoom int - the zoom level to get the pixel coordinate for
 *
 * @return GPoint - a point with the pixel x and y coordinate
 **/
EuclideanProjection.prototype.fromLatLngToPixel=function(a,b){
  var c = Math.round(this.getMapExtent(b)/2 + a.lng()*this.getMapExtent(b)/360);
  var d = Math.round(this.getMapExtent(b)/2 + (-2 * a.lat()) * this.getMapExtent(b) / 360);
  c = c - (this.getMapExtent(b) - this.getMapWidth(b))/2;
  d = d - (this.getMapExtent(b) - this.getMapHeight(b))/2;

  c += this.getPadding(b).x;
  d += this.getPadding(b).y;

  c += this.getShift(b).x;
  d += this.getShift(b).y;

  return new GPoint(c,d);
};

/**
 * Converts a pixel x/y coordinate within a tileset to a latitude/longitude
 * value.
 *
 * @param pCoord GPoint - the x/y point to get lat/lng for
 * @param pZoom int - the zoom level
 * @param pUnbounded boolean - whether or not to wrap coordinates at the edge
 *   of the map (i.e. 181 degrees becomes -179 degrees)
 **/
EuclideanProjection.prototype.fromPixelToLatLng=function(a,b,c){
  a.x -= this.getShift(b).x;
  a.y -= this.getShift(b).y;

  a.x -= this.getPadding(b).x;
  a.y -= this.getPadding(b).y;

  a.x += (this.getMapExtent(b) - this.getMapWidth(b))/2;
  a.y += (this.getMapExtent(b) - this.getMapHeight(b))/2;

  var d = (a.x - this.getMapExtent(b)/2) * 360 / this.getMapExtent(b);
  var e = (a.y - this.getMapExtent(b)/2) * 360 / this.getMapExtent(b) / -2;
  return new GLatLng(e,d,c);
};

/**
 * Checks to see if a given tile coordinate is within the range of the map.
 * NOTE: The coordinate passed in is not a pixel coordinate.  Rather, it is
 * the coordinate of the tile itself.
 *
 * This is used by Google Maps to determine whether or not to display a tile.
 * If true is returned, the tile is displayed.  If false is returned, the tile is
 * not displayed.  This is used to control wrapping of the tile images.
 *
 * @param pCoord GPoint - the x/y tile coordinate to check
 * @param pZoom int - the zoom level to check
 * @param pTileSize int - the size of a tile
 *
 * @return boolean - true if the tile is in range, false otherwise
 **/
EuclideanProjection.prototype.tileCheckRange=function(a,b,c){
  if (a.y<0||a.y>=this.getTileBounds(b).y) {
    return false;
  }
  if(a.x<0||a.x>=this.getTileBounds(b).x){
    return false;
  }
  return true
}

/**
 * This function gets the pixel width of the map.  This is used to control
 * wrapping of markers on the map.
 *
 * @param pZoom int - the zoom level
 *
 * @return int - the wrap width of the map
 **/
EuclideanProjection.prototype.getWrapWidth=function(zoom) {
  return this.getTileBounds(zoom).x*256*8;
}


// Create a new tile layer with the Turbine copyright
var tilelayers1 = [new GTileLayer( new GCopyrightCollection( "" ), 0, 7 )];
tilelayers1[0].getCopyright = function ( a, b ) {
  return { prefix:"", copyrightTexts:[""] };
};

// override the getTileUrl function to return the proper tile image based on the
// coordinate
tilelayers1[0].getTileUrl = function ( a, b ) {
  return GOOGLE_MAP_CONTENT_URL + "lotro/tiles/"+b+"/me_"+b+"_"+a.x+"x"+a.y+".png";
};

// override the isPng function to alway return true (all of our tiles are pngs)
tilelayers1[0].isPng = function ( ) {
  return true;
};

// create a new GMapType with the tile layer and EuclideanProjection
var G_GAME_MAP = new GMapType( tilelayers1, new EuclideanProjection( 8 ), "Game", { errorMessage:"No Data Available", textColor:"white" } );

// a generic function for cancelling the propogation of the wheel event
function wheelevent(e) { 
  if (!e) e = window.event; 
  if (e.preventDefault) e.preventDefault(); 
  e.returnValue = false; 
} 
