$(document).ready(function() {
// DOM Loaded watcher
	PB.domLoaded();
});


// Create Namespace for project.
// ALL PLACEBOOK CODE SHOULD GO IN THIS OBJECT!!
var PB = PB ? PB : {
	// Naming Conventions:
	// Vars have all lowercase names with underscores (_) between words
	//
	// Functions have names with no spaces or underscores between words
	// The first letter of each word is capitalized, except the first word
	//
	// Objects have names with no spaces or underscores between words 
	// The first letter of each word is capitalized, including the first word
	
	// "Global" pb vars (Global to Placebook that is)
	debug: false,				// Set to true to enable messages in console log
	sr_id: 0,					// Server Request ID. Counts up for each AJAX request to give it a unique ID
								// a sr_desc is also attached to each request with a description, but it is a local
	categories: [],				// Holds cache of category list. Categories[<cat id>] will be the name.
	templates: [],				// Cache holding HTML templates
	map: null,					// Google Map root object
	init_done: false,			// Set to true when pb has started up
	visible_locations: [],		// Holds cache of locations in view
	mouseover_location: null,	// An index to visible_locations to indicate which location the mouse is hovering over
								// Used for mouseover effects such as loading and showing geometry on mouse over
	viewing_location: null,		// An index to visible_locations to indicate which location is currently selected
	geocode_results: null,		// An array holding the results of the last geocode (Search) results.
	rev_geo_timer: 500,			// Setting to stop too many geocode requests going to google. Set in milliseconds
	jquery_layout: null,		// Onject used to control the jQuery Layout mod
	current_panel: null,		// The id of the currently showing panel.

	// Clustering vars
	cluster: false,				// Setting to true causes server replies for locations to use clustering
	waiting_to_view: 0,			// If in cluster mode and we click say a mylocs link to view a location which isnt loaded, this gets set
	cluster_cachebounds: null,	// Bounds of points loaded in cluster mode
	cluster_cachescale: 2.5,	// Multiplier for size of bounds to cache markers outside the viewport in cluster mode
	cluster_loadbounds: null,	// If centre of viewport leaves this bounds in cluster mode, readLocations is called
	cluster_loadscale: 2,		// Multiplier to scale up viewport for loadbounds
	cluster_cacheoverlay: null,	// Google Maps polyline to show cache bounds 
	cluster_loadoverlay: null,	// Google Maps polyline to show load bounds
	zoom: null,					// Holds current zoom in cluster mode
	
	// === AJAX Debugging Functions. Left at start to help in Firebug ;) ======================================================================================

	showDialog: function(dialog) {
	// Shows a dialog box
		// ToDo: Should I not work out a way of initing once?
		$(dialog).dialog({
			width: "500px",
			height: "300px",
			autoOpen: false
		});

		if ($(dialog).dialog("isOpen")) {
			$(dialog).dialog("close");
		} else {
			$(dialog).dialog("open");
		}
	},
	
	checkResponse: function(response) {
	// Checks a back-end response for good or bad.
	// This does not cover a PHP file not being found, only it's response if it is.
		if (PB.isSet(response) && PB.isSet(response[0][0]) && PB.isSet(response[0][0].status) && response[0][0].status == "ok") {
			return true;
		} else {
			return false;
		}
	},

	prettyPrint: function(out) {
	// Attempts to format (X)HTML or JSON into a readable format for logging
		if (PB.isSet(out)) {
			try {
				// Try HTML Mode
				out = " (HTML):\n\n"+out.replace(/^\s*</,"&lt;"); // Does it start with a (< ? Not including whitespace)
					out = " (HTML):\n\n"+out.replace(/</g,"&lt;"); // replace other < with &lt; so we can print it.
			} catch(err) {
				try {
					// If not, JSON
					out = " (JSON):\n\n"+unescape(JSON.stringify(out, null, '\t'));
				} catch (err2) {
					// Raw
					var tmp = null;		// leave so I can put a break here for Firebug
				}
			}
			return out;
		} else {
			return "";
		}
	},

	cleanReq: function(req) {
	// Cleans server request object for printing
		req = unescape(req);
		req = req.replace(/\+/gi," ");
		return req;
	},

	setupAjaxHandling: function() {
	// Sets up ajax handling routines
		$(window).ajaxStart(function() {
			// loading animations
			$('#pb_loading').show();
		});

		$(window).ajaxStop(function() {
			$('#pb_loading').hide();
		});

		$(window).ajaxSend(function(event, XMLHttpRequest, ajaxOptions) {
			PB.sr_id ++;
			XMLHttpRequest.pb_sr_id = PB.sr_id;
			
			// Unserialize the data to get at the options
			var sr_desc = $.unserialise(ajaxOptions.data);
			// Grab Server Request Description from options
			if (PB.isSet(sr_desc.sr_desc)) {
				sr_desc = PB.cleanReq(sr_desc.sr_desc);
			} else {
				sr_desc = "(Description not set)";
			}
			// Dump template for sr_id to the log
			$("#pb_log").prepend($.tempest(PB.templates.pb_log_entry, {
				sr_id: PB.sr_id,
				sr_desc: sr_desc
			}));
			// Output request to Server Response Log
			$("#pb_log_sr_id_req_"+PB.sr_id).html("<pre>AJAX Request"+PB.prettyPrint(ajaxOptions)+"</pre>")
			// Set Server Response Log entry to red
			$("#pb_log_sr_id_status_"+XMLHttpRequest.pb_sr_id).addClass("pb_sr_started");
		});
		
		$(window).ajaxSuccess(function(event, XMLHttpRequest, ajaxOptions) {
			
			response = $.parseJSON(XMLHttpRequest.responseText);
			// Check if Server-side PHP replied with an "OK" JSON status object
			if (PB.checkResponse(response)) {
				// Ajax Request succeeded and response good
				
				// Start string with prettied response object
				var out = "<pre>Reply"+PB.prettyPrint(response[0][0])+"</pre>";
				// Remove response object leaving payload
				response.shift();
				// If payload is only one array, strip it to leave the naked object
				// This way, if it is HTML, the prettifier will show it in HTML mode
				if (!PB.isSet(response[1])) {
					response = response[0];
				}
				if (PB.isSet(response)) {
					out += "<hr><pre>Payload"+PB.prettyPrint(response)+"</pre>";
				}
				// Dump prettied string to Server Response Log
				$("#pb_log_sr_id_resp_"+XMLHttpRequest.pb_sr_id).html(out);
				
				// Set Server Response Log entry to green
				$("#pb_log_sr_id_status_"+XMLHttpRequest.pb_sr_id).removeClass("pb_sr_started");
				$("#pb_log_sr_id_status_"+XMLHttpRequest.pb_sr_id).addClass("pb_sr_ok");
			} else {
				// AJAX Request succeeded and response bad
				
				// Output result to Server Response Log
				$("#pb_log_sr_id_resp_"+XMLHttpRequest.pb_sr_id).html("<pre>Reply"+PB.prettyPrint(response[0][0])+"</pre>");

				// Leave Server Response Log entry on amber
				
				// Alert user to entry in Server Response Log
				PB.showErrorDialog(XMLHttpRequest.pb_sr_id,'error_server_resp');
			}
		});

		$(window).ajaxError(function(event, XMLHttpRequest, ajaxOptions) {
			// AJAX Request Failed
			
			// Output results to Server Response Log
			$("#pb_log_sr_id_resp_"+XMLHttpRequest.pb_sr_id).html("<pre>Reply"+PB.prettyPrint(XMLHttpRequest.responseText)+"</pre>");
			
			// Set Server Response Log entry to red
			$("#pb_log_sr_id_status_"+XMLHttpRequest.pb_sr_id).removeClass("pb_sr_started");
			$("#pb_log_sr_id_status_"+XMLHttpRequest.pb_sr_id).addClass("pb_sr_error");
			
			// Alert user to entry in Server Response Log
			PB.showErrorDialog(PB.sr_id,'error_server_comm');
		});
	},
	
	showErrorDialog: function(id,template){
	// Creates and shows a dialog alerting the user when an error occurs
		var tmp_id = 'pb_error_'+id;
		// Create uniquely named DIV
		$("body").append(
			// With contents from the template
			$.tempest(PB.templates[template],{
				id: 	tmp_id,
				sr_id:	id
			})
		);
		
		// Create the dialog
		$("#"+tmp_id).dialog({
			close: function(event, ui) {
				$("#"+this.id).remove();
			},
			buttons: {
				"Ok": function() {
					// Destroy the unique DIV on exit
					$(this).dialog("close")
				}
			}
		});
		
		// Open the Server Response Log panel
		PB.jquery_layout.open("south");
	},
	
	debug: function(msg) {
	// Prints debug info in the log if in debug mode
		if (PB.debug){
			console.log(msg);
		}
	},

	// === Code essential to startup ==========================================================================================================================
	domLoaded: function() {
	// Called when DOM finished loading
		if (!PB.loadTemplate("pb_log_entry") || !PB.loadTemplate("error_server_comm") || !PB.loadTemplate("error_server_resp")) {
			alert("Could not load debug templates, Exiting...");
			return;
		}
		// TABS-WEST - sortable
		$(".ui-layout-west")
			.tabs()
			.find(".ui-tabs-nav")
				.sortable({ axis: 'x', zIndex: 2 })
		;

		// PAGE LAYOUT
		PB.jquery_layout = $('body').layout({
			center__onresize: PB.onResize,
			west__size:			320,
			east__size:			320,
			north__size:		100,
			south__size:		100,
			south__initClosed:	true,
			north__resizable:	false

		});

		/*
		$('.ui-layout-south').layout({
			center__paneSelector:	".south-center",
			west__paneSelector:		".south-west",
			west__size:				450
		});
		*/

		PB.setupAjaxHandling();
		
		//$(window).bind('resize', PB.onResize);
		PB.onResize();		// Needed else wait cursor does not centre properly.
		
		// Init the edit form and associated vars
		PB.EditLoc.initialize();

		// Set up a callback for when login status changes
		FB.Event.subscribe('auth.sessionChange', function(response) {
			PB.loginChange();
		});
		
		// Init map
		var latlng = new google.maps.LatLng(51.64, 0);
		var myOptions = {
			zoom: 8,
			center: latlng,
			navigationControlOptions: {style: google.maps.NavigationControlStyle.ANDROID},
			mapTypeId: google.maps.MapTypeId.ROADMAP
		};
		PB.map = new google.maps.Map(document.getElementById("pb_map_canvas"), myOptions);
		// Note: Map is probably NOT visible and it's "bounds" etc are still null at this point
		
		// Add click Listener to map
		google.maps.event.addListener(PB.map, 'click', function(event) {
			if (PB.EditLoc.editmode) {
				PB.EditLoc.mapClicked(event.latLng);	// fire click in edit mode
			}
		});
		
		// Set an event listener for 'idle' on the map. Once the map loads, this will trigger.
		// it is then safe to query map bounds etc, so PB.initDone is called to start the main program
		google.maps.event.addListener(PB.map, 'idle', PB.mapMoved);
		
		// Main init done.
		
		// Set up User Panel
		$(".ui-layout-west")
			.tabs()
			.find(".ui-tabs-nav")
			.sortable(/*{ axis: 'x', zIndex: 2 }*/)
		;
		// Force to 2nd panel...
		$( ".ui-layout-west" ).tabs( "select" , 1 )
		// Add click listener...
		$( ".ui-layout-west" ).bind( "tabsshow", function(event, ui) {
			PB.current_panel = ui.panel.id;
			PB.showUserPanel(ui.panel.id);
		});
		// Then force to 1st panel to generate click and activate panel #1
		$( ".ui-layout-west" ).tabs( "select" , 0 )
		
		// Load category list
		if (!PB.isSet(PB.categories) || !PB.categories.length) {
			var params = {
				sr_desc: "Loading Categories",
				action: 'cats'
			};
			PB.categories = [];
			PB.categories[0] = "None";
			$.ajax({
				async: false,
				type: "GET",
				dataType: "json",
				url: "server/read.php",
				data: params,
				success: function(transport) {
					if (PB.checkResponse(transport)) {
						transport.shift();
						$.each(transport, function(key, value) {
							PB.categories[value.id] = unescape(value.name);
						});
					}
				},
				error: function(transport) {
					alert ("Failed to load Category List. Program will run, but things may be quirky");
					return false;
				}
			});			
		}

		PB.ZoomBox.initialize();
		PB.LocBox.initialize();
		
		google.maps.event.addListener(PB.map, 'idle', PB.mapMoved);
	},

	scaleBounds: function (bounds, scale){
		var min = bounds.getSouthWest().lat();
		var max = bounds.getNorthEast().lat();
		var mid = max - min;
		
		var north = min + (mid * scale);
		var south = max - (mid * scale);
		
		if (north > 90){
			north = 90;
		}
		if (south < -90){
			south = -90;
		}

		min = bounds.getSouthWest().lng();
		max = bounds.getNorthEast().lng();
		mid = max - min;
		
		var east = min + (mid * scale);
		var west = max - (mid * scale);

		if (east > 180){
			east = 180;
		}
		if (west < -180){
			west = -180;
		}
		
		return new google.maps.LatLngBounds(new google.maps.LatLng(south,west),new google.maps.LatLng(north,east));

	},
	
	mapMoved: function() {
	// Called when the map moves.
	// When only loading points inside the viewport, this is needed to be called often.
	// At load time (PB.init_done=0), it is called when the map has finished loading.
		PB.reverseGeocodeHere();
		if (!PB.init_done) {
			PB.init_done = true;
		} else if (!PB.cluster){
			return true;	// On subsequent moves after init, do not go past this point if not in cluster mode
		}
		if (PB.cluster) {
			// Reset the bounds and load locations if one of the following is true:
			// First move of map
			// Any part of viewport outside cluster_loadbounds
			// Zoom has changed
			if (!PB.isSet(PB.cluster_cachebounds) || !PB.cluster_loadbounds.contains(PB.map.getBounds().getNorthEast()) || !PB.cluster_loadbounds.contains(PB.map.getBounds().getSouthWest()) || PB.zoom != PB.map.getZoom()){
				PB.cluster_cachebounds = PB.scaleBounds(PB.map.getBounds(),PB.cluster_cachescale);
				PB.cluster_loadbounds = PB.scaleBounds(PB.map.getBounds(),PB.cluster_loadscale);
				
				if (PB.isSet(PB.cluster_cacheoverlay)){
					PB.cluster_cacheoverlay.setMap(null);
				}
				PB.cluster_cacheoverlay = PB.boundsToPolyLine(PB.cluster_cachebounds);
				PB.cluster_cacheoverlay.setOptions({
					strokeColor: "#FF0000"
				});				
				PB.cluster_cacheoverlay.setMap(PB.map);
				
				if (PB.isSet(PB.cluster_loadoverlay)){
					PB.cluster_loadoverlay.setMap(null);
				}
				/*
				PB.cluster_loadoverlay = PB.boundsToPolyLine(PB.cluster_loadbounds);
				PB.cluster_loadoverlay.setOptions({
					strokeColor: "#00FF00"
				});				
				PB.cluster_loadoverlay.setMap(PB.map);
				*/
				PB.zoom = PB.map.getZoom();
			} else {
				return true;
			}
		}
		PB.readLocations();
	},
	
	// === Code to do with viewing Locations ==================================================================================================================

	readLocations: function() {
	// Get locations via AJAX and display them
		//PB.debug("READING LOCATIONS");
		var data = {
			sr_desc: "Reading Locations",
			action: 'read',
			zoom: PB.map.getZoom()
		};
		if (PB.cluster){
			data.nelat = PB.cluster_cachebounds.getNorthEast().lat();
			data.nelng = PB.cluster_cachebounds.getNorthEast().lng();
			data.swlat = PB.cluster_cachebounds.getSouthWest().lat();
			data.swlng = PB.cluster_cachebounds.getSouthWest().lng();
			data.cluster = 1;
		} else {
			data.cluster = 0;
		}
		$.ajax({
			type: "GET",
			dataType: "json",
			url: "server/read.php",
			data: data,
			success: function(transport) {
				if (PB.checkResponse(transport)) {
					PB.clearMarkers();
					PB.visible_locations = transport;
					PB.visible_locations.shift();								// remove the status data from the server
					for (var i=0;i < PB.visible_locations.length;i++) {
						// Is is a regular marker or a cluster marker?
						if (PB.visible_locations[i].ctype == 'loc') {
							// regular marker
							PB.visible_locations[i].index = i;					// Added here so that templating function can easily be passed index
							PB.visible_locations[i].geom_requested = false;		// When you mouse over (ie before you click marker), geometry may be requested via AJAX. This records if a request has been made.
							PB.visible_locations[i].mousestate = false;			// true if mouse currently over, false if not
							if (PB.visible_locations[i].geomtype >= 2) {
								PB.visible_locations[i].geom_loaded = false;
							} else {
								PB.visible_locations[i].geom_loaded = true;
							}
							PB.decodeLocation(PB.visible_locations[i]);
							PB.visible_locations[i].eletime_requested = false;
							if ( PB.visible_locations[i].eletime_length  != 0){
								PB.visible_locations[i].eletime_loaded = false;
							} else {
								PB.visible_locations[i].eletime_loaded = true;
							}
							PB.visible_locations[i].marker = new google.maps.Marker({
								pb_index: i,		// Lookup so marker can find its entry in visible_locations
								position: new google.maps.LatLng(PB.visible_locations[i].marker_point[0],PB.visible_locations[i].marker_point[1]),
								title: PB.visible_locations[i].name
							});
							PB.visible_locations[i].marker.pb_geomtype = PB.visible_locations[i].geomtype;
							PB.setMarkerOptions(PB.visible_locations[i].marker,0);
							PB.visible_locations[i].marker.setMap(PB.map);
							//PB.debug("Adding marker "+i);
							
							google.maps.event.addListener(PB.visible_locations[i].marker, 'click', function(event) {
								PB.viewLocation(this.pb_index);
							});
							google.maps.event.addListener(PB.visible_locations[i].marker, 'mouseover', function(event) {
								PB.visible_locations[this.pb_index].mousestate=true;
								if (!PB.visible_locations[this.pb_index].geom_loaded) {
									PB.requestGeom(this.pb_index);
								} else {
									PB.showHideGeom(this.pb_index);
								}

							});
							google.maps.event.addListener(PB.visible_locations[i].marker, 'mouseout', function(event) {
								PB.visible_locations[this.pb_index].mousestate=false;
								PB.showHideGeom(this.pb_index);
							});
						} else {
							PB.visible_locations[i].marker = new google.maps.Marker({
								pb_index: i,		// Lookup so marker can find its entry in visible_locations
								position: new google.maps.LatLng(PB.visible_locations[i].marker_point[0],PB.visible_locations[i].marker_point[1]),
								icon: 'images/markers/locations/cluster.png',
								title: PB.visible_locations[i].cc+" Locations clustered"
							});
							google.maps.event.addListener(PB.visible_locations[i].marker, 'click', function() {
								var loc = PB.visible_locations[this.pb_index];
								var tb = new google.maps.LatLngBounds(new google.maps.LatLng(loc.swlat,loc.swlng),
								new google.maps.LatLng(loc.nelat,loc.nelng));
								PB.map.fitBounds(tb);
							});
							PB.visible_locations[i].marker.setMap(PB.map);
							//PB.debug("Adding marker "+i);

						
						}
						// If we are in cluster mode and clicked a location to view, but it wasnt loaded...
						// ... the map gets panned to its coords and readLocations gets called.
						// If it is in view now, show it in the Locations Panel.
						if (PB.visible_locations[i].loc_id == PB.waiting_to_view){
							PB.viewLocation(i);
						}
					}
				}
			}
		});
	},
	
	clearMarkers: function() {
	// Clears markers from the map
		//PB.debug("CLEARING OVERLAYS");
		if (PB.isSet(PB.visible_locations)){
			for(var i=0; i<PB.visible_locations.length; i++) {
				if (PB.isSet(PB.visible_locations[i].marker)){
					PB.visible_locations[i].marker.setMap(null);
					//PB.debug("Deleting Marker "+i);
				}
				if (PB.isSet(PB.visible_locations[i].line)) {
					PB.visible_locations[i].line.setMap(null);
				}
			}
		}
		PB.visible_locations = null;
	},

	boundsToPolyLine: function(bounds){
	// Converts a bounds to a polyline for displaying
		return new google.maps.Polyline({
			path: [
				new google.maps.LatLng(bounds.getSouthWest().lat(), bounds.getSouthWest().lng()),
				new google.maps.LatLng(bounds.getNorthEast().lat(), bounds.getSouthWest().lng()),
				new google.maps.LatLng(bounds.getNorthEast().lat(), bounds.getNorthEast().lng()),
				new google.maps.LatLng(bounds.getSouthWest().lat(), bounds.getNorthEast().lng())
			],
			strokeOpacity: 1.0,
			strokeWeight: 2
		});

	},
	

	hideOverlays: function(id) {
	// Hides all overlays associated with a database location
	// NB: id is a location ID (As in the DB field) NOT an index to visible_locations!
		for(var i=0; i<PB.visible_locations.length; i++) {
			if (id == PB.visible_locations[i].loc_id) {
				PB.visible_locations[i].marker.setMap(null);
				if (PB.isSet(PB.visible_locations[i].line)) {
					PB.visible_locations[i].line.setMap(null);
				}
			}
		}
	},

	showOverlays: function(id) {
	// Unhides all overlays associated with a database location
		for(var i=0; i<PB.visible_locations.length; i++) {
			if (id == PB.visible_locations[i].loc_id) {
				PB.visible_locations[i].marker.setMap(PB.map);
				if (PB.isSet(PB.visible_locations[i].line)) {
					PB.visible_locations[i].line.setMap(PB.map);
				}
			}
		}
	},

	viewLocation: function(index) {
	// Display the details of a location
		if (!PB.EditLoc.editmode) {
			PB.stopViewingLocation();
			PB.setMarkerOptions(PB.visible_locations[index].marker,1);
			PB.viewing_location = index;
			
			if (!PB.visible_locations[index].geom_loaded && !PB.visible_locations[index].geom_requested) {
				PB.requestGeom(index);
			}
			if (!PB.isSet(PB.templates.pb_view_location)) {
				if (!PB.loadTemplate("pb_view_location")) {
					// FAILED TO LOAD VIEW LOCATION TEMPLATE
					return false;
				}
			}
			PB.insertPanel(".ui-layout-east",PB.templates.pb_view_location,PB.visible_locations[index]);
			PB.showHideViewEditButtons();
			try{
				//FB.XFBML.Host.parseDomTree();
				FB.XFBML.parse();
			} catch (err) {
			
			}
			return true;
		}
	},

	stopViewingLocation: function() {
	// Called when exiting view mode
		if (PB.isSet(PB.viewing_location)) {
			PB.setMarkerOptions(PB.visible_locations[PB.viewing_location].marker,0);
			var tmp = PB.viewing_location;
			PB.viewing_location = null;
			PB.showHideGeom(tmp);
		}
	},

	requestEletime: function(index) {
	// Get the geometry (line or polygon) data on mouseover / click
	// (This data is not loaded with the main load to save bandwidth)
		PB.visible_locations[index].eletime_requested = true;
		$.ajax({
			type: "GET",
			dataType: "json",
			url: "server/read.php",
			data: {
				sr_desc: "Requesting Geometry",
				action: 'eletime',
				id: PB.visible_locations[index].loc_id,
				index: index
			},
			success: function(transport) {
				if (PB.checkResponse(transport)) {
					var location = transport;
					location = location[1];		// [0] is server response
					PB.visible_locations[location.index].eletime = location.eletime;
					PB.visible_locations[location.index].eletime_loaded = true;
					PB.visible_locations[location.index].eletime_requested = false;
					PB.showEletimeGraph();
				}
				return false;
			}
		});
	},

	requestGeom: function(index) {
	// Get the geometry (line or polygon) data on mouseover / click
	// (This data is not loaded with the main load to save bandwidth)
		PB.visible_locations[index].geom_requested = true;
		$.ajax({
			type: "GET",
			dataType: "json",
			url: "server/read.php",
			data: {
				sr_desc: "Requesting Geometry",
				action: 'geom',
				id: PB.visible_locations[index].loc_id,
				index: index			// Attach "index" data value, which server will echo back on reply
									// That way, when the asynch reply comes back, we know which object to attach it to.
			},
			success: function(transport) {
				if (PB.checkResponse(transport)) {
					var location = transport;
					location = location[1];		// [0] is server response
					// location.index  is an index to visible_locations, mirrored by the server from our request.
					PB.visible_locations[location.index].geom = location.geom;
					PB.visible_locations[location.index].geom_loaded = true;
					PB.visible_locations[location.index].geom_requested = false;
					PB.showHideGeom(location.index);
				}
				return false;
			}
		});
	},

	showHideGeom: function(index) {
	// Show or hide the geometry data (line or polygon)
		if (PB.visible_locations[index].geomtype >= 2) {		// we don't need to do anything for points
			if ((PB.visible_locations[index].mousestate == true || index == PB.viewing_location) && index != PB.mouseover_location) {	// Do we need to show the poly?
				PB.mouseover_location = index;
				if (!PB.isSet(PB.visible_locations[index].line)) {		// Build line if needed
					var tmp_geom = PB.jsonToLatLng(PB.visible_locations[index].geom);
					if (tmp_geom.length > 1) {
						if (PB.visible_locations[index].geomtype == 2) {
							
							PB.visible_locations[index].line = new google.maps.Polyline({
								path: tmp_geom
							});
						} else {
							PB.visible_locations[index].line = new google.maps.Polygon({
								paths: [tmp_geom]
							});
						}
					}
				}
				PB.visible_locations[index].line.setMap(PB.map);
				PB.setPolyOptions(PB.visible_locations[index].line);
			} else {
				if (PB.isSet(PB.visible_locations[index].line) && PB.viewing_location != index) {
					PB.visible_locations[index].line.setMap(null);
					PB.setPolyOptions(PB.visible_locations[index].line);
					PB.mouseover_location = null;
				}
			}
		}
	},

	jsonToLatLng: function(json) {
	// Converts database's response JSON object into Google LatLngs
		var geom = [];
		for(var i=0;i<json.length;i++) {
			var latlng = json[i];
			geom.push(new google.maps.LatLng(latlng[0],latlng[1]));
		}
		return geom;
	},

	decodeLocation: function(loc) {
	// Escapes fields passed in JSON that were encoded with escape()
	// Also renames from server naming format to client naming format
		loc.name = unescape(loc.name);
		loc.description = unescape(loc.description);
		//loc.marker_point = loc.marker_point;
		loc.category_name = PB.categories[loc.category];
		if (PB.isSet(loc.eletime_length)){
			loc.eletime_length = parseInt(loc.eletime_length);
		}
	},

	setPolyOptions: function(geom,type) {
	// Sets polygon styling
		if (PB.isSet(type) && type == true) {	// is it a temp marker?
			var fc = '#FF6666'
			var sc = '#666666'
		} else {
			var fc = '#66FF66';
			var sc = '#000000'
		}
		geom.setOptions({
			strokeWeight: 2,
			strokeColor: sc
		});
		if (PB.isSet(geom.getPaths)) {	// is it a polygon?
			geom.setOptions({
				fillColor: fc
			});
		}
	},

	setMarkerOptions: function(tm,sel,t) {
	// Sets marker graphics styling
	// tm = marker
	// sel = selected? 1|0
	// t = temp? 1|0   - temp markers are used in edit mode
		var imagepath = null;
		if (sel==1) {
			switch(tm.pb_geomtype) {
				case 1:
					imagepath = 'images/markers/locations/point-selected.png';
					break;
				case 2:
					imagepath = 'images/markers/locations/line-selected.png';
					break;
				case 3:
					if (PB.isSet(t) && t) {
						imagepath = 'images/markers/temp/area.png';
					} else {
						imagepath = 'images/markers/locations/area-selected.png';
					}
					break;
				default:
			}
		} else if (sel ==0) {
			switch(tm.pb_geomtype) {
				case 1:
					imagepath = 'images/markers/locations/point.png';
					break;
				case 2:
					imagepath = 'images/markers/locations/line.png';
					break;
				case 3:
					if (PB.isSet(t) && t) {
						imagepath = 'images/markers/temp/area.png';
					} else {
						imagepath = 'images/markers/locations/area.png';
					}
					break;
				default:
			}
		} else {
			imagepath = '';
		}
		if (PB.isSet(imagepath)) {
			var image = new google.maps.MarkerImage(imagepath,
				new google.maps.Size(31, 36),	// Size
				new google.maps.Point(0,0),		// Origin
				new google.maps.Point(15, 34));	// Anchor
			var shadow = new google.maps.MarkerImage('images/markers/locations/shadow.png',
				new google.maps.Size(51, 36),	// Size
				new google.maps.Point(0,0),		// Origin
				new google.maps.Point(15, 34));	// Anchor
			// clickable region
			var shape = {
				coord: [1, 1, 1, 29, 29, 29, 29 , 1],
				type: 'poly'
			};
		tm.setOptions({
			icon: image,
			shape: shape,
			shadow: shadow
		});
		}
	},
	
	// === Generic Utility functions ==========================================================================================================================

	isSet: function(tmp_var) {
	// Checks if something is set and not null
		if (typeof tmp_var != 'undefined') {
			if (tmp_var != null) {
				return true;
			}
		}
		return false;
	},

	// === Eletime Functions (Show graph of elevation / time ==================================================================================================
	
	loadEletimeGraph: function() {
		
	},
	showEletimeGraph: function () {
	// Kick off the displaying of the Eletime Graph in view mode or edit mode
		var i = 0;
		var path = [];
		var load_ele = false;
		// Start by building an array of Google LatLngs for the line to be displayed
		if (PB.EditLoc.editmode) {
			if (!PB.isSet(PB.EditLoc.polygon) || PB.EditLoc.polygon.length < 2){
				return false;
			}
			// Build from location being edited in edit mode
			for (i = 0; i < PB.EditLoc.polygon.length ; i++){
				path.push(PB.EditLoc.polygon.getAt(i));
			}
			if (PB.isSet(PB.EditLoc.eletime) && PB.EditLoc.eletime.length) {
				load_ele = true;
			}
		} else {
			// Build from currently selected location in view mode
			if (!PB.isSet(PB.visible_locations[PB.viewing_location].geom) || PB.visible_locations[PB.viewing_location].geom.length < 2){
				return false;
			}
			path = PB.jsonToLatLng(PB.visible_locations[PB.viewing_location].geom);
			if (PB.isSet(PB.visible_locations[PB.viewing_location].eletime) && PB.visible_locations[PB.viewing_location].eletime.length) {
				load_ele = true;
			}
		}
		
		if(load_ele){
			// If elevation and time data present for this location (ie we imported from GPS)...
			// Then use the height data from that
			var ele_path = [];
			for (i = 0; i < path.length; i++){
				if (PB.EditLoc.editmode) {
					ele_path.push({"elevation": PB.EditLoc.eletime[i][0],"location": PB.EditLoc.polygon.getAt(i)});
				} else {
					ele_path.push({"elevation": PB.visible_locations[PB.viewing_location].eletime[i][0],"location": PB.visible_locations[PB.viewing_location].geom[i]});
				}
			}
			PB.drawEletimeGraph(ele_path);
		} else {
			// Else get height data from Google
			// Create a PathElevationRequest object using this array.
			// Ask for 256 samples along that path.
			var elevator = new google.maps.ElevationService();
			if (elevator) {
				var path_request = {
					'path': path,
					'samples': 256
				}
				elevator.getElevationAlongPath(path_request, PB.getEletimeFromGoogle);
			}
		}
	},

	getEletimeFromGoogle: function (results, status){
	// Requests elevation data from Google
		if (status == google.maps.ElevationStatus.OK) {
			PB.drawEletimeGraph(results);
		}
	},

	drawEletimeGraph: function (elevations) {
	// Plot elevation graph from an array
	// Array elements should be objectes with these properties:
	// elevation: (int)
	// location: (google LatLng)
		if (PB.EditLoc.editmode){
			var target = 'pb_edit_loc_eletime_graph';
		} else {
			var target = 'pb_view_location_eletime_graph';
		}

		var data = new google.visualization.DataTable();
		data.addColumn('string', 'Sample');
		data.addColumn('number', 'Elevation');
		for (var i = 0; i < elevations.length; i++) {
		  data.addRow(['', elevations[i].elevation]);
		}

		// Create a new chart in the chart DIV.
		var chart;
		chart = new google.visualization.ColumnChart(document.getElementById(target));

		// Draw the chart using the data within its DIV.
		document.getElementById(target).style.display = 'block';
		chart.draw(data, {
			//width: 640,
			height: 200,
			legend: 'none',
			titleY: 'Elevation (m)'
		});
	},

	// === User Interface functions ===========================================================================================================================

	checkPanelState: function(panel) {
	// Checks state of panel
		return $("#"+panel).attr("pb_state");
	},

	refreshUserPanel: function(panel) {
	// Sets a panel that has been loaded to refresh
		/*
		var oldval = PB.checkPanelState(panel);
		if (oldval == 0) {
			// already not loaded, it will refresh when it is opened anyway
		} else if (oldval == 2) {	// Template and data loaded
			$("#"+panel).attr("pb_state",0);	// Set just to not loaded
		}
		*/
		$("#"+panel).attr("pb_state",0);	// Tell panel to reload on next show
		// render panel if it is visible
		if (panel == PB.current_panel){
			PB.showUserPanel(panel);
		}
		return true;
	},

	clickClear: function(field, default_text) {
	// Clears a field when you click on it, as long as it contains default text
		if (field.value == default_text) {
			field.value = "";
		}
	},

	insertPanel: function(parent, template, data ) {
	// Inserts a panel of data from a template
		$(parent).html($.tempest(template, data));
		PB.blurChildren(parent);
	},

	blurChildren: function(parent) {
	// Sets onclick="blur()" to all widgets that suffer from the dotted line when you click them
		$(parent+" :radio, :button, a:link").bind('focus',function(event) { 
			$(this).blur();
		});
	},

	onResize: function() {
	// Called when screen is resized
		$("#pb_gps_import").position({
			of: $('body'),
			my: 'center center',
			at: 'center center',
			offset: 0
		});
		$("#pb_loading").position({
			of: $('body'),
			my: 'center center',
			at: 'center center',
			offset: 0
		});
		if (PB.isSet(PB.map)) {
			google.maps.event.trigger(PB.map, 'resize')
		}
	},

	loadTemplate: function(template) {
	// Loads a template via an AJAX call
	// returns false if there is a problem
	// returns true if all is OK, and the template is loaded into the PB.templates cache
		if (PB.isSet(PB.templates[template])){
			return true;
		} else {
			var out = $.ajax({
				async: false,
				type: "GET",
				dataType: "json",
				url: "server/load_template.php",
				data: {
					template: template,
					sr_desc: "Loading Template"
				},
				cache: true,
				success: function(data, textStatus, XMLHttpRequest) {
					XMLHttpRequest.pb_status=1;
				},
				error: function(XMLHttpRequest, textStatus, errorThrown) {
					XMLHttpRequest.pb_status=0;
				}
			});
			if (out.pb_status) {
				out = $.parseJSON(out.responseText);		
				if (PB.checkResponse(out)) {
					PB.templates[template] = unescape(out[1]);
					return true;
				}
			}
		}
	},

	showUserPanel: function(panel) {
	// Call to update a User Panel (MyLocs, FrLocs etc).
	// panel states (Indicated state panel contents is in):
	// 0 = No template loaded.
	// (Means no template loaded to panel, not no template loaded into cache)
	// Set this value to cause a panel to reload.
	// 1 = Template loaded, ready for data
	// showUserPanel will always load the template HTML into the panel, even if the panel is not in view
	// 2 = Data loaded and current
	// If the panel is in view, showUserPanel will load the data, display it and advance to this state.
		if (!PB.isSet(panel) || panel == "") {
			return false;
		}
		// Is the base template loaded?
		if ($("#"+panel).attr("pb_state") == 0) {
			switch (panel) {
				case "pb_user_panel_home":
					// Output the template to the user panel
					if (PB.loadTemplate(panel)) {
						$("#"+panel).html(unescape(PB.templates[panel]));
						
						$("#"+panel).attr("pb_state",1)
					}
					break;
				case "pb_user_panel_myloc":
					if (PB.loadTemplate(panel) && PB.loadTemplate(panel+"_entry")) {
						$("#"+panel).html(PB.templates[panel]);
						
						$("#"+panel).attr("pb_state",1)
					}
					break;
				case "pb_user_panel_frloc":
					if (PB.loadTemplate(panel) && PB.loadTemplate(panel+"_entry")) {
						$("#"+panel).html(PB.templates[panel]);
						
						$("#"+panel).attr("pb_state",1)
					}
					break;
				case "pb_user_panel_search":
					if (PB.loadTemplate(panel) && PB.loadTemplate(panel+"_geocode") && PB.loadTemplate("pb_user_panel_frloc_entry")) {
						$("#"+panel).html(unescape(PB.templates[panel]));
						
						$("#"+panel).attr("pb_state",1)
					}
					break;
				case "pb_user_panel_help":
					if (PB.loadTemplate(panel)) {
						$("#"+panel).html(unescape(PB.templates[panel]));
						
						$("#"+panel).attr("pb_state",1)
					}
					break;
			}
		}
		PB.showHideLoginSensitiveButtons();

		// Is the data populated and the panel visible?
		// If not visible, do not proceed past loading the template.
		// showUserPanel will get called the next time the tab is clicked, so it will update then.
		if ((PB.checkPanelState(panel) == 1) && (panel == PB.current_panel)) {
			switch (panel) {
				case "pb_user_panel_home":
					// nothing to do.
					break;
				case "pb_user_panel_myloc":
					if (PB.getFbUid()) {
						$.ajax({
							async: true,
							type: "GET",
							dataType: "json",
							url: "server/read.php",
							cache: false,
							data: {
								sr_desc: "Reading My Locations",
								action: 'mylocs'
							},
							success: function(response, textStatus, XMLHttpRequest) {
								if (PB.checkResponse(response)) {
									response.shift();
									PB.showLocationList(response,"#pb_mylocs_list",false);
									$("#pb_user_panel_myloc").attr("pb_state",2);
									PB.blurChildren("#pb_mylocs_list");
								}
							}
						});
					} else {
						$('#pb_mylocs_list').html('');
					}
					break;
				case "pb_user_panel_frloc":
					if (PB.getFbUid()) {
						$.ajax({
							async: true,
							type: "GET",
							dataType: "json",
							url: "server/read.php",
							cache: false,
							data: {
								sr_desc: "Reading Friends' Locations",
								action: 'frlocs'
							},
							success: function(response, textStatus, XMLHttpRequest) {
								if (PB.checkResponse(response)) {
									response.shift();
									PB.showLocationList(response,"#pb_frlocs_list",true);
									$("#pb_user_panel_frloc").attr("pb_state",2);
									PB.blurChildren("#pb_frlocs_list");
								}
							}
						});
					} else {
						$('#pb_frlocs_list').html('');
					}
					break;
				case "pb_user_panel_help":
					
					break;
			}
		}
	},

	showLocationList: function(response,target,show_owner) {
	// Displays server response for a list of Locations
		$(target).html('');
		for(var i=0;i<response.length;i++) {
			PB.decodeLocation(response[i]);
			switch(response[i].geomtype) {
				case 1:
					response[i].imgurl = 'images/markers/locations/point.png';
					break;
				case 2:
					response[i].imgurl = 'images/markers/locations/line.png';
					break;
				case 3:
					response[i].imgurl = 'images/markers/locations/area.png';
					break;
				default:
			}
			if (PB.isSet(show_owner) && show_owner == true){
				$(target).append($.tempest(PB.templates.pb_user_panel_frloc_entry, response[i]));
			} else {
				$(target).append($.tempest(PB.templates.pb_user_panel_myloc_entry, response[i]));
			}
		}
		//FB.XFBML.Host.parseDomTree();
		FB.XFBML.parse();
	},
	
	showHideViewEditButtons: function() {
	// Show or hide Login Status Sensitive buttons in the location panel
		// Altitude Graph
		if (PB.isSet(PB.viewing_location) && (PB.visible_locations[PB.viewing_location].geomtype == 2)){
			// Show the Show Altitude Graph button
			$('#pb_view_location_eletime').show();
		} else {
			// Hide the Show Altitude Graph button
			$('#pb_view_location_eletime').hide();
		}
		// Edit / Delete buttons in view mode
		if (PB.isSet(PB.viewing_location) && PB.isPbOwner(PB.visible_locations[PB.viewing_location].owner_fb_id)) {
			$('#pb_view_location_edit_loc').show();
		} else {
			$('#pb_view_location_edit_loc').hide();
		}
	},

	showHideLoginSensitiveButtons: function() {
	// Show or hide Login Status Sensitive buttons in the user panel.
	// (Also handles login box)
		if (PB.getFbUid()) {
			$('.pb_loggedin_show').show();
			$('.pb_loggedin_hide').hide();
		} else {
			$('.pb_loggedin_hide').show();
			$('.pb_loggedin_show').hide();
		}
	},

	attractAttention: function(obj) {
	// Attract attention to a UI element
		obj = "#"+obj;
		$(obj).effect("pulsate",{},500);
	},

	flashMarker: function(mn) {
	// Flash a marker
		PB.visible_locations[mn].marker.setMap(null);
		window.setTimeout('PB.visible_locations['+mn+'].marker.setMap(PB.map)',500);;
		window.setTimeout('PB.visible_locations['+mn+'].marker.setMap(null)',1000);;
		window.setTimeout('PB.visible_locations['+mn+'].marker.setMap(PB.map)',1500);;
	},

	ZoomBox : {
	// Object that handles the zoom counter in the interface
		initialize: function(id,pid) {
			google.maps.event.addListener(PB.map, 'zoom_changed', this.update);
			this.update();
		},
		update: function() {
			$('#pb_zoombox div').html(PB.map.getZoom());
		}
	},

	LocBox: {
	// Object that handles the location (ie Lat, Lng) box in the interface
		initialize: function(id, pid) {
			google.maps.event.addListener(PB.map, 'center_changed', this.update);
			this.update();
		},
		update: function() {
			var locdiv = $("#pb_locbox");
			var tmp_loc = PB.map.getCenter();
			$('#pb_locbox div:eq(0)') .html(Math.round(tmp_loc.lat()*10000)/10000);
			$('#pb_locbox div:eq(1)').html(Math.round(tmp_loc.lng()*10000)/10000);
		}	
	},
	
	// === Mapping Functions ==================================================================================================================================

	moveToLocation: function(tmp_id,tmp_lat, tmp_lng) {
	// Moves the map to a location and opens it's view window if available
		var found = 0;
		if (PB.visible_locations && PB.visible_locations.length) {
			for (var i=0;i<PB.visible_locations.length;i++) {
				if (tmp_id == PB.visible_locations[i].loc_id) {
					google.maps.event.trigger(PB.visible_locations[i].marker, 'click');
					found = 1;
				}
			}
		}
		if (!found){
			PB.waiting_to_view = tmp_id;
		}
		PB.map.panTo(new google.maps.LatLng(tmp_lat, tmp_lng));
	},

	geocodeLocation: function(loc,limit) {
	// Does Geocode (Name to Coordinates)
		var geocoder = new google.maps.Geocoder();
		//$("#pb_search_form input[name*='geo_viewport']").val()
		
		var options = {
			address: loc
		};
		if (limit) {
			options.bounds = PB.map.getBounds();
		}
		geocoder.geocode( options, function(results, status) {
			if (status == google.maps.GeocoderStatus.OK) {
				$("#pb_search_results").html('');
				PB.geocode_results = results;
				results = null;
				$.each(PB.geocode_results, function(key, value) {
					str = "";
					$.each(value.address_components, function(key, value) {
						if (key != 0) {
							str += ", ";
						}
						str += value.short_name;
					});
					$("#pb_search_results").append($.tempest(PB.templates.pb_user_panel_search_geocode, {
						name: str,
						results_index: key
					}));
				});
			} else {
				alert("Geocode was not successful for the following reason: " + status);
			}
		});
	},
	
	reverseGeocodeHere: function() {
	// Does a reverse geocode of where the map is looking
		var d = new Date();
		// If PB.reverseGeocodeHere.timer is set and is in the future, exit without doing reverse geocode
		if ((PB.isSet(PB.reverseGeocodeHere.timer)) && (d.getTime()-PB.reverseGeocodeHere.timer < 0)) {
			return false;
		} else {
			// We are sending a reverse geocode request to Google. Set a timer to stop us spamming requests
			PB.reverseGeocodeHere.timer = (d.getTime()+PB.rev_geo_timer);
		}

		loc = PB.map.getCenter();
		var geocoder = new google.maps.Geocoder();
			geocoder.geocode( { bounds: PB.map.getBounds(), latLng: loc}, function(results, status) {
				if (status == google.maps.GeocoderStatus.OK) {
					$('#pb_revgeo').html((results[0].address_components[0].long_name));
				} else {
					$('#pb_revgeo').html("'The middle of nowhere'");
			}
		});
	},

	fitBounds: function(index) {
	// Pans and Zooms the map to the bounds of an entry in the geocode results cache.
		PB.map.fitBounds(PB.geocode_results[index].geometry.viewport);
	},

	searchPbLocations: function(search) {
	// Searches the Placebook database for locations
		$.ajax({
			async: true,
			type: "GET",
			dataType: "json",
			url: "server/read.php",
			cache: false,
			data: {
				sr_desc: "Searching PB database",
				action: 'search',
				search: search
			},
			success: function(response, textStatus, XMLHttpRequest) {
				if (PB.checkResponse(response)) {
					response.shift();
					PB.showLocationList(response,"#pb_search_results",true);
				}
			}
		});
	},

	// === Facebook and Login / Logout functions ==============================================================================================================
	
	logOut: function() {
	// Logs out of Facebook
		if (PB.isSet(PB.EditLoc.editmode) && PB.EditLoc.editmode > 0) {
			if (!confirm("You are editing a location, if you log out, changes will be lost.\n\nAre you sure you want to log out?")) {
				return false;
			} else {
				PB.EditLoc.exit();
			}
		}
		FB.logout(function(response) {

		});
		return true;
	},

	loginChange: function() {
	// Called when user logs in or out of facebook
		PB.showHideViewEditButtons();
		PB.showHideLoginSensitiveButtons();
		PB.refreshUserPanel("pb_user_panel_frloc");
		PB.refreshUserPanel("pb_user_panel_myloc");
		FB.XFBML.parse();
	},

	isPbOwner: function(ownerid) {
	// Is the passed ownerid the current user? (Safe, even if logged out)
		if (PB.getFbUid() == ownerid) {
			return true;
		}
		return false;
	},

	getFbUid: function() {
	// Gets the Facebook ID of the user
	// Returns false if logged out.
		// Bodge to get PB working again
		/*
		var tmp = FB.getSession();
		if (PB.isSet(tmp) && PB.isSet(tmp.uid)) {
			return tmp.uid;
		} else {
			return false;
		}
		*/
		return 515937277;
	},

	// === Edit Location OBJECT ===============================================================================================================================
	EditLoc: {
		initialize: function() {
			// Call at start of program and after each edit (or cancel!) to reset
			this.editmode = 0;							// State of edit form 0 = not editing, 1 = adding, 2 = editing
			this.editing_id = null;						// Loc ID of location being edited if editmode = 2
			this.geomtype = 0;							// Type of location being edited. 0 = None, 1 = point, 2 = line, 3 = area
			this.marker_overlays = [];					// Array of edit markers (Google Marker objects)
			this.polygon = new google.maps.MVCArray;	// A Google MVCArray of Google LatLngs that represents the poly. Updating it also updates the poly (polygon_overlay) in real-time
														// This holds point, line AND area data.
														// If in area mode and a seperate marker_point has been added, then PB.EditLoc.marker_point is the marker_point for the database...
														// ... else the first element of PB.EditLoc.polygon is the marker_point
			this.polygon_overlay = null;				// A Google Polygon object that is the visual representation of polygon.
			this.marker_point = null;					// Holds the marker_point, if it exists (A Google Marker object)
			this.eletime = [];							// An array holding the elevation and time data for the location, if imported. This array should be in sync (ie same number of entries) as polygon
		},
		
		mapClicked: function(loc) {
		// Process map clicks in edit mode
			if (PB.EditLoc.geomtype) {		// has the location type been chosen?
				switch (PB.EditLoc.geomtype) {
					case 1:		// Point
						//console.log("click in point mode");
						if (!PB.EditLoc.polygon.length) {
							PB.EditLoc.polygon.insertAt(PB.EditLoc.polygon.length, loc);
						} else {
							PB.EditLoc.polygon.setAt(0, loc);
						}

						if (PB.EditLoc.marker_overlays.length) {
							PB.EditLoc.marker_overlays[0].setMap(null);
							PB.EditLoc.marker_overlays[0] = PB.EditLoc.createMarker(loc);
						} else {
							PB.EditLoc.marker_overlays.push(PB.EditLoc.createMarker(loc));
						}
						break;
					case 2:		// Line
						//console.log("click in line mode");
						//console.log("Point "+PB.EditLoc.polygon.length);
						PB.EditLoc.polygon.insertAt(PB.EditLoc.polygon.length, loc);
						if (PB.isSet(PB.EditLoc.eletime) && PB.EditLoc.eletime.length) {
							PB.EditLoc.eletime.push(new Array(null,''));
						}
						PB.EditLoc.polygon_overlay.setPath(PB.EditLoc.polygon);
						
						PB.EditLoc.marker_overlays.push(PB.EditLoc.createMarker(loc));
						break;
					case 3:		// Area
						//console.log("click in area mode");
						PB.EditLoc.polygon.insertAt(PB.EditLoc.polygon.length, loc);
						if (PB.isSet(PB.EditLoc.eletime) && PB.EditLoc.eletime.length) {
							PB.EditLoc.eletime.push(new Array(null,null));
						}
						PB.EditLoc.polygon_overlay.setPath(PB.EditLoc.polygon);
						
						PB.EditLoc.marker_overlays.push(PB.EditLoc.createMarker(loc));
						break;
					default:
				}
				// Set the marker icons and show them
				PB.EditLoc.setMarkerIcons();
				
				// Update the state of the edit form
				PB.EditLoc.updateEditForm();
			}
		},
		
		createMarker: function(loc) {
		// Create a temporary marker
		// Does not set icon - setMarkerIcons is used for that.
		// DO NOT CALL setMarkerIcons IN HERE AS THIS IS OFTEN CALLED IN A LOOP!
			//console.log("Creating marker: lat "+loc.lat()+', lng "+loc.lng());
			var marker = new google.maps.Marker({		// ToDo: Does setting options THEN adding to map make faster? Surely so
				position: loc,
				draggable: true
			});
			marker.setTitle("#" + PB.EditLoc.polygon.length);

			google.maps.event.addListener(marker, 'click', function() {
				marker.setMap(null);
				for (var i = 0, I = PB.EditLoc.marker_overlays.length; i < I && PB.EditLoc.marker_overlays[i] != marker; ++i);
				PB.EditLoc.marker_overlays.splice(i, 1);
				PB.EditLoc.eletime.splice(i, 1);
				PB.EditLoc.polygon.removeAt(i);
				PB.EditLoc.setMarkerIcons();
				PB.EditLoc.updateEditForm();
			});
			google.maps.event.addListener(marker, 'drag', function() {
				for (var i = 0, I = PB.EditLoc.marker_overlays.length; i < I && PB.EditLoc.marker_overlays[i] != marker; ++i);
				PB.EditLoc.polygon.setAt(i, marker.getPosition());
			});
			google.maps.event.addListener(marker, 'dragend', function() {
				for (var i = 0, I = PB.EditLoc.marker_overlays.length; i < I && PB.EditLoc.marker_overlays[i] != marker; ++i);
				//PB.EditLoc.placedbyclick(1);
				PB.EditLoc.polygon.setAt(i, marker.getPosition());
			});
			return marker;
		},
		
		insertMarkerPoint: function(loc,t) {
		// Inserts the marker_point
			// Check there are at least two points first...
			if (PB.isSet(PB.EditLoc.polygon) && PB.EditLoc.polygon.length > 1){
				if (!PB.isSet(loc)) {
					// Try and find centre point
					var tb = new google.maps.LatLngBounds();
					for (var i=0;i<PB.EditLoc.marker_overlays.length;i++) {
						tb.extend(PB.EditLoc.marker_overlays[i].getPosition());
					}
					loc = tb.getCenter();
				}
				if (PB.isSet(PB.EditLoc.marker_point)){
					PB.EditLoc.marker_point.setMap(null);
				}
				PB.EditLoc.marker_point = new google.maps.Marker({
					map: PB.map,
					draggable: true
				});
				PB.EditLoc.marker_point.pb_geomtype = 3;
				PB.setMarkerOptions(PB.EditLoc.marker_point,0,t);
				PB.EditLoc.marker_point.setOptions({
					position: loc
				});
			}
		},
		
		removeMarkerPoint: function() {
		// Removes the marker_point overlay (Used sometimes in area mode)
			PB.EditLoc.marker_point.setMap(null);
			PB.EditLoc.marker_point = null;
			PB.EditLoc.updateEditForm();
		},
		
		encodeMarkerPoint: function() {
		// Returns the string value of the marker_point
			if (PB.isSet(PB.EditLoc.marker_point) && PB.EditLoc.geomtype == 3) {
				return PB.EditLoc.marker_point.getPosition().lng()+" "+PB.EditLoc.marker_point.getPosition().lat();
			} else {
				return PB.EditLoc.marker_overlays[0].position.lng()+" "+PB.EditLoc.marker_overlays[0].position.lat();
			}
		},
		
		setMarkerIcons: function() {
		// Set icons for temporary overlays
			for (var i=0;i<PB.EditLoc.marker_overlays.length;i++) {
				if (i == 0) {
					if (i == PB.EditLoc.marker_overlays.length-1) {
						var image = new google.maps.MarkerImage('images/markers/temp/point.png',
							// Size
							new google.maps.Size(21, 25),
							// Origin
							new google.maps.Point(0,0),
							// Anchor
							new google.maps.Point(10, 23));
					} else {
						var image = new google.maps.MarkerImage('images/markers/temp/start.png',
							// Size
							new google.maps.Size(21, 25),
							// Origin
							new google.maps.Point(0,0),
							// Anchor
							new google.maps.Point(10, 23));
					}
				} else if (i == PB.EditLoc.marker_overlays.length-1) {
					var image = new google.maps.MarkerImage('images/markers/temp/end.png',
						// Size
						new google.maps.Size(21, 25),
						// Origin
						new google.maps.Point(0,0),
						// Anchor
						new google.maps.Point(10, 23));
				} else {
					var image = new google.maps.MarkerImage('images/markers/temp/node.png',
						// Size
						new google.maps.Size(11, 11),
						// Origin
						new google.maps.Point(0,0),
						// Anchor
						new google.maps.Point(5, 5));
				}
				PB.EditLoc.marker_overlays[i].setIcon(image);
				PB.EditLoc.marker_overlays[i].setMap(PB.map);
			}
		},

		editLocation:function(obj) {
		// Add or Edit a location
			// Do some checks
			// Load the template
			if (!PB.isSet(PB.templates.pb_edit_location)) {
				if (!PB.loadTemplate("pb_edit_location")) {
					// Just exit. User is warned by error reporting system and can try again.
					return false;
				}
			}
			if (obj == null) {
				// Add Mode
				if (PB.getFbUid()) {
					PB.EditLoc.editmode = 1;
					PB.EditLoc.geomtype = 1;	// force to point mode
				} else {
					return false;
				}
			} else {
				// Edit Mode
				if (PB.getFbUid()) {
					if (!PB.EditLoc.editmode) {
						PB.EditLoc.editmode = 2;
						//PB.EditLoc.geomtype = obj.geomtype;	// force to point mode
					}
				} else {
					alert("Please log in to edit locations");
					return false;
				}

			}
			// Insert edit form from template
			if (PB.EditLoc.editmode == 2) {
				var mode="Edit";
			} else {
				var mode="Add";
			}
			PB.insertPanel("#pb_loc_panel",PB.templates.pb_edit_location,{mode: mode});

			// Add category list to template
			$.each(PB.categories, function(key, value) {
				 $("#pb_edit_loc_form select[name*='category']").append('<option value="'+key+'">'+unescape(value)+'</option>');
			});

			// Set up submit behaviour - pass thru validate
			$("#pb_edit_loc_form").validate({
				submitHandler: PB.EditLoc.submitLocation
			});
			
			// Add message for category select
			$("#pb_edit_loc_form select[name*='category']").rules("add", {
				messages: {
					min: "Please select a category"
				}
			});

			// Populate form in edit mode
			if (obj != null) {
				var params = {
					sr_desc: "Loading Edit Form Data",
					action: 'alldata',
					id: obj
				};
				$.ajax({
					type: "GET",
					dataType: "json",
					url: "server/read.php",
					data: params,
					success: function(transport) {
						if (PB.checkResponse(transport)) {
							PB.stopViewingLocation();
							PB.hideOverlays(obj);
							PB.EditLoc.editing_id = obj;
							
							PB.EditLoc.editmode = 2;
							PB.EditLoc.populateEditForm(transport[1]);

						}
						$("#pb_edit_loc_form textarea[name*='description']").wysiwyg();
						// Set the radio button to initial state (Needed for Add and Edit mode)
						PB.EditLoc.setGeomTypeRadio();
						PB.EditLoc.updateEditForm();	// Show initial valdation state
					}
				});			
				return false;	// This is NEEDED else the page gets redirected.
			} else {
				$("#pb_edit_loc_form textarea[name*='description']").wysiwyg();
				PB.EditLoc.setGeomTypeRadio();
				PB.EditLoc.updateEditForm();	// Show initial valdation state
			}
			
			// !!! Important !!! 
			// always return false to prevent standard browser submit and page navigation 
			return false; 
		},
		
		changeGeomType: function(lt) {
		// Change from one location geometry type to another.
		// Changing from no type to a type sets up for that type
		//console.log("Switching from mode "+PB.EditLoc.geomtype+" to "+lt);
			switch (lt) {	// Switch on NEW geomtype
							// Check on OLD geomtype inside conditionals
				case 1:		// Point
					// Hide Altitude Graph button
					$('#pb_edit_loc_eletime').hide();
					// Hide Extra marker button
					$('#pb_edit_loc_extra').hide();
					
					if (PB.isSet(PB.EditLoc.marker_point)) {
						PB.EditLoc.removeMarkerPoint();
					}
					if (PB.EditLoc.geomtype == 0) {
						//console.log("initing point");
						PB.EditLoc.polygon_overlay = new google.maps.Polyline({
							path: PB.EditLoc.polygon
						});
						PB.EditLoc.polygon_overlay.setMap(map);
					} else if (PB.EditLoc.geomtype == 2) {	// Line => Point
						//console.log("Converting line to point");
						if (PB.EditLoc.polygon.length > 1) {
							alert ("WARNING: You have changed from a line to a point\nIf you submit changes without changing back to a line or area, you will lose data!");
						}
						PB.EditLoc.polygon_overlay.setMap(null);
						for (var i=PB.EditLoc.marker_overlays.length-1;i>0;i--) {
							PB.EditLoc.marker_overlays[i].setMap(null);
							PB.EditLoc.marker_overlays.pop();
						}
					} else if (PB.EditLoc.geomtype == 3) { 	// Area => Point
						//console.log("Converting area to point");
						if (PB.EditLoc.polygon.length > 1) {
							alert ("WARNING: You have changed from an area to a point\nIf you submit changes without changing back to a line or area, you will lose data!");
						}
						PB.EditLoc.polygon_overlay.setMap(null);
						for (var i=PB.EditLoc.marker_overlays.length-1;i>0;i--) {
							PB.EditLoc.marker_overlays[i].setMap(null);
							PB.EditLoc.marker_overlays.pop();
						}
					}
					break;
				case 2:		// Line
					// Show Altitude Graph button
					$('#pb_edit_loc_eletime').show();
					// Hide Extra marker button
					$('#pb_edit_loc_extra').hide();

					if (PB.isSet(PB.EditLoc.marker_point)) {
						PB.EditLoc.removeMarkerPoint();
					}
					if (PB.EditLoc.geomtype == 0) {
						//console.log("initing line");
						PB.EditLoc.polygon_overlay = new google.maps.Polyline({
							path: PB.EditLoc.polygon
						});
						PB.EditLoc.polygon_overlay.setMap(PB.map);
					} else if (PB.EditLoc.geomtype == 1) {	// Point => Line
						//console.log("Converting point to line");
						if (PB.isSet(PB.EditLoc.polygon_overlay)) {
							PB.EditLoc.polygon_overlay.setMap(null);
						}
						if (PB.EditLoc.polygon.length > 1) {	// coming back to polyline from point
							for (var i=1;i<PB.EditLoc.polygon.length;i++) {
								PB.EditLoc.marker_overlays.push(PB.EditLoc.createMarker(PB.EditLoc.polygon.getAt(i)));
							}
							// recreate polygon bodge - because the line stays closed if it was previoulsy a polygon otherwise.
							var tmp_line = PB.EditLoc.polygon_overlay.getPath();
							PB.EditLoc.polygon = new google.maps.MVCArray;
							for(var i=0;i<tmp_line.length;i++) {
								PB.EditLoc.polygon.insertAt(PB.EditLoc.polygon.length, tmp_line.getAt(i));
							}
							PB.EditLoc.polygon_overlay = null;
							// end bodge
						}
						PB.EditLoc.polygon_overlay = new google.maps.Polyline({
							path: PB.EditLoc.polygon
						});
						PB.EditLoc.polygon_overlay.setMap(PB.map);
					} else if (PB.EditLoc.geomtype == 3) {	// Area => Line
						//console.log("Converting area to line");
						PB.EditLoc.polygon_overlay.setMap(null);
						PB.EditLoc.polygon_overlay = new google.maps.Polyline({
							path: PB.EditLoc.polygon
						});
						PB.EditLoc.polygon_overlay.setMap(PB.map);
					}
					break;
				case 3:		// Area
					// Hide Altitude Graph button
					$('#pb_edit_loc_eletime').hide();

					// Show Extra marker button
					$('#pb_edit_loc_extra').show();

					if (PB.EditLoc.geomtype == 0) {
						//console.log("Initing area");
						PB.EditLoc.polygon_overlay = new google.maps.Polygon({
							paths: new google.maps.MVCArray([PB.EditLoc.polygon])
						});
						PB.EditLoc.polygon_overlay.setMap(PB.map);
					} else if (PB.EditLoc.geomtype == 1) {	// Point => Area
						if (PB.EditLoc.polygon.length > 1) {	// coming back to polyline from point
							for (var i=1;i<PB.EditLoc.polygon.length;i++) {
								PB.EditLoc.marker_overlays.push(PB.EditLoc.createMarker(PB.EditLoc.polygon.getAt(i)));
							}
						}
						//console.log("Converting point to area");
						PB.EditLoc.polygon_overlay = new google.maps.Polygon({
							paths: new google.maps.MVCArray([PB.EditLoc.polygon])
						});
						PB.EditLoc.polygon_overlay.setMap(PB.map);
					} else if (PB.EditLoc.geomtype == 2) {	// Line => Area
						//console.log("Converting line to area");
						PB.EditLoc.polygon_overlay.setMap(null);
						PB.EditLoc.polygon_overlay = new google.maps.Polygon({
							paths: new google.maps.MVCArray([PB.EditLoc.polygon])
						});
						PB.EditLoc.polygon_overlay.setMap(PB.map);
					}
					break;
				default:	// None set
				
			};

			//PB.setPolyOptions(PB.EditLoc.polygon_overlay,true);
			PB.EditLoc.geomtype = lt;
			PB.EditLoc.updateEditForm();
		},
		
		populateEditForm:function(location) {
		// Shows and populates the edit form
			// take data from JSON object array and build overlays
			PB.EditLoc.geomtype = location.geomtype;
			
			PB.decodeLocation(location);	// run unescape on relevant fields
			if (PB.isSet(location.name)) {
				$("#pb_edit_loc_form input[name*='name']").val(location.name);
			}
			if (PB.isSet(location.description)) {
				$("#pb_edit_loc_form textarea[name*='description']").val(location.description);
			}
			// Convert location data from JSON format to Google format
			if (location.geomtype == 1) {
				var tmp_geom = PB.jsonToLatLng([location.marker_point]);
			} else {
				var tmp_geom = PB.jsonToLatLng(location.geom);
			}
			if (PB.isSet(location.category)) {
				$("#pb_edit_loc_form select[name*='category']").val(location.category);
			}
			
			// Move the map (in case marker is off screen)
			PB.map.panTo(tmp_geom[0]);
			// Build geometry markers
			PB.EditLoc.polygon = new google.maps.MVCArray;
			for (var i=0;i<tmp_geom.length;i++) {
				//console.log(tmp_geom[i]);
				PB.EditLoc.polygon.insertAt(i,tmp_geom[i]);
				PB.EditLoc.marker_overlays.push(PB.EditLoc.createMarker(tmp_geom[i]));
			}
			PB.EditLoc.setMarkerIcons();
			if (location.geomtype > 2) {
				// Polygon type
				PB.EditLoc.polygon_overlay = new google.maps.Polygon({
					paths: new google.maps.MVCArray([PB.EditLoc.polygon])
				});
			} else if ( location.geomtype > 0 ) {
				// Polyline type
				PB.EditLoc.polygon_overlay = new google.maps.Polyline({
					path: PB.EditLoc.polygon
				});
			}
			// Set up marker_point
			if (PB.isSet(location.marker_point) && location.geomtype == 3 ) {
				PB.EditLoc.insertMarkerPoint(new google.maps.LatLng(location.marker_point[0],location.marker_point[1]),true);
				//PB.EditLoc.insertMarkerPoint(PB.jsonToLatLng(location.marker_point),true);
			}
			// Load elevation data?
			if (PB.isSet(location.eletime)) {
				PB.EditLoc.eletime = location.eletime;
			}
			// Configure overlays for edit mode styling
			PB.setPolyOptions(PB.EditLoc.polygon_overlay,true);
			// Show overlays
			PB.EditLoc.polygon_overlay.setMap(PB.map);
		},

		updateEditForm: function() {
		// Updates status of edit form.
			// Update the minimum number of points for this location type
			var msg = "Please click the map to add ";
			switch (PB.EditLoc.geomtype) {
				case 1:
					msg += "a point.";
					break;
				case 2: 
					msg += "more points.<br><br>You must add at least two points for a line type.";
					break;
				case 3:
					msg += "more points.<br><br>You must add at least three points for an area, else it's just a line ;)";
					break;
				default:
			}
			$("#pb_edit_loc_form input[name*='geom_count']").rules("add", {
				min: PB.EditLoc.geomtype,
				messages: {
					min: msg
				}
			});
			$("#pb_edit_loc_form input[name*='geom_count']").val(PB.EditLoc.marker_overlays.length);
			$("#pb_edit_loc_form").validate().form();
			// Show / hide "Extra Marker" button
			if (PB.EditLoc.geomtype == 3) {
				$("#pb_add_marker_point").show();
			} else {
				$("#pb_add_marker_point").hide();
			}
			return PB.EditLoc.marker_overlays.length;
		},
		
		submitLocation: function(form) {
		// Submit handler for edit form validator
			var options = { 
				//beforeSubmit:  PB.EditLoc.preSubmit,  // pre-submit callback
				success:       PB.EditLoc.postSubmit,  // post-submit callback 
				data: { 
					marker_point: PB.EditLoc.encodeMarkerPoint
					, geomtype: PB.EditLoc.geomtype 
				},
				error: function(){
					// Do something here in addition to standard error handling?
				}
			};
			
			// ToDo: Clear pb_editlocform_geomtype radio button from data field
			options.data.pb_editlocform_geomtype = null;
			
			// Set Add / Edit mode
			if (PB.EditLoc.editmode > 1) {
				options.data.action = 'editloc'
				options.data.id = PB.EditLoc.editing_id;
				options.data.sr_desc = "Editing Location";
			} else {
				options.data.action = 'addloc'
				options.data.sr_desc = "Adding Location";
			}
			
			// Store poly data if line or area
			if (PB.EditLoc.geomtype > 1) {
				var tmp_str = '';
				if (PB.EditLoc.polygon.length) {
					for (var i=0;i<PB.EditLoc.polygon.length;i++) {
						if (i != 0) {
							tmp_str += ',';
						}
						tmp_str += PB.EditLoc.polygon.getAt(i).lng() +' '+PB.EditLoc.polygon.getAt(i).lat();
					}
					options.data.geometry = tmp_str;
				}
			}
			// Save elevation / time data if present
			if (PB.isSet(PB.EditLoc.eletime) &&  PB.EditLoc.eletime.length) {
				options.data.eletime = JSON.stringify(PB.EditLoc.eletime);
			}

			$(form).ajaxSubmit(options)
		},
		
		preSubmit: function(form_data, jq_form, options) { 
		// Called as form is about to be submit
			var queryString = $.param(form_data); 
			// jq_form is a jQuery object encapsulating the form element.  To access the 
			// DOM element for the form do this: 
			// var formElement = jq_form[0]; 
		
			// here we could return false to prevent the form from being submitted; 
			return true; 
		},
		
		postSubmit: function(response, status_text, xhr, $form)  { 
		// Handles the result of submitting the form.
			//alert('status: ' + status_text + '\n\nresponse: \n' + response + '\n\nThe output div should have already been updated with the response.');
			
			// should use checkResponse() here !
			PB.EditLoc.exit();
			PB.readLocations();
			// Tell My Locations to update
			PB.refreshUserPanel("pb_user_panel_myloc");
		},
		
		clearOverlays: function() {
		// Clear temporary overlays
			for (var i=0;i<PB.EditLoc.marker_overlays.length;i++) {
				PB.EditLoc.marker_overlays[i].setMap(null);
			}
			if (PB.isSet(PB.EditLoc.polygon_overlay)) {
				PB.EditLoc.polygon_overlay.setMap(null);
			}
			if (PB.isSet(PB.EditLoc.marker_point)) {
				PB.EditLoc.removeMarkerPoint();
			}
		},

		setGeomTypeRadio: function() {
		// Sets the location type radio
			$("input[name*='pb_editlocform_geomtype']:nth("+(PB.EditLoc.geomtype-1)+")").attr("checked","checked");
			PB.EditLoc.changeGeomType(PB.EditLoc.geomtype);		// call this here so that optional elements that rely on location type get triggered.
		},

		deleteLocation: function(id) {
		// Delete a location - DO NOT name delete else IE will barf
			var tmp_id = null;
			if (PB.isSet(id)) {
				tmp_id = id;
			} else if (PB.EditLoc.editing_id) {
				tmp_id = PB.EditLoc.editing_id;
			}
			if (tmp_id) {
				if (confirm('Do you really want to delete this location permanently?')) {
					var params = {
						sr_desc: "Deleting Location",
						action: "delloc",
						id: tmp_id
					};
					$.ajax({
						type: "POST",
						url: "server/write.php",
						data: params,
						dataType: "json",
						success: function(transport) {
							if (PB.checkResponse(transport)) {
								PB.EditLoc.exit();
								PB.readLocations();
								// Tell My Locations to update
								PB.refreshUserPanel("pb_user_panel_myloc");
							}
						}
					});
				}
			}
		},

		gpxImport: function() {
			if (!PB.isSet(PB.templates.pb_edit_loc_form_gpx_import)) {
				if (!PB.loadTemplate("pb_edit_loc_form_gpx_import")) {
					return false;
				}
			}
			
			$("#pb_gps_import").html(PB.templates.pb_edit_loc_form_gpx_import);
			// Set up GPX Import form
			var options = { 
				//target:        '#output1',   // target element(s) to be updated with server response 
				beforeSubmit:  PB.EditLoc.gpxPreSubmit,  // pre-submit callback
				success:       PB.EditLoc.gpxPostSubmit,  // post-submit callback 
				dataType: "json",
				data:			{
					sr_desc: "Importing GPS file"
				}
			};

			$("#pb_edit_loc_form_gpx_import").ajaxForm(options);

			$( "#pb_gps_import" ).dialog({
				modal: true
			});
		},

		gpxSubmit: function(data) {
		// Parses uploaded file
			var location = [];
			location.geom = [];
			location.eletime = [];
			location.geomtype = 2;
			
			var tmp_import = XMLObjectifier.xmlToJSON(XMLObjectifier.textToXML(data));
			if (tmp_import && tmp_import.RootName && tmp_import.RootName == 'gpx') {
				if (tmp_import.trk[0].trkseg[0].trkpt.length) {
					
				} else {
					alert ('Could not fint trk.trkseg.trkpt structure');
				}
			} else {
				alert ('Not a GPX file');
			}
			if (PB.isSet(tmp_import.trk[0].name) && $("#pb_edit_loc_form input[name*='name']").val() == '') {
				location.name = tmp_import.trk[0].name[0].Text;
			} else {
				location.name = $("#pb_edit_loc_form input[name*='name']").val();
			}
			if ($("#pb_edit_loc_form textarea[name*='description']").val() != '') {
				location.description = $("#pb_edit_loc_form textarea[name*='description']").val();
			} else {
				location.description = '';
			}
			var tmp_import = tmp_import.trk[0].trkseg[0].trkpt;
			for (var i=0;i<tmp_import.length;i++) {
				location.geom[i] = new Array(tmp_import[i].lat,tmp_import[i].lon);
				location.eletime[i] = new Array(Number(tmp_import[i].ele[0].Text), tmp_import[i].time[0].Text.replace('"',''));
			}
			PB.map.panTo(new google.maps.LatLng(tmp_import[0].lat,tmp_import[0].lon));
			tmp_import = null;	// does this help free mem before the build?
			PB.EditLoc.populateEditForm(location);
			PB.EditLoc.setGeomTypeRadio();
			PB.EditLoc.updateEditForm();
		},

		gpxPreSubmit: function(form_data, jq_form, options) {
			/*
			var queryString = $.param(form_data); 
			if (!confirm('About to submit: \n\n' + queryString)) {
				return false;
			}
			*/
			return true; 
		},

		gpxPostSubmit: function(response, status_text, xhr, $form) {
		// Shows result of GPS import
			//alert('status: ' + status_text + '\n\nresponse: \n' + response + '\n\nThe output div should have already been updated with the response.');
			if (PB.checkResponse(response)){
				response.shift();
				PB.EditLoc.gpxSubmit(unescape(response));
				$( "#pb_gps_import" ).dialog("close");
				$( "#pb_gps_import" ).html("Import Successful");
				$( "#pb_gps_import" ).dialog({
					modal: true,
					title: "GPX Import",
					buttons: { "OK": function() { $(this).dialog("close"); } } 
				});
				PB.blurChildren("#pb_gps_import");
			}
		},
		
		exit: function() {
		// Exits edit mode and sets vars accordingly.
		// Note that it should not reload the view, that is handled by sumbit.
			PB.mouseover_location = null;
			PB.viewing_location = null;
			
			if (PB.EditLoc.editmode > 1) {
				// If we are editing a location, then remove it's marker on exiting edit mode.
				if (PB.isSet(PB.EditLoc.editing_id)) {
					PB.showOverlays(PB.EditLoc.editing_id);
					PB.EditLoc.editing_id = null;
				}
			}
			if (PB.EditLoc.editmode) {
				// Exit add / edit mode
				// clear overlays
				PB.EditLoc.clearOverlays();
				// reset back to initial state
				PB.EditLoc.initialize();
				//PB.readLocations();
			}
			$('#pb_loc_panel').html('');
		}
		
		// === End of EditLoc ===
	},
	
	dummyEnd: function(){
		// used so I don't have to remember to put a comma after declarations in PB
		// Always use a comma and leave this at the end.
	}
};

// ============================================================================================================================================================
// ============================== END OF PB NAMESPACE. DO NOT PUT THINGS AFTER HERE THAT ARE OUTSIDE NAMESPACES! ==============================================
// ============================================================================================================================================================



// === 3RD PARTY ===
(function($) {
// JQuery Unserialize v1.0 by James Campbell
// http://plugins.jquery.com/node/11882
// DO NOT move inside PB namespace, this is a jQuery extension
	$.unserialise = function(Data) {
		var Data = Data.split("&");
		var Serialised = [];
		$.each(Data, function() {
			var Properties = this.split("=");
			Serialised[Properties[0]] = Properties[1];
		});
		return Serialised;
	};
})(jQuery);
// == 3P END ===
