/*
	Google Map Magnifier control from GoogleMappers.com
	Richard Kagerer original source of the component.
	http://boxme.net/wdch/test.php
*/

function GxMagnifierNamespace() {

	/*************************************************************************************************
	* GxMagnifier class
	*************************************************************************************************/

	/*************************************************************************************************
	* Browser check
	*************************************************************************************************/

	// Browser-check (currently based on http://www.hypergurl.com/attack.html but I'd like to pull
	// this straight from the GMap library).
	var n4=(document.layers);
	var n6=(document.getElementById&&!document.all);
	var ie=(document.all);
	var o6=(navigator.appName.indexOf("Opera") != -1);

	// Constructor
	// - GMap underlyingMap = a reference to the main map
	// - bool makeCopy? can be false to cause a new map to be created
	// - HTMLElement customContainer? can be specified as a target for magnified map
	function GxMagnifierb(underlyingMap, useBlank, customContainer) { // [PUBLIC]

		// Set up default state
		if (useBlank == undefined || useBlank == null) useBlank = false;
		this.p_initDefaults(useBlank, customContainer);

		// Store reference to the underlying map and its container
		this.um = underlyingMap;
		//this.umc = this.um.container;
		this.umc = this.um;
		
		// Set up reference to main map (k currently)
		

		// Create/set up div for the map
		if (customContainer) {
			this.container = customContainer;
			this.p_setupContainerMinimal(); // minimum configuration that must be done on container
		} else {
			this.p_createContainer(); // create new container inside underlying map's container
		}

		// Create and set up the magnified map
		this.p_createMagnifiedMap(useBlank);

		// Bind to events
		this.p_setupListeners();
		

		// Hide and place in main map bounds
		if (!this.parked) {
			this.hide();
			//centerElementOnP(this.container, new google.maps.Point(0,0));
		}
		centerElementOnP(this.container, new google.maps.Point(0,0));

	}

	/*************************************************************************************************
	* GxMagnifier initialization
	*************************************************************************************************/

	// Called by GxMagnifier constructor to set the GxMagnifier default configuration state
	GxMagnifierb.prototype.p_initDefaults = function(useBlank, customContainer) { // [PRIVATE]

		// ---- These hold the state properties that can be adjusted by user ---------------------------

		// How much to magnify by when autoMagnifyEnabled and mapSyncEnabled are true
		this.magnifyFactor = DEFAULT_MAGNIFY_FACTOR; // [PRIVATE]

		// Update the magnified map when the main map's zoom/position/type are changed
		this.mapSyncEnabled = true; // [PRIVATE]

		// Disable this to set the magnified map to a permenant zoom level.
		// NOTE: If you're setting a permenant zoom level that is zoomed out and your
		// magnified map is crowded with markers, you might want to uncouple the two maps
		// using disableMapSync instead of this setting,
		this.autoMagnifyEnabled = true;

		// Normal mode: Move the magnified map around when the user mouses over the main map
		// Parked mode: Update magnified map center when mouse is moved over main map
		this.mouseTrackingEnabled = true; // [PRIVATE]

		// Normal mode: Pans the main map when the magnifier is visible and is brought to an edge
		// Parked mode: Ignored
		this.autoPanEnabled = true;  // [PRIVATE]

		// Normal mode: Hide the magnified window if the user clicks anywhere in the main map
		// Parked mode: Ignored
		this.autoHideEnabled = true;  // [PRIVATE]

		// Normal mode: After the user clicks the magnified map, the main map is panned to
		// the point clicked and a brief moment is allowed to elapse before the map
		// is zoomed.  Ignored when zooming out (i.e. when this.magnifyFactor is negative).
		// Parked mode: Ignored
		this.panBeforeZoomInEnabled = false;  // [PRIVATE]

		// If prefetching is disabled, the underlying map is NOT updated if it is hidden
		// and zoom/move events occur on the main map (saves bandwidth, at the cost of
		// having to wait for tiles to dowsenload when the magnified map is shown).
		this.prefetchEnabled = false;  // [PRIVATE]

		// Future versions may do more things when the user clicks the magnified map.
		// This setting provides a way for the user to tell us they will do all the click
		// handling themselves.  Automatically true if a custom container is given.
		this.defaultClickHandlerEnabled = (customContainer) ? false : true; // [PRIVATE]

		// Overlay event capturing, to keep the map overlays in sync
		// Enabled by default if we're starting with a copy of the map
		this.overlaySyncEnabled = false;

		// The width of the magnified map's border
		this.borderWidth = 2;

		// ---- Below, non-user-adjustable properties are initialized ----------------------------------

		this.usingCustomContainer = (customContainer) ? true : false; // [PRIVATE]

		// If parked, we don't move or resize container (e.g. is in docked control mode)
		// Always true if customContainer specified
		this.parked = (customContainer) ? true : false; // [PRIVATE]

		// Initialize static objects
		this.mousePt = new google.maps.Point(); //current X,Y relative to page // [PUBLIC]
		this.mousePtUmc = new google.maps.Point(); // current X,Y relative to main map's container // [PUBLIC]

		// Generate a unique ID used for this GxMagnifier (used to cross-reference overlays
		// if overlaySyncEnabled is true)
		this.GxMagnifierId = Math.round(Math.random() * 1000);
		this.overlayAttributeTagOnOrg = 'GxMagnifier' + this.GxMagnifierId + '_MapOverlay';

		// ---- These lines can be obfuscated out; only here to keep track of object-level variables ---
		this.um = undefined;  // underlying map, set by constructor // [PUBLIC]
		this.umc = undefined; // underlying map container, set by constructor // [PUBLIC]
		this.container = undefined; // magnified map container, set by constructor  // [PUBLIC]
		this.omap = undefined; // the magnified map, set by constructor  // [PUBLIC]
		this.loadingMessageContainer = undefined; // [PRIVATE]
		this.mousePt = undefined; // mouse point on page, set in p_onDocMouseMove // [PRIVATE]
		this.mousePtUmc = undefined; // mouse point in main map container // [PRIVATE]
		this.mouseIsInUmc = undefined;  // [PRIVATE]
		this.mouseIsInContainer = undefined; // never set in unparked mode // [PRIVATE]
		this.mouseWasInContainer = undefined; // only set in parked mode [PRIVATE]


}

	// Creates a new container inside the underlying map's container to hold the
	// magnified map.  Requires this.um to be set.  Sets this.container.
	GxMagnifierb.prototype.p_createContainer = function() { // [PRIVATE]

		// Create div element for the magnified map.
		//this.container = this.um.ownerDocument.createElement('div');
		this.container = this.um.l.ownerDocument.createElement('div');

		// Apply minimum set of required properties
		this.p_setupContainerMinimal();

		// Apply other properties
		setCursorC(this.container, "crosshair");
		with (this.container.style) {
			// Keep off screen until we have some positioning information
			left = '-1000px'; top = '-1000px';
			//border = this.borderWidth + 'px solid black';
			// Enabling next line seems to cause setCenterLatLng() on the magnified map to
			// behave incorrectly.  That's why -1000 is used above.
			// display = 'none';
		}

		// Add it to the document
		this.umc.appendChild(this.container);
	}

	// Sets up a custom container (keep it to a minimum - we don't want to fool around with user's
	// container any more than we have to).
	// This is also called by gxmCreateContainer() to avoid code duplication.
	GxMagnifierb.prototype.p_setupContainerMinimal = function() {           // [PRIVATE]
		this.container.style.position = 'absolute'; // required for zIndexing
	}

	// Create the magnified GMap.  Sets this.omap.
	// - bool makeCopy? determines whether the underlying map should be copied, or whether a
	// new blank map (more API-safe) should be created.
	GxMagnifierb.prototype.p_createMagnifiedMap = function(useBlank) {  // [PRIVATE]

		// Set default size and position
		if (!this.parked) {
			this.setSize(new google.maps.Size(this.umc.offsetWidth / 3, this.umc.offsetHeight / 3));
		}
		
		

		// Create the map
		//this.omap = new GMap(this.container);
		this.omap = new GMap2(this.container);

		// Configure the map
		this.p_setupMagnifiedMap();

		// Create "Loading Tiles" message
		//this.p_createLoadingMessage();

		// Initially, make it match the current map's Lng/Lat and type, but at the magnified zoom level
		// WARNING: We MUST do these two lines before attempting to add overlays, or the attempt to
		// add them will cause an error.
		//this.p_syncMapTypes(true, true); // force the sync, ignore mapSyncEnabled
		//this.omap.centerAndZoom(this.um.getCenterLatLng(), this.p_getNewZoomLevel())
		var zl = this.p_getNewZoomLevel();
		var center = this.um.getCenter();
		var mt = this.um.getCurrentMapType()
		this.omap.setCenter(center, zl, mt);

		// Syncronize overlays
		if (this.overlaySyncEnabled) this.syncOverlays();
	}

	// Performs initial setup on the magnified map
	GxMagnifierb.prototype.p_setupMagnifiedMap = function(wasAPICopied) {  // [PRIVATE]

		// Hide the copyright tags in the magnifier (we can already see them on the main map, and they
		// really clutter things up on the magnified view).  Note this doesn't appear to be required if
		// the map was copied via the undocumented map.copy(), but no harm doing it anyway.
		this.container.firstChild.nextSibling.style.display = 'none';
		this.container.firstChild.nextSibling.nextSibling.style.display = 'none';

		// Features
		this.omap.disableDragging();
		this.omap.disableInfoWindow();
	}

	// Creates the "Loading tiles" div and appends to the magnified map's container.
	// Sets this.loadingMessageContainer.
	GxMagnifierb.prototype.p_createLoadingMessage = function(msg) { // [PRIVATE]

	}

	/*************************************************************************************************
	* GxMagnifier event handling
	*************************************************************************************************/

	GxMagnifierb.prototype.p_setupListeners = function() {  // [PRIVATE]

		// Bind map recenter/zoom/type change events.  These is not written inline as the
		// same functionality must be invoked from this.show() if prefetching is off.
		google.maps.Event.bind(this.um, "move", this,
			function() {
				//this.p_cancelDelayedZoom();
				if (this.mapSyncEnabled) this.p_refreshMapLatLng();

			});

		

		//google.maps.Event.bind(this.um, "maptypechanged", this, this.p_syncMapTypes);

		// When the underlying map is zoomed, the cursor is now over a different lat/lng point,
		// so the magnified map must be recentered in addition to being rezoomed.
		google.maps.Event.bind(this.um, "zoomend", this,
			function() {
				this.p_cancelDelayedZoom();
				this.p_refreshMapZoom();
				this.p_refreshMapLatLng();
			});

		// Listen to overlay events
		google.maps.Event.bind(this.um, "addoverlay", this,
			function(o) {
				if (this.overlaySyncEnabled) {
					// alert(this.container.offsetTop + " saw " + this.umc.id + " add overlay ") //for debug
					this.p_addSyncedOverlay(o);
				 }
			});

		google.maps.Event.bind(this.um, "removeoverlay", this,
			function(o) {
				this.p_removeSyncedOverlay(o);
			});

		google.maps.Event.bind(this.um, "clearoverlays", this,
			function() {
				if (this.overlaySyncEnabled) {
					this.omap.clearOverlays();
					// would be nice to go and clean up circular references, but Javascript GC should do that
				 }
			});

		// Capture page mouse events
		// Note: Does "document" have to be replaced with "window" for other browsers?
		// google.maps.Event.bindDom(this.omap.ownerDocument, "mousemove", this, this.p_onDocMouseMove); // NONAPI MED
		//google.maps.Event.bindDom(this.omap.k.ownerDocument, "mousemove", this, this.p_onDocMouseMove); // NONAPI MED
		//this.omap.j.ownerDocument <==> document.getElementById("map").ownerDocument;
		google.maps.Event.bindDom(this.container, "click", this, this.p_onContainerClick); // NONAPI MED
	}

	// Document mouse move callback
	// Sets this.mousePt, this.mousePtUmc, this.mouseIsInUmc.
	// Note we can't cache the size/position of the map/container, because they can be change without
	// us knowing it (especially in parked mode).
	GxMagnifierb.prototype.p_onDocMouseMove = function(event) {
		//if (!this.mouseTrackingEnabled) return;
		// Get mouse coordinates on page and convert to container
		this.mousePt = getMouseCoordinates(event);
		this.mousePtUmc = pageCoordToElement(this.mousePt, this.umc)
		this.mouseIsInUmc = isPtContained(this.mousePtUmc, this.umc);   // more efficient than hitTest

		if (!this.mouseTrackingEnabled) return;


		if (this.parked) {

			this.mouseIsInContainer = hitTest(this.mousePt, this.container);

			// Check if we need to fire a mouseover or mouseout event
			this.p_checkFireMouseEvent();

			// Update Lat/Lng center of magnified map (does rest of validation checks)
			this.p_refreshMapLatLng();

		} else {
			// Move the magnified map window to the correct spot.  It will take care of updating the map.
			this.p_refreshWindowCenter();

		}

	}

	// Should only call when in parked mode
	GxMagnifierb.prototype.p_checkFireMouseEvent = function() {
		if (!this.isVisible()) return;
		if (this.mouseWasInContainer != this.mouseIsInContainer) {
			google.maps.Event.trigger(this, this.mouseIsInContainer ? "mouseover" : "mouseout");
			this.mouseWasInContainer = this.mouseIsInContainer;
		}
	}

	// Magnified map click callback
	GxMagnifierb.prototype.p_onContainerClick = function(event) {

		// Activate listeners
		//GEvent.trigger(this, 'click', this.omap.getCenterLatLng());
		google.maps.Event.trigger(this, 'click', this.omap.getCenter());

		// Check for internal handling override
		if (!this.defaultClickHandlerEnabled) return;

		// If zooming in, check if we're supposed to do a pan and pause first.
		//if (this.panBeforeZoomInEnabled && this.omap.getZoomLevel() < this.um.getZoomLevel()) {
		if (this.panBeforeZoomInEnabled && this.omap.getZoom() < this.um.getZoom()) {
      // @todo andreh - port to new api here
			//this.um.recenterOrPanToLatLng(this.omap.getCenterLatLng());
			//this.um.panTo(this.omap.getCenter());
			this.p_queueDelayedZoom();
		} else {
			//this.um.centerAndZoom(this.omap.getCenterLatLng(), this.omap.getZoomLevel());
			this.um.setCenter(this.omap.getCenter(), this.omap.getZoom());

		}

		//Hide magnified map if appropriate
		if (this.autoHideEnabled) this.hide();
	}

	GxMagnifierb.prototype.p_queueDelayedZoom = function() {
		this.p_cancelDelayedZoom();
		GxMagnifierDelayedZoomMap = this.um;
		//var s = 'GxMagnifierDelayedZoom(' + this.umc.id + ', ' + this.omap.getZoomLevel() + ');';
		var s = 'GxMagnifierDelayedZoom(' + this.umc.id + ', ' + this.omap.getZoom() + ');';
		this.delayedZoom_tmr = setTimeout(s, 300, this.um);
	}

	GxMagnifierb.prototype.p_cancelDelayedZoom = function() {
		if (this.delayedZoom_tmr) clearTimeout(this.delayedZoom_tmr);
		GxMagnifierDelayedZoomMap = undefined;
	}

	// A utility for the user
	GxMagnifierb.prototype.createImage = function(imageSrc) {
		//return createImageC(imageSrc, this.container, this.omap.ownerDocument);
		return createImageC(imageSrc, this.container, this.omap.l.ownerDocument);
	}

	/*
	// Also just here as utility for user
	GxMagnifierb.prototype.centerElementOn = function(el, pt, borderAdjustment) {
		centerElementOnP(el, pt, borderAdjustment);
	}*/

	/*************************************************************************************************
	* GxMagnifier control
	*************************************************************************************************/

	GxMagnifierb.prototype.show = function() {
		if (this.isVisible()) return;
		google.maps.Event.trigger(this, "show");
		if (!this.prefetchEnabled) this.p_refreshMap(true);  // make sure map is in right place, force
		this.container.style.visibility = '';

	}

	GxMagnifierb.prototype.hide = function() {
		if (!this.isVisible()) return;
		google.maps.Event.trigger(this, "hide");
		// Note: we can't use display:none as FireFox then will report offsetWidth as 0
		this.container.style.visibility = 'hidden';
		this.p_stopAutoPan();
	}

	// Sets the size of the magnifier window
	GxMagnifierb.prototype.setSize = function(size) {
		this.container.style.width = getBoundedValue(size.width, 5, this.umc.offsetWidth) + 'px';
		this.container.style.height = getBoundedValue(size.height, 5, this.umc.offsetHeight) + 'px';

		// Tell the map its container has resized
		if (this.omap) {
			if (!this.parked) this.p_refreshWindowCenter();
		}
	}

	GxMagnifierb.prototype.showLoadingMessage = function(html) {
		this.loadingMessageContainer.style.display = '';
		if (html) this.loadingMessageContainer.innerHTML = html;
	}

	GxMagnifierb.prototype.hideLoadingMessage = function() {
		this.loadingMessageContainer.style.display = 'none';
	}

	// Ignored if using custom container
	GxMagnifierb.prototype.park = function(pt) {
		if (this.usingCustomContainer) return;

		// Center the window on the given point
		if (pt) centerElementOnP(this.container, pt, this.borderWidth);

		// Set up state variables required for being parked
		// If we weren't in parked mode before, then the mouseIsInContainer flag is not
		// up to date.  It must be updated now, or .show() will invoke a p_refreshMapLatLng()
		// with the old value.
		this.mouseIsInContainer = hitTest(this.mousePt, this.container);

		// Enter parked mode
		this.parked = true;

		// Update map to given point
		this.p_centerMapAtUmcCoord(pt);

	}

	// Park the magnifier at the given marker.  Note that it is NOT attached to the marker,
	// and will not move around as the map scrolls.  If you wanted to implement attaching,
	// a new state flag (e.g. attachedToLngLat).
	// Ignored if using custom container
	GxMagnifierb.prototype.parkAtMarker = function(m, offsetLeft, offsetTop) {
		if (this.usingCustomContainer) return;
		if (!offsetLeft) offsetLeft = 0;
		if (!offsetTop) offsetTop = 0;
		// Get anchor pixel of marker.  This is a total hack.  // NONAPI MED
		var markerCenterPx = new GPoint(
			m.iconImage.offsetLeft + m.icon.iconAnchor.x
			+ m.iconImage.offsetParent.offsetLeft
			+ offsetLeft,
			m.iconImage.offsetTop + m.icon.iconAnchor.y
			+ m.iconImage.offsetParent.offsetTop
			+ offsetTop);
		this.park(markerCenterPx);
	}

	GxMagnifierb.prototype.unpark = function(w) {
		if (this.usingCustomContainer) return;
		this.parked = false;
	}

	/*************************************************************************************************
	* GxMagnifier overlay synchronization
	*************************************************************************************************/

	// Synchronize overlays with those of the main map, by clearing existing ones
	// and rebuilding.  Not efficient.  Intended to be used after making a new map with
	// the makeCopy=false parameter.
	// This was written based on a similar function in the API internals
	GxMagnifierb.prototype.syncOverlays = function() {  // [PUBLIC]
		// Adapted from API internals
		this.omap.clearOverlays();
	}

	// Creates a circular reference situation, but Javascript GC should be
	// able to deal with that, even if p_removeSyncedOverlay doesn't get called
	// Returns the newly added overlay
	GxMagnifierb.prototype.p_addSyncedOverlay = function(orgOverlay) {
		// Make a copy
		var ourCopy = orgOverlay.copy(); // NONAPI MED
		// Set a new property on the original that points to our copy
		orgOverlay[this.overlayAttributeTagOnOrg] = ourCopy;
		// Add to our map
		this.omap.addOverlay(ourCopy);
		return ourCopy;
	}

	GxMagnifierb.prototype.p_removeSyncedOverlay = function(orgOverlay) {
		try {
			// Get our copy of it
			var attr = this.overlayAttributeTagOnOrg;
			var ourCopy = orgOverlay[attr];
			if (ourCopy) {
				// Break circular reference (do first in case next line fails)
				orgOverlay[attr] = undefined;
				// Remove from our map
				this.omap.removeOverlay(ourCopy);
			 }
		} catch (e) {
			// Ignore errors
			// e.g. if our map's clearOverlays was called then removeOverlay()
			// may fail.
		}
	}

	/*************************************************************************************************
	* GxMagnifier refreshing
	*************************************************************************************************/

	// Especially if using a custom container, the user may have decided to hide
	// it while we "weren't looking".  This function determines if we're visible
	// or not (although the user really should call our show() and hide()).
	GxMagnifierb.prototype.isVisible = function() {
		// If anyone has a better suggestion to this method, I'm all ears
		return (this.container.style.visibility.toLowerCase() != 'hidden')
	}

	// Perform a full refresh of the magnified map's center/zoom/type.
	// Mainly used in response to a show() command if prefetching was off,
	// to set the map to the correct place before it is shown, but also
	// can be called from refresh() or any event handlers that don't
	// care about really fast execution.
	GxMagnifierb.prototype.p_refreshMap = function(force) {
		if (!this.p_okayToRefreshMap(force)) return;
		//this.p_syncMapTypes(force);
		this.p_refreshMapZoom(force);
		this.p_refreshMapLatLng(force); // does its own checks
	}

	// If we're not visible and prefetch is off, then don't do any
	// refreshing on the map unless forced.  Never do any refreshing before
	// this.omap is set.
	GxMagnifierb.prototype.p_okayToRefreshMap = function(force) {

		// This validator is called first by all map refresh functions.
		// If we're not visible and prefetch is off, then don't do any
		// refreshing on the map (unless forced).  This helps save bandwidth.
		// Also, never do any refreshing before this.omap is set.
		// Also, don't refresh if the container is still sitting at the initial
		// -1000,-1000 position.
		// WARNING: This method is called from the GxMagnifier constructor,
		// (via p_RefreshMapZoom) when only minimal object level variables may be set up.

		return (this.isVisible() || this.prefetchEnabled || force) &&
			(this.omap) && (this.container.style.left != '-1000px');
	}

	// Set magnifier map to same type as main map
	// ignoreMapSyncSetting is just for use during startup
	GxMagnifierb.prototype.p_syncMapTypes = function(force, ignoreMapSyncSetting) {
		if (!this.p_okayToRefreshMap(force)) return;
		if (!this.mapSyncEnabled && !ignoreMapSyncSetting) return;
		var zl = this.um.getZoom();
		var center = this.um.getCenter();
		var mt = this.um.getCurrentMapType()
		this.omap.setCenter(center, zl, mt);		
	}

	// Set the magnifier zoom to the appropriate level
	GxMagnifierb.prototype.p_refreshMapZoom = function(force) {
		if (!this.p_okayToRefreshMap(force) || !this.mapSyncEnabled
			|| !this.autoMagnifyEnabled) return;
		//this.omap.zoomTo(this.p_getNewZoomLevel());
		this.omap.setZoom(this.p_getNewZoomLevel());
		this.p_refreshMapLatLng(true);
	}

	GxMagnifierb.prototype.p_getNewZoomLevel = function() {
		// Subtract magnify factor from present zoom level of main map
		// Limit to valid range, and update zoom level
		//var z = this.um.getZoomLevel() - this.magnifyFactor;
		//return getBoundedValue(z, 0, this.omap.spec.numZoomLevels); // NONAPI LOW
		
		// @todo - p_getNewZoomLevel
		// numZoomLevels -> getBoundsZoomLevel
		// http://www.mapki.com/wiki/APIv2#Using_map.getBoundsZoomLevel.28.29
		//var bounds = this.GxMagnifier.um.getBounds();
		//var zl = this.GxMagnifier.um.getBoundsZoomLevel(bounds);
		var magn =  this.magnifyFactor;
		var zoom =  this.um.getZoom();
		var z =  zoom + magn;
		var zl = 19;
		var ret = getBoundedValue(z, 0, zl);
		return ret; // NONAPI LOW
	}

	// Sets the center Lat/Lng of the magnified map to where its supposed to be
	// after either a) the cursor has moved, or b) the underlying map has moved,
	// resulting in the cursor hovering over a new Lat/Lng.
	// Since this function can be invoked from a handful of sources (including
	// this.p_onDocMouseMove, this.refresh, and the underlying map's zoom event)
	// we go through all applicable state validation checks.
	GxMagnifierb.prototype.p_refreshMapLatLng = function(force) {

		// In addition to usual validation, make sure mouse coordinates are
		// available and mouse tracking is turned on.
		if (!this.p_okayToRefreshMap(force)) return;

			// Set map to point under middle of magnifier window
			// Note that in non-parked mode the magnifier container is a child of
			// the map window, so only minimal coordinate conversion is neccessary.
			var pt = getElementCenter(document.getElementById('lens_tool'));

			// Testing has determined you need to add 0.5 to the y coordinate in order
			// to prevent a 1px "jiggling" of the magnified map as you move it up and
			// down over the main map.  I think it's a rounding loss that happened
			// somewhere along the way.  For safety, I add it to x, too (although it
			// doesn't seem to be neccessary).  To see the effect, put these back to
			// 0 and set this.magnifyFactor to 0.
			pt.y += 0.6;
			pt.x += 0.6;

			this.p_centerMapAtUmcCoord(pt);

	}

	GxMagnifierb.prototype.p_centerMapAtUmcCoord = function(pt) {
		//this.omap.centerAtLatLng(this.um.containerCoordToLatLng(pt));  // NONAPI MED
		var center = this.um.fromContainerPixelToLatLng(pt);
		var zl     = this.omap.getZoom();
		if (this.mapSyncEnabled){
			//var mt = this.um.getCurrentMapType()
			this.omap.setCenter(center, zl);  // NONAPI MED
		}else{
			this.omap.setCenter(center, zl);  // NONAPI MED
		}
	}

	// Centers the magnified map window after the mouse has moved, and updates
	// the map.  Also invokes autoPanning if required.
	// Requirements:
	// - not in parked mode, mouse coordinates set
	GxMagnifierb.prototype.p_refreshWindowCenter = function() {
		if (!this.mousePtUmc) return;

		// Get underlying map container bounds.  If autoPan is enabled, then
		// restrict the mouse so that the container stays in the main map
		// window's bounds.  If it's disabled, allow the mouse to travel out
		// of the main map window far enough to completely hide the magnified
		// map, then stop updating.
		var b = new google.maps.Bounds(0, 0, this.umc.offsetWidth +100, this.umc.offsetHeight +100);
		b = expandBounds(b,
			(this.container.offsetWidth  / 2) * (this.autoPanEnabled ? -1 : 1),
			(this.container.offsetHeight / 2) * (this.autoPanEnabled ? -1 : 1));
		b = offsetBounds(b, this.borderWidth); // fix for borderWidth "bug"

		var newCenter = getBoundedPt(this.mousePtUmc, b);

		centerElementOnP(this.container, newCenter, this.borderWidth);

		// If autoPan is enabled and the mouse is in the autoPan region
		// The autopan region is an imaginary border that extends 1/2 of
		// the container width/height in from the main map's edges.
		// i.e. the mouse is inside the main map container, but outside
		// the mouse bounding region that keeps the full magnifier shown.
		if (this.mouseIsInUmc && !isPtInBounds(this.mousePtUmc, b)) {
			// Pass off to the autopan function.  It will pan the map, which will
			// generate a "move" event, which in turn will invoke p_refreshMapLatLng
			this.p_doAutoPan();
		} else {
			this.p_stopAutoPan();
			this.p_refreshMapLatLng();
		}

	}

	/*************************************************************************************************
	* GxMagnifier autopanning
	*************************************************************************************************/

	// NOTE: The AutoPan functionality was written in a prior version of this component (when the
	// magnifier wasn't a child of the main map's container).  However, because it is nicely
	// encapsulated, it was not needed to rewrite the autoPan code for the overhaul that became
	// GxMagnifier.1.js.  Of course, it could probably be rewritten in a more optimized manner
	// that makes better use of information already calculated by the rest of the event chain.
	// Also, I haven't done a lot of testing to make sure it integrates perfectly into the rest
	// of the component.

	// Checks if the mouse is in the autoPanEnabled region.  If it is, then pans the map.
	// The autoPan logic is written (in terms of naming styles) to handle horizontal
	// panning, and then reused for handling vertical pans.
	var panningEW, panningNS, factor;  // store whether or not we're autopanning
	GxMagnifierb.prototype.p_doAutoPan = function() {
		if (!this.p_okayToRefreshMap()) return;
		// Check for horizontal autopan
		panningEW = this.p_checkPan(this.mousePtUmc.x, 0, this.umc.offsetWidth,
			this.container.offsetWidth, panningEW, 39, 37);
		// Reuse same function to check for vertical pan
		panningNS = this.p_checkPan(this.mousePtUmc.y, 0, this.umc.offsetHeight,
			this.container.offsetHeight, panningNS, 40, 38);
    // change factor to modify pan speed
    //if( factor > -100 ){	factor = factor -20;	}
    factor = -100;
		this.um.panBy(new google.maps.Size(factor*panningEW,factor*panningNS));				
	}

	GxMagnifierb.prototype.p_stopAutoPan = function() {
		panningEW = 0;
		panningNS = 0;
		factor = -100;
	}

	// This is the "control" portion of the autoPanEnabled logic.  It decides what to do
	// when the mouse is in an autoPanEnabled region.  Given that p_getAutoPanRatio() returns
	// a fraction, this function could theoretically be extended to accelerate
	// panning as the mouse travels further away from the center of the map.
	GxMagnifierb.prototype.p_checkPan = function(x, umcLeft, umcWidth, mapWidth, panning, rightKey, leftKey) {
		var ratio = this.p_getAutoPanRatio(x, umcLeft, umcWidth, mapWidth);
		return ratio;
	}

	// This is the "evaluate" portion of the autoPanEnabled logic.  It determines if the mouse
	// is in an autoPanEnabled region, and returns a fraction indicating how far into that
	// region the mouse has gone.
	// Returns a positive value if mouse is in right/bottom region, a negative value if its in
	// left/top region, and otherwise 0.
	// The specific value returned is between 0 and 1, proportional to how far into the AutoPan
	// region the mouse is (i.e. larger the farther away the mouse is from the map center).
	GxMagnifierb.prototype.p_getAutoPanRatio = function(x, containerLeft, containerWidth, mapWidth) {
		// Check the right-side autopan region
		var n = this.p_getOverlapRatio(x,
			(containerLeft + containerWidth - mapWidth / 2),  // left boundary of right-side autopan region
			(containerLeft + containerWidth));                // right boundary of same
		if (n != 0) return n;
		// Check the left-side autopan region, but reverse (1-n) and negate
		// (-n) returned fraction to make the "slider control" go the other way.
		n = this.p_getOverlapRatio(x,
			(containerLeft),                                  // left boundary of the left-side autopan region
			(containerLeft + mapWidth / 2));                  // right boundary of same
		if (n != 0) return -(1-n);

		// Otherwise simply return 0
		return 0;
	}

	// Returns how far into an imaginary box a co-ordinate is.
	// If x is outside of [left, right], 0 is returned, otherwise
	// a value between 0 and 1 is returned, proportional to much
	// bigger x is than left.
	// (Imagine a horizontal slider control)
	GxMagnifierb.prototype.p_getOverlapRatio = function(x, left, right) {
		if ((x > left) && (x < right)) {
			return (x - left) / (right - left);
		} else {
		 return 0;
		}
	}

	/*************************************************************************************************
	* GxMagnifier configuration
	*************************************************************************************************/

	var DEFAULT_MAGNIFY_FACTOR = 0;
	var currentMapZoom;
	// EXPOSE
	GxMagnifierb.prototype.setMagnification = function(levels) {
		this.magnifyFactor = (levels != undefined && levels != null) ? (levels) : DEFAULT_MAGNIFY_FACTOR;
		currentMapZoom = this.magnifyFactor;
		// Set the new zoom level
		this.p_refreshMapZoom(true);
	}

	GxMagnifierb.prototype.enableMapSync = function() {
		this.mapSyncEnabled = true;
	}

	GxMagnifierb.prototype.disableMapSync = function() {
		this.mapSyncEnabled = false;
	}

	GxMagnifierb.prototype.enableAutoMagnify = function() {
		this.autoMagnifyEnabled = true;
	}

	GxMagnifierb.prototype.disableAutoMagnify = function() {
		this.autoMagnifyEnabled = false;
	}

	GxMagnifierb.prototype.enableMouseTracking = function() {
		this.mouseTrackingEnabled = true;
	}

	GxMagnifierb.prototype.disableMouseTracking = function() {
		this.mouseTrackingEnabled = false;
	}

	GxMagnifierb.prototype.enableAutoPan = function() {
		this.autoPanEnabled = true;
	}

	GxMagnifierb.prototype.disableAutoPan = function() {
		this.p_stopAutoPan();
		this.autoPanEnabled = false;
	}

	GxMagnifierb.prototype.enablePanBeforeZoomIn = function() {
		this.panBeforeZoomInEnabled = true;
	}

	GxMagnifierb.prototype.disablePanBeforeZoomIn = function() {
		this.panBeforeZoomInEnabled = false;
	}

	GxMagnifierb.prototype.enablePrefetch = function() {
		this.prefetchEnabled = true;
	}

	GxMagnifierb.prototype.disablePrefetch = function() {
		this.prefetchEnabled = false;
	}

	GxMagnifierb.prototype.enableDefaultClickHandler = function() {
		this.defaultClickHandlerEnabled = true;
	}

	GxMagnifierb.prototype.disableDefaultClickHandler = function() {
		this.defaultClickHandlerEnabled = false;
	}

	GxMagnifierb.prototype.enableOverlaySync = function() {
		this.overlaySyncEnabled = true;
	}

	GxMagnifierb.prototype.disableOverlaySync = function() {
		this.overlaySyncEnabled = false;
	}

	GxMagnifierb.prototype.setBorderWidth = function(w) {
		this.borderWidth = w;
		this.container.style.borderWidth = w + 'px';
	}

	GxMagnifierb.prototype.setCursor = function(c) {
		setCursorC(this.container, c);
	}


	GxMagnifierb.prototype.setCenterForOverlay = function() {
		this.p_refreshMapLatLng();
	}

	GxMagnifierb.prototype.dragLens = function() {
		this.p_refreshMapLatLng();
		if (navigator.userAgent.indexOf("Firefox")!=-1) return;
		this.p_onDocMouseMove();
		this.p_doAutoPan();
	}
	


	/*************************************************************************************************
	* Helper functions for bounding to regions
	*************************************************************************************************/

	// Note: These functions are intended for use with pixel-type GPoints
	// and GBounds only.  They aren't written to work with Lat/Lng.

	// WISHLIST: Write helpers like isLatLngInBounds(), getBoundLatLng().
	// Be wary of international date line (see discussion at bottom of file)

	// WARNING: These functions may be called from rapid events like mousemove,
	// so they should be kept efficient.

	// Returns the given value, adjusting it if required so that it falls between
	// minVal and maxVal
	function getBoundedValue(val, minVal, maxVal) {
		if (val < minVal) return minVal;
		if (val > maxVal) return maxVal;
		return val;
	}

	function isValueInBounds(val, minVal, maxVal) {
		return ((val >= minVal) && (val <= maxVal));
	}

	function isPtInBounds(p, bound) {
		return (isValueInBounds(p.x, bound.minX, bound.maxX) &&
			isValueInBounds(p.y, bound.minY, bound.maxY));
	}


	// Determines whether the given point is in bounds of the given element.
	// Point must be specified in terms of the ELEMENT'S coordinate system,
	// not its offsetParent's (i.e. if pt is (0,0) then it falls on the
	// top-left corner of the element).
	// Primarily for use with a point returned from pageCoordToElement.
	function isPtContained(pt, el) {
		return (isValueInBounds(pt.x, 0, el.offsetWidth) &&
						isValueInBounds(pt.y, 0, el.offsetHeight));
	}

	// Adjusts the point's x and y as required to ensure it falls within
	// the given GBound.
	function getBoundedPt(pt, bounds) {
		var p = new google.maps.Point();
		p.x = getBoundedValue(pt.x, bounds.minX, bounds.maxX);
		p.y = getBoundedValue(pt.y, bounds.minY, bounds.maxY);
		return p;
	}

	// Returns a new GBounds whose limits are grown/shrunk by the
	// given amounts.  If amountVert is not specified, then the
	// bounds is shrinked evenly on all sides by amountHorz.
	// If justShift is true then the bounds are shifted right
	// and down by the given amount instead of expanded (used by
	// offsetBounds()).
	function expandBounds(bounds, amountHorz, amountVert, justShift) {
		var b = new google.maps.Bounds();
		if (!amountVert) amountVert = amountHorz
		justShift = (justShift) ? -1 : 1;
		b.minX = bounds.minX - amountHorz * justShift;
		b.maxX = bounds.maxX + amountHorz;
		b.minY = bounds.minY - amountVert * justShift;
		b.maxY = bounds.maxY + amountVert;
		return b;
	}

	// Returns a new GBounds whose limits are shifted by the
	// given amounts.  If amountVert is not specified, then
	// amountHorz is used for both the right and down shift.
	function offsetBounds(bounds, amountHorz, amountVert) {
		return expandBounds(bounds, amountHorz, amountVert, true);
	}


	function getElementSize(container) {
		return new google.maps.Size(container.offsetWidth, container.offsetHeight);
	}

	// Tests whether a point in page coordinates (typically the cursor location)
	// is inside of a particular container
	function hitTest(pt, el) {
		// Convert point to element's coordinates
		return isPtContained(pageCoordToElement(pt, el), el);
	}

	/*************************************************************************************************
	* Other helper functions
	*************************************************************************************************/


	// Adapted from API internals
	function setCursorC(a,b){
		try {
			a.style.cursor=b
		} catch (c) {
			if (b=="pointer") {
				setCursorC(a,"hand")
			}
		}
	}

	// Returns the center position of the element (in terms of its offsetParent)
	// BUG: In FireFox and IE under Windows in strict mode at least, the offsetWidth
	// function doesn't include the element's border width(s).  For a 2px border
	// this function will give a result 2px further left and 2px higher up than
	// we would like.  For now, this solved with a "borderAdjustment" hack.
	function getElementCenter(el, borderAdjustment) {
		var p = new google.maps.Point();
		if (currentMapZoom == 0){
			var x_adjust = 3.35;
			var y_adjust = 6.2;
		}
		if (currentMapZoom == 1){
			var x_adjust = 2.5;
			var y_adjust = 4.2;
		}
		if (currentMapZoom == 2){
			var x_adjust = 2.2;
			var y_adjust = 3.7;
		}

		
		if (!borderAdjustment) borderAdjustment = 0;
		p.x = el.offsetLeft + el.offsetWidth / x_adjust + borderAdjustment;
		p.y = el.offsetTop + el.offsetHeight / y_adjust + borderAdjustment;
		return p;
	}

	// Centers the given element on the given point (in terms of its offsetParent).
	// See "BUG" note in getElementCenter().
	function centerElementOnP(el, pt, borderAdjustment) {
		if (!borderAdjustment) borderAdjustment = 0;
		el.style.left = pt.x - el.offsetWidth / 2.0 - borderAdjustment + 'px';
		el.style.top = pt.y - el.offsetHeight / 2.0 - borderAdjustment + 'px';
	}

	

	// Convert a coordinate based on the page body to one relative to to the container,
	// by walking up all offsetParent's until we hit the document.
	// See "BUG" note in getElementCenter().
	function pageCoordToElement(pt, el) {
		var p = new google.maps.Point(pt.x, pt.y);
		for (var o = el; (o) && (o.offsetParent); o = o.offsetParent) {
			p.x -= o.offsetLeft;
			p.y -= o.offsetTop;
		}
		return p;
	}

	// Detect mouse coordinates relative to the document body
	function getMouseCoordinates(e) {
		var p = new google.maps.Point();
		if(!e) var e = window.event;
		if(e.pageX || e.pageY){
			p.x = e.pageX;
			p.y = e.pageY;
		} else if(e.clientX || e.clientY) {
			p.x = e.clientX + document.documentElement.scrollLeft;
			p.y = e.clientY + document.documentElement.scrollTop;
		}
		return p;
	}

	// Perform a delayed zoom
	function GxMagnifierDelayedZoomb(mapContainer, level) {
		if (GxMagnifierDelayedZoomMap) {
			//GxMagnifierDelayedZoomMap.zoomTo(level);
			GxMagnifierDelayedZoomMap.setZoom(level);
			GxMagnifierDelayedZoomMap = undefined;
		}
	}

	

	/*************************************************************************************************
	* Expose the Namespace
	*************************************************************************************************/
	// Make public interface (expose objects)
	function makeInterface(a) {
		var b = a || window;
		b.GxMagnifier = GxMagnifierb;
		b.GxMagnifierDelayedZoom = GxMagnifierDelayedZoomb;
		b.GxMagnifierDelayedZoomMap = undefined; // used for passing map to setTimeout
	}

	makeInterface();

}
GxMagnifierNamespace();
