// ------------------------------------------------
// MapIF.js
// Author:	jpotter@troupscreeksoftware.com
// Date:	Oct 30, 2006
// Desc:	Generic Map Interface
// Copyright 2006 Troups Creek Software LLC
// ------------------------------------------------
// Serves as a standard interface any of the map components. That is, the application
// interfaces with this class rather than the specific vendor-supplied API. 
// -----------------------------------------
// MapIF::MapIF
function MapIF()
{
	// data -------------------
	this.mapObj = null;
	this.lastClick = null;
	this.lastCenter = null;
	this.openedPopup = false;
	this.popupContent = "";
	this.ignoreZoom = false;
	// -------------------------------------------------------------------------------------------
	//            B A S I C   O P E R A T I O N S
	// -------------------------------------------------------------------------------------------
	// Fundamental methods.

	// -----------------------------------------
	// MapIF::init
	this.init = function()
	{	// initialize our map
		var isOk = true;
		// depending on which provider we are using, set up the map and add the events we're interested in
		switch(gConfig.provider)
		{
			case "Google":
				this.mapObj = new GoogleIF();
				break;
			case "Yahoo":
				this.mapObj = new YahooIF();
				break;
			case "MSVE":
				this.mapObj = new MsveIF();
				break;
		}
		// initialize the map and if successful, establish the events to handle
		if(this.mapObj.init(gConfig.latitude, gConfig.longitude, gConfig.zoomlevel, gConfig.type, gConfig.controls))
		{
			this.lastCenter = this.mapObj.getCenter();
			// establish event handlers by map provider
			switch(gConfig.provider)
			{
				case "Google":
					this.mapObj.addMapEvent("click");			// detect map click (check for double)
					this.mapObj.addMapEvent("zoom");			// detect map zoom in / out
					this.mapObj.addMapEvent("moveend");			// detect end of map movement
					break;
				case "Yahoo":
					this.mapObj.addMapEvent("click");			// detect map click (check for double)
					this.mapObj.addMapEvent("zoom");			// detect map zoom in / out
					this.mapObj.addMapEvent("move");			// detect any map movement
					this.mapObj.addMapEvent("moveend");			// detect end of map movement
					break;
				case "MSVE":
					this.mapObj.addMapEvent("click");			// detect map click (check for double)
					this.mapObj.addMapEvent("zoom");			// detect map zoom in / out
					this.mapObj.addMapEvent("move");			// detect any map movement
					break;
			}
			// now see if there is an overriding starting location to go to
			if(gApp.startLoc != "")
			{	// locate the map to this address (probably just a zip code)
				this.locateByQuery(gApp.startLoc);
			}
		}
		else
		{
			isOk = false;
		}
		return isOk;
	}

	// -----------------------------------------
	// MapIF::unloadMap
	this.unloadMap = function()
	{	// Reclaim map memeory
		if(this.mapObj)
			this.mapObj.unload();
	}

	// -------------------------------------------------------------------------------------------
	//           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

	// -----------------------------------------
	// MapIF::checkDoubleClick
	this.checkDoubleClick = function(p1, p2)
	{	// check for potential "double-click" event
		// GOOGLE:
		//			if click is on map, p1=null and p2 is point.y, point.x (lng, lat)
		//			if click is on marker, p1=marker obj and p2 is undefined
		// YAHOO:
		//			if click is on map, p1=map info and p2 is undefined
		//			if click is on marker, well, we just don't care
		// MSVE:
		//			if click is on map, p1=map info and p2 is undefined
		switch(gConfig.provider)
		{
			case "Google":
				// unless we clicked on the map, ignore it
				if(p2 != null)
				{
					if(gMap.lastClick == null)
					{
						gMap.lastClick = new Object();
						gMap.lastClick.lat = p2.y;
						gMap.lastClick.lng = p2.x;
					}
					else
					{
						if(gMap.lastClick.lng == p2.x && gMap.lastClick.lat == p2.y)
						{
							gMap.onMapDoubleClick(gMap.lastClick.lat, gMap.lastClick.lng);
						}
						else
						{
							gMap.lastClick.lat = p2.y;
							gMap.lastClick.lng = p2.x;
						}
					}
				}
				break;
			case "Yahoo":
				// unless we clicked on the map, ignore it
				// NOTE: Yahoo always hits the event handler twice ?!?
				if(p1 != null && p1.YGeoPoint)
				{
					if(gMap.lastClick == null)
					{
						gMap.lastClick = new Object();
						gMap.lastClick.lat = p1.YGeoPoint.Lat;
						gMap.lastClick.lng = p1.YGeoPoint.Lon;
					}
					else
					{
						if(gMap.lastClick.lat == p1.YGeoPoint.Lat && gMap.lastClick.lng == p1.YGeoPoint.Lon)
						{
							gMap.onMapDoubleClick(gMap.lastClick.lat, gMap.lastClick.lng);
						}
						else
						{
							gMap.lastClick.lat = p1.YGeoPoint.Lat;
							gMap.lastClick.lng = p1.YGeoPoint.Lon;
						}
					}
				}
				break;
			case "MSVE":
				// unless we clicked on the map, ignore it
				if(p1 != null)
				{	// get the lat/lng
					var x = p1.mapX;
					var y = p1.mapY;
					var pixel = new VEPixel(x,y);
					var latlng = gMap.mapObj.map.PixelToLatLong(pixel);
					// see where we clicked
					if(p1.elementID)
					{	// defined - that means we've clicked on a marker
						var markerClicked = gMap.mapObj.map.GetShapeByID(p1.elementID);
						gMap.mapObj.showMarkerInfo(markerClicked);
					}
					else
					{	// if clicked on map, elementID is null/undefined
						if(gMap.lastClick == null)
						{
							gMap.lastClick = new Object();
							gMap.lastClick.lat = latlng.Latitude;
							gMap.lastClick.lng = latlng.Longitude;
						}
						else
						{
							if(gMap.lastClick.lng == latlng.Longitude && gMap.lastClick.lat == latlng.Latitude)
							{
								gMap.onMapDoubleClick(gMap.lastClick.lat, gMap.lastClick.lng);
							}
							else
							{
								gMap.lastClick.lat = latlng.Latitude;
								gMap.lastClick.lng = latlng.Longitude;
							}
						}
					}
				}
				break;
		}
	}

	// -----------------------------------------
	// MapIF::onMapClick
	this.onMapClick = function(p1, p2)
	{	// handle map "click" event
	}

	// -----------------------------------------
	// MapIF::onMapDoubleClick
	// if fired from check, lat and lng are valid
	// if fired from <div>, lat and lng are undefined
	this.onMapDoubleClick = function(lat, lng)
	{	// handle map "double-click" event
		if(gConfig.locked == "0")
		{	// not locked - save our current settings as a new default start location
			var settings = gMap.getCurrentMapSettings();
			// settings are not always valid (e.g. MSVE map in birds-eye mode)
			if(settings.lat && settings.lng && settings.zoom)
				{gData.updateMapCookie(settings.lat, settings.lng, settings.zoom);}
		}
	}

	// -----------------------------------------
	// MapIF::onMapMove
	this.onMapMove = function()
	{	// handle map "move" event
		switch(gConfig.provider)
		{
			case "Google":
				// Google handles the move end sufficiently, so we don't use this handler
				gMsg.displayMsg(2,"Google map move not handled!");
				break;
			case "MSVE":
				// the map does not move when a popup is opened, so don't worry about that, but
				// we should close any existing popup if we're moving around
				gMap.mapObj.hideMarkerInfo();
				gApp.triggerFlow(eMapMoved);
				break;
			case "Yahoo":
				// Yahoo fires this when the map control moves the map
				var curCenter = gMap.mapObj.getCenter();
				if((gMap.lastCenter.lat != curCenter.lat) || (gMap.lastCenter.lng != curCenter.lng) || (gApp.itemToOpen != 0))
				{	// it changed so save the new center point and notify the controller
					gMap.lastCenter = curCenter;
					if(gMap.openedPopup)
						{gApp.triggerFlow(eMapPopup);}
					else
						{gApp.triggerFlow(eMapMoved);}
				}
				gMap.openedPopup = false;	// always reset the flag
				break;
		}
	}

	// -----------------------------------------
	// MapIF::onMapMoveEnd
	this.onMapMoveEnd = function(p1)
	{	// handle map "moveend" event
		switch(gConfig.provider)
		{
			case "Google":
				// we may have just finished a "move", or a "click", or a "zoom"... OR, we may have
				// opened a popup window and it moved the map, so we need to check to see if the map
				// actually moved OR if we are supposed to open a popup, AND if we already opened a popup
				var curCenter = gMap.mapObj.getCenter();
				if((gMap.lastCenter.lat != curCenter.lat) || (gMap.lastCenter.lng != curCenter.lng) || (gApp.itemToOpen != 0))
				{	// it changed so save the new center point and notify the controller
					gMap.lastCenter = curCenter;
					if(gMap.openedPopup)
						{gApp.triggerFlow(eMapPopup);}
					else
						{gApp.triggerFlow(eMapMoved);}
				}
				gMap.openedPopup = false;	// always reset the flag
				break;
			
			case "Yahoo":
				// Yahoo fires this when the user drags the map
				var curCenter = gMap.mapObj.getCenter();
				if((gMap.lastCenter.lat != curCenter.lat) || (gMap.lastCenter.lng != curCenter.lng) || (gApp.itemToOpen != 0))
				{	// it changed so save the new center point and notify the controller
					gMap.lastCenter = curCenter;
					if(gMap.openedPopup)
						{gApp.triggerFlow(eMapPopup);}
					else
						{gApp.triggerFlow(eMapMoved);}
				}
				break;
				
			case "MSVE":
				// MS does not always fire a move end, noteably on big moves, so we don't use it
				gMsg.displayMsg(2,"MSVE map move end not handled!");
				break;
		}		
	}

	// -----------------------------------------
	// MapIF::onMapMoveStart
	this.onMapMoveStart = function()
	{	// handle map "movestart" event
	}

	// -----------------------------------------
	// MapIF::onMapZoom
	this.onMapZoom = function(p1, p2)
	{	// handle map "zoom" event
		// GOOGLE:
		//			p1=oldLevel and p2=newLevel
		// YAHOO:
		//			p1=mapInfo and p2=undefined
		// MSVE:
		//			p1=mapInfo and p2=undefined
		// see if we're ignorning the event
		if(gMap.ignoreZoom)
		{
			gMap.ignoreZoom = false;	//ignore this time, but reset it for next time
		}
		else
		{
			switch(gConfig.provider)
			{
			case "Google":
				// updated for Google V2 API
				if(gMap.mapObj.exceedsZoomMax(p2, gConfig.zoommax))
				{	// can't allow more than max zoom level due to performance drop
					// so just auto reset it at the limit -- no warning
					gMap.ignoreZoom = true;
					gMap.mapObj.setZoom(gConfig.zoommax);
				}
				break;
			case "Yahoo":
				if(gMap.mapObj.exceedsZoomMax(p1.zoomObj.current, gConfig.zoommax))
				{	// can't allow more than max zoom level due to performance drop
					// so just auto reset it at the limit -- no warning
					gMap.ignoreZoom = true;
					gMap.mapObj.setZoom(gConfig.zoommax);
				}
				else
				{
					// workaround boundries bug in Yahoo 
					var settings = gMap.mapObj.getCenter();
					gMap.mapObj.centerMap(settings.lat, settings.lng, p1.zoomObj.current);
				}
				break;
			case "MSVE":
				if(gMap.mapObj.exceedsZoomMax(p1.zoomLevel, gConfig.zoommax))
				{	// can't allow more than max zoom level due to performance drop
					// so just auto reset it at the limit -- no warning
					gMap.ignoreZoom = true;
					gMap.mapObj.setZoom(gConfig.zoommax);
				}
				break;
			}
			// let our controller know that the map changed
			gApp.triggerFlow(eMapZoomed);
		}
	}

	// -----------------------------------------
	// MapIF::onMarkerClick
	this.onMarkerClick = function()
	{	// handle marker "click" event
	}

	// -------------------------------------------------------------------------------------------
	//            M A P   O P E R A T I O N S
	// -------------------------------------------------------------------------------------------
	// Methods that directly manipulate the map.

	// -----------------------------------------
	// MapIF::addMapFeature
	this.addMapFeature = function(lat, lng, icon)
	{	// Add a feature or correction to the map
		this.mapObj.addMapFeature(lat, lng, icon);
	}

	// -----------------------------------------
	// MapIF::centerMap
	this.centerMap = function(lat, lng)
	{	// center the map on the given location
		var zoom = this.mapObj.getZoom()
		this.mapObj.centerMap(lat, lng, zoom);
		// save our location
		gData.updateMapCookie(lat, lng, zoom);
	}

	// -----------------------------------------
	// MapIF::getCurrentMapSettings
	this.getCurrentMapSettings = function()
	{	// return the map settings for our current location
		var settings = this.mapObj.getCenter();
		settings.zoom = this.mapObj.getZoom();
		return settings;
	}

	// -----------------------------------------
	// MapIF::getMapBounds
	this.getMapBounds = function()
	{	// Retrieve the bounds of the map given its current settings
		var mapBounds = this.mapObj.getBounds();
		return mapBounds;
	}

	// -----------------------------------------
	// MapIF::getMapZoom
	this.getMapZoom = function()
	{	// Retrieve the current zoom level of the map
		var mapZoom = this.mapObj.getZoom();
		return mapZoom;
	}

	// -----------------------------------------
	// MapIF::getMinZoom
	this.getMinZoom = function()
	{	// Return the absolute minimim zoom level of the map
		var minZoom = this.mapObj.getMinZoom();
		return minZoom;
	}

	// -----------------------------------------
	// TODO: review access to gApp entry window
	// MapIF::locateByAddress
	this.locateByAddress = function()
	{	// get the lat/lng for the new address
		var addr = gApp.itemEntryWindow.document.getElementById("txtAddress").value;
		switch(gConfig.provider)
		{
			case "Google":
				var geocoder = new GClientGeocoder();		// use the built-in geocoder
				geocoder.getLatLng(addr, function(point) 
					{
						if(!point) 
						{
							gMsg.displayMsg(2,"Address Lookup Failed: [" + addr + "] not found");
						} 
						else 
						{
							gMap.centerMap(point.y, point.x);
							gApp.updateItemLocation(true, false);
							gMsg.displayMsg(1,"Address found. Map and location updated.");
						}
					}
				);
				break;
			case "MSVE":
				this.mapObj.locateByAddress(addr);
				break;
			case "Yahoo":
				this.mapObj.locateByAddress(addr);
				break;
		}
	}

	// -----------------------------------------
	// MapIF::locateByQuery
	this.locateByQuery = function(addr)
	{	// get the lat/lng for the starting location based on the provided address - silent process
		// and the map event handlers will take care of the rest
		switch(gConfig.provider)
		{
			case "Google":
				var geocoder = new GClientGeocoder();		// use the built-in geocoder to center map
				geocoder.getLatLng(addr, function(point) 
					{if(point) gMap.centerMap(point.y, point.x);});
				break;
			case "MSVE":
				this.mapObj.locateByAddress(addr);
				break;
			case "Yahoo":
				this.mapObj.locateByAddress(addr);
				break;
		}
	}

	// -----------------------------------------
	// TODO: review access into gApp edit window
	// MapIF::locateStartAddress
	this.locateStartAddress = function()
	{	// get the lat/lng for the starting address
		var addr = gApp.editConfigWindow.document.getElementById("txtAddress").value;
		switch(gConfig.provider)
		{
			case "Google":
				var geocoder = new GClientGeocoder();		// use the built-in geocoder
				geocoder.getLatLng(addr, function(point) 
					{
						if(point)
							gMap.centerMap(point.y, point.x);
						else
							gMsg.displayMsg(2,"Address Lookup Failed: [" + addr + "] not found");
					});
				break;
			case "MSVE":
				this.mapObj.locateByAddress(addr);
				break;
			case "Yahoo":
				this.mapObj.locateByAddress(addr);
				break;
		}
	}

	// -----------------------------------------
	// MapIF::moveEast
	this.moveEast = function()
	{	// Pan the map east 1 time
		this.mapObj.moveEast()
	}

	// -----------------------------------------
	// MapIF::moveNorth
	this.moveNorth = function()
	{	// Scroll the map north 1 time
		this.mapObj.moveNorth()
	}

	// -----------------------------------------
	// MapIF::moveSouth
	this.moveSouth = function()
	{	// Scroll the map south 1 time
		this.mapObj.moveSouth()
	}

	// -----------------------------------------
	// MapIF::moveWest
	this.moveWest = function()
	{	// Pan the map west 1 time
		this.mapObj.moveWest()
	}

	// -----------------------------------------
	// MapIF::setMapType
	this.setMapType = function(type)
	{	// Set the type of map (road, arial, hybrid)
		this.mapObj.setType(type)
	}

	// -----------------------------------------
	// MapIF::zoomIn
	this.zoomIn = function(isAuto)
	{	// Increase the zoom level of the map
		this.ignoreZoom = isAuto;	// if autozooming, block additional event handling
		this.mapObj.increaseZoom();
	}

	// -----------------------------------------
	// MapIF::zoomOut
	this.zoomOut = function()
	{	// Decrease the zoom level of the map
		this.mapObj.decreaseZoom();
	}

	// -------------------------------------------------------------------------------------------
	//            M A R K E R   O P E R A T I O N S
	// -------------------------------------------------------------------------------------------
	// Methods that manipulate markers on the map.

	// -----------------------------------------
	// MapIF::addMapMarker
	this.addMapMarker = function(lat, lng, markerContent, icon, openNow)
	{	// Add a marker to the map
		this.mapObj.addMarker(lat, lng, markerContent, icon, gConfig.markerOnOpen, openNow);
	}

	// -----------------------------------------
	// MapIF::okToShowMarkers
	this.okToShowMarkers = function(numToShow)
	{	// See if we can show all markers - too many and we have performance issues
		isOk = true;
		if(numToShow > gConfig.markerMax)
		{	// problem - may be too many to show for performance reasons
			// so get the zoom level and if we can, zoom in a bit and try again
			if(this.mapObj.okToZoom())
			{	// not ok - we need to increase zoom and try again
				isOk = false;
			}
		}
		return isOk;
	}

	// -----------------------------------------
	// MapIF::removeAllMapMarkers
	this.removeAllMapMarkers = function()
	{	// Remove all markers from the map
		this.mapObj.removeMarkers();
		gApp.updateMapFeatures();		// restore features / corrections
	}

	// -----------------------------------------
	// MapIF::removeLastMapMarker
	this.removeLastMapMarker = function()
	{	// Remove the last marker that was added from the map
		this.mapObj.clearLastMarker();
	}
}

