/*************************************
 * GLOBAL VARIABLES
 */
PATH_TO_ICON_FOLDER="/public/globalista/images/map/icons/";
DATA_FILE_URL="/search/p/action/feed";
CLOSE_MAP=function() {hideMap()};

/*************************************
 * DO NOT EDIT BELOW HERE
 */
if(typeof(ESS)=="undefined") var ESS={};
ESS.Utils={
	gel:function(_elemId){
		return document.getElementById(_elemId);
	},
	createElem:function(_nodeName){
		return document.createElement(_nodeName);
	},
	createTxtNode:function(_nodeValue){
		return document.createTextNode(_nodeValue);
	},
	gelstn:function(_tagName,_parentElem){
		if(!_parentElem)
			_parentElem=document;
		return _parentElem.getElementsByTagName(_tagName);
	},
	gelscn:function(_className,_parentElem){
		var _allElems=ESS.Utils.gelstn("*",_parentElem);
		var _returnArr=[];
		_className=_className.replace(/\-/g,"\\-");
		var _regExp=new RegExp("(^|\\s)"+_className+"(\\s|$)");
		for(var i=0; i<_allElems.length; i++){
			if(_regExp.test(_allElems[i].className))
				_returnArr.push(_allElems[i]);
		}
		return _returnArr;
	},
	inArray:function(_needle,_haystack){
		for(var i=0; i<_haystack.length; i++){
			if(_haystack[i]===_needle)
				return true;
		}
		return false;
	},
	truncate:function(_string,_len,_ending){
		var _newStr=_string.substr(0,_len);
		_newStr=_newStr.replace(/(\.*) $/,"$1");
		if(_newStr.length<_string.length)
			_newStr=_newStr+_ending
		return _newStr;
	}
};
var gel=ESS.Utils.gel;
var createElem=ESS.Utils.createElem;
var createTxtNode=ESS.Utils.createTxtNode;
var gelstn=ESS.Utils.gelstn;
var gelscn=ESS.Utils.gelscn;
var inArray=ESS.Utils.inArray;
var truncate=ESS.Utils.truncate;
ESS.Map={
	
	/* Important HTML element IDs used by multiple methods
	 */
	MapElemId:null,
	FilterElemId:null,
	
	/* Store for currently displayed Markers and Info Window
	 */
	Displayed:{
		Markers:{},
		InfoWindow:null
	},
	
	/* Load the map in specified HTML element
	 * @param {string} _elemId : HTML element to load map into
	 * @param {object} _defViewOpts : Optional object literal defining initial map view
	 */
	loadMap:function(_elemId,_defViewOpts){
		// Save destination HTML elem ID
		if(!gel(_elemId))
			throw new Error("Cannot find element with ID "+_elemId+". A DIV element with this ID must be present in the page for the map to load.");
		ESS.Map.MapElemId=_elemId;
		// Capture selected filters if passed as param by mistake
		if(typeof(_defViewOpts)=="string")
			ESS.Map.DefaultView.Layers=_defViewOpts.split(",");
		// Otherwise save init options
		else {
			// Reset custom center, zoom, perm layers and limitToIds
			ESS.Map.DefaultView.Center={Lat:53.5,Lng:-2.7};
			ESS.Map.DefaultView.ZoomLevel=2;
			ESS.Map.Layers.AlwaysOn=[];
			ESS.Map.Data.QueryParams=[];
			// Get user defined options
			for(var _opt in _defViewOpts){
				switch(_opt){
					case "center":
						var _coOrds=_defViewOpts.center.split(/,(?: )?/);
						if(_coOrds.length!=2)
							throw new Error("Error reading Lat/Lng co-ordinates");
						ESS.Map.DefaultView.Center={
							isCustom:true,
							Lat:_coOrds[0],
							Lng:_coOrds[1]
						};
					break;
					case "zoom":
						ESS.Map.DefaultView.ZoomLevel=_defViewOpts.zoom
					break;
					case "type":
						ESS.Map.DefaultView.Type=_defViewOpts.type;
						ESS.Map.Data.QueryParams.push({
							ParamName:"maptype",
							Value:escape(_defViewOpts.type)
						});
					break;
					case "selectedLayer":
						var _layerIds=_defViewOpts.selectedLayer.split(/,(?: )?/);
					case "selectedLayers":
						if(!_layerIds)
							var _layerIds=_defViewOpts.selectedLayers.split(/,(?: )?/);
						ESS.Map.DefaultView.Layers=[];
						for(var i=0; i<_layerIds.length; i++){
							if(_layerIds[i]!="all"){
								ESS.Map.DefaultView.Layers.push(_layerIds[i]);
							}
							else {
								ESS.Map.DefaultView.Layers="all";
								break;
							}
						}
					break;
					case "showId":
						ESS.Map.Layers.AlwaysOn=[_defViewOpts[_opt]];
						ESS.Map.DefaultView.Layers=[];
					break;
					case "limitToIds":
						if(typeof(_defViewOpts[_opt]) == "string")
							var _values = _defViewOpts[_opt].split(/,(?: )?/)
						else
							var _values = _defViewOpts[_opt];
						ESS.Map.Data.QueryParams.push({
							ParamName:"ids",
							Value:_values
						});
					break;
					case "limitToDestination":
						ESS.Map.Data.QueryParams.push({
							ParamName:"destination",
							Value:escape(_defViewOpts[_opt])
						});
					break;
					case "view":
						ESS.Map.DefaultView.View=_defViewOpts[_opt];
					break;
				}
			}
		}
		if(ESS.Map.DefaultView.Layers=="all")
			ESS.Map.Layers.Selected[ESS.Map.DefaultView.Type]=ESS.Map.Layers.Available[ESS.Map.DefaultView.Type];
		else
			ESS.Map.Layers.Selected[ESS.Map.DefaultView.Type]=ESS.Map.DefaultView.Layers;
		// If API has not been loaded (ie. first run)
		if(typeof(google.maps)=="undefined"){
			// Show loading for map
			ESS.Map.showMapLoading();
			// Load maps API - must be at least v2.92 for "callback" support
			google.load("maps","2.92",{callback:function(){
				// Check for browser compatibility
				if(!google.maps.BrowserIsCompatible()){
					alert("Google maps is not compatible with your browser.");
					return;
				}
				// Init map
				ESS.Map.MapObj=new google.maps.Map2(gel(ESS.Map.MapElemId));
				new google.maps.KeyboardHandler(ESS.Map.MapObj);
				ESS.Map.MapObj.enableScrollWheelZoom();
				// Init controls
				ESS.Map.addNavigationControls();
				ESS.Map.addDrawerControls();
				ESS.Map.addInnerShadows();
				// Set center point
				ESS.Map.MapObj.setCenter(
					new google.maps.LatLng(ESS.Map.DefaultView.Center.Lat, ESS.Map.DefaultView.Center.Lng),
					ESS.Map.DefaultView.ZoomLevel
				);
				//
				switch(ESS.Map.DefaultView.View){
					case "map":
						ESS.Map.MapObj.setMapType(G_NORMAL_MAP);
					break;
					case "satellite":
						ESS.Map.MapObj.setMapType(G_SATELLITE_MAP);
					break;
					case "hybrid":
						ESS.Map.MapObj.setMapType(G_HYBRID_MAP);
					break;
				}
				// OnUnload to close memory leaks
				gel(ESS.Map.MapElemId).onunload=google.maps.Unload;
				// Init error pane
				ESS.Map.initErrorElem();
				// Create icons ready for placing
				ESS.Map.createIcons();
				// Extend GOverlay object for custom Info Windows
				ESS.Map.extendGOverlay();
				// Get data - calls placeMarkers as callback
				ESS.Map.Data.load();
			}});
		}
		// If API is already loaded
		else {
			// Clear all markers and info windows
			ESS.Map.MapObj.clearOverlays();
			for(var _clustererId in ESS.Map.Clusterers){
				ESS.Map.Clusterers[_clustererId].removeMarkers();
				delete ESS.Map.Clusterers[_clustererId];
			}
			ESS.Map.Displayed.Markers={};
			// Reset center and zoom
			ESS.Map.MapObj.setCenter(
				new google.maps.LatLng(ESS.Map.DefaultView.Center.Lat, ESS.Map.DefaultView.Center.Lng),
				ESS.Map.DefaultView.ZoomLevel
			);
			// Init controls
			ESS.Map.addDrawerControls();
			// Place new markers
			ESS.Map.Data.load();
		}
	},
	
	/* Data related methods
	 * REQUIRES GLOBAL VARIABLE : DATA_FILE_URL
	 */
	Data:{
		QueryParams:[],
		Url:null,
		XmlHttpObj:null,
		Xml:null,
		Json:null,
		load:function(){
			// Show loader
			ESS.Map.showDataLoading();
			// Get data
			ESS.Map.Data.XmlHttpObj=google.maps.XmlHttp.create();
			ESS.Map.Data.XmlHttpObj.onreadystatechange=function(){
				if(ESS.Map.Data.XmlHttpObj.readyState==4){
					if(ESS.Map.Data.XmlHttpObj.status==200 || ESS.Map.Data.XmlHttpObj.status===0){
						ESS.Map.Data.Xml=ESS.Map.Data.XmlHttpObj.responseXML;
						var _partnerNodes=gelstn("marker",ESS.Map.Data.Xml.documentElement);
						for(var i=0; i<_partnerNodes.length; i++){
							var _id=google.maps.Xml.value(gelstn("id",_partnerNodes[i])[0]);
							var _layer=google.maps.Xml.value(gelstn("type",_partnerNodes[i])[0]).replace("'","").replace(/ /g,"_")+"s";
							var _name=google.maps.Xml.value(gelstn("name",_partnerNodes[i])[0]);
							var _location={
								lat:google.maps.Xml.value(gelstn("lat",_partnerNodes[i])[0]),
								lng:google.maps.Xml.value(gelstn("lng",_partnerNodes[i])[0])
							};
							var _address={
								line_1:google.maps.Xml.value(gelstn("line_1",_partnerNodes[i])[0]),
								line_2:google.maps.Xml.value(gelstn("line_2",_partnerNodes[i])[0]),
								city:google.maps.Xml.value(gelstn("city",_partnerNodes[i])[0]),
								postcode:google.maps.Xml.value(gelstn("postcode",_partnerNodes[i])[0])
							};
							var _description=google.maps.Xml.value(gelstn("description",_partnerNodes[i])[0]);
							var _link=google.maps.Xml.value(gelstn("link",_partnerNodes[i])[0]);
							// If layer group does not exist then create it
							if(!ESS.Map.Data.Json)
								ESS.Map.Data.Json={};
							if(typeof(ESS.Map.Data.Json[_layer])!="object")
								ESS.Map.Data.Json[_layer]=new Array();
							// Save partner data grouped by layer
							ESS.Map.Data.Json[_layer].push({
								Id:_id,
								Name:_name,
								Link:_link,
								Layer:_layer,
								Location:{
									Lat:_location.lat,
									Lng:_location.lng
								},
								Address:{
									Line_1:_address.line_1,
									Line_2:_address.line_2,
									City:_address.city,
									Postcode:_address.postcode
								},
								Description:_description
							});
						}
						// Data ready
						ESS.Map.Data.onReady();
					}
					else
						ESS.Map.showError("Error retrieving XML data");
				}
			};
			// Check for data query params
			var _queryStr = "";
			if(ESS.Map.Data.QueryParams.length > 0) {
				_queryStr += "/";
				for(var i=0; i<ESS.Map.Data.QueryParams.length; i++){
					if('' != ESS.Map.Data.QueryParams[i].Value) {
						_queryStr += ESS.Map.Data.QueryParams[i].ParamName + "/" + ESS.Map.Data.QueryParams[i].Value + "/";
					}					
				}
			}
			ESS.Map.Data.Url = DATA_FILE_URL + _queryStr;
			// Load data
			ESS.Map.Data.XmlHttpObj.open("GET",ESS.Map.Data.Url,true);
			ESS.Map.Data.XmlHttpObj.send(null);
		},
		onReady:function(){
			// Hide loader
			ESS.Map.hideLoading();
			// Write name
			if(ESS.Map.Layers.AlwaysOn.length > 0){
				var _markerData = ESS.Map.Data.getJsonDataById(ESS.Map.Layers.AlwaysOn[0]);
				var _elem = gel("permanentLayerItemName");
				_elem.className=_markerData.Layer.toLowerCase();
				_elem.innerHTML=truncate(_markerData.Name,10,"...");
				_elem.title=_markerData.Name;
			}
			// Place markers
			ESS.Map.placeMarkers(true);
			// Save position
			ESS.Map.MapObj.savePosition();
			// Subscribe to interaction events
			google.maps.Event.addListener(ESS.Map.MapObj,"moveend",function(){
				ESS.Map.placeMarkers();
			});
			google.maps.Event.addListener(ESS.Map.MapObj,"zoomend",function(){
				// Hide any Info Windows and show marker
				// TO DO: keep info window open!!
				var _infoWins=gelscn("custom-info-window");
				for(var i=0; i<_infoWins.length; i++){
					if(_infoWins[i].style.display!="none"){
						_infoWins[i].style.display="none";
						if(navigator.userAgent.match(/MSIE 6\./))
							var _markerElems=gelstn("div",_infoWins[i].parentNode);
						else
							var _markerElems=gelstn("img",_infoWins[i].parentNode);
						for(var j=0; j<_markerElems.length; j++){
							_markerElems[j].style.visibility="visible";
						}
					}
				}
			});
		},
		getJsonDataById:function(_id){
			for(var _layer in ESS.Map.Data.Json){
				for(var i=0; i<ESS.Map.Data.Json[_layer].length; i++){
					if(ESS.Map.Data.Json[_layer][i].Id == _id)
						return ESS.Map.Data.Json[_layer][i];
				}
			}
			return null;
		}
	},
	
	/* Default map view
	 * NOTE: 53.5,-2.7 and zoom=6 shows most of GB
	 */
	DefaultView:{
		Center:{Lat:53.5,Lng:-2.7},
		ZoomLevel:2,
		Type:"global",
		Layers:"all",
		View:"map"
	},
	
	/* Layers specification
	 * Available : used to build filter interface
	 * Selected : used by placeMarkers() to decide which marker types to place
	 */
	Layers:{
		Available:{
			global:["Destinations","Events"],
			itinerary:["Hotels","Restaurants"],
			item:["Hotels","Restaurants"]
		},
		Selected:{
			global:["Destinations","Events"],
			itinerary:["Hotels","Restaurants"],
			item:["Hotels","Restaurants"]
		},
		AlwaysOn:[]
	},
	
	/* Icon specifications
	 */
	Icons:{
		// Normal Icons
		Destinations:{
			IconWidth:42,
			IconHeight:38,
			IconAnchor:[21,36],
			IconFileName:"markers_destination.png",
			InfoWindowAnchor:[21,36],
			GIcon:null
		},
		Events:{
			IconWidth:42,
			IconHeight:38,
			IconAnchor:[21,36],
			IconFileName:"markers_event.png",
			InfoWindowAnchor:[21,36],
			GIcon:null
		},
		Hotels:{
			IconWidth:42,
			IconHeight:38,
			IconAnchor:[21,36],
			IconFileName:"markers_hotel.png",
			InfoWindowAnchor:[21,36],
			GIcon:null
		},
		Restaurants:{
			IconWidth:42,
			IconHeight:38,
			IconAnchor:[21,36],
			IconFileName:"markers_restaurant.png",
			InfoWindowAnchor:[21,36],
			GIcon:null
		},
		// Cluster Icons
		DestinationsCluster:{
			IconWidth:42,
			IconHeight:38,
			IconAnchor:[21,36],
			IconFileName:"markers_multi-destination.png",
			InfoWindowAnchor:[21,36],
			GIcon:null
		},
		EventsCluster:{
			IconWidth:42,
			IconHeight:38,
			IconAnchor:[21,36],
			IconFileName:"markers_multi-event.png",
			InfoWindowAnchor:[21,36],
			GIcon:null
		},
		HotelsCluster:{
			IconWidth:42,
			IconHeight:38,
			IconAnchor:[21,36],
			IconFileName:"markers_multi-hotel.png",
			InfoWindowAnchor:[21,36],
			GIcon:null
		},
		RestaurantsCluster:{
			IconWidth:42,
			IconHeight:38,
			IconAnchor:[21,36],
			IconFileName:"markers_multi-restaurant.png",
			InfoWindowAnchor:[21,36],
			GIcon:null
		},
		// When specific ID is passed
		DestinationsSingle:{
			IconWidth:42,
			IconHeight:38,
			IconAnchor:[21,36],
			IconFileName:"markers_destination.png",
			InfoWindowAnchor:[21,36],
			GIcon:null
		},
		EventsSingle:{
			IconWidth:42,
			IconHeight:38,
			IconAnchor:[21,36],
			IconFileName:"markers_event.png",
			InfoWindowAnchor:[21,36],
			GIcon:null
		},
		HotelsSingle:{
			IconWidth:42,
			IconHeight:38,
			IconAnchor:[21,36],
			IconFileName:"markers_hotel.png",
			InfoWindowAnchor:[21,36],
			GIcon:null
		},
		RestaurantsSingle:{
			IconWidth:42,
			IconHeight:38,
			IconAnchor:[21,36],
			IconFileName:"markers_restaurant.png",
			InfoWindowAnchor:[21,36],
			GIcon:null
		}
	},
	
	/*
	 */
	Clusterers:{},
	
	/*
	 */
	addInnerShadows:function(){
		var _shadowsContainer=createElem("div");
		_shadowsContainer.id="innerShadows";
			var _topDiv=createElem("div");
			_topDiv.id="shadow-top";
			_shadowsContainer.appendChild(_topDiv);
			var _rightDiv=createElem("div");
			_rightDiv.id="shadow-right";
			_shadowsContainer.appendChild(_rightDiv);
			var _bottomDiv=createElem("div");
			_bottomDiv.id="shadow-bottom";
			_shadowsContainer.appendChild(_bottomDiv);
		ESS.Map.MapObj.getContainer().appendChild(_shadowsContainer);
	},
	
	/*
	 */
	addNavigationControls:function(){
		function CustomNavigationControls(){}
		CustomNavigationControls.prototype = new google.maps.Control();
		
		CustomNavigationControls.prototype.initialize = function(map) {
			var container = createElem("div");
			container.id="customNavigationControls";
			
			var imageLayer = createElem("div");
			imageLayer.className="imageLayer";
			container.appendChild(imageLayer);
			
			var clickLayer = createElem("div");
			clickLayer.className="clickLayer";
			container.appendChild(clickLayer);
		
			var zoomInDiv = createElem("div");
			zoomInDiv.className = "zoom-in";
			clickLayer.appendChild(zoomInDiv);
			zoomInDiv.appendChild(createTxtNode("Zoom In"));
			GEvent.addDomListener(zoomInDiv, "click", function() {
				ESS.Map.MapObj.zoomIn();
			});
		
			var zoomOutDiv = createElem("div");
			zoomOutDiv.className = "zoom-out";
			clickLayer.appendChild(zoomOutDiv);
			zoomOutDiv.appendChild(createTxtNode("Zoom Out"));
			GEvent.addDomListener(zoomOutDiv, "click", function() {
				ESS.Map.MapObj.zoomOut();
			});
			
			var panLeftDiv = createElem("div");
			panLeftDiv.className = "pan-left";
			clickLayer.appendChild(panLeftDiv);
			panLeftDiv.appendChild(createTxtNode("Pan left"));
			GEvent.addDomListener(panLeftDiv, "click", function() {
				ESS.Map.MapObj.panDirection(1,0);
			});
			
			var panRightDiv = createElem("div");
			panRightDiv.className = "pan-right";
			clickLayer.appendChild(panRightDiv);
			panRightDiv.appendChild(createTxtNode("Pan Right"));
			GEvent.addDomListener(panRightDiv, "click", function() {
				ESS.Map.MapObj.panDirection(-1,0);
			});
			
			var panUpDiv = createElem("div");
			panUpDiv.className = "pan-up";
			clickLayer.appendChild(panUpDiv);
			panUpDiv.appendChild(createTxtNode("Pan left"));
			GEvent.addDomListener(panUpDiv, "click", function() {
				ESS.Map.MapObj.panDirection(0,1);
			});
			
			var panDownDiv = createElem("div");
			panDownDiv.className = "pan-down";
			clickLayer.appendChild(panDownDiv);
			panDownDiv.appendChild(createTxtNode("Pan Right"));
			GEvent.addDomListener(panDownDiv, "click", function() {
				ESS.Map.MapObj.panDirection(0,-1);
			});
			
			var recenterDiv = createElem("div");
			recenterDiv.className = "recenter";
			clickLayer.appendChild(recenterDiv);
			recenterDiv.appendChild(createTxtNode("Recenter"));
			GEvent.addDomListener(recenterDiv, "click", function() {
				ESS.Map.MapObj.returnToSavedPosition();
			});
		
			ESS.Map.MapObj.getContainer().appendChild(container);
			return container;
		}
		
		CustomNavigationControls.prototype.getDefaultPosition = function() {
			return new google.maps.ControlPosition(G_ANCHOR_TOP_LEFT, new google.maps.Size(16,16));
		}
		
		ESS.Map.MapObj.addControl(new CustomNavigationControls());
	},
	
	/*
	 */
	addDrawerControls:function(){
		function CustomMapTypeControls(){}
		CustomMapTypeControls.prototype = new google.maps.Control();
		
		CustomMapTypeControls.prototype.initialize = function(map) {
			
			if(gel("legend-wrapper")){
				gel("legend-wrapper").innerHTML = "";
				var slideMask = gel("legend-wrapper");
			}
			else {
				var slideMask = createElem("div");
				slideMask.id = "legend-wrapper";
			}
			
			var outerContainer = createElem("div");
			outerContainer.id = "legend-container";
			ESS.Map.FilterElemId=outerContainer.id;
			outerContainer.className = "closed";
			var container = createElem("div");
			container.id = "legend";
			outerContainer.appendChild(container);
			slideMask.appendChild(outerContainer);
			
			// Show/hide tab
			var showHideContainer = createElem("div");
			showHideContainer.id="toggleView";
			var hideDiv = createElem("div");
			hideDiv.className="click-to-hide";
			showHideContainer.appendChild(hideDiv);
			hideDiv.appendChild(createTxtNode("Hide"));
			var drawer = this;
			GEvent.addDomListener(hideDiv, "click", function() {
				hideDiv.style.display="none";
				showDiv.style.display="block";
				ESS.Map.hideFilter();
			});
			hideDiv.style.display="none";
			var showDiv = createElem("div");
			showDiv.className="click-to-show";
			showHideContainer.appendChild(showDiv);
			showDiv.appendChild(createTxtNode("Show"));
			var drawer = this;
			GEvent.addDomListener(showDiv, "click", function() {
				showDiv.style.display="none";
				hideDiv.style.display="block";
				ESS.Map.openFilter();
			});
			container.appendChild(showHideContainer);
			
			// Shadow
			var shadowDiv = createElem("div");
			shadowDiv.id="drawerShadow";
			container.appendChild(shadowDiv);
			
			// Init layer filters
			var _h2=createElem("h2");
			_h2.className="options";
			_h2.appendChild(createTxtNode("Options"));
			container.appendChild(_h2);
			
			if(ESS.Map.Layers.AlwaysOn.length > 0){
				var _ul=createElem("ul");
				_ul.className = "option-permanent-layer";
					var _li=createElem("li");
					_li.id="permanentLayerItemName";
					_li.className="loadingName";
					_li.appendChild(createTxtNode("Loading..."));
					_li.style.cursor="pointer";
					GEvent.addDomListener(_li, "click", function() {
						// Uncheck all layer selections
						var layerSelectionsContainer = ESS.Utils.gelscn("option-filters", ESS.Utils.gel("legend"))[0];
						if(!layerSelectionsContainer) return;
						var layerSelections = ESS.Utils.gelstn("input", layerSelectionsContainer);
						for(var i=0; i<layerSelections.length; i++) {
							layerSelections[i].checked = false;
						}
						ESS.Map.handleLayerSelection();
						// Reset first selection flag
						ESS.Map.firstSelection = true;
						// Reset map
						ESS.Map.MapObj.returnToSavedPosition();
					});
				_ul.appendChild(_li);
				container.appendChild(_ul);
				var _p=createElem("p");
				_p.appendChild(createTxtNode("Show other:"));
				container.appendChild(_p);
			}
			
			var _ul=createElem("ul");
			_ul.className = "option-filters";
			var _checkTheseIdsAfterRender=[];
			for(var i=0; i<ESS.Map.Layers.Available[ESS.Map.DefaultView.Type].length; i++){
			
				var _lyrLabel=ESS.Map.Layers.Available[ESS.Map.DefaultView.Type][i].replace(/ /g,"_");
				var _lyrId=_lyrLabel.replace(/_/g," ").split(" ")[0].toLowerCase();
				var _li=createElem("li");
				_li.id=_lyrId;
				_li.className="unselected";
					var _label=createElem("label");
					_label.setAttribute("for","lbl-"+_lyrId);
					_label.appendChild(createTxtNode(_lyrLabel));
					var _input=createElem("input");
					_input.type="checkbox";
					_input.name=_lyrId;
					_input.id="lbl-"+_lyrId;
					_input.onclick=ESS.Map.handleLayerSelection;
					
				if(inArray(_lyrLabel.replace(/_/g," "),ESS.Map.Layers.Selected[ESS.Map.DefaultView.Type]))
					_checkTheseIdsAfterRender.push("lbl-"+_lyrId);
				
				_li.appendChild(_input);
				_li.appendChild(_label);
				_ul.appendChild(_li);
			}
			container.appendChild(_ul);

			var _h2=createElem("h2");
			_h2.className="view";
			_h2.appendChild(createTxtNode("View"));
			container.appendChild(_h2);
			
			// Map type controls
			var _ul = createElem("ul");
			_ul.className = "option-view";
			
			var mapLi = createElem("li");
			var _inputMap=createElem("input");
			_inputMap.type="checkbox";
			_inputMap.name="mapView";
			_inputMap.id="inp-mapView";
			var _label=createElem("label");
			_label.setAttribute("for","inp-mapView");
			_label.appendChild(createTxtNode("Map"));
			mapLi.appendChild(_inputMap);
			mapLi.appendChild(_label);
			_ul.appendChild(mapLi);
			GEvent.addDomListener(_inputMap, "click", this.handleViewOptionClick);
			
			var satelliteLi = createElem("li");
			var _inputSat=createElem("input");
			_inputSat.type="checkbox";
			_inputSat.name="hybridView";
			_inputSat.id="inp-hybridView";
			var _label=createElem("label");
			_label.setAttribute("for","inp-hybridView");
			_label.appendChild(createTxtNode("Satellite"));
			satelliteLi.appendChild(_inputSat);
			satelliteLi.appendChild(_label);
			_ul.appendChild(satelliteLi);
			GEvent.addDomListener(_inputSat, "click", this.handleViewOptionClick);
			
			container.appendChild(_ul);
			
			ESS.Map.MapObj.getContainer().appendChild(slideMask);
			
			// Run thru and check applied filters after written to page (for IE)
			for(var i=0; i<_checkTheseIdsAfterRender.length; i++){
				gel(_checkTheseIdsAfterRender[i]).checked=true;
				gel(_checkTheseIdsAfterRender[i]).parentNode.className="";
			}
			switch(ESS.Map.DefaultView.View){
				case "satellite":
					_inputSat.checked = true;
				break;
				case "map":
					_inputMap.checked = true;
				break;
				case "hybrid":
					_inputMap.checked = true;
					_inputSat.checked = true;
				break;
			}
			
			// Open drawer after 1 secs
			setTimeout(function(){
				showDiv.style.display="none";
				hideDiv.style.display="block";
				ESS.Map.openFilter();
			},1000);
			
			return container;
		};
		
		CustomMapTypeControls.prototype.handleViewOptionClick = function() {
			// Make sure one option is always checked
			if(!gel("inp-hybridView").checked && !gel("inp-mapView").checked)
				this.checked=true;
			// Both checked
			if(gel("inp-hybridView").checked && gel("inp-mapView").checked)
				ESS.Map.MapObj.setMapType(G_HYBRID_MAP);
			// Just satellite
			else if(gel("inp-hybridView").checked && !gel("inp-mapView").checked)
				ESS.Map.MapObj.setMapType(G_SATELLITE_MAP);
			// Just map
			else if(!gel("inp-hybridView").checked && gel("inp-mapView").checked)
				ESS.Map.MapObj.setMapType(G_NORMAL_MAP);
		};
		
		CustomMapTypeControls.prototype.getDefaultPosition = function() {
			return new google.maps.ControlPosition(G_ANCHOR_TOP_LEFT, new google.maps.Size(0, 0));
		};
		
		ESS.Map.MapObj.addControl(new CustomMapTypeControls());
	},
	
	/* Create and show loading icon while map initially loads
	 * - map will overwrite loading elem when loaded
	 * addLoadingElem()
	 * 		@param {string} _id : ID to add to element when created
	 * 		@param {HtmlElement} _childNode : HtmlElement node to append to HTML element when created
	 * hideLoading()
	 * 		@param {string} _id : ID tof element to hide. Defaults to "loading-data-container"
	 */
	addLoadingElem:function(_id,_childNode){
		var _container=createElem("div");
		_container.id=_id;
		if(_childNode)
			_container.appendChild(_childNode);
		else
			_container.innerHTML="&nbsp;";
		gel(ESS.Map.MapElemId).appendChild(_container);
	},
	showDataLoading:function(){
		if(gel("loading-data-container"))
			gel("loading-data-container").parentNode.removeChild(gel("loading-data-container"));
		var _childNode=createElem("div");
		_childNode.id="loading-data";
		_childNode.innerHTML="Loading location data";
		ESS.Map.addLoadingElem("loading-data-container",_childNode);
	},
	showMapLoading:function(){
		ESS.Map.addLoadingElem("loading-map");
	},
	hideLoading:function(_id){
		if(!_id) _id="loading-data-container";
		gel(_id).style.display="none";
	},
	
	/* Create, show and hide errors
	 * showError()
	 * 		@param {string} _message : Error message to show
	 */
	initErrorElem:function(){
		var _container=createElem("div");
		_container.id="error-container";
		var _messageContainer=_container.cloneNode(true);
		_messageContainer.id="error-message";
		_container.appendChild(_messageContainer);
		gel(ESS.Map.MapElemId).appendChild(_container);
	},
	showError:function(_message){
		gel("error-message").innerHTML=_message;
		gel("error-container").style.display="block";
		ESS.Map.hideLoading();
	},
	hideError:function(){
		gel("error-container").style.display="none";
	},
	
	/* Show/hide filter interface
	 * openFilter() and hideFilter() handle animation effect
	 */
	openFilter:function(){
		// Elem to slide
		var _filterElem=gel(ESS.Map.FilterElemId);
		
		// Maximise containing div
		var _filterContainer=_filterElem.parentNode;
		if(_filterContainer.className!="maximised")
			_filterContainer.className="maximised";
		
		//
		var _filterWidth = parseInt(_filterElem.offsetWidth);
		
		if(_filterElem.style.left)
			var _filterLeft = parseInt(_filterElem.style.left);
		else
			var _filterLeft = 0;
		
		
			
		if(_filterLeft < _filterWidth){
			_filterElem.style.left = _filterLeft + Math.ceil((_filterWidth - _filterLeft) / 2) + "px";
			if(_filterElem.className == "closed")
				_filterElem.className = "";
			setTimeout(ESS.Map.openFilter,100);
		}
		else {
			_filterElem.style.left=null;
			_filterElem.className="open";
		}
	},
	hideFilter:function(){
		// Elem to slide
		var _filterElem=gel(ESS.Map.FilterElemId);
		
		// Starting width and left position
		var _filterWidth = parseInt(_filterElem.offsetWidth);
		if(_filterElem.style.left)
			var _filterLeft = parseInt(_filterElem.style.left);
		else
			var _filterLeft = _filterWidth;
		
		if(_filterLeft > 1){
			_filterElem.style.left = Math.floor(_filterLeft / 2) + "px";
			setTimeout(ESS.Map.hideFilter,100);
		}
		else {
			// Remove inline style
			_filterElem.className="closed";
			_filterElem.style.left=null;
			// Minimise containing div
			_filterElem.parentNode.className="minimised";
		}
	},
	
	/* Handle layer selections and
	 * "show/hide all" buttons
	 */
	firstSelection:true,
	handleLayerSelection:function(){
		// Reset selected layers
		ESS.Map.Layers.Selected[ESS.Map.DefaultView.Type]=[];
		// Clear any markers and info windows
		ESS.Map.MapObj.clearOverlays();
		for(var _clustererId in ESS.Map.Clusterers){
			ESS.Map.Clusterers[_clustererId].removeMarkers();
			delete ESS.Map.Clusterers[_clustererId];
		}
		ESS.Map.Displayed.Markers={};
		// Add selected layers
		for(var i=0; i<ESS.Map.Layers.Available[ESS.Map.DefaultView.Type].length; i++){
			var _lyrLabel=ESS.Map.Layers.Available[ESS.Map.DefaultView.Type][i].replace(/ /g,"_");
			var _lyrId=_lyrLabel.replace(/_/g," ").split(" ")[0].toLowerCase();
			var _elem=gel("lbl-"+_lyrId);
			if(_elem.checked){
				ESS.Map.Layers.Selected[ESS.Map.DefaultView.Type].push(_lyrLabel.replace(/_/g," "));
				_elem.parentNode.className="";
			}
			else {
				_elem.parentNode.className="unselected";
			}
		}
		// Zoom out
		if(ESS.Map.firstSelection && ESS.Map.Layers.AlwaysOn.length > 0) {
			switch(ESS.Map.DefaultView.Type){
				case "item":
					ESS.Map.MapObj.zoomOut();
				break;
				case "global":
					ESS.Map.MapObj.setZoom(6);
				break;
			}
			ESS.Map.firstSelection = false;
		}
		ESS.Map.placeMarkers();
	},
	selectAllLayers:function(){
		ESS.Map.toggleAllLayers(true);
	},
	hideAllLayers:function(){
		ESS.Map.toggleAllLayers(false);
	},
	toggleAllLayers:function(_checked){
		for(var i=0; i<ESS.Map.Layers.Available[ESS.Map.DefaultView.Type].length; i++){
			var _lyrLabel=ESS.Map.Layers.Available[ESS.Map.DefaultView.Type][i].replace(/ /g,"_");
			var _lyrId=_lyrLabel.replace(/_/g," ").split(" ")[0].toLowerCase();
			gel("lbl-"+_lyrId).checked=_checked;
		}
		ESS.Map.handleLayerSelection();
	},
	
	/* Place markers on map
	 * NOTE: GIcons should be created first by ESS.Map.createIcons
	 */
	placeMarkers:function(firstLoad){
		// For permanent markers
		for(var i=0; i<ESS.Map.Layers.AlwaysOn.length; i++){
			var _id = ESS.Map.Layers.AlwaysOn[i];
			// Get Json object from ID
			var _markerData = ESS.Map.Data.getJsonDataById(_id);
			if(!ESS.Map.Displayed.Markers[_markerData.Id]){
				// Use Clusterer to render
				ESS.Map.Clusterers["permLayer_"+_id] = new ClusterMarker(ESS.Map.MapObj,{
					markers:[ESS.Map.createMarker(_markerData)],
					clusteringEnabled:false,
					fitMapMaxZoom:(ESS.Map.DefaultView.Type=="item"? 13:10)
				});
				ESS.Map.Clusterers["permLayer_"+_id].refresh(true);
			}
		}
		// For every marker in the loaded data
		for(var _layer in ESS.Map.Data.Json){
			// If layer is not selected then do not place
			if(!inArray(_layer.replace(/_/g," "),ESS.Map.Layers.Selected[ESS.Map.DefaultView.Type]))
				continue;
			// Temporarily collect markers to pass to clustering object
			var _markerCollection=[];
			// Get all markers in current viewport for this layer
			for(var i=0; i<ESS.Map.Data.Json[_layer].length; i++){
				var _markerData = ESS.Map.Data.Json[_layer][i];
				// If not already displayed
				if(!ESS.Map.Displayed.Markers[_markerData.Id]){
					// Add marker to collection - will be added to map by clusterer
					_markerCollection.push(ESS.Map.createMarker(_markerData));
				}
			}
			if(!ESS.Map.Clusterers[_layer]){
				ESS.Map.Clusterers[_layer] = new ClusterMarker(ESS.Map.MapObj,{
					markers:_markerCollection,
					clusterMarkerTitle:"Click to zoom in and show %count "+_layer+"...",
					clusterMarkerIcon:ESS.Map.Icons[_layer+"Cluster"].GIcon,
					minClusterSize:4
				});
			}
			else if(_markerCollection.length > 0){
				ESS.Map.Clusterers[_layer].addMarkers(_markerCollection);
			}
			ESS.Map.Clusterers[_layer].refresh(true);
		}
		// Handle initial center and zoom of map
		if(firstLoad) {
			// If center and zoom passed via loadMap
			if(ESS.Map.DefaultView.Center.isCustom){
				ESS.Map.MapObj.setCenter(
					new google.maps.LatLng(ESS.Map.DefaultView.Center.Lat, ESS.Map.DefaultView.Center.Lng),
					ESS.Map.DefaultView.ZoomLevel
				);
			}
			// ...or center on permanent marker
			else if(ESS.Map.Layers.AlwaysOn.length > 0)
				ESS.Map.Clusterers["permLayer_"+ESS.Map.Data.getJsonDataById(ESS.Map.Layers.AlwaysOn[0]).Id].fitMapToMarkers();
			// ...or fit marker bounds if only one layer shown
			else if(ESS.Map.DefaultView.Layers!="all" && ESS.Map.Layers.Selected[ESS.Map.DefaultView.Type].length==1)
				ESS.Map.Clusterers[ESS.Map.Layers.Selected[ESS.Map.DefaultView.Type]].fitMapToMarkers();
			// ...or fit marker bounds to first layer
			else
				ESS.Map.Clusterers[ESS.Map.Layers.Selected[ESS.Map.DefaultView.Type][0]].fitMapToMarkers();
		}
	},
	createMarker:function(_markerSpec){
		// Create marker
		var _point = new google.maps.LatLng(_markerSpec.Location.Lat, _markerSpec.Location.Lng);
		var _marker = new google.maps.Marker(_point,{icon:ESS.Map.Icons[_markerSpec.Layer].GIcon});
		// Record marker
		ESS.Map.Displayed.Markers[_markerSpec.Id] = true;
		// Create custom info window
		var _infoWin = new ESS.Map.CustomInfoWindow(
			_markerSpec.Location.Lat,
			_markerSpec.Location.Lng,
			{
				content:ESS.Map.getInfoWindowHtml(_markerSpec),
				className:_markerSpec.Layer.toLowerCase()
			}
		);
		_marker.InfoWindow = _infoWin;
		google.maps.Event.addListener(_marker,"click",function(){this.InfoWindow.show(this)});
		return _marker;
	},
	markerWillBeWithinViewportBounds:function(_markerData){
		var _bounds=ESS.Map.MapObj.getBounds();
		var _mapSwBounds=_bounds.getSouthWest();
		var _mapNeBounds=_bounds.getNorthEast();
		return (
			_markerData.Location.Lat > _mapSwBounds.lat() &&
			_markerData.Location.Lat < _mapNeBounds.lat() &&
			_markerData.Location.Lng > _mapSwBounds.lng() &&
			_markerData.Location.Lng < _mapNeBounds.lng()
		);
	},
	
	/* Generate Info Window content from data object
	 * @param {object} _markerObj : Marker specification object
	 * @returns {string}
	 */
	getInfoWindowHtml:function(_markerObj){
		var _html="";
		_html+="<h3><a href=\""+_markerObj.Link+"\" title=\""+_markerObj.Name+"\">"+truncate(_markerObj.Name,25,"...")+"</a></h3>";
		_html+="<p>";
		if(_markerObj.Description)
			_html+=_markerObj.Description;
		else {
			if(_markerObj.Address.Line_1)
				_html+=_markerObj.Address.Line_1+"<br />";
			if(_markerObj.Address.Line_2)
				_html+=_markerObj.Address.Line_2+"<br />";
			if(_markerObj.Address.City)
				_html+=_markerObj.Address.City+"<br />";
			if(_markerObj.Address.Postcode)
				_html+=_markerObj.Address.Postcode;
		}
		_html+="</p>";
		_html+="<a class=\"moreLink gmnoprint\" href=\""+_markerObj.Link+"\" onclick=\"CLOSE_MAP()\" title=\"Read more about "+_markerObj.Name+"\">Read more &raquo;</a>";
		return _html;
	},
	
	/* Create GIcons from icon specs
	 * createIcon()
	 * 		@param {object} _mkrConfigObj : Icon specification object (ESS.Map.Icons)
	 * 		@returns {GIcon}
	 */
	createIcons:function(){
		for(var _iconType in ESS.Map.Icons){
			ESS.Map.Icons[_iconType].GIcon=ESS.Map.createIcon(ESS.Map.Icons[_iconType]);
		}
	},
	createIcon:function(_mkrConfigObj){
		var GIcon=new google.maps.Icon();
		GIcon.image=PATH_TO_ICON_FOLDER+_mkrConfigObj.IconFileName;
		GIcon.iconSize=new google.maps.Size(_mkrConfigObj.IconWidth,_mkrConfigObj.IconHeight);
		GIcon.iconAnchor=new google.maps.Point(_mkrConfigObj.IconAnchor[0],_mkrConfigObj.IconAnchor[1]);
		GIcon.infoWindowAnchor=new google.maps.Point(_mkrConfigObj.InfoWindowAnchor[0],_mkrConfigObj.InfoWindowAnchor[1]);
		GIcon.shadow=PATH_TO_ICON_FOLDER+"Shadow.png";
		GIcon.shadowSize=new google.maps.Size(61,38);
		return GIcon;
	},
	
	/* Get bounds of all markers on specified layer
	 * @param {string} _layer : Layer name as defined by "filter_category" for each ESS.Map.Data.Json item
	 * @returns {GLatLngBounds}
	 */
	getLayerBounds:function(_layer){
		var _markers=ESS.Map.Data.Json[_layer];
		var _w,_e,_n,_s;
		// Sort array of markers by Lat
		_markers.sort(function(_a,_b){
			var _x=parseFloat(_a.Location.Lat);
	  		var _y=parseFloat(_b.Location.Lat);
	    	return ((_x<_y)? -1:((_x>_y)? 1:0));
		});
		_w=_markers[0].Location.Lat;
		_e=_markers[_markers.length-1].Location.Lat;
		// Sort array of markers by Lng
		_markers.sort(function(_a,_b){
			var _x=parseFloat(_a.Location.Lng);
	  		var _y=parseFloat(_b.Location.Lng);
	    	return ((_x<_y)? -1:((_x>_y)? 1:0));
		});
		_s=_markers[0].Location.Lng;
		_n=_markers[_markers.length-1].Location.Lng;
		var _bounds=new google.maps.LatLngBounds(new google.maps.LatLng(_w,_s),new google.maps.LatLng(_e,_n));
		return _bounds;
	},
	
	/* Callback function to wrap methods extending GOverlay
	 * class with ESS.Map.CustomInfoWindow class.
	 * initialize() and redraw() methods replace GOverlay methods
	 * show()
	 * 		@param {GMarker} _marker : Marker whose Info Window we want to show
	 */
	extendGOverlay:function(){
		ESS.Map.CustomInfoWindow=function(_lat,_lng,_opts){
			// Variable options
			this.LatLng=new google.maps.LatLng(_lat,_lng);
			this.Content=_opts.content;
			if(_opts.className)
				this.ExtraClasses=_opts.className;
			// Hardcoded dims and coords :)
			this.Width=235;
			this.Height=100;
			this.OffsetHorizontal=-150;
			this.OffsetVertical=(this.Height*-1)+2;
		}
		ESS.Map.CustomInfoWindow.prototype=new google.maps.Overlay();
		ESS.Map.CustomInfoWindow.prototype.initialize=function(){
			// Outer containing div
			var _divOuter=createElem("div");
			if(this.ExtraClasses)
				_divOuter.className="custom-info-window "+this.ExtraClasses;
			else
				_divOuter.className="custom-info-window";
			// Inner containing divs
			var _divInner1=_divOuter.cloneNode(false);
			_divInner1.className="imageLayer";
			_divOuter.appendChild(_divInner1);
			var _divInner2=_divOuter.cloneNode(false);
			_divInner2.className="contentLayer";
			_divOuter.appendChild(_divInner2);
			// Content div
			var _divContent=_divOuter.cloneNode(false);
			_divContent.className="custom-info-window-content";
			_divContent.innerHTML=this.Content;
			_divInner2.appendChild(_divContent);
			// Close button
			var _closeBtn=createElem("a");
			_closeBtn.className="close-info-window";
			function createCloseInfoWindowFunc(_overlay){
				return function(){ 
					google.maps.Event.trigger(_overlay,"closeclick");
					ESS.Map.MapObj.removeOverlay(_overlay);
					_overlay.Marker.show();
				};
			}
			google.maps.Event.addDomListener(_closeBtn,'click',createCloseInfoWindowFunc(this));
			_divInner2.appendChild(_closeBtn);
			// Hide for now
			_divOuter.style.display="none";
			// Add to marker pane of map
			ESS.Map.MapObj.getPane(G_MAP_MARKER_PANE).appendChild(_divOuter);
			// Save properties
			this.Div=_divOuter;
			this.Map=ESS.Map.MapObj;
		};
		ESS.Map.CustomInfoWindow.prototype.remove=function(){
		  this.Div.parentNode.removeChild(this.Div);
		};
		ESS.Map.CustomInfoWindow.prototype.redraw=function(_force){
			// Redraw if coords have changed
			if(!_force) return;
			// Calculate the DIV coordinates of two opposite corners of our bounds to
			// get the size and position of our map
			var _pixPosition=this.Map.fromLatLngToDivPixel(this.LatLng);
			// Now position our DIV based on the DIV coordinates of our bounds
			this.Div.style.width=this.Width+"px";
			this.Div.style.left=(_pixPosition.x+this.OffsetHorizontal)+"px";
			this.Div.style.height=this.Height+"px";
			this.Div.style.top=(_pixPosition.y+this.OffsetVertical)+"px";
			this.Div.style.display="block";
			if(this.ZIndex)
				this.Div.style.zIndex=this.ZIndex;
		};
		ESS.Map.CustomInfoWindow.prototype.show=function(_marker){
			// If InfoWindow is displayed then remove it
			if(ESS.Map.Displayed.InfoWindow){
				ESS.Map.MapObj.removeOverlay(ESS.Map.Displayed.InfoWindow);
				ESS.Map.Displayed.InfoWindow.Marker.show();
				ESS.Map.Displayed.InfoWindow=null;
			}
			// Show it
			ESS.Map.Displayed.InfoWindow=_marker.InfoWindow;
			// Save z-index to apply later
			if(_marker.R && _marker.R[0])
				this.ZIndex=_marker.R[0].style.zIndex;
			ESS.Map.MapObj.addOverlay(_marker.InfoWindow);
			// Hide marker
			this.Marker=_marker;
			_marker.hide();
			// Center the map to accomodate the calling cards dimensions
			var _point=new google.maps.LatLng(this.LatLng.lat(),this.LatLng.lng());
			var _pixelPos=this.Map.fromLatLngToDivPixel(_point);
			//_pixelPos.y=_pixelPos.y;
			this.Map.panTo(this.Map.fromDivPixelToLatLng(_pixelPos));
		};
	}
};
var loadMap=ESS.Map.loadMap;