if (typeof(SJL) == 'undefined' || ( ! SJL instanceof Object)) SJL = new Object();

if (typeof(SJL.SLGoogleMap) == 'undefined' || ( ! SJL.SLGoogleMap instanceof Object)) {
	
	SJL.SLGoogleMap = function() {		
		
		/**
		 * The Map "class" constructor.
		 * @param 	object 		init 			Initialisation object containing information essential to the construction of the map.
		 * @param 	object 		options		Map UI options.
		 */
		function Map(init, options) {
						
			// Check that we have the required information.			
			if ( ! init instanceof Object ||
				typeof(init.map_container) == 'undefined' ||
				typeof(init.map_lat) == 'undefined' ||
				typeof(init.map_lng) == 'undefined' ||
				typeof(init.map_zoom) == 'undefined' ||
				typeof(init.pin_lat) == 'undefined' ||
				typeof(init.pin_lng) == 'undefined') {
					return false;
				}
				
			// Set the default map options.
			var map_options = {
				'ui_zoom'					: false,
				'ui_scale' 				: false,
				'ui_overview'			: false,
				'ui_map_type'			: false,
				'map_drag'				: false,
				'map_click_zoom'	: false,
				'map_scroll_zoom'	: false,
				'pin_drag'				: false,
				'background'			: '#FFFFFF',
				'map_types'				: ''
			}
			
			// An index containing the available map types.
			var valid_map_types = {
				'hybrid'			: G_HYBRID_MAP,
				'normal'			: G_NORMAL_MAP,
				'physical'		: G_PHYSICAL_MAP,
				'satellite'		: G_SATELLITE_MAP
			};
			
			default_map_type = valid_map_types['normal'];
			
			// Override the default options.			
			for (o in options) {
				if (map_options[o] != 'undefined') {
					map_options[o] = options[o];
				}
			}
			
			// Create the map.
			this.__map = new GMap2(document.getElementById(init.map_container), {backgroundColor: map_options['background']});
			this.__map.setCenter(new GLatLng(init.map_lat, init.map_lng), init.map_zoom);		// MUST explicitly call setCenter.
			
			// Customise the map UI.			
			ui = this.__map.getDefaultUI();
			
			// Everything else is controlled by our map_options object.
			// - Zoom / pan controls.
			if (this.__map.getSize().height <= 325) {
				ui.controls.smallzoomcontrol3d = map_options.ui_zoom;
				ui.controls.largemapcontrol3d = false;
			} else {
				ui.controls.smallzoomcontrol3d = false;
				ui.controls.largemapcontrol3d = map_options.ui_zoom;
			}
			
			// - Map dragging.
			map_options.map_drag ? this.__map.enableDragging() : this.__map.disableDragging();
			
			// - Map zooming.
			ui.zoom.doubleclick = map_options.map_click_zoom;
			ui.zoom.scrollwheel = map_options.map_scroll_zoom;
			
			// - Scale control.
			ui.controls.scalecontrol = map_options.ui_scale;
			
			// - Map type control.
			ui.controls.maptypecontrol = ui.controls.menumaptypecontrol = false;			
			if (this.__map.getSize().width <= 475) {
				ui.controls.menumaptypecontrol = map_options.ui_map_type;
			} else {
				ui.controls.maptypecontrol = map_options.ui_map_type;					
			}
			
			// - Explicitly-set available map types.
			if (map_options.map_types) {
				map_types = map_options.map_types.split('|');
				
				/**
				 * At this point in time we have no idea what we've got.
				 * We assume the worst, and set all the map types to false.
				 * Then we loop through, activating only those that are explicitly
				 * required.
				 *
				 * In the midst of all this, we also determine the default map type.
				 */
				
				ui.maptypes.normal 			= false;
				ui.maptypes.satellite		= false;
				ui.maptypes.hybrid			= false;
				ui.maptypes.physical		= false;
								
				map_types_count 	= map_types.length;
				default_map_set		= false;
				
				for (i = 0; i < map_types_count; i++) {				
					map_types[i] = map_types[i].toLowerCase();
					
					if (valid_map_types[map_types[i]]) {
						ui.maptypes[map_types[i]] = true;
						
						if (!default_map_set) {
							default_map_type 	= valid_map_types[map_types[i]];
							default_map_set		= true;
						}
					}
				}
			}
			
			// - Set the default map type.
			this.__map.setMapType(default_map_type);
			
			// - Set the UI options.
			this.__map.setUI(ui);
			
			/**
			 * @bug
			 * The overview map always appears as type G_NORMAL_MAP, regardless of the type of the
			 * main map. There doesn't seem to be a way to change this, or force a refresh of the map.
			 */
			
			// - Overview control (need to do this separately, for reasons best known to Google).
			if (map_options.ui_overview) {
				this.__map.addControl(new GOverviewMapControl());
			}
			
			// A shortcut variable that we can reference in our function literals below.
			var t = this;
			
			// Add the map "pin".
			this.__marker = new GMarker(new GLatLng(init.pin_lat, init.pin_lng), {clickable: map_options.pin_drag, draggable: map_options.pin_drag, autoPan: true});
			this.__map.addOverlay(this.__marker);
			
			// Add an event listener to the map "pin".
			if (map_options.pin_drag) {
				this.__marker_listener = GEvent.addListener(this.__marker, 'dragend', function(latlng) {
					t.set_location(latlng);
				});
			}			
			
			// If we have a "map_field", we need to update it every time our map changes.
			if (typeof(init.map_field) == 'string' && init.map_field.length) {
				this.__map_field = jQuery('#' + init.map_field);
			
				// Add the event listener.
				if (this.__map_field.length) {				
					this.__map_listener = GEvent.addListener(this.__map, 'moveend', function() {
						var map_data = t.get_location();
						var pin_data = t.get_marker();						
						var field_data = map_data.latlng.lat() + ',' + map_data.latlng.lng() + ',' + map_data.zoom + ',' + pin_data.lat() + ',' + pin_data.lng();
						t.__map_field.val(field_data);
					});
				}
			}
			
			// If we have an "address_input" field, and an "address_submit" field, we need to
			// link these to our map.
			if (typeof(init.address_input) == 'string' && typeof(init.address_submit) == 'string' && init.address_input.length && init.address_submit.length) {
				this.__address_input 	= jQuery('#' + init.address_input);
				this.__address_submit	= jQuery('#' + init.address_submit);
			
				if (this.__address_input.length && this.__address_submit.length) {
					this.__in_lookup = false;
				
					// Set a flag every time we enter or leave the address_input field.
					this.__address_input.unbind('focus').bind('focus', function(e) {
						t.__in_lookup = true;
					}).unbind('blur').bind('blur', function() {
						t.__in_lookup = false;
					});
				
					// Find the specified address.
					this.__address_submit.unbind('click').bind('click', function(e) {
						var address = jQuery.trim(t.__address_input.val());
						if (address !== '') t.pinpoint_address(address, function() {
							// Update the map and pin data.
							var map_data = t.get_location();
							var pin_data = t.get_marker();						
							var field_data = map_data.latlng.lat() + ',' + map_data.latlng.lng() + ',' + map_data.zoom + ',' + pin_data.lat() + ',' + pin_data.lng();
							t.__map_field.val(field_data);
						});
						return false;
					}).parents('form').submit(function(e) {
						if (t.__in_lookup) {
							t.__address_submit.click();
							return false;
						}
					});
				}
			}
			
			return this;
		}
		
		
		/**
		 * Sets the map location and zoom level.
		 * @param 	int 	latlng 	A GLatLng object containing the marker's latitude and longitude.
		 * @param		int		zoom		The map zoom level.
		 * @return 	object 	An object containing the map's latitude, longitude, and zoom.
		 */
		Map.prototype.set_location = function(latlng, zoom) {			
			// Check the parameters.
			if (jQuery.isFunction(latlng.lat) == false) {				
				if ((typeof(latlng.lat) == 'undefined') || (typeof(latlng.lng) == 'undefined')) {
					return false;
				} else {
					latlng = new GLatLng(latlng.lat, latlng.lng);
				}			
			}

			if (this.__map) {
				this.__map.setZoom(zoom);
				this.__map.panTo(latlng);
			}
			return this.get_location();
		}
		
		
		/**
		 * Gets the map location and zoom level.
		 * @return 	object 	An anonymous object with two properties: latlng (GLatLng object); zoom (integer).
		 */
		Map.prototype.get_location = function() {
			if (this.__map) {
				loc = this.__map.getCenter();				
				return {
					latlng: loc,
					zoom: this.__map.getZoom()
				};
			} else {
				return false;
			}
		}
		
		
		/**
		 * Sets the location of the map marker.
		 * @param 	object		latlng				A GLatLng object, or an anonymous object with the properties lat and lng.
		 * @return 	object 		A GLatLng object containing the marker's latitude and longitude.
		 */
		Map.prototype.set_marker = function(latlng) {
			// Check the parameters.
			if (jQuery.isFunction(latlng.lat) == false) {				
				if ((typeof(latlng.lat) == 'undefined') || (typeof(latlng.lng) == 'undefined')) {
					return false;
				} else {
					latlng = new GLatLng(latlng.lat, latlng.lng);
				}				
			}

			this.__marker.setLatLng(latlng);
			return this.get_marker();
		}


		/**
		 * Returns the latitude and longitude of the map marker. If no marker exists,
		 * return FALSE.
		 * @return 		object 		A GLatLng object containing the marker's latitude and longitude.
		 */		
		Map.prototype.get_marker = function() {
			if (this.__marker) {
				loc = this.__marker.getLatLng();
				return loc;
			} else {
				return false;
			}
		}
		
		
		/**
		 * Attempts to locate the given address on the map.
		 * @param 	string		address			The address to locate (can be a postcode).
		 * @param		function	callback		The function to call when we're all done here (optional).
		 */		
		Map.prototype.pinpoint_address = function(address, callback) {
			var regexp, geo, map, local;

			if (jQuery.trim(address) == '') return;

			// Google Maps is rather bad at locating UK postcodes, so we need
			// to be sneaky. If we get given a postcode, we use the Google AJAX
			// search API to get its latitude and longitude.
			regexp = new RegExp("(GIR 0AA|[A-PR-UWYZ]([0-9]{1,2}|([A-HK-Y][0-9]|[A-HK-Y][0-9]([0-9]|[ABEHMNPRV-Y]))|[0-9][A-HJKS-UW])[ ]*[0-9][ABD-HJLNP-UW-Z]{2})", "i");

			// Convenience variable.
			t = this;

			if (address.match(regexp)) {
				local = new GlocalSearch();

				// Search callback handler.
				local.setSearchCompleteCallback(null, function() {	
					if (local.results[0]) {
						t.set_location({lat: local.results[0].lat, lng: local.results[0].lng});
						t.set_marker({lat: local.results[0].lat, lng: local.results[0].lng});
						
						if (callback instanceof Function) {
							callback();
						}
					}
				});

				// Execute the postcode search.
				local.execute(address + ", UK");
			} else {
				// Create a new GClientGeocoder object to help us find the address.
				geo = new GClientGeocoder();
				geo.getLatLng(address, function(latlng) {
					if (latlng !== null) {
						t.set_location(latlng);
						t.set_marker(latlng);
						
						if (callback instanceof Function) {
							callback();
						}
					}
				});
			} // if - else
			
		}
		
		
		// Return our publically-accessible object.
		return ({Map : Map});	
		
	}();
}


// Create the Google Maps.
jQuery(document).ready(function() {
	if (GBrowserIsCompatible() && typeof(SJL.google_maps) !== 'undefined' && SJL.google_maps instanceof Array) {
		for (var i in SJL.google_maps) {
			map = new SJL.SLGoogleMap.Map(SJL.google_maps[i].init, SJL.google_maps[i].options);
		}
	}	
});

// Tidy up after ourselves.
jQuery(window).unload(function() {
	if (GBrowserIsCompatible()) {
		GUnload();
	}
});
