/**
 * Object to contain the google map stuff
 */
function GoogleMap(container) {
	var me = this;
	/**
	 * Object to hold the bounds for NZ, as well as the offset for the viewport
	 */
	this.nz = {
		// bounds found by picking points on Google Maps, not 100% accurate! (ie. is larger than necessary)
		n : -34.368378,
		s : -47.338823,
		e : 178.591690,
		w : 166.245117,
		bounds : null,
		zoom : 4,
		center : null,
		wellington : {
			lat : -41.288889,
			lng : 174.777222
		}
	};

	this.element = container;

	this.map = null;
	/** can be overridden if a different marker manager is needed, or different settings */
	this.markermanager = null;

	// Call this function when the page has been loaded
	this.init = function(hideControls) {
		var element = document.getElementById(this.element);
		if (GBrowserIsCompatible() && element) {

			var mapTypes = G_DEFAULT_MAP_TYPES;
			for (var i=0; i<mapTypes.length; i++) { // restrict minimum zoom
				mapTypes[i].getMinimumResolution = function(latLng){return me.nz.zoom;};
			}

			this.map = new GMap2(element, {mapTypes: mapTypes});

			this.nz.bounds = new GLatLngBounds(new GLatLng(this.nz.s, this.nz.w), new GLatLng(this.nz.n, this.nz.e));
			this.nz.center = new GLatLng(this.nz.wellington.lat, this.nz.wellington.lng);

			this.map.setCenter(this.nz.center, this.nz.zoom);
			if (typeof MarkerManager != 'undefined') {
				this.markermanager = new MarkerManager(this.map);
			} else {
				this.markermanager = new SimpleMarkerManager(this.map);
			}

			this.map.enableScrollWheelZoom();
			this.map.enableContinuousZoom();
			var ui = this.map.getDefaultUI();
			if (hideControls) {
				ui.controls.maptypecontrol = false;
				ui.controls.menumaptypecontrol = false;
			}
			this.map.setUI(ui);
			//googlemap.map.removeMapType(G_PHYSICAL_MAP);

			this.nz.zoom = this.map.getBoundsZoomLevel(this.nz.bounds);
			this.map.setZoom(this.nz.zoom);

			// Add a move listener to restrict the bounds range
			GEvent.addListener(this.map, "move", this._checkBounds);

			GEvent.addListener(window, "unload", GUnload);
		}
	};

	// use this incase the marker manager needs to be changed
	this.setMarkerManager = function(markermanager) {
		this.markermanager = markermanager;
	};

	this.addMarkers = function(markers, minZoom, maxZoom) {
		this.markermanager.addMarkers(markers, minZoom, maxZoom);
		this.markermanager.refresh();
	};
	this.addMarker = function(marker, minZoom, maxZoom) {
		this.markermanager.addMarker(marker, minZoom, maxZoom);
	};
	this.removeMarker = function(marker) {
		if (marker && this.markermanager.removeMarker) {
			this.markermanager.removeMarker(marker);
		}
	};

	this.setCenter = function(point, zoom) {
		if (!this.nz.bounds.containsLatLng(point)) { // reset the location to the middle of NZ
			point = this.nz.center;
			zoom = this.nz.zoom;
		}
		if (zoom == undefined || zoom == null) {
			zoom = Math.min(19, this.maxZoom());
		}
		if (zoom != this.map.getZoom()) {
			this.map.setCenter(point, zoom);
		} else { // smooth scroll if point is in map
			this.map.panTo(point);
		}
	};

	this.normaliseLatLng = function(latLng) {
		var lng = latLng.lng();
		if (lng < 0) {
			lng = 360 + lng;
			latLng = new GLatLng(latLng.lat(), lng, true);
		}
		return latLng;
	}

	this.maxZoom = function() {
		return this.map.getCurrentMapType().getMaximumResolution()+1;
	};

	/**
	 * Restrict the amount of movement the map can make.
	 * Limit so that NZ can't be outside the view port.
	 */
	this._checkBounds = function(e) {
		var center = this.getCenter();
		if (!me.nz.bounds.containsLatLng(center)) {
			var lat = center.lat();
			var lng = center.lng();

			if (lat < me.nz.s) {
				lat = me.nz.s;
			} else if (lat > me.nz.n) {
				lat = me.nz.n;
			}
			if (lng < me.nz.w) {
				lng = me.nz.w;
			} else if (lng > me.nz.e) {
				lng = me.nz.e;
			}
			me.setCenter(new GLatLng(lat, lng), this.getZoom());
		}
	}
}

/**
 * According to Google, 6 digits is an accuracy of about 11cm/4in,
 * so 8 digits will be more than enough! (Wikipedia: 1.11mm)
 */
function roundNumber8(num) {
	var pow = Math.pow(10,8);
	return Math.round(num*pow)/pow;
}

/**
 * A simple marker manager to manage just one marker.
 * Doesn't do bounds checking, but does check zoom.
 * Main purpose of this is to prevent changes to the marker when zooming.
 * Current implementations of MarkerManager (v1.1) remove then readd the marker!
 */
function SimpleMarkerManager(map) {
	var me = this;

	this.name = 'SimpleMarkerManager';

	this._map = map;
	this._marker = null;
	this._infoShown = false;
	this._minZoom = 0;
	this._maxZoom = 20;
	this._shown = false;
	this.addMarker = function(marker, minZoom, maxZoom) {
		if (this._marker) {
			this.removeMarker();
		}
		this._marker = marker;
		this._minZoom = minZoom;
		if (maxZoom) {
			this._maxZoom = maxZoom;
		}
		this._checkZoom();
		GEvent.addListener(this._map, 'zoomend', this.refresh);
	};
	this.addMarkers = function(markers, minZoom, maxZoom) {
		this.addMarker(markers[0], minZoom, maxZoom);
	};
	this.removeMarker = function() {
		this._hideMarker();
	};
	this.getMarker = function(lat, lng, zoom) {
		if (this._marker.getLatLng().equals(new GLatLng(lat, lng)) && this._minZoom<=zoom && zoom<=this._maxZoom) {
			return this._marker;
		}
		return null;
	}
	this.refresh = function() {
		me._checkZoom();
	};
	this.getMarkerCount = function(zoom) {
		return (zoom<=this._maxZoom && zoom>=this._minZoom && this._marker && this._shown) ? 1 : 0;
	};
	this._checkZoom = function() {
		var zoom = this._map.getZoom();
		if (zoom>this._maxZoom || zoom<this._minZoom) {
			this.removeMarker();
		} else if (zoom<=this._maxZoom && zoom>=this._minZoom) {
			this._showMarker();
		}
	};
	this._showMarker = function() {
		if (!this._shown) {
			this._map.addOverlay(this._marker);
			this._shown = true;
			if (this._infoShown && this._marker.showInfoWin) { // shouldn't be here! Needed to get the info window showing correctly
				this._marker.showInfoWin();
			}
		}
	};
	this._hideMarker = function() {
		if (this._shown) {
			this._infoShown = !this._map.getInfoWindow().isHidden();
			this._map.removeOverlay(this._marker);
			this._map.closeInfoWindow();
			this._shown = false;
		}
	};
}