/* CONSTANTS */
var VEI_ZOOM_MAX = 17;	// Maximum map zoom value (closest)
var VEI_ZOOM_MIN = 7;	// Minimum map zoom value (farthest)
var VEI_SHOWPOI = 0;	// Not used as of 8/14/07; true when points of interest should be displayed on the map
var VEI_CLASSID = 0;	// Not used as of 8/14/07; GMap-specific
var VEI_CSSCLASS_AUTOCB = 'routetoggle';	// CSS class assigned to the DIV that contains all automatically-inserted route-toggling checkboxes
var VEI_CSSCLASS_AUTOCB_SPAN = 'toggleroute';	// CSS class for each span within 'routetoggle' DIV
var VEI_CSSCLASS_SECTIONUL = 'mapsectionmenu';	// CSS class for the list of all predefined map sections 
var VEI_MAP_ZOOMCONTROL_LEFT = 5;			// Not used as of 8/14/07: GMap-specific; Number of pixels from the left side of the map to place the zoom control
var VEI_DEFAULT_LONG = -82.859332; 		// Default longitude coordinate
var VEI_DEFAULT_LAT = 40.245142; 		// Default latitude coordinate
var VEI_DEFAULT_ZOOM = 7;				// Default map zoom level; VE zoom level ranges from 1(out) to 19(in)
var VEI_DEFAULT_CONTAINER = 'mainMap';	// ID of default HTML container for visual map
var VEI_ROUTE_TRAIL1 = {'rid':1, 'inputid':'auto', 'visible':true, 'name':'Route 1'};	// Definition of route 1 (paved trail)
var VEI_ROUTE_TRAIL2 = {'rid':2, 'inputid':'auto', 'visible':true, 'name':'Route 2'};	// Definition of route 2 (street)
var VEI_ROUTE_TRAIL3 = {'rid':3, 'inputid':'auto', 'visible':true, 'name':'Route 3'};	// Definition of route 3 (open)
var VEI_ROUTE_SECTIONS = [
	{ 'summary':"Northern Leg (Cleveland to Clinton)", 'center':new VELatLong(41.107025524835285,-81.60369873046875), 'zoom':9 }, 
	{ 'summary':"Heart of Ohio (Clinton to Mt Vernon)", 'center':new VELatLong(40.58058466412763,-82.10357666015625), 'zoom':9 },
	{ 'summary':"Southern Leg (Mt Vernon to Cincinatti)", 'center':new VELatLong(39.74521015328692,-83.3367919921875), 'zoom':8 }
];

/**
 * @author Michael Tipton
 * @class VEInterface
 * @requires Microsoft Virtual Earth 5.0 Map API
 * @requires jQuery library (jquery.com)
 * @param {Object} obj_init Initialization parameters
 * 		.container	{String} Id of the DOM container for the map
 * 		.routes		{Array} List of definitions of all available route overlays
 * 			.rid		{Integer} Numeric id of the route
 * 			.inputid	{String} Id of the input that indicates visibility
 * 			.input		{DOM Object} Reference to the checkbox input (acquired during init())
 * 			.visible	{Boolean} True when the route overlay is visible
 * 			.line		{Object} GPolyline definition
 * 			.points		{Array} List of all GLatLng points
 * 			.style		{Object}
 *		.autocheckboxes	{Boolean} True when the checkboxes for each of the routes should be automatically generated and inserted directly after the VEMap container
 */
