/*
 * jQuery MSN Virtual Earth Plugin
 * 
 * @author: Jay Morrow
 * @version: 2.0
 * @requires: jQuery 1.2.6 or later  (http://www.jquery.com)
 *			  Call to Microsoft Virtual Earth 6.1 or later.  
 *  		  Example: 
 *			  <script src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.1"></script>
 *			  NOTE:  This call must go first in the javascript order.
 *
 * @dependancies (optional): jQuery UI 1.5 or later (http://ui.jquery.com)
 *							 Accordion Plugin (Required for Categories)
 *							 Sortable Plugin (Required for Trip Planner sorting)
 *
 * The jQuery Virtual Earth Plugin uses the Microsoft Virtual Earth mapping software
 * and geoRSS formatted XML to create interactive maps that can be embedded on your 
 * site. The structure is based off of the corresponding HTML page, and the XML structure
 * has to be the same as the corresponding XML.  If you don't have anything to put in a specific
 * node, leave it empty.  But make sure each location has all of the required nodes. In future releases I 
 * may make it so you can specifiy what all of your elements are called, but until then don't change
 * any of the exisiting classes or ID's.
 * 
 * @Initiating the Map
 * $(element).msnMap();
 * This creates the default map.  Everything except for the first location (default) is hidden.
 * The list of locations are printed in the order in which they appear in the XML.  You can not 
 * use query string to pass locations from one page to another.
 *
 * @Variables
 * url : Define the location of the geoRSS xml file you are going to use. Default path: 'xml/map.xml'
 *
 * dom : Defines the ID where to place the Virtual Earth map. Default ID: 'map'
 *
 * printNames : Controls the printing of the different locations.  Required if you want to create
 *				different routes.  Default: 'true'
 *
 * categories : Places the different locations according to their types.  Creates a dropdown menu to
 *				change which list is showing. Default: 'false'
 *
 * hidePins : Hides all of the pins except for the default location.  Changing this will show all of
 *			  locations when the map loads the XML.  Default: 'true'
 *
 * sortable : Makes the Route List sortable.  YUO MUST HAVE THE jQUERY SORTABLE PPLUG IN FOR THIS FEATURE
 *			  TO WORK.  Default: 'false'
 *
 * acceptQuery : Allows you to specify a name and address and pass it via a query string to display on the
 *				 map when it loads.  The custom location will also become the first item in your Trip Planner
 *				 list. The map will still display the default location. The program will parse any spaces in 
 *				 the url correctly. If you give the query a list of ids, only those corresponding ids in the
 *				 xml will display.
 *				 Default: 'false'
 *				 Query Fields: 'name', 'address', 'ids'
 *				 'ids' format: 1,2,3,4,5....
 * clickMap : If clickMap is enabled it will allow users to right-click on the map to add a new point.  If the map 
 *			  can find an address corresponding to the point you clicked on, it will use that.  If it can't, it will
 *			  use the coordinates.  Default: 'false'
 *
 * miniMap : Adds or removes an icon that will show/hide a mini map.  By default the icon shows in the lower
 * 			 left hand cornder of the map. Default: 'false'
 *
 * kilometers : Changes the distance units to kilomters.  Default: 'false'
 *
 * dashboard : Controls the display/size of the dashboard.  
 *			   Values are 'Tiny', 'Small', 'Normal' and 'Hide'.  Default: 'Normal'
 *
 * @Examples
 * 
 * Showing all pins on map load
 * $(element).msnMap({hidePins: false});
 *
 * Create categories, make the route List sortable, and accept query strings
 * $(element).msnMap({categories: true, sortable: true, acceptQuery: true}); 
 *
 * Using query strings in the URL
 * map.php?name=Point 1&address=500 Example St. Somewhere, USA 11111
 *
 */

