﻿///////////////////////////////////////////////////////////////////////////////////////////////////
// The activity map "namespace"
///////////////////////////////////////////////////////////////////////////////////////////////////
ActivityMap = {

	// The Google map object
	map:null,

	// The Google map controls
	mapPanZoomControl:null,
	mapTypeControl:null,
	
	// The <img> control that is shown during an AJAX request
	statusImgCtrl:null,

	// The URL of the page used to find activities in view
	rootPathUrl:"./",
	
	// The marker that can be moved by the user
	placeableMarker:null,

	// The control that contains the activity search results
	resultCtrl:null,
	
	// The <span> control that is used to display the results of an activity search
	statusMsgCtrl:null,
	isPerformingSearch:false,
	hasPendingSearch:false,
	isPerformingGeocode:false,

	// The activity marker that currently has a popup
	displayedActivity:null,
	
	// The messges to display the results of an activity search
	activityMsgs:new Array(4),
	activityMsgsSet:false,
	maxViewableActivites:10,
	
	// The activities retrieved from an AJAX request
	retrievedActivities:null,

	// The function that gets called when an activity is selected
	onActivitySelectStr:null,
	
	// Whether or not activities are displayed on the map
	showActivites:true,
	
	// A function that is called when an address is properly formatted by Google
	OnAddressGeolocated:null,
	
	// The message indices within activityMsgs
	MSG_ONE:0,
	MSG_MANY:1,
	MSG_NONE:2,
	MSG_ERROR:3,
	MSG_TOOMANY:4,
	
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	// Get an activity from the retrievedActivities array by ID
	///////////////////////////////////////////////////////////////////////////////////////////////
	GetActivityInfo:function( activityID )
	{
		if( !ActivityMap.retrievedActivities )
			return null;
		
		// Go through the retrieved activities and see if any matches the passed-in ID
		for( var activityIndex = 0; activityIndex < ActivityMap.retrievedActivities.length; ++activityIndex )
		{
			// If this is the activity to be found
			if( ActivityMap.retrievedActivities[activityIndex].activityID == activityID )
				return ActivityMap.retrievedActivities[activityIndex];
		}
		
		// If the code reached this point then the ID is unknown
		return null;
	},
	
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	// Center the map on a passed in address
	///////////////////////////////////////////////////////////////////////////////////////////////
	CenterOnAddress:function( addressValue )
	{
		// If a geocoding is already in the works then do nothing
		if( ActivityMap.isPerformingGeocode )
			return;
		ActivityMap.isPerformingGeocode = true;
		
		// Retrieve location information, pass it to OnGeocoded()
		var geocoder = new google.maps.ClientGeocoder();
		geocoder.getLocations( addressValue, ActivityMap.OnGeocoded );
	},
	
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	// Handles the response from a geocoding request
	///////////////////////////////////////////////////////////////////////////////////////////////
	OnGeocoded:function( response )
	{
		ActivityMap.isPerformingGeocode = false;
		
		// If the address was invalid
		if( response.Status.code != 200 )
		{
			// If there is an address search result function then call it with a fail parameter
			if( ActivityMap.OnAddressGeolocated )
				ActivityMap.OnAddressGeolocated( null );
			return;
		}
		
		// Retrieve the object
		var place = response.Placemark[0];

		// Retrieve the latitude and longitude
		var point = new google.maps.LatLng(place.Point.coordinates[1],
											place.Point.coordinates[0]);

		var zoomLevel = 15;
		if( place.AddressDetails )
			zoomLevel = GetZoomFromGeocodeAccuracy( place.AddressDetails.Accuracy );
		
		// Center the map on this point
		ActivityMap.map.setCenter( point, zoomLevel );
		
		// If the placeable marker is static then move it with the view
		if( !PlaceableMarker.GetDraggable() )
			PlaceableMarker.CenterMarkerOnMap();
			
		// Allow a field to retrieve the formatted address
		if( place.address && ActivityMap.OnAddressGeolocated )
			ActivityMap.OnAddressGeolocated( place.address );
	}
};