function VEInterface(obj_init)
{
	//##########################################################################
	// VEInterface Object PROPERTIES
	/**
	 * Save the initialization object for future reference
	 * @type Object
	 */
	this.obj_init = (typeof(obj_init)!="undefined" ? obj_init : {'container':VEI_DEFAULT_CONTAINER, 'routes':[VEI_ROUTE_TRAIL1,VEI_ROUTE_TRAIL2,VEI_ROUTE_TRAIL3], 'autocheckboxes':true});
	/** 
	 * DOM Container for the visual VEMap
	 * @type DOM Object 
	 */
	this.dom_container;
	/**
	 * Reference to the instance of the VEMap object
	 * @type Object
	 */
	this.obj_map;
	/**
	 * All 'routes' that may be displayed on this map instance
	 * @type Array
	 */
	this.arr_routes = this.obj_init.routes;
	/**
	 * Associative array/object that basically lets us quickly find the index of a specific route ID within arr_routes.
	 * Very similar to the Mozilla Array.indexOf() method. After init() has executed,
	 * should be able to find the index by using obj_routeHash[routeId] to get 
	 * the numeric index of that route within arr_routes.
	 */
	this.obj_routeHash = new Object();
	/**
	 * Property to contain a reference to the asynchronous HTTP request made for 
	 * updates of either points of interest or routes. Can be used to abort an 
	 * existing request 
	 */
	this.obj_ajaxRequest;
	/**
	 * Contains a reference to the container for all automatically-generated checkboxes.
	 * @type DOM Object
	 */
	this.dom_autoCheckboxes = false;
	/**
	 * Reference to the DOM element that lists all predefined map sections (i.e., Northern Leg, etc)
	 */
	this.dom_ulMapSections = false;
	
	//##########################################################################
	// MODEL methods
	/**
	 * Intializes the VE map object instance, gets reference to map DOM container
	 */
	this.init = function()
	{
		try
		{
			// Acquire references to DOM container and VEMap elements
			var obj_this = this;
			this.dom_container = document.getElementById(this.obj_init.container);	// Save reference to DOM object
			try
			{
				this.obj_map = new VEMap(this.obj_init.container);
				this.obj_map.SetDashboardSize(VEDashboardSize.Small);
				this.obj_map.LoadMap();
			} catch(obj_vemapError) { alert("There was an error while loading the trail map"); }
			this.obj_map.AttachEvent("onchangeview",function() {obj_this.updateVisibleRoutes()})
			
			// Insert the container for the route-visibility-toggling checkboxes, if applicable
			if (typeof(this.obj_init.autocheckboxes)!="undefined")
			{
				if (this.obj_init.autocheckboxes==true)
				{
					this.dom_autoCheckboxes = document.createElement("div");
					this.dom_autoCheckboxes.className = VEI_CSSCLASS_AUTOCB;
					$(this.dom_container).after(this.dom_autoCheckboxes);
				}
			}
			
	    	// Connect all routes to their visibility control inputs, if applicable
	    	for (var r=0; r<this.arr_routes.length; r++)
	    	{
	    		// There is no input connected to this route overlay's visibility
	    		if (this.arr_routes[r].inputid=='')
	    		{
	    			this.arr_routes[r].input = false;
	    		}
				else if (this.arr_routes[r].inputid=='auto')	// Automatically insert the checkbox
				{
					var dom_span = document.createElement("span");
					dom_span.className = VEI_CSSCLASS_AUTOCB_SPAN;
					var dom_cb = document.createElement("input");
					dom_cb.setAttribute('type','checkbox');
					dom_cb.setAttribute('id','cbRoute'+r);
					dom_cb.setAttribute('route',r);
					this.arr_routes[r].input = dom_cb;
					var dom_label = document.createElement("label");
//					dom_label.style.borderBottom = "solid 2px #000";
//					dom_label.style.fontWeight = "bold";
//					dom_label.style.padding = "1px";
					dom_label.setAttribute('for','cbRoute'+r);
					dom_label.innerHTML = 'Route ' + r;
					dom_span.appendChild(dom_cb);
					dom_span.appendChild(dom_label);
					this.dom_autoCheckboxes.appendChild(dom_span);
					dom_cb.setAttribute('checked',true);
				}
	    		else	// The id of the checkbox is provided
	    		{
		    		this.arr_routes[r].input = document.getElementById('cb' + this.arr_routes[r].inputid);
	    		}

	    		if (this.arr_routes[r].input!==false) 
	    		{
		    		$(this.arr_routes[r].input).click( function(obj_event) {obj_this.clickRouteVisibility(obj_event)} );
	    		}
	    		// Create entry in obj_routeHash for this route
	    		this.obj_routeHash[this.arr_routes[r].rid] = r;
	    	};
			
    		this.loadMapSectionsMenu();
/*	    	
	    	this.checkRouteVisibility();
//	    	this.updateVisibleRoutes();
*/

	    	this.mapDefaultCenter();	// Automatically calls updateVisibleRoutes(), since it is essentially a movement of the viewing area

		} catch(obj_err) { alert("VEInterface.init()\nERROR: " + obj_err.message); }
	};
	
	//--------------------------------------------------------------------------
	/**
	 * Generates a VEMap Polyline object for each segment in the route, given the points and style of a route, and saves
	 *  that object under the appropriate route.
	 * @param {Integer} int_route Index of the route (within arr_routes) for which a Polyline should be generated
	 */
	this.generateRoute = function(int_route)
	{
		var obj_layer = new VEShapeLayer();
		var obj_rgb = this.convertColorHexToRGB(this.arr_routes[int_route].style.color);
		var obj_color = new VEColor(obj_rgb.red,obj_rgb.green,obj_rgb.blue,this.arr_routes[int_route].style.opacity);
		for (var s=0; s<this.arr_routes[int_route].segmentpoints.length; s++)
		{
			// Check that there are visible points for this segment in this viewport
			if (this.arr_routes[int_route].segmentpoints[s].length>0)
			{
				// Catch a VEShape exception per segment, not route, so that the 
				//  other segments will show up even if one has an error
				try
				{
					// If there are less than 2 points for this segment, copy the first one
					if (this.arr_routes[int_route].segmentpoints[s].length<2)
					{
						var obj_pointCopy = new VELatLong(this.arr_routes[int_route].segmentpoints[s][0].Latitude,this.arr_routes[int_route].segmentpoints[s][0].Longitude)
						this.arr_routes[int_route].segmentpoints[s].push(obj_pointCopy);
					}
					var obj_poly = new VEShape(VEShapeType.Polyline,this.arr_routes[int_route].segmentpoints[s]);
					obj_poly.SetLineColor(obj_color);
					obj_poly.SetLineWidth(this.arr_routes[int_route].style.width);
					obj_poly.HideIcon();
	 				this.arr_routes[int_route].segmentlines[s] = obj_poly;
	 				obj_layer.AddShape(obj_poly);
				} catch(obj_err) { alert("VEInterface.generateRoute()\nERROR: " + obj_err.message); }
			}
		}
		this.arr_routes[int_route].shapelayer = obj_layer;
	};
	
	//--------------------------------------------------------------------------
	/**
	 * Converts the hex string value of a color to an RGB object of numeric values
	 * @param {String} str_hex
	 * @type {Object}
	 */
	this.convertColorHexToRGB = function(str_hex)
	{
		var obj_rgb = { 'red':0, 'green':0, 'blue':0 };
		obj_rgb.red = parseInt(str_hex.substr(0,2),16);
		obj_rgb.green = parseInt(str_hex.substr(2,2),16);
		obj_rgb.blue = parseInt(str_hex.substr(4,2),16);
		return obj_rgb;
	}
	
	//##########################################################################
	// VIEW methods
	/**
	 * Displays the shapelayer for the specified route
	 */
	this.showRoute = function(int_route)
	{
		try
		{
			if (typeof(this.arr_routes[int_route])!="undefined")
			{
				if (typeof(this.arr_routes[int_route].shapelayer)!="undefined")
				{
					this.obj_map.AddShapeLayer(this.arr_routes[int_route].shapelayer);
//					this.arr_routes[int_route].shapelayer.Show();
					
				}
/*				
				// Iterate through all segments for this route
				for (var s=0; s<this.arr_routes[int_route].segmentlines.length; s++)
				{
					if (typeof(this.arr_routes[int_route].segmentlines[s])!="undefined")
					{
						this.obj_map.AddShape(this.arr_routes[int_route].segmentlines[s]);
					}
				}
*/				// Set the appropriate color for the route toggle checkmark line
 				if (typeof(this.arr_routes[int_route].input)!="undefined")
 				{
 					var dom_lbl = $(this.arr_routes[int_route].input).siblings("label").get(0);
 					dom_lbl.style.borderColor = '#' + this.arr_routes[int_route].style.color;
 					dom_lbl.innerHTML = this.arr_routes[int_route].name;
 				}
 			}
		}
		catch(obj_err)
		{
//			var obj_this = this;
//			window.setTimeout(function() { obj_this.showRoute(int_route) },500);
			alert("VEInterface.showRoute()\nERROR: " + obj_err.message); 
		}
	};
	
	//--------------------------------------------------------------------------
	/**
	 * Wrapper for the GMap2.removeOverlay method
	 */
	this.deleteRoute = function(int_route)
	{
		try
		{
			if (typeof(this.arr_routes[int_route])!="undefined")
			{
				if (typeof(this.arr_routes[int_route].shapelayer)!="undefined")
				{
					this.obj_map.DeleteShapeLayer(this.arr_routes[int_route].shapelayer);
//					this.arr_routes[int_route].shapelayer.DeleteAllShapes();
//					this.obj_map.DeleteShape(this.arr_routes[int_route].line);
				}
			}
		} catch(obj_err) { alert("VEInterface.deleteRoute()\nERROR: " + obj_err.message); }
	};
	
	//--------------------------------------------------------------------------
	/**
	 * Wrapper for the GMap2.setCenter method
	 */
	this.mapDefaultCenter = function()
	{
//		this.obj_map.setCenter(new GLatLng(VEI_DEFAULT_LAT,VEI_DEFAULT_LONG));
		this.obj_map.SetCenterAndZoom(new VELatLong(VEI_DEFAULT_LAT,VEI_DEFAULT_LONG),VEI_DEFAULT_ZOOM);		
	};
	
	//--------------------------------------------------------------------------
	/**
	 * 
	 */
	this.goToMapSection = function(int_section)
	{
		if (typeof(int_section)!="undefined")
		{
			try
			{
				this.obj_map.SetCenterAndZoom(VEI_ROUTE_SECTIONS[int_section].center,VEI_ROUTE_SECTIONS[int_section].zoom);
			} catch(obj_err) { alert("VEInterface.goToMapSection()\ERROR: " + obj_err.message); }
		}
	}
	
	//--------------------------------------------------------------------------
	/**
	 * Displays a list of all map sections defined in VEI_ROUTE_SECTIONS
	 */
	this.loadMapSectionsMenu = function()
	{
		var dom_ul = document.createElement("ul"); 
		dom_ul.className = VEI_CSSCLASS_SECTIONUL;
		var obj_this = this;
		for (var s=0; s<VEI_ROUTE_SECTIONS.length; s++)
		{
			var dom_li = document.createElement("li");
			var dom_a = document.createElement("a");
			dom_a.setAttribute('href','#');
			dom_a.setAttribute('mapsection',s);
			dom_a.innerHTML = VEI_ROUTE_SECTIONS[s].summary;
			dom_li.innerHTML = ">";
			dom_li.appendChild(dom_a);
			$(dom_a).click(function() 
				{ 
					var int_section = this.getAttribute('mapsection');
					obj_this.goToMapSection(int_section); 
				})
			dom_ul.appendChild(dom_li);
		}
		$(this.dom_autoCheckboxes).after(dom_ul);	// Insert the UL after the list of trail surface types
		this.dom_ulMapSections = dom_ul;	// Save a reference to this UL at the object instance scope
	}
	
	//##########################################################################
	// CONTROLLER methods
	/**
	 * Refreshed route definitions from server. Called after map is moved or zoom 
	 * is changed.
	 * @return void
	 */
	this.updateVisibleRoutes = function()
	{
		try
		{
			// Determine all routes that are visible
			var arr_visibleRoutes = new Array(); 
			// Create an array that has all visible route IDs
			for (var r=0; r<this.arr_routes.length; r++)
			{
				if (this.arr_routes[r].visible===true) arr_visibleRoutes.push(this.arr_routes[r].rid);
			}
			
			// Only proceed with server calls if there is at least one visible route
			if (arr_visibleRoutes.length>0)
			{
				if (typeof(this.obj_ajaxRequest)!="undefined")
				{
					// If there is already a map update request in process, abort it
					if (this.obj_ajaxRequest.readyState!=0 && this.obj_ajaxRequest.readyState!=4)
					{
						this.obj_ajaxRequest.abort();
					}
				}				
				
				// jQuery AJAX configuration
				var obj_this = this; // Alias used in assignment of event handlers
				// Definition of the asynchronous request object for retrieving updated route coordinates
				var obj_ajax = 
				{
					'dataType':"xml", 
					'type':"POST",
					'error':function(obj_request,str_error,obj_exception) {obj_this.ajaxError(obj_request,str_error,obj_exception);}, 
					'success':function(obj_data) {obj_this.ajaxReceiveRoutePoints(obj_data);},
					'url':"/WebServices/maproute.asmx/RetrieveRouteData"
				};
//					'url':"routedata.xml"
//					'url':"WebServices/maproute.asmx/RetrieveRouteData"
		        
		        // Map Bounds
		        var obj_mapView = this.obj_map.GetMapView();
		        var int_zoomLevel = this.obj_map.GetZoomLevel();
		        
		        // (mrt) int_sampleMod is crucial for the efficient filtering of route 
		        //  points. As the user zooms OUT, more points are removed.
		        //  In an effort to make the filtering occur within the database query
		        //  as much as possible, I determined that we could essentially take 
		        //  every nth point, where n = 2 ^ (VEI_ZOOM_MAX - int_zoomLevel - 1)
		        //  The value of n is used within the [stored procedure] SELECT query to MOD the
		        //  individual unique point IDs against. The end result is that, for example,
		        //  at the zoom level of 16 (the second closest you can get), the value of 
		        //  n = 2 ^ (17 - 16 - 1) = 2 ^ 0 = 1
		        //  Then, in the WHERE clause of the query, we could say 'WHERE RouteDetailId%n=0',
		        //  which would become 'WHERE RouteDetailId%1=0', which would select every record
		        //  and do no filtering. For a zoom value of 7 (the farthest),
		        //  n = 2 ^ (17 - 7 - 1) = 2 ^ 9 = 512, so only every 512th point is used.
		        //  The goal behind the filtering is to only allow roughly 50-70 
		        //  points to be transferred from the server to the client. 
		        //  The only significant exception to these calculations is zoom level 17,
		        //  which automatically gets set to 'no filtering' when all is said and done.
		        var int_power = (VEI_ZOOM_MAX-int_zoomLevel-1>=0 ? VEI_ZOOM_MAX-int_zoomLevel-1 : 0);
		        var int_sampleMod = Math.pow(2,int_power);
		        var obj_bottomRight = obj_mapView.BottomRightLatLong;
		        var obj_topLeft = obj_mapView.TopLeftLatLong;
 
				obj_ajax.data = "RouteID=" + arr_visibleRoutes.join("&RouteID=") + "&ZoomLevel=" + int_sampleMod 
					+ "&MaxLong=" + obj_bottomRight.Longitude + "&MaxLat=" + obj_topLeft.Latitude
					+ "&MinLong=" + obj_topLeft.Longitude + "&MinLat=" + obj_bottomRight.Latitude;
				this.obj_ajaxRequest = $.ajax(obj_ajax);
			}
	   	} catch(obj_err) { alert("VEInterface.updateVisibleRoutes()\nERROR: " + obj_err.message); }
	};
	
	//--------------------------------------------------------------------------
	/**
	 * Handler for click event of the checkbox input connected to the route
	 * @param {Integer} int_route Route affected by the changed visibility selection
	 */
	this.clickRouteVisibility = function(obj_event)
	{
		try
		{
			var dom_trigger;
			if (obj_event.target)
			{
				dom_trigger = obj_event.target;
			}
			else
			{
				dom_trigger = obj_event.srcElement;
			}

			var int_route = dom_trigger.getAttribute('route');
			var bln_selected = Boolean(this.arr_routes[int_route].input.checked);
			if (typeof(this.arr_routes[int_route].shapelayer)!="undefined")
			{
				if (bln_selected)
				{
					this.arr_routes[int_route].shapelayer.Show();
				}
				else
				{
					this.arr_routes[int_route].shapelayer.Hide();
				}
				this.checkRouteVisibility();
			}
		} catch(obj_err) { alert("VEInterface.clickRouteVisibility()\nERROR: " + obj_err.message); }
	};
	
	//--------------------------------------------------------------------------
	/**
	 * Loops through all defined routes and their appropriate input elements to determine
	 * if the route should be visible or not
	 */
	this.checkRouteVisibility = function()
	{
		try
		{
			for (var v=0; v<this.arr_routes.length; v++)
			{
				// If there is no valid reference to a visibility checkbox, assume the route is always visible
				if (this.arr_routes[v].input===false)
				{
					this.arr_routes[v].visible = true;
				}
				else
				{
					this.arr_routes[v].visible = (Boolean(this.arr_routes[v].input.checked)==true ? true : false);
				}
			}
		} catch(obj_err) { alert("VEInterface.checkRouteVisibility()\nERROR: " + obj_err); }
	};
	
	//--------------------------------------------------------------------------
	/**
	 * Callback for successful ajax request for retrieval of route points
	 * @param {Object} obj_xml Successful XML server response
	 */
	this.ajaxReceiveRoutePoints = function(obj_xml)
	{
		try
		{
            // If the data is a valid XML document
            if (obj_xml.documentElement)
            {
            	// Get array of all returned routes
                var arr_routeElements = obj_xml.documentElement.getElementsByTagName("Route");
                // If there is at least one route
                if (arr_routeElements.length>0)
                {
                	// Go through all routes, parse out the lat/long coordinates, and save the ooints in route segments
                	for (var re=0; re<arr_routeElements.length; re++)
                	{
		                var obj_visual = new Object();	// Visual characteristics of the line
		                var arr_pointNodes;				// Array of all point tag elements in this route
		                
		                // Set Route characteristics
		                obj_visual.color = arr_routeElements[re].getAttribute("color");
		                obj_visual.width = arr_routeElements[re].getAttribute("width");
		                obj_visual.opacity = arr_routeElements[re].getAttribute("opacity");
		                var str_id = arr_routeElements[re].getAttribute("id");
		                
		                // Save all latitude/longitude coordinates 
		               arr_pointNodes = arr_routeElements[re].getElementsByTagName("Pt");
		                
/*		                
		                arr_points.push(new VELatLong(41.7,-83.3));	// toledo
		                arr_points.push(new VELatLong(41.51,-81.56));	// cleveland
		                arr_points.push(new VELatLong(41.06,-81.5));	// akron
		                arr_points.push(new VELatLong(40.83,-81.35));	// canton
		                arr_points.push(new VELatLong(39.98,-82.97));	// columbus
		                arr_points.push(new VELatLong(38.36,-81.63));	// charleston,wv
		                
*/
						var arr_segments = new Array();	// Array of all segments that make up this route
						var arr_points = new Array();
						
		                for (var pn=0; pn<arr_pointNodes.length; pn++)
		                {
							if (arr_pointNodes[pn].getAttribute("start")==1)
							{
//				                arr_points.push(new VELatLong(parseFloat(arr_pointNodes[pn].getAttribute("lat")),parseFloat(arr_pointNodes[pn].getAttribute("lon"))))
								arr_segments.push(arr_points);	// Save this segment
								arr_points = new Array();	// Array of all points that comprise this route segment
							}
			                arr_points.push(new VELatLong(parseFloat(arr_pointNodes[pn].getAttribute("lat")),parseFloat(arr_pointNodes[pn].getAttribute("lon"))))
		                }
		                arr_segments.push(arr_points);	// Save the last segment
		                
		                // Save the points within the arr_routes property
						var int_rIndex = this.obj_routeHash[str_id];
						this.deleteRoute(int_rIndex);	// Remove the old version of this route
						
						this.arr_routes[int_rIndex].segmentpoints = arr_segments;
						this.arr_routes[int_rIndex].segmentlines = new Array(arr_segments.length);
						this.arr_routes[int_rIndex].style = obj_visual;
						var str_rName = arr_routeElements[re].getAttribute("name");
						this.arr_routes[int_rIndex].name = (str_rName!="" ? str_rName : 'r'+re);
						
						// Draw the route overlay
						this.generateRoute(int_rIndex);
			            this.showRoute(int_rIndex);

                	}
                }
                 
            }
		} catch(obj_err) { alert("VEInterface.ajaxReceiveRoutePoints\nERROR: " + obj_err.message); }
		
	};
	
	//--------------------------------------------------------------------------
	/**
	 * Handles AJAX errors 
	 * @param {Object} obj_request
	 * @param {String} str_error
	 * @param {Object} obj_exception
	 */
	this.ajaxError = function(obj_request,str_error,obj_exception)
	{
		alert("VEInterface General AJAX ERROR: " + str_error + ' -- ' + obj_exception.message);
	};
	
}
