function InfoBox(map) {
  // Now initialize all properties.
  this.map_ = map;

  // We define a property to hold the image's div. We'll actually create this div
  // upon receipt of the add() method so we'll leave it null for now.
  this.div_ = null;
  this.anchor_ = null;
  this.content_ = null;

  // Explicitly call setMap() on this overlay
  this.setMap(map);
}

InfoBox.prototype = new google.maps.OverlayView();

InfoBox.prototype.onAdd = function() {
	// Create the DIV and set some basic attributes.
	var div = $('<div>').addClass('gmap-info-box')[0];

	var content = $('<div>').addClass('gmap-info-box-content')[0];
	div.appendChild(content);
	
	var arrow = $("<span>").addClass('gmap-info-box-arrow')[0];
	div.appendChild(arrow);
	
	// Set the overlay's div_ property to this DIV
	this.div_ = div;
	this.setContent(this.content_);
	
	// We add an overlay to a map via one of the map's panes.
	// We'll add this overlay to the overlayImage pane.
	var panes = this.getPanes();
	panes.floatPane.appendChild(this.div_);
	this.hide();
	if (this.anchor_) {
		var pixPosition = this.getProjection().fromLatLngToDivPixel(this.anchor_.getPosition());
		$(this.div_)
			.css({
				top:pixPosition.y,
				left:pixPosition.x
			});
		this.panToShow();
	}
	
	// Cancel event propagation. So you can click inside the InfoBox without events bubbling up to the map
	var cancelHandler = function (e) {
		e.cancelBubble = true;
		e.stopPropagation();
	};
	var ignoreHandler = function (e) {
		e.returnValue = false;
		e.preventDefault();
		e.cancelBubble = true;
		e.stopPropagation();
	};
	google.maps.event.addDomListener(this.div_, "mousedown", cancelHandler);
	google.maps.event.addDomListener(this.div_, "click", cancelHandler);
	google.maps.event.addDomListener(this.div_, "dblclick", cancelHandler);
	google.maps.event.addDomListener(this.div_, "contextmenu", ignoreHandler);
}

InfoBox.prototype.draw = function() {
	if (this.anchor_) {
		var pixPosition = this.getProjection().fromLatLngToDivPixel(this.anchor_.getPosition());
		$(this.div_)
			.css({
				top: pixPosition.y,
				left: pixPosition.x
			});
		this.setContent(this.content_);
		this.show();
	}
}

InfoBox.prototype.onRemove = function() {
  this.div_.parentNode.removeChild(this.div_);
  this.div_ = null;
}

/**
 * Adds the InfoBox to the specified map. If <tt>anchor</tt>
 *  (usually a <tt>google.maps.Marker</tt>) is specified, the position
 *  of the InfoBox is set to the position of the <tt>anchor</tt>.
 * @param {MVCObject} [anchor]
 */
InfoBox.prototype.open = function (map, anchor) {
	if (anchor) {
		this.anchor_ = anchor;
	}
	this.setMap(map);
	if (this.div_) {
		this.panToShow();
	}
};

InfoBox.prototype.close = function () {
	if (this.getMap()) {
		this.setMap(null);
	}
};

// Note that the visibility property must be a string enclosed in quotes
InfoBox.prototype.hide = function() {
  if (this.div_) {
    this.div_.style.visibility = "hidden";
  }
}

InfoBox.prototype.show = function() {
  if (this.div_) {
    this.div_.style.visibility = "visible";
  }
}

/**
 * Sets the content of the InfoBox.
 *  The content can be plain text or HTML.
 * @param {string} content
 */
InfoBox.prototype.setContent = function (content) {
	this.content_ = content;
	if (this.div_) {
		$(this.div_).find(' > .gmap-info-box-content:first').html(this.content_);
	}
};

InfoBox.prototype.panToShow = function(){
	if(!this.getMap()) return;
	
	var $infoBoxContent = $(this.div_).find('.gmap-info-box-content');
    var map = this.getMap();
    var bounds = map.getBounds();

    // The degrees per pixel
    var mapDiv = map.getDiv();
    var mapWidth = mapDiv.offsetWidth;
    var mapHeight = mapDiv.offsetHeight;
    var boundsSpan = bounds.toSpan();
    var longSpan = boundsSpan.lng();
    var latSpan = boundsSpan.lat();
    var degPixelX = longSpan / mapWidth;
    var degPixelY = latSpan / mapHeight;

    // The bounds of the map
    var mapWestLng = bounds.getSouthWest().lng();
    var mapEastLng = bounds.getNorthEast().lng();
    var mapNorthLat = bounds.getNorthEast().lat();
    var mapSouthLat = bounds.getSouthWest().lat();

    // The bounds of the box
    var position = this.anchor_.getPosition();
    var iwOffsetX = - 15;
    var iwOffsetY = - $infoBoxContent.outerHeight() - 30;
    var padX = 0;
    var padY = 25;
    var iwWestLng = position.lng() + (iwOffsetX - padX) * degPixelX;
    var iwEastLng = position.lng() + (iwOffsetX + $infoBoxContent.outerWidth() + padX) * degPixelX;
    var iwNorthLat = position.lat() - (iwOffsetY - padY) * degPixelY;
    var iwSouthLat = position.lat() - (iwOffsetY + $infoBoxContent.outerHeight() + padY) * degPixelY;

    // Calculate center shift
    var shiftLng =
      (iwWestLng < mapWestLng ? mapWestLng - iwWestLng : 0) +
      (iwEastLng > mapEastLng ? mapEastLng - iwEastLng : 0);
    var shiftLat =
      (iwNorthLat > mapNorthLat ? mapNorthLat - iwNorthLat : 0) +
      (iwSouthLat < mapSouthLat ? mapSouthLat - iwSouthLat : 0);

    if (!(shiftLat === 0 && shiftLng === 0)) {
      // Move the map to the new shifted center.
      var c = map.getCenter();
      map.setCenter(new google.maps.LatLng(c.lat() - shiftLat, c.lng() - shiftLng));
    }
}