///////////////////////////////////////////////////////////////////////////////////////////////////
// Initialize the Google map object
///////////////////////////////////////////////////////////////////////////////////////////////////
function LoadMap( showActivites, enablePanZoom, enableMouseWheel )
{
	// Create the map object using a <div> as the placeholder
	ActivityMap.map = new google.maps.Map2( YAHOO.util.Dom.get("main_map") );
	
	// Store if activities are shown
	ActivityMap.showActivites = showActivites;
	
	// Set pan, zoom, and mouse wheel support
	SetPanZoomEnable( enablePanZoom, enableMouseWheel );
	
	// Make zooming smooth
	ActivityMap.map.enableContinuousZoom();
	
	// Create the placeable marker, invisible by default
	var INITIAL_CENTER_PT = new google.maps.LatLng(37.370157,-96.679687);
	var options = { draggable:true,
					clickable:false,
					autoPan:true };
	ActivityMap.placeableMarker = new google.maps.Marker(INITIAL_CENTER_PT, options);
	
	// Hookup the events
	google.maps.Event.addListener( ActivityMap.map, "zoomend", OnMapZoom );			
	google.maps.Event.addListener( ActivityMap.map, "moveend", OnMapViewChange );
	google.maps.Event.addListener( ActivityMap.placeableMarker, "dragend", PlaceableMarker.OnMarkerMoved );
	google.maps.Event.addListener( ActivityMap.map, "click", OnMapActivityMarkerClicked );

	// Center the map on a random location
	ActivityMap.map.setCenter(INITIAL_CENTER_PT, 4);
	
	// Add the placeable marker
	ActivityMap.map.addOverlay( ActivityMap.placeableMarker );
	if( PlaceableMarker.visible )
		ActivityMap.placeableMarker.show();
	else
		ActivityMap.placeableMarker.hide();
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// Set the controls that will be used to convey information about the status of an activity
// search or address lookup to the user
///////////////////////////////////////////////////////////////////////////////////////////////////
function SetStatusCtrlIDs( imgCtrlID, msgCtrlID, resultCtrlID, onActivitySelJS )
{
	ActivityMap.statusImgCtrl = YAHOO.util.Dom.get(imgCtrlID);
	ActivityMap.statusMsgCtrl = YAHOO.util.Dom.get(msgCtrlID);
	ActivityMap.resultCtrl = YAHOO.util.Dom.get( resultCtrlID );
	ActivityMap.onActivitySelectStr = onActivitySelJS;
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// Set the messages to display the results of an activity search
///////////////////////////////////////////////////////////////////////////////////////////////////
function SetActivityMsgs( oneFound, manyFound, noneFound, errorOnFind, tooManyFound )
{
	ActivityMap.activityMsgs[ActivityMap.MSG_ONE] = oneFound;
	ActivityMap.activityMsgs[ActivityMap.MSG_MANY] = manyFound;
	ActivityMap.activityMsgs[ActivityMap.MSG_NONE] = noneFound;
	ActivityMap.activityMsgs[ActivityMap.MSG_ERROR] = errorOnFind;
	ActivityMap.activityMsgs[ActivityMap.MSG_TOOMANY] = tooManyFound;
	
	ActivityMap.activityMsgsSet = true;
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// Set if the map can be panned and zoomed
///////////////////////////////////////////////////////////////////////////////////////////////////
function SetPanZoomEnable( enabled, enableMouseWheel )
{
	// If pan/zoom is enabled
	if( enabled )
	{
		if( !ActivityMap.map.draggingEnabled() )
			ActivityMap.map.enableDragging();
		
		if( ActivityMap.mapPanZoomControl == null )
		{
			if( enableMouseWheel )
				ActivityMap.mapPanZoomControl = new google.maps.SmallMapControl();
			else
				ActivityMap.mapPanZoomControl = new google.maps.LargeMapControl();
				
			ActivityMap.map.addControl( ActivityMap.mapPanZoomControl );
		}
		
		if( ActivityMap.mapTypeControl == null )
		{
			ActivityMap.mapTypeControl = new google.maps.MapTypeControl();
			ActivityMap.map.addControl( ActivityMap.mapTypeControl );
		}
		
		if( !ActivityMap.map.scrollWheelZoomEnabled() && enableMouseWheel )
			ActivityMap.map.enableScrollWheelZoom();
	}
	// Else panning and zooming is disabled
	else
	{
		if( ActivityMap.map.draggingEnabled() )
			ActivityMap.map.disableDragging();
		
		if( ActivityMap.mapPanZoomControl != null )
		{
			ActivityMap.map.removeControl( ActivityMap.mapPanZoomControl );
			ActivityMap.mapPanZoomControl = null;
		}
		
		if( ActivityMap.mapTypeControl != null )
		{
			ActivityMap.map.removeControl( ActivityMap.mapTypeControl );
			ActivityMap.mapTypeControl = null;
		}
		
		if( ActivityMap.map.scrollWheelZoomEnabled() && enableMouseWheel )
			ActivityMap.map.disableScrollWheelZoom();		
	}
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// An event handler invoked when the zoom level is changed on the map
///////////////////////////////////////////////////////////////////////////////////////////////////
function OnMapZoom( oldLevel, newLevel )
{
	OnMapViewChange();
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// An event handler invoked when the map is panned to a new location
///////////////////////////////////////////////////////////////////////////////////////////////////
function OnMapViewChange()
{
	// If activities are not shown then do nothing
	if( !ActivityMap.showActivites )
		return;
		
	// If a request is already being processed
	if( ActivityMap.isPerformingSearch )
	{
		// Flag that the search should run again when done
		ActivityMap.hasPendingSearch = true;
		return;
	}
	
	// Get the visible bounds
	var bounds = ActivityMap.map.getBounds();
	var north = bounds.getNorthEast().lat();
	var east = bounds.getNorthEast().lng();
	var south = bounds.getSouthWest().lat();
	var west = bounds.getSouthWest().lng();
	
	// Build the URL to retrieve activities
	var activitiesUrl = ActivityMap.rootPathUrl + "Manage/GetActivitiesAjax.aspx?n=";
	activitiesUrl += north + "&e=";
	activitiesUrl += east + "&s=";
	activitiesUrl += south + "&w=";
	activitiesUrl += west;
	
	if( ActivityMap.statusImgCtrl != null )
		ActivityMap.statusImgCtrl.style.visibility = "visible";
	
	// Start the request to view activities
	ActivityMap.isPerformingSearch = true;
	GDownloadUrl( activitiesUrl, OnGetActivitiesInView );
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// An event handler invoked when the call to GDownloadUrl to retrieve activities has
// completed
///////////////////////////////////////////////////////////////////////////////////////////////////
function OnGetActivitiesInView( data, responseCode )
{
	if( ActivityMap.statusImgCtrl != null )
		ActivityMap.statusImgCtrl.style.visibility = "hidden";
		
	// If the response code is invalid or the handler function fails
	if( responseCode != 200 || !HandledReturnedActivities( data ) )
	{
		if( ActivityMap.statusMsgCtrl != null && ActivityMap.activityMsgsSet )
			ActivityMap.statusMsgCtrl.innerHTML = ActivityMap.activityMsgs[ActivityMap.MSG_ERROR];
	}
	
	// Mark the search as done and rerun if there is a pending search
	ActivityMap.isPerformingSearch = false;
	if( ActivityMap.hasPendingSearch )
	{
		ActivityMap.hasPendingSearch = false;
		OnMapViewChange();
	}
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// Handle a valid response from the server when retrieving activityes
///////////////////////////////////////////////////////////////////////////////////////////////////
function HandledReturnedActivities( data )
{
	// Download the data in data.xml and load it on the map. The format we
	// expect is:
	// <activities>
	//   <activity lat="37.441" lng="-122.141"/>
	//   <activity lat="37.322" lng="-121.213"/>
	// </activities>
	
	// Erase all of the currently loaded activities on the map except for the one displaying the
	// info window
	if( ActivityMap.retrievedActivities )
	{
		for( var actIndex = 0; actIndex < ActivityMap.retrievedActivities.length; ++actIndex )
		{
			// If this is not the activity currently displayed in an info window
			//if( ActivityMap.retrievedActivities[actIndex] != ActivityMap.displayedActivity )
				ActivityMap.map.removeOverlay( ActivityMap.retrievedActivities[actIndex].marker );
		}
		if( PlaceableMarker.visible )
			ActivityMap.placeableMarker.show();
		else
			ActivityMap.placeableMarker.hide();
			
		// All markers were unloaded so clear the list of activities
		ActivityMap.retrievedActivities = null;
	}
	
	var activities = null;
	
	try
	{
		// Parse the returned XML
		var xml = GXml.parse(data);
		activities = xml.documentElement.getElementsByTagName("activity");
	}
	catch( error )
	{
		return false;
	}
	
	// Go through the activities
	var displayedActivityNewMarker = null;
	var fullResultsHtml = "";
	ActivityMap.retrievedActivities = new Array( activities.length );
	for (var actIndex = 0; actIndex < activities.length; ++actIndex )
	{
		var curActivity = activities[actIndex];
		
		// Get the rating value
		var avgRating = parseFloat( curActivity.getAttribute("avgrating") );
		
		// Get the icon for this activity
		var activiyIcon = new google.maps.Icon(G_DEFAULT_ICON);
		if( avgRating > 3.33 )
			activiyIcon.image = ActivityMap.rootPathUrl + "img/markers/green_";
		else if( avgRating > 1.66 )
			activiyIcon.image = ActivityMap.rootPathUrl + "img/markers/yellow_";
		else
			activiyIcon.image = ActivityMap.rootPathUrl + "img/markers/red_";
		
		// Initialize the marker icon
		activiyIcon.image += (actIndex + 1).toString() + ".png";
		activiyIcon.shadow = ActivityMap.rootPathUrl + "img/markers/shadow.png";
		activiyIcon.iconSize = new GSize(35, 35);
		activiyIcon.shadowSize = new GSize(52, 35);
		activiyIcon.iconAnchor = new GPoint(17, 33);
		activiyIcon.infoWindowAnchor = new GPoint(5, 1);

		// Get the point on the map for this activity
		var activityLoc = new google.maps.LatLng(parseFloat(curActivity.getAttribute("lat")),
								parseFloat(curActivity.getAttribute("lng")));
		
		// Add the marker
		var options = { title:curActivity.getAttribute("name"), icon:activiyIcon };
		var newMarker = new google.maps.Marker(activityLoc, options);
		newMarker.activityID = parseInt( curActivity.getAttribute("activityid") );
		ActivityMap.map.addOverlay( newMarker );
		
		// Store the object
		var newActivityObject = { activityID : parseInt(curActivity.getAttribute("activityid")),
									name : curActivity.getAttribute("name"),
									whatyoudo : curActivity.getAttribute("whatyoudo"),
									numratings : curActivity.getAttribute("numratings"),
									rating : curActivity.getAttribute("avgrating"),
									pos : activityLoc,
									marker : newMarker };
		ActivityMap.retrievedActivities[actIndex] = newActivityObject;
											
		// If this is the activity with the info window open
		if( ActivityMap.displayedActivity
			&& ActivityMap.retrievedActivities[actIndex].activityID == ActivityMap.displayedActivity.activityID )
		{
			displayedActivityNewMarker = ActivityMap.retrievedActivities[actIndex];
		}
											
		// Get the result <li> html
		fullResultsHtml += GetLIHTMLForActivity( newActivityObject );
	}
	
	// If the displayed activity is not visible
	if( ActivityMap.displayedActivity )
	{
		// If the displayed activity is no longer visible
		if( !displayedActivityNewMarker )
		{
			ActivityMap.map.closeInfoWindow();			
		}
		else
		{
			// Store the new activity
			ActivityMap.displayedActivity = displayedActivityNewMarker;
		}
	}
	
	// If there is a result control
	if( ActivityMap.resultCtrl != null )
	{
		if( fullResultsHtml.length == 0 )
		{
			ActivityMap.resultCtrl.style.display = "none";
		}
		else
		{
			// Close the ordered list of results and display them
			fullResultsHtml = "<ul>" + fullResultsHtml + "</ul>";
			
			ActivityMap.resultCtrl.innerHTML = fullResultsHtml;
			ActivityMap.resultCtrl.style.display = "block";
		}
	}
	
	// If there is a control to receive a result message
	if( ActivityMap.statusMsgCtrl != null && ActivityMap.activityMsgsSet )
	{
		var resultMsg = "";
		
		if( activities.length <= 0 )
			resultMsg = ActivityMap.activityMsgs[ActivityMap.MSG_NONE];
		else if( activities.length == 1 )
			resultMsg = ActivityMap.activityMsgs[ActivityMap.MSG_ONE];
		else if( activities.length >= ActivityMap.maxViewableActivites )
			resultMsg = ActivityMap.activityMsgs[ActivityMap.MSG_TOOMANY];
		else
			resultMsg = ActivityMap.activityMsgs[ActivityMap.MSG_MANY].replace( "{0}", activities.length.toString() );
		
		// Display the message
		ActivityMap.statusMsgCtrl.innerHTML = resultMsg;
	}
	
	// Return success
	return true;
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// An event handler invoked when the user clicks on an activity marker
///////////////////////////////////////////////////////////////////////////////////////////////////
function OnMapActivityMarkerClicked( overlay, point )
{
	if( overlay == null || !overlay.activityID )
		return;
	OpenActivityMarker( overlay.activityID );
	
	// Call any special handlers
	if( ActivityMap.onActivitySelectStr )
		eval( ActivityMap.onActivitySelectStr + "( " + overlay.activityID + " )" );
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// Display a particular activity marker info window
///////////////////////////////////////////////////////////////////////////////////////////////////
function OpenActivityMarker( activityID )
{
	// If there is an activity info window currently displayed
	if( ActivityMap.displayedActivity )
	{
		// Close the current window
		ActivityMap.map.closeInfoWindow();
		ActivityMap.displayedActivity = null;
	}
	
	// Get the selected activity
	ActivityMap.displayedActivity = ActivityMap.GetActivityInfo( activityID );
	if( !ActivityMap.displayedActivity )
		return;
		
	// Generate the HTML used in the info window
	var markerHtml = "<div><h1><a href=\"" + GetViewActivityUrl( ActivityMap.displayedActivity.activityID );
	markerHtml += "\">" + ActivityMap.displayedActivity.name + "</a></h1><p>" + ActivityMap.displayedActivity.whatyoudo + "</p></div>";
	
	ActivityMap.map.openInfoWindowHtml( ActivityMap.displayedActivity.pos, markerHtml );
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// Get the <li> item used for the result view for a specific activity
///////////////////////////////////////////////////////////////////////////////////////////////////
function GetLIHTMLForActivity( activity )
{
	// If there is a function to call when an activity is selected
	var functionCall = "";
	var clickCode = "";
	if( ActivityMap.onActivitySelectStr )
	{
		functionCall = "OpenActivityMarker(" + activity.activityID + " ); ";
		functionCall += ActivityMap.onActivitySelectStr + "( " + activity.activityID + " )";
		clickCode = " onclick='" + functionCall + ";' ";
	}
	
	// Open the list item
	var retHtml = "<li" + clickCode + ">";
	
	// Add the title
	retHtml += "<a href='javascript:" + functionCall + "'><table cellpadding='0' cellspacing='0'><tr><td rowspan='2' style='width:100%;'><h1>";
	retHtml += activity.whatyoudo + "</h1></td><td style='width:60px;'>";
	
	// Add the rating
	retHtml += "<div style='width:60px; height:12px;'>";
	for( var curRatingIndex = 0; curRatingIndex < 5; ++curRatingIndex )
	{
		var imgPath = ActivityMap.rootPathUrl + "img/star_small_on.gif";
		if( curRatingIndex >= activity.rating )
			imgPath = ActivityMap.rootPathUrl + "img/star_small_off.gif";
			
		retHtml += "<img" + clickCode + "alt=\"star rating\" src='" + imgPath + "' width='12px' height='12px' />";
	}
	retHtml += "</div></td></tr><tr><td style='width:60px;'>";
	retHtml += "<em" + clickCode + ">" + activity.numratings + " votes</em>";
	
	retHtml += "</td></tr></table></a></li>";
	
	return retHtml;
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// Get the URL to view an activity's details
///////////////////////////////////////////////////////////////////////////////////////////////////
function GetViewActivityUrl( activityID )
{
	return ActivityMap.rootPathUrl + "ViewActivity.aspx?ActivityID=" + activityID.toString();
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// Get the zoom level based on the geocoding accuracy
///////////////////////////////////////////////////////////////////////////////////////////////////
function GetZoomFromGeocodeAccuracy( accuracy )
{
	// An accuracy of 1 is country-level accuracy while 8 is street address accuracy
	// A zoom of 0 is zoomed far out, while 17 is the most zoomed-in as of writing this November
	// 24, 2007
	if( accuracy < 2 )
		return 4;
	else if( accuracy < 7 )
		return (2 * accuracy) + 3;
		
	return 17;
}


///////////////////////////////////////////////////////////////////////////////////////////////////
// The placeable marker "namespace"
///////////////////////////////////////////////////////////////////////////////////////////////////
PlaceableMarker = {

	///////////////////////////////////////////////////////////////////////////////////////////////
	// Set if the marker is visible
	///////////////////////////////////////////////////////////////////////////////////////////////
	SetVisible:function( isVisible )
	{
		PlaceableMarker.visible = isVisible;
		
		if( isVisible )
			ActivityMap.placeableMarker.show();
		else
			ActivityMap.placeableMarker.hide();
	},
	
	visible:false,
	
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	// Set if the user can move the marker
	///////////////////////////////////////////////////////////////////////////////////////////////
	SetDraggable:function( isEnabled )
	{
		if( isEnabled )
			ActivityMap.placeableMarker.enableDragging();
		else
			ActivityMap.placeableMarker.disableDragging();
	},
	
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	// Get if the user can move the marker
	///////////////////////////////////////////////////////////////////////////////////////////////
	GetDraggable:function()
	{
		return ActivityMap.placeableMarker.draggingEnabled();
	},
	
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	// Set the location of the marker
	///////////////////////////////////////////////////////////////////////////////////////////////
	SetLocation:function( latitude, longitude )
	{
		ActivityMap.placeableMarker.setLatLng( new google.maps.LatLng(latitude,longitude) );
	},
	
	GetLat:function(){ return ActivityMap.placeableMarker.getLatLng().lat(); },
	GetLon:function(){ return ActivityMap.placeableMarker.getLatLng().lng(); },
	
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	// Center the map on the marker
	///////////////////////////////////////////////////////////////////////////////////////////////
	CenterMapOnMarker:function()
	{
		ActivityMap.map.setCenter( ActivityMap.placeableMarker.getLatLng() );
	},
	
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	// Move this marker to the center of the map
	///////////////////////////////////////////////////////////////////////////////////////////////
	CenterMarkerOnMap:function()
	{
		ActivityMap.placeableMarker.setLatLng( ActivityMap.map.getCenter() );
		PlaceableMarker.OnMarkerMoved();
	},
	
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	// An event handler invoked when the marker is moved
	///////////////////////////////////////////////////////////////////////////////////////////////
	OnMarkerMoved:function()
	{
		eval( PlaceableMarker.MarkerMovedCallback );
	},
	
	MarkerMovedCallback:""
};