function MobyInfoWindow(options) {
  google.maps.OverlayView.call(this);
  this.offsetVertical_ = -195;
  this.offsetHorizontal_ = 2;
  this.width_ = 500;

  // Once the properties of this OverlayView are initialized, set its map so
  // that we can display it.  This will trigger calls to panes_changed and
  // draw.
  this.setMap(this.map_);
  
  // MVCObject's set methods give errors if not called after setMap here
  this.setOptions(options);
}

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

MobyInfoWindow.prototype.open = function(map, anchor) {
  this.set_location(anchor.position);
  this.latlng_ = this.get_location();
  this.map_ = map;
  
  var self = this;
  
  this.boundsChangedListener_ =
    google.maps.event.addListener(this.map_, "bounds_changed", function() {
      return self.panMap.apply(self);
    });

  this.setMap(this.map_);
};

MobyInfoWindow.prototype.close = function() { this.setMap(null); };
MobyInfoWindow.prototype.setOptions = function(options) { this.setValues(options);};

MobyInfoWindow.prototype.set_content = function(content) { this.set("content", content);};
MobyInfoWindow.prototype.get_content = function() { return this.get("content"); };

MobyInfoWindow.prototype.set_location = function(location) {this.set("location", location);};
MobyInfoWindow.prototype.get_location = function() {return this.get("location");};

MobyInfoWindow.prototype.set_zIndex = function(zIndex) {this.set("zIndex", zIndex);};
MobyInfoWindow.prototype.get_zIndex = function() { return this.get("zIndex");};

MobyInfoWindow.prototype.remove = function() {
	if (this.div_) {
		this.div_.parentNode.removeChild(this.div_);
		this.div_ = null;
	}
};

MobyInfoWindow.prototype.draw = function() {
	// Creates the element if it doesn't exist already.
	this.createElement();
	if (!this.div_) return;
	
	// Calculate the DIV coordinates of two opposite corners of our bounds to
	// get the size and position of our MobyInfoWindow
	var pixPosition = this.getProjection().fromLatLngToDivPixel(this.latlng_);
	if (!pixPosition) return;
	
	// Now position our DIV based on the DIV coordinates of our bounds
	this.div_.style.width = this.width_ + "px";
	this.div_.style.left = (pixPosition.x + this.offsetHorizontal_) + "px";
	//this.div_.style.height = this.height_ + "px";
	this.div_.style.top = (pixPosition.y + this.offsetVertical_) + "px";
	this.div_.style.display = 'block';
};

MobyInfoWindow.prototype.createElement = function() {
	var panes = this.getPanes();
	var div = this.div_;
	if (!div) {
		// This does not handle changing panes.  You can set the map to be null and
		// then reset the map to move the div.
		div = this.div_ = document.createElement("div");
		div.style.border = "0px none";
		div.style.position = "absolute";
		div.style.background = "url('http://mobypicture.s3.amazonaws.com/layout/v1/marks_the_spot.png') no-repeat 1px 185px";
		div.style.width = this.width_ + "px";
		div.style.height =  "400px";
		try {
			div.style.zIndex = this.get_zIndex();
		} catch (err) { div.style.zIndex = 9000; }
		
		var innerDiv = document.createElement("div");
		innerDiv.style.border = "0px none";
		innerDiv.style.position = "absolute";
		innerDiv.style.margin = "0px 0px 0px 11px";
		innerDiv.style.background = "url('http://mobypicture.s3.amazonaws.com/layout/v1/almost_grey.png')";
		innerDiv.style.width = (this.width_ - 12) + "px";
		// div.style.height = this.height_ + "px";
		
		var contentDiv = document.createElement("div");
		contentDiv.id = "MobyInfoWindow_content"
		contentDiv.style.padding = "4px"
		contentDiv.innerHTML = this.get_content();
		
		var topDiv = document.createElement("div");
		$(topDiv).css({
			'float': 'right',
			width: 32,
			height: 32,
			padding: 0,
			margin: 0
		});
		var closeImg = document.createElement("img");
		closeImg.style.width = "32px";
		closeImg.style.height = "32px";
		closeImg.style.cursor = "pointer";
		closeImg.src = "http://gmaps-samples.googlecode.com/svn/trunk/images/closebigger.gif";
		topDiv.appendChild(closeImg);
		
		google.maps.event.addDomListener(closeImg, 'click', this.removeMobyInfoWindow(this));
		
		innerDiv.appendChild(topDiv);
		innerDiv.appendChild(contentDiv);
		div.appendChild(innerDiv);
		div.style.display = 'none';
		panes.floatPane.appendChild(div);
		this.panMap();
	} else if (div.parentNode != panes.floatPane) {
		// The panes have changed.  Move the div.
		div.parentNode.removeChild(div);
		panes.floatPane.appendChild(div);
		// Update the div's content
		document.getElementById("MobyInfoWindow_content").innerHTML = this.get_content();
	} else {
		// The panes have not changed, so no need to create or move the div.
		// Update the div's content
		document.getElementById("MobyInfoWindow_content").innerHTML = this.get_content();
	}
};

MobyInfoWindow.prototype.removeMobyInfoWindow = function(ib) {
	return function() {
		ib.setMap(null);
	};
};

/* Pan the map to fit the MobyInfoWindow.
 */
MobyInfoWindow.prototype.panMap = function() {
	// if we go beyond map, pan map
	var map = this.map_;
	var bounds = map.getBounds();
	if (!bounds) return;
	
	// The position of the infowindow
	var position = this.latlng_;
	
	// The dimension of the infowindow
	var iwWidth = $(this.div_).width();
	var iwHeight = $(this.div_).height();
	
	// The offset position of the infowindow
	var iwOffsetX = this.offsetHorizontal_;
	var iwOffsetY = this.offsetVertical_;
	
	// Padding on the infowindow
	var padX = 300;
	var padY = 30;
	
	// 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 infowindow
	var iwWestLng = position.lng() + (iwOffsetX - padX) * degPixelX;
	var iwEastLng = position.lng() + (iwOffsetX + iwWidth + padX) * degPixelX;
	var iwNorthLat = position.lat() - (iwOffsetY - padY) * degPixelY;
	var iwSouthLat = position.lat() - (iwOffsetY + iwHeight + 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);
	
	// The center of the map
	var center = map.getCenter();
	
	// The new map center
	var centerX = center.lng() - shiftLng;
	var centerY = center.lat() - shiftLat;
	
	// center the map to the new shifted center
	map.setCenter(new google.maps.LatLng(centerY, centerX));
	
	// Remove the listener after panning is complete.
	try {
		google.maps.event.removeListener(this.boundsChangedListener_);
	} catch (err) { /* */ }
	this.boundsChangedListener_ = null;
};
