// ------------------------------------------------
// GoogleIF.js
// Author:	jpotter@troupscreeksoftware.com
// Date:	Oct 30, 2006
// Desc:	Google Map Interface
// Copyright 2006 Troups Creek Software LLC
// ------------------------------------------------
// Provides a standard interface to the Google map component.
//
// prerequisites:
//		hosting page MUST specify element for map with "id=MapContainer" (see init function, below)
// ------------------------------------------------
// GoogleIF::GoogleIF
function GoogleIF()
{
	// data -------------------
	this.map = null;
	this.lastMarker = null;
	this.baseMarker = null;
	this.baseFeature = null;
	this.iconList = null;
	this.onClickXform = null;					// added for V2 API
	this.onMouseOverXform = null;				// added for V2 API
	// -------------------------------------------------------------------------------------------
	//            B A S I C   O P E R A T I O N S
	// -------------------------------------------------------------------------------------------
	// Fundamental methods.

	// -----------------------------------------
	// GoogleIF::init
	this.init = function(lat, lng, zoom, type, ctrl)
	{	// initialize our map
		// make sure our script was loaded and the Google map tools are available
		var isOk = true;
		if(typeof GPoint != "undefined")
		{	// create a lat/lng point object [note lng/lat are as X,Y]
			var gPoint = new GPoint(lng, lat);
			// create a Google map and draw it 
			this.map = new GMap2(document.getElementById("MapContainer"));
			this.centerMap(lat, lng, zoom);
			// Add a pan/zoom control for user navigation, plus a map-type control
			switch(ctrl)
			{
				case "default":		// no controls - just menu buttons
					break;
				case "pan":			// small pan/zoom w/o type
					this.map.addControl(new GSmallMapControl());
					break;
				case "type":		// type w/o pan/zoom
					this.map.addControl(new GMapTypeControl());
					break;
				case "small":		// small pan/zoom + map type
					this.map.addControl(new GSmallMapControl());
					this.map.addControl(new GMapTypeControl());
					break;
				case "large":		// large pan/zoom + map type
					this.map.addControl(new GLargeMapControl());
					this.map.addControl(new GMapTypeControl());
					break;
			}
			// Set the map view type
			this.setType(type);
			// Create a base icon for all of our markers that specifies the
			// shadow, icon dimensions, etc.
			this.baseMarker = new GIcon();
			this.baseMarker.shadow = "images/" + gConfig.domainid + "/shadow50.png";
			this.baseMarker.iconSize = new GSize(20, 34);
			this.baseMarker.shadowSize = new GSize(37, 34);
			this.baseMarker.iconAnchor = new GPoint(9, 34);
			this.baseMarker.infoWindowAnchor = new GPoint(9, 2);
			this.baseMarker.infoShadowAnchor = new GPoint(18, 25);
			// create another for map corrections / map features without
			// a shadow or info window
			this.baseFeature = new GIcon();
			this.baseFeature.iconSize = new GSize(28, 28);
			this.baseFeature.iconAnchor = new GPoint(9, 28);
			// initialize a cache for the icons
			this.iconList = new Array();
			// create xslt transform for use in the marker info windows (for V2 API)
			var xslPath = gData.getDataPath();	
			if(window.ActiveXObject)
			{	// IE specific processing
				this.onClickXform = new ActiveXObject("MSXML2.FreeThreadedDOMDocument.3.0");
				this.onClickXform.async = false;
				this.onClickXform.load(xslPath + "/MarkerContentOnClick.xsl");
				this.onMouseOverXform = new ActiveXObject("MSXML2.FreeThreadedDOMDocument.3.0");
				this.onMouseOverXform.async = false;
				this.onMouseOverXform.load(xslPath + "/MarkerContentOnMouseover.xsl");
			}
			else if(document.implementation && document.implementation.createDocument && XSLTProcessor)
			{	// Mozilla, Firefox processing
				this.onClickXform = document.implementation.createDocument("","",null);
				this.onClickXform.async = false;
				this.onClickXform.load(xslPath + "/MarkerContentOnClick.xsl");
				this.onMouseOverXform = document.implementation.createDocument("","",null);
				this.onMouseOverXform.async = false;
				this.onMouseOverXform.load(xslPath + "/MarkerContentOnMouseover.xsl");
			}
		}
		else
		{
			gMsg.displayMsg(3,"The Google Map Service is currently unavailable.");
			isOk = false;
		}
		return isOk;
	}

	// -----------------------------------------
	// GoogleIF::unload
	this.unload = function()
	{	// reclaim lost browser memory
		// GUnload();
	}
	
	// -------------------------------------------------------------------------------------------
	//           E V E N T   H A N D L E R S
	// -------------------------------------------------------------------------------------------
	// Set of methods that handle user interaction events, e.g. mouse clicks.
	//
	//	NOTE: within the event handlers, the "this" reference does not necessarily refer to the map interface 
	//  object - it may refer to the element that triggered the event... hence, the use of the global references

	// -----------------------------------------
	// GoogleIF::onMarkerInfoOpen
	this.onMarkerInfoOpen = function(id)
	{	// handle marker "infowindowopen" event
		// run up the popup flag so we don't inadvertently dismiss the popup we just opened
		// and then set up a timer to clear the flag ... at some point it'll be cleared anyway
		gMap.openedPopup = true;
		var pid = setTimeout("gMap.mapObj.onPostPopup()", 3000);
		// save the center point in case we didn't move
		gMap.lastCenter = gMap.mapObj.getCenter();
		// save the id so we know what to log, and log it
		gApp.openedId = id;
		if(gApp.isAdminMode)
		{	// admin mode, revise the edit capabilities
			gApp.setupUIEdit(true);
		}
		else
		{	// user mode, add to the click-thru log
			gData.logContact("popup");
		}
	}

	// -----------------------------------------
	// GoogleIF::onMarkerInfoClose
	this.onMarkerInfoClose = function()
	{	// handle marker "infowindowclose" event
		gMap.openedPopup = false;
		gApp.openedId = 0;
		if(gApp.isAdminMode)
			{gApp.setupUIEdit(false);}
	}

	// -----------------------------------------
	// GoogleIF::onPostPopup
	this.onPostPopup = function()
	{	// clear the popup flag so that subsequent processing doesn't mistakenly use it
		// long after the event occurred (i.e. if the map doesn't move when we open the 
		// popup, we need to clear this flag via this mechanism because there is NO OTHER EVENT)
		gMap.openedPopup = false;
	}

	// -------------------------------------------------------------------------------------------
	//            M A P   O P E R A T I O N S
	// -------------------------------------------------------------------------------------------
	// Methods that directly manipulate the map.

	// -----------------------------------------
	// GoogleIF::addMapEvent
	this.addMapEvent = function(reason)
	{	// add a listener for a specific MAP event
		switch(reason)
		{
			case "click":		// fired on map click - we try to handle the double
				GEvent.bind(this.map, "click", gMap, gMap.checkDoubleClick);
				break;		
			case "move":		// fired continuously as map moves
				GEvent.bind(this.map, "move", gMap, gMap.onMapMove);
				break;			
			case "movestart":	// fired on start of move
				GEvent.bind(this.map, "movestart", gMap, gMap.onMapMoveStart);
				break;			
			case "moveend":		// fired on end of move + on zoom
				GEvent.bind(this.map, "moveend", gMap, gMap.onMapMoveEnd);
				break;			
			case "zoom":		// fired on zoom
				GEvent.bind(this.map, "zoomend", gMap, gMap.onMapZoom);
				break;
		}
	}

	// -----------------------------------------
	// GoogleIF::addMapFeature
	this.addMapFeature = function(lat, lng, icon)
	{	// add a map feature / correction to the Google map		
		var gpt = new GPoint(lng, lat);
		var gmr = this.createMapFeature(gpt, icon);
		this.map.addOverlay(gmr);
	}

	// -----------------------------------------
	// GoogleIF::centerMap
	this.centerMap = function(lat, lng, zoom)
	{	// center the map on the given location
		var gLL = new GLatLng(lat, lng);
		this.map.setCenter(gLL, zoom);
	}

	// -----------------------------------------
	// GoogleIF::getBounds
	this.getBounds = function()
	{	// get the bounds of the current map
		var mapBounds = new Object();
		var bounds = this.map.getBounds();	// returns a GLatLngBounds, then use getSouthWest() getNorthEast()
		var swll = bounds.getSouthWest();
		var nell = bounds.getNorthEast();
		mapBounds.minLat = swll.lat();
		mapBounds.minLng = swll.lng();
		mapBounds.maxLat = nell.lat();
		mapBounds.maxLng = nell.lng();
		// check for International Date Line complications
		if(mapBounds.minLng > mapBounds.maxLng)
		{	// min > max, that's a problem
			var diff = (180.0 - mapBounds.minLng);
			mapBounds.minLng = -1*(180.0 + diff);
		}
		return mapBounds;
	}

	// -----------------------------------------
	// GoogleIF::getCenter
	this.getCenter = function()
	{	// get the center position of the current map
		var settings = new Object();
		var latlng = this.map.getCenter();		// returns a GLatLng
		settings.lat = latlng.lat();
		settings.lng = latlng.lng();
		return settings;
	}

	// -----------------------------------------
	// GoogleIF::getMinZoom
	this.getMinZoom = function()
	{	// get the minimum zoom level of the map
		// Google ranges from 0 (farthest out) to 17 (farthest in, depending on location)
		return 17;	// was 0 in V1, but there ought to be a getMinZoom method on GMap2
	}

	// -----------------------------------------
	// GoogleIF::getZoom
	this.getZoom = function()
	{	// get the zoom level of the current map
		var level = this.map.getZoom();
		return level;
	}

	// -----------------------------------------
	// GoogleIF::decreaseZoom
	this.decreaseZoom = function()
	{	// decrease the zoom level by 1 unit
		var level = this.getZoom();
		this.setZoom(level-1);
	}

	// -----------------------------------------
	// GoogleIF::increaseZoom
	this.increaseZoom = function()
	{	// increase the zoom level by 1 unit
		var level = this.getZoom();
		this.setZoom(level+1);
	}

	// -----------------------------------------
	// GoogleIF::okToZoom
	this.okToZoom = function()
	{	// see if we can zoom in any more
		var curZoom = this.getZoom();
		var minZoom = this.getMinZoom();
		return (curZoom < minZoom);
	}

	// -----------------------------------------
	// GoogleIF::exceedsZoomMax
	this.exceedsZoomMax = function(newVal, maxVal)
	{	// see if we have exceeded our zoom limit
		return (newVal < maxVal);
	}

	// -----------------------------------------
	// GoogleIF::setType
	this.setType = function(type)
	{	// Set the map view type
		switch(type)
		{
			case "road":
				this.map.setMapType(G_NORMAL_MAP);
				break;
			case "arial":
				this.map.setMapType(G_SATELLITE_MAP);
				break;
			case "hybrid":
				this.map.setMapType(G_HYBRID_MAP);
				break;
		}
	}

	// -----------------------------------------
	// GoogleIF::moveEast
	this.moveEast = function()
	{	// pan right
		this.map.panDirection(-1,0);
	}

	// -----------------------------------------
	// GoogleIF::moveNorth
	this.moveNorth = function()
	{	// scroll up 
		this.map.panDirection(0,1);
	}

	// -----------------------------------------
	// GoogleIF::moveSouth
	this.moveSouth = function()
	{	// scroll down 
		this.map.panDirection(0,-1);
	}

	// -----------------------------------------
	// GoogleIF::moveWest
	this.moveWest = function()
	{	// pan left 
		this.map.panDirection(1,0);
	}

	// -----------------------------------------
	// GoogleIF::setZoom
	this.setZoom = function(level)
	{	// set the zoom level of the current map
		this.map.setZoom(level);
	}

	// -------------------------------------------------------------------------------------------
	//            M A R K E R   O P E R A T I O N S
	// -------------------------------------------------------------------------------------------
	// Methods that manipulate markers on the map.
	
	// -----------------------------------------
	// GoogleIF::addMarkerEvent
	this.addMarkerEvent = function(marker, reason, xmlDOM)
	{	// add a listener for a specific MARKER event
		switch(reason)
		{
			case "click":
				if(xmlDOM != null)
				{
					GEvent.addListener(marker, "click", function()
						{
							var resultStr = "";
							if(gApp.isIE5min)
							{	// IE specific
								// load the template (for faster processing and to be able to pass parameters)
								var xslTemplate = new ActiveXObject("Msxml2.XSLTemplate.3.0");
								xslTemplate.stylesheet = gMap.mapObj.onClickXform;
								// create an xslt processor...
								var xslProcessor = xslTemplate.createProcessor();
								xslProcessor.input = xmlDOM;
								xslProcessor.transform();
								resultStr = xslProcessor.output;
							}
							else if(gApp.isFF)
							{	// the xslt and serial processors must be created each time to avoid caching previous contents
								var ffXsltProcessor = new XSLTProcessor();
								var ffResultPro = new XMLSerializer();
								ffXsltProcessor.importStylesheet(gMap.mapObj.onClickXform);
								resultStr = ffResultPro.serializeToString(ffXsltProcessor.transformToDocument(xmlDOM));
								ffXsltProcess = null;	// reclaim memory
								ffResultPro = null;
							}
							marker.openInfoWindowHtml(resultStr);		
						}
					);
				}
				break;
			case "mouseover":
				if(xmlDOM != null)
				{
					GEvent.addListener(marker, "mouseover", function()
						{
							var resultStr = "";
							if(gApp.isIE5min)
							{	// IE specific
								// load the template (for faster processing and to be able to pass parameters)
								var xslTemplate = new ActiveXObject("Msxml2.XSLTemplate.3.0");
								xslTemplate.stylesheet = gMap.mapObj.onClickXform;
								// create an xslt processor...
								var xslProcessor = xslTemplate.createProcessor();
								xslProcessor.input = xmlDOM;
								xslProcessor.transform();
								resultStr = xslProcessor.output;
							}
							else if(gApp.isFF)
							{	// the xslt and serial processors must be created each time to avoid caching previous contents
								var ffXsltProcessor = new XSLTProcessor();
								var ffResultPro = new XMLSerializer();
								ffXsltProcessor.importStylesheet(gMap.mapObj.onClickXform);
								resultStr = ffResultPro.serializeToString(ffXsltProcessor.transformToDocument(xmlDOM));
								ffXsltProcess = null;	// reclaim memory
								ffResultPro = null;
							}
							marker.openInfoWindowHtml(resultStr);		
						}
					);
				}
				break;
			case "infowindowopen":
				if(gApp.isW3 && (xmlDOM != null))
				{
					var itemId = xmlDOM.getElementsByTagName("id")[0].firstChild.nodeValue;
					GEvent.addListener(marker, "infowindowopen", function()
						{
							gMap.mapObj.onMarkerInfoOpen(itemId);
						});
				}
				else
				{
					GEvent.bind(marker, "infowindowopen", gMap, gMap.mapObj.onMarkerInfoOpen);
				}
				break;
			case "infowindowclose":
				GEvent.addListener(marker, "infowindowclose", function()
					{
						gMap.mapObj.onMarkerInfoClose();
					});
				break;
		}
	}

	// -----------------------------------------
	// GoogleIF::addMarker
	this.addMarker = function(lat, lng, content, icon, trigger, openNow)
	{	// add a pin on the Google map		
		var gpt = new GPoint(lng, lat);
		var gmr = this.createMarker(gpt, content, icon, trigger);
		this.map.addOverlay(gmr);
		this.lastMarker = gmr;		// always save the last one added
		// if requested, immediately open this marker's infowindow
		if(openNow)
		{
			GEvent.trigger(gmr, trigger, gmr);
		}
	}

	// -----------------------------------------
	// GoogleIF::clearLastMarker
	this.clearLastMarker = function()
	{	// clear the last marker added
		if(this.lastMarker != null)
		{
			this.closeInfoWindow();
			this.map.removeOverlay(this.lastMarker);
			this.lastMarker = null;
		}
	}

	// -----------------------------------------
	// GoogleIF::closeInfoWindow
	this.closeInfoWindow = function()
	{	// close the last opened marker info window
		this.map.closeInfoWindow();
	}

	// -----------------------------------------
	// GoogleIF::createMapFeature
	this.createMapFeature = function(gPoint, iconimage)
	{	// use the icon to create the new marker
		var icon = this.getIcon(iconimage, this.baseFeature);
		var opt = new Object();						// V2 API
		opt.icon = icon;
		opt.clickable = false;
		var aMarker = new GMarker(gPoint, opt);		// no event handling
		return aMarker;
	}

	// -----------------------------------------
	// GoogleIF::createMarker
	this.createMarker = function(gPoint, content, iconimage, trigger)
	{	// creates a new Google marker with the supplied custom text
		var icon = this.getIcon(iconimage, this.baseMarker);
		var opt = new Object();
		opt.icon = icon;
		if(trigger == "click")
		{	// add a tooltip that shows the marker item title
			if(content == null)
			{	// no content, must be a new item
				opt.title = "new item";
			}
			else
			{	// xml dom content, get the title
				opt.title = content.getElementsByTagName("title")[0].firstChild.nodeValue;
			}
		}
		var aMarker = new GMarker(gPoint, opt);
		// skip event processing for the 'new item' marker
		if(content != null)
		{	// marker opens on trigger, and we need to allow various functionality as it opens and closes
			this.addMarkerEvent(aMarker, trigger, content);
			this.addMarkerEvent(aMarker, "infowindowopen", content);
			this.addMarkerEvent(aMarker, "infowindowclose", null);
		}
		return aMarker;
	}

	// -----------------------------------------
	// GoogleIF::getIcon
	this.getIcon = function(iconimage, baseIcon)
	{	// retrieve the icon to use
		// iconimage naming convention is external to Google IF, so don't hardcode any strings
		var icon = null;
		var foundIcon = false;
		if(this.iconList.length > 0)
		{	// see if we can find the iconimage in the array
			for(var i=0; i < this.iconList.length; i++)
			{
				icon = this.iconList[i];
				if(icon.image == ("images/" + iconimage))
				{	// found it!
					foundIcon = true;
					break;
				}
			}
		}
		// foundIcon will be false if it is the first time through, or if we couldn't find the icon
		if(!foundIcon)
		{	// must be new - add the new icon to the iconList
			icon = new GIcon(baseIcon);
			icon.image = "images/" + iconimage;
			this.iconList[this.iconList.length] = icon;
		}
		return icon;
	}

	// -----------------------------------------
	// GoogleIF::removeMarkers
	this.removeMarkers = function()
	{	// remove all markers from the map
		this.closeInfoWindow();	// take down any popup
		this.map.clearOverlays();
	}
}