(function($) {
	var msn_map = null;
	var layer = null;
	var xml = null;
	var opts = null;
	var query = null;
	var ids = null;
	var cstmPoints = new Array();
	var contextPin = null;
	var counter = 0;
	
	$.fn.msnMap = function(options) {
		var defaults = {
			url : "xml/map.xml",
			dom : "map",
			printNames : true,
			categories : false,
			hidePins : true,
			sortable : false,
			acceptQuery : false,
			miniMap : false,
			kilometers : false,
			dashboard : 'Normal',
			clickMap : false
		};
		opts = $.extend(defaults, options);
		
		return this.each(function() {
			if(opts.sortable) {
				$("#route-list").sortable({ 
					items: "li", 
					revert: false, 
					handle: "span", 
					containment: '#route-list', 
					axis: 'y' 
				}); 
			}
			
			$('#map-help-toggle').toggle(function() { $('#map-help').show(); return false; },
										 function() { $('#map-help').hide(); return false; });
			
			$.get(opts.url, function(data) { 
				xml = $.mapFNC.getData(data); 
				if(opts.acceptQuery) {
					var q = window.location.search.substring(1);
					if(q.length>0) {
						query = $.mapFNC.parseQuery(q);
						if(query['ids']!=null) {
							opts.hidePins=false;
							ids = query['ids'].split(',');
						}
					}
				}

				if(opts.printNames) {
					var types = null;
					if(opts.categories) {
						types = $.mapFNC.getTypes();
						$('#places-wrapper').prepend("<h3>Locations</h3>");
					}
					$.mapFNC.printLocs(types);
				}
				$.mapFNC.showMap();
				$.mapFNC.activateControls();
				
				if(opts.acceptQuery)
					if(q.length>0)
						if(query['name']!=null && query['address']!=null)
							$.mapFNC.findCustom(query['name'], query['address']);
			});
		});
	};
	
	$.mapFNC = {
		showMap: function() {
			msn_map = new VEMap(opts.dom);
			
			switch(opts.dashboard) {
				case 'Tiny':
					msn_map.SetDashboardSize(VEDashboardSize.Tiny);
					break;
				case 'Small':
					msn_map.SetDashboardSize(VEDashboardSize.Small);
					break;
				case 'Hide':
					msn_map.HideDashboard();
					break;
			}
			
			msn_map.LoadMap();
			msn_map.SetMapStyle(VEMapStyle.Shaded);
			msn_map.SetMouseWheelZoomToCenter(false);	
			
			if(opts.clickMap) {
				msn_map.AttachEvent("onclick", $.mapFNC.showContext);
				msn_map.AttachEvent("onmousedown", $.mapFNC.hideContext);
			}
			
			if(opts.miniMap)
				$.mapFNC.showMiniMap();
			
			layer = new VEShapeLayer();
			var veLayerSpec = new VEShapeSourceSpecification(VEDataType.GeoRSS, opts.url, layer);
            msn_map.ImportShapeLayerData(veLayerSpec, $.mapFNC.setShapes );
		},
		showMiniMap: function() {
			var pos = $('#map').height();
			var hideBlock = $('<div />')
							.addClass('mini-control').attr({id: 'mini-hide', title: 'Hide mini map'})
							.bind("click", function() { 
								$(this).hide(); 
								$('#MSVE_minimap').animate({top: "+=152px"}, "slow", function() {
									block.show();													  
								}); 
							}).hide();
								
			var block = $('<div />')
						.addClass('mini-control').attr({id: 'mini-show', title: 'Show mini map'})
						.bind("click", function() { 
							$(this).hide(); 
							$('#MSVE_minimap').animate({top: "-=152px"}, "slow", function() {
								hideBlock.show();
							}); 
						});
        	
			if (msn_map.GetMapMode() == VEMapMode.Mode3D)
            	msn_map.SetMapMode(VEMapMode.Mode2D);
            msn_map.ShowMiniMap(0, pos, VEMiniMapSize.Small);  
			
			$('#MSVE_minimap_resize').remove();
			$('#map').append(block, hideBlock);
        },
		setShapes: function() {
			var numShapes = layer.GetShapeCount();
			for(var i=0; i < numShapes; i++) {
				var shape = layer.GetShapeByIndex(i)
				if(ids!=null) {
					shape.Hide();
					if($.mapFNC.checkIds(i))
						$.mapFNC.setProperties(shape, i);						
				} else {
					$.mapFNC.setProperties(shape, i);
					if(opts.hidePins) {
						shape.Hide();
					} else
						$('.showOnMap:eq('+i+')').trigger('click');
				}
			}
			if(opts.hidePins) {
				$('.showOnMap:eq(0)').trigger('click');
				$.mapFNC.centerMap(0, 11);
			} else if(ids!=null)
				$('.showOnMap').trigger('click');
		},
		checkIds: function(num) {
			var len = ids.length;
			for(var x = 0; x<len; x++) {
				if(xml[num].id==ids[x]) 
					return true;
			}
			return false;
		},
		getData: function(data) {
			var items = new Array();
			$('item', data).each(function(i) {
				items[i] = new Object();
				$(this).children().each(function() {
					items[i][this.tagName] = $(this).text();									 
				});
			});
			return items;
		},
		getTypes: function() {
			var types = new Array();
			$.each(xml, function(i, val) {
				var dup = false;
				var tem = val.type;
				$.each(types, function(j, val2) {
					if(tem == val2)
						dup = true;
				}); 
				if(!dup)
					types.push(tem);
			});
			return types;
		},
		parseQuery: function(str) {
			var vars = str.split("&");
			var pairs = new Array();
			for (var i=0;i<vars.length;i++) {
				var p = vars[i].split("=");
				pairs[p[0]] = $.mapFNC.urlDecode(p[1]);
				}
			return pairs;
		},
		urlDecode: function(encodedString) {
			var output = encodedString;
			var binVal, thisString;
			var myregexp = /(%[^%]{2})/;
			while ((match = myregexp.exec(output)) != null && match.length > 1 && match[1] != '') {
				binVal = parseInt(match[1].substr(1),16);
				thisString = String.fromCharCode(binVal);
				output = output.replace(match[1], thisString);
			}
			return output;
		},
		printLocs: function(sec) {
			if(sec!=null) {
				$.each(sec, function(i, val) {
					var count = 0;
					var li = $('<li />').attr('id', "map-"+val);
					var int = $('<a />').addClass('aControl').attr('href', '#').html(val.charAt(0).toUpperCase()+val.substring(1,val.length)).prependTo(li);
					var div = $('<div />').appendTo(li);
					var innerDiv = $('<div />').addClass('ui-accordion-container').appendTo(div);
					$.each(xml, function(j) {
						if(ids!=null) {
							if($.mapFNC.checkIds(j)) {
								if(xml[j].type == val) {
									count++;
									xml[j].pincount = count;
									innerDiv.append($.mapFNC.addPlace(j, count));
								}
							}
						} else {
							if(xml[j].type == val) {
								count++;
								xml[j].pincount = count;
								innerDiv.append($.mapFNC.addPlace(j, count));
							}
						}
					});
					if($(innerDiv).children().length>0)
						$('#places').append(li);
				});
				$("#places").accordion({ 
					header: '.aControl', 
					navigationFilter: ''
				}).bind("accordionchange", function() {
					$('.ui-accordion-container').css('overflow', 'auto');	
				}).children('li').click(function() {
					if(!$(this).hasClass('selected'))
						$('.ui-accordion-container').css('overflow', 'hidden');								 
				});
			} else {
				$.each(xml, function(j) {
					$('#places').append($.mapFNC.addPlace(j));
				});
			}
		},
		addPlace: function(num, x) {
			var pinnum  = x!=null?x:num+1;
			var mapItem = $('<div />')
				.attr('id', "map-item-"+num)
				.addClass('map-item');
			
			var pin = $('<div />')
				.addClass('pushpin')
				.addClass( (pinnum+'').length > 1 ? 'pushpin-large' : '')
				.append('<img src="'+xml[num].pushpin+'" alt="" />')
				.hover(function () {
					$.mapFNC.showInfoBox(num);
				}, function () {
					$.mapFNC.hideInfoBox();
				})
				.append('<span>'+pinnum+'</span>');
			
				
			var div = $('<div />').addClass('item-info');
			
			var mapLoc = $('<div />').addClass('map-location').appendTo(div);
			var mapLink = $('<a />')
				.attr('href', '#')
				.addClass('showOnMap')
				.html(xml[num].title)
				.bind("click", function() { $.mapFNC.pinControl(this, num); return false; })
				.appendTo(mapLoc);
			
			var routeLoc = $('<div />').addClass('addToRoute').attr('id', 'add-route'+num).appendTo(div);
			var routeLink = $('<a />')
				.attr('href', '#')
				.html('<img src="images/map/btn-router-planner.gif" alt="Add to Router Planner" />')
				.bind("click", function() { $.mapFNC.addToRoute(num); $(this).parent().css('visibility', 'hidden'); return false; })
				.appendTo(routeLoc);
				
			return mapItem.append(pin, div);
		},
		activateControls: function() {	
			$('#cstm-submit').click(function() { $.mapFNC.findCustom(); return false; });
			$('#getRoute').click(function() { $.mapFNC.getRouteList(); return false; });
			$('#clearRoute').click(function() { $.mapFNC.clearRouteList(); return false; });
			if(opts.clickMap) {
				$('#context-add a').click(function() {
					if($('#context-add div').is(':visible'))
						$('#context-add div').hide();
					else
						$('#context-add div').show();
					return false;
				});	
				$('.context-zoom').click(function() {
						msn_map.SetCenterAndZoom(contextPin, this.rel);
						$.mapFNC.hideContext();
				});
				$('.context-center').click(function() {
						msn_map.SetCenter(contextPin);
						$.mapFNC.hideContext();
				});
			}
		},
		showContext: function(e) {
			if(e.rightMouseButton) {
				contextPin = msn_map.PixelToLatLong(new VEPixel(e.mapX, e.mapY));
				
				msn_map.FindLocations(contextPin, function(locations) {
					if(locations!=null) 
						$('#context-title').html(locations[0].Name);
					else 
						$('#context-title').html("Lat: "+contextPin.Latitude.toFixed(2)+" Long: "+contextPin.Longitude.toFixed(2));
					
					var tmpPin = $('<img />').addClass('context-pin')
						.attr('src', 'http://st1.maps.live.com/i/bin/1.3.20080320105134.31/pins/red_circ7px.gif')
						.css({position: 'absolute', left: e.clientX+'px', top: e.clientY+'px', cursor: 'pointer', zIndex: 1000})
						.appendTo('#map-container');
						
					$('#context-menu').css({left: e.clientX+10+'px', top: e.clientY+'px'}).show();
					
					$('#context-add form').bind("submit", function() {
						if($.trim($('#context-add #name').val())!='') {
							if(locations!=null) 
								$.mapFNC.createCustom($('#context-add #name').val(), locations[0].Name);
							else 
								$.mapFNC.createCustom($('#context-add #name').val());
							
							$('#context-add div').hide().find('#name').val('');
							$.mapFNC.hideContext();
						}
						else
							alert('You must give this location a name');
						return false;
					});
				});			
			}
		},
		hideContext: function() {
			$('#context-menu, #context-add div').hide();
			$('#context-add form').unbind("submit");
			$('.context-pin').remove();
		},
		findCustom: function(cstmName, cstmAddress) {
			var name = cstmName||$.trim($('#cstm-name').val());
			var address = cstmAddress||$.trim($('#cstm-address').val());
			if(name == '' || address == '') {
				alert("Please include a name and address.");
				$('#cstm-name').focus();
			} else {
				try {
					//what, where, findType, shapeLayer, startIndex, numberOfResults, showResults, createResults, useDefaultDisambiguation, setBestMapView, callback
					msn_map.Find(null, address, null, null, null, null, null, null, null, false, function(l, resultsArray, places, hasMore, veErrorMessage) {
						if(places) {
							contextPin =  places[0].LatLong;
							$.mapFNC.createCustom(name, address);
						}
					});
				} catch(e) {
					alert(e.message);	
				}
			}			
		},
		createCustom: function(name, addy) {
			var count = cstmPoints.length;
			cstmPoints[count] = new Array();
			cstmPoints[count].name = name;
			cstmPoints[count].address = addy;
			cstmPoints[count].coords = contextPin;
			
			var shape = new VEShape(VEShapeType.Pushpin, contextPin);
			shape.SetCustomIcon('<div class="pushpin"><img src="images/map/pins/pin-violet.gif" alt="" /><span>'+(count+1)+'</span></div>');
			shape.SetTitle(name);
			shape.SetDescription(addy?addy:name);
			layer.AddShape(shape);
			
			
			var point = shape.GetPoints();
			if($.mapFNC.adjustView(point))
				msn_map.IncludePointInView(new VELatLong(point[0].Latitude, point[0].Longitude));
			
			var routeItem = $('<li id="cstm-'+count+'"><span class="dragPoint">'+name+'</span></li>');
			
			var removeRoute = $('<img src="images/map/x.gif" title="Remove from itinerary" />')
				.addClass('removePoint')
				.css('cursor', 'pointer')
				.bind('click', {id: shape.GetID(), num: count}, $.mapFNC.removeCustom);
			
			$('#route-list').append(routeItem.append(removeRoute));
			if(opts.sortable)
				$('#route-list').sortable('refresh');	
		},
		removeCustom: function(event) {
			var pin = event.data.id;
			var num = event.data.num;
			$('#cstm-'+num, '#route-list').remove();
			
			var shape = msn_map.GetShapeByID(pin);
			msn_map.DeleteShape(shape);
		},
		pinControl: function(el, num) {
			if($(el).next().hasClass('remove'))
				$.mapFNC.centerMap(num, 11);	
			else {
				var remove = $('<img src="images/map/x.gif" title="Remove icon from map" />')
					.addClass('remove').attr('id','remove'+num)
					.css({cursor: 'pointer', position: 'absolute', right: '5px', top: '5px'})
				$(el).after(remove);			
				$.mapFNC.showPushPin(num);
			}
		},
		showPushPin: function(num) {	
			var shape = layer.GetShapeByIndex(num);
			var point = shape.GetPoints();
			shape.Show();
			
			if($.mapFNC.adjustView(point))
				msn_map.IncludePointInView(new VELatLong(point[0].Latitude, point[0].Longitude));
			
			$('#remove'+num).click(function() { 
				$.mapFNC.hidePushPin(shape); 
				$(this).remove(); 
				return false; 
			});
		},
		setProperties: function(shape, num) {
			var x = '<div id="point-info">';
			if(xml[num].image!='')
				x += '<div id="point-image"><img src="'+xml[num].image+'" alt="'+xml[num].title+'" /></div>';
			if(xml[num].description!='') 
				x += '<div id="point-description">'+xml[num].description+'</div>';
			if(xml[num].moreinfo!='')
				x += '<div id="point-more-info"><a href="'+xml[num].moreinfo+'" title="Get more information about this property">More Info</a></div>';
			x += '</div>';
			
			shape.SetDescription(x);
			
			var count = xml[num].pincount!=null?xml[num].pincount:num+1;
			shape.SetCustomIcon('<div class="pushpin"><img src="'+xml[num].pushpin+'" alt="" /><span>'+count+'</span></div>');
		},
		hidePushPin: function(point) {
			point.Hide();
		},
		showInfoBox: function(num) {
			var shape = layer.GetShapeByIndex(num);
			if(shape.GetVisibility())
        		msn_map.ShowInfoBox(shape);
        },
        hideInfoBox: function() {
        	msn_map.HideInfoBox();
        },
		centerMap: function(num, zoom) {
			var shape = layer.GetShapeByIndex(num).GetPoints();
			msn_map.SetCenterAndZoom(new VELatLong(shape[0].Latitude, shape[0].Longitude), zoom);
		},
		adjustView: function(point) {
			var pinLatitude = point[0].Latitude;
			var pinLongitude = point[0].Longitude;
			
			var view = msn_map.GetMapView();
			var mapTopLeftLat = view.TopLeftLatLong.Latitude;
			var mapTopLeftLong = view.TopLeftLatLong.Longitude;
			var mapBotRightLat = view.BottomRightLatLong.Latitude;
			var mapBotRightLong = view.BottomRightLatLong.Longitude;

			if (mapTopLeftLat>pinLatitude && mapBotRightLat<pinLatitude && mapTopLeftLong<pinLongitude && mapBotRightLong>pinLongitude)
				return false;
			return true;
		},
		addToRoute: function(num) {
			var _sort = opts.sortable?'<span class="dragPoint">'+xml[num].title+'</span>':xml[num].title;
			var routeItem = $('<li id="p'+num+'">'+_sort+'</li>');
			
			var el = $('#map-item-'+num).children('.map-location').children('.showOnMap');
			if(!el.next().hasClass('remove'))
				$.mapFNC.pinControl(el, num);
			
			var removeRoute = $('<img src="images/map/x.gif" title="Remove from itinerary" />')
				.addClass('removePoint')
				.css('cursor', 'pointer')
				.bind('click', {id: num}, $.mapFNC.removeFromRoute);
			$('#route-list').append(routeItem.append(removeRoute));	
			if(opts.sortable)
				$('#route-list').sortable('refresh');				
			},
		removeFromRoute: function(event) {
			var num = event.data.id;
			$('#p'+num, '#route-list').remove();
			$('.addToRoute').eq(num).css('visibility', 'visible');
		},
		getRouteList: function() {
			var myOptions = new VERouteOptions;
			var routeOrder = new Array();
			var tempID;
			$('li', '#route-list').each(function() {
				if ($(this).attr("id") != '') {												 
					if($(this).attr("id").indexOf('cstm-')!=-1) {
						tempID = $(this).attr("id").replace(/cstm-/g, "");
						place = cstmPoints[tempID].address||cstmPoints[tempID].coords;
					} else {			
						tempID = $(this).attr("id").replace(/\D/g, "");
						var place = xml[tempID].address;
						if(place=='') {
							var shape = layer.GetShapeByIndex(parseInt(tempID)).GetPoints();
							place = new VELatLong(shape[0].Latitude, shape[0].Longitude);
						}
					}
				routeOrder.push(place);
				}
			});
			if(routeOrder.length < 2)
				alert("Please add another location to your trip");
			else {
				myOptions.DrawRoute      = true;
				myOptions.RouteCallback  = $.mapFNC.printRoute;
				if(opts.kilometer)
					myOptions.DistanceUnit = VERouteDistanceUnit.Kilometer;
				msn_map.GetDirections(routeOrder, myOptions);					
			}
		},
		clearRouteList: function() {
			$('li', '#route-list').each(function() {
				$('.removePoint', this).trigger('click');						 
			});
			$('#route-info').html('');
			$('.addToRoute').css('visibility', 'visible');
			try { msn_map.DeleteRoute(); }            
			catch (err) { alert(err.message); } 
			for(var x = 0; x < layer.GetShapeCount(); x++) {
				var shape = layer.GetShapeByIndex(x);
				if(shape.GetVisibility()) {
					$.mapFNC.centerMap(x, 11);
					return;
				}
			}			
		},
		printRoute: function(route) {		
			var legs     = route.RouteLegs;
			var _unit 	 = opts.kilometer?' km':' mi';
			var time 	 = $.mapFNC.convertTime(route.Time);
			
			var turns    =  "<h4>My Route</h4>";
				turns 	 += "<p>Total distance: " + route.Distance.toFixed(1) + _unit + "<br />";
				turns	 += "Estimated Total Time: " + time + "</p>";
			var numTurns = 0;
			var leg      = null;

			$.each(legs, function(i) {
				leg = legs[i]; 
				var turn = null;  
				$.each(leg.Itinerary.Items, function(j) {
					turn = leg.Itinerary.Items[j];
					numTurns++;
					turns += numTurns + ". " + turn.Text + " (" + turn.Distance.toFixed(1) + _unit +")<br />";
				});
			});
			
			$('#route-info').html('');
			$('#route-info').append(turns); 	
			$.scrollTo('div#route-info');
		},
		convertTime: function(time) {
			var hrs, mins;
			tmpTime = ((time/60)/60).toString();
			if(tmpTime.indexOf('.')!=-1) {
				var tmp = tmpTime.split('.');
				hrs = tmp[0];
				mins = parseFloat("."+tmp[1]);
				mins = ((mins*60)/100).toFixed(2);
				mins = mins*100;
			}
			return hrs + " hours " + mins + " minutes";
		}
	};
})(jQuery);

