LHS.MapVE = Class.create();
LHS.MapVE.prototype = Object.extend(new LHS.Map(), {
	initialize: function (id, options) {
		if (VEShapeType.Polyline) {
			this.id = id;
			this.scrollStart = null;
			this.options = null;
			this.searchBox = null;
			this.searchLayer = null;
			this.options = {
				width: 100,
				height: 400
			};
			this.options = Object.extend(this.options, options);

			this.holder = $('map-' + id.substring(id.lastIndexOf('-') + 1, id.length)) || document.body;
			this.layers = $H({});
			this.tileLayers = $H({});
			this.userShapes = [];
			this.zoomLevel = null;
			this.clickFlag = false;
			this.extentShape = null;
			this.extentLayer = null;
			/*
			 * new Ajax.Request($context + '/veapi', { method: 'get', onSuccess:
			 * this.createMap.bind(this) });
			 */
			this.createMap();
		}
	},
	/**
	 * Turns the object tag into a map using the options that have been set
	 */
	createMap: function () {
		var latLon, selMode;
		var me = this;
		this.map = new VEMap(this.id);
		// this.map.SetClientToken(transport.responseText);

		if (this.options.latitude && this.options.longitude) {
			latLon = new VELatLong(this.options.latitude, this.options.longitude);
		}

		if (this.options.latitude === '') {
			latLon = new VELatLong(54.901882187385, -2.131347656249992);
		}

		if (this.options.latitude === '') {
			this.options.zoom = 5;
		}

		if (this.options.baseMap == 'a') {
			this.options.baseMap = 'Aerial';
		} else if (this.options.baseMap == 'h') {
			this.options.baseMap = 'Hybrid';
		} else if (this.options.baseMap == 'o') {
			this.options.baseMap = 'Birdseye';
		} else if (this.options.baseMap == 'b') {
			this.options.baseMap = 'BirdseyeHybrid';
		} else if (this.options.baseMap === '') {
			this.options.baseMap = 'Road';
		} else if (this.options.baseMap == 'r') {
			this.options.baseMap = 'Road';
		}

		this.map.LoadMap(latLon, this.options.zoom, VEMapStyle[this.options.baseMap], (this.options.fixed === 'true'), selMode, false);
		if (this.options.controls) {
			this.map.HideDashboard();
		}
		if (this.options.extent) {
			$H(this.options.extent).each(function (pair) {
				if (pair.key != 'zoomLevel') {
					var value = pair.value.evalJSON();
					this.options.extent[pair.key] = new VELatLong(value.x, value.y);
				}
			}.bind(this));

			this.options.extent.topRight = new VELatLong(this.options.extent.topLeft.Latitude, this.options.extent.bottomRight.Longitude);
			this.options.extent.bottomLeft = new VELatLong(this.options.extent.bottomRight.Latitude, this.options.extent.topLeft.Longitude);

			this.options.extent.points = [this.options.extent.topLeft, this.options.extent.topRight, this.options.extent.bottomRight, this.options.extent.bottomLeft, this.options.extent.topLeft];

			Object.extend(this.options.extent, {
				width: $(this.id).getWidth(),
				height: $(this.id).getHeight()
			});
			if (this.options.extent.admin) {
				this.createExtentBox(this.options.extent.points);
			} else {
				this.limitExtent();
			}
		}
		this.options.zoomToLayer = (this.options.zoomToLayer == 'true') ? true : false;
		if (this.options.geoKML) {
			this.options.geoKML = $T.isString(this.options.geoKML) ? [this.options.geoKML] : this.options.geoKML;
			this.options.geoKML.each(function (kml) {
				this.loadKML(kml, null, this.options.zoomToLayer);
			}.bind(this));
			this.map.ClearInfoBoxStyles();
		}
		if (this.options.geoRSS) {
			this.options.geoRSS = $T.isString(this.options.geoRSS) ? [this.options.geoRSS] : this.options.geoRSS;
			this.options.geoRSS.each(function (rss) {
				this.loadRSS(rss, null, this.options.zoomToLayer);
			}.bind(this));
		}
		if (this.options.geoTiles) {
			this.loadTiles(this.options.geoTiles);
		}
		if (this.options.geoJSON || this.options.layer) {
			this.options.geoJSON = (this.options.geoJSON) ? this.options.geoJSON : this.options.layer;
			this.options.geoJSON = $T.isString(this.options.geoJSON) ? [this.options.geoJSON] : this.options.geoJSON;
			this.options.geoJSON.each(function (json) {
				this.loadJSON(json.unescapeHTML(), this.options.zoomToLayer);
			}.bind(this));
		}
		var birdsEyeInfo = $('MSVE_obliqueNotification');
		if (birdsEyeInfo) {
			birdsEyeInfo.style.visibility = 'hidden';
		}
		this.options.portalMap = (this.options.portalMap == 'true') ? true : false;
		this.map.AttachEvent('onmousedown', this.mapDrag.bind(this, $(this.id)));
		this.setupFeatureCreation();
		this.setupAreaSearch();
		this.setupLayerToggles();
		this.addMapForm();
		if (this.controls.btnGroup) {
			this.controls.btnGroup.hide();
			this.togglePopup(true, 'startMsg');
		}
	},
	panChangeView: function (e) {
		if (this.clickFlag && this.options.extent) {
			var width = this.options.extent.width;
			var height = this.options.extent.height;
			var currentBoundingBox = [this.map.PixelToLatLong(new VEPixel(0, 0)), this.map.PixelToLatLong(new VEPixel(width, 0)), this.map.PixelToLatLong(new VEPixel(width, height)), this.map.PixelToLatLong(new VEPixel(0, height)),
					this.map.PixelToLatLong(new VEPixel(0, 0))];
			currentBoundingBox.each(function (latlong) {
				var points = this.options.extent.points;
				var j = points.length - 1;
				var inPoly = false;
				var lat = latlong.Latitude;
				var lon = latlong.Longitude;
				for ( var i = 0; i < points.length; i++) {
					if (points[i].Longitude < lon && points[j].Longitude >= lon || points[j].Longitude < lon && points[i].Longitude >= lon) {
						if (points[i].Latitude + (lon - points[i].Longitude) / (points[j].Longitude - points[i].Longitude) * (points[j].Latitude - points[i].Latitude) < lat) {
							inPoly = true;
						}
					}
					j = i;
				}

				if (inPoly === false) {
					if (e.zoomLevel < this.options.extent.zoomLevel) {
						this.map.SetZoomLevel(this.options.extent.zoomLevel);
					}
					this.map.SetCenter(this.options.extent.center);
					throw $break;
				}
			}.bind(this));

			this.clickFlag = false;
		}
	},
	removeExtent: function () {
		if (this.extentLayer) {
			this.map.DeleteShapeLayer(this.extentLayer);
		}
		LHS.Remoting.include($context + '/creationService/mapping/removeExtent?id=' + this.id.substring(this.id.lastIndexOf('-') + 1, this.id.length), 'globalHiddenDiv');
	},
	limitExtent: function () {

		Event.observe('Compass', 'mousedown', function (e) {
			this.options.extent.center = this.map.GetCenter();
			this.clickFlag = true;
		}.bind(this));

		Event.observe('Compass', 'mouseup', function (e) {
			this.clickFlag = false;
		}.bind(this));

		this.map.AttachEvent('onstartpan', function (e) {
			if (e.leftMouseButton && this.options.extent) {
				this.options.extent.center = this.map.GetCenter();
				this.clickFlag = true;
			}
		}.bind(this));

		this.map.AttachEvent("onstartzoom", function (e) {
			if (this.options.extent) {
				this.clickFlag = true;
				this.options.extent.center = this.map.GetCenter();
			}
		}.bind(this));

		this.map.AttachEvent('onchangeview', this.panChangeView.bind(this));
	},
	captureExtent: function () {
		var width = $(this.id).getWidth();
		var height = $(this.id).getHeight();
		var points = [this.map.PixelToLatLong(new VEPixel(0, 0)), this.map.PixelToLatLong(new VEPixel(width, 0)), this.map.PixelToLatLong(new VEPixel(width, height)), this.map.PixelToLatLong(new VEPixel(0, height)),
				this.map.PixelToLatLong(new VEPixel(0, 0))];
		var form = $('frmExtent_' + this.id.substring(this.id.lastIndexOf('-') + 1, this.id.length));
		form.populate({
			'botRightLat': points[2].Latitude,
			'botRightLong': points[2].Longitude,
			'topLeftLat': points[0].Latitude,
			'topLeftLong': points[0].Longitude,
			'zoom': this.map.GetZoomLevel()
		});

		LHS.Remoting.invoke(form);
		this.createExtentBox(points);
	},
	createExtentBox: function (points) {
		if (this.extentShape) {
			this.map.DeleteShapeLayer(this.extentLayer);
		}
		this.extentShape = new VEShape(VEShapeType.Polyline, points);
		this.extentLayer = new VEShapeLayer();
		this.map.AddShapeLayer(this.extentLayer);
		this.extentLayer.AddShape(this.extentShape);
		this.extentShape.SetCustomIcon('');
		this.extentShape.HideIcon();
	},
	deleteLastLayer: function () {
		this.map.DeleteShapeLayer(this.map.GetShapeLayerByIndex(this.map.GetShapeLayerCount() - 1));
	},
	/**
	 * @param {String}
	 *            where - The text to search for. eg a postcode
	 * @param {Event}
	 *            event - The event created when the user hit the button or link
	 */
	positionPopup: function (type) {
		if (this.controls.pointPopup) {
			var leftPosition = 50 + ($('main').getWidth() / 2);
			switch (type) {
				case 'startMsg':
					leftPosition = leftPosition - 160;
					this.controls.pointPopup.setStyle({
						position: 'absolute',
						top: this.map.GetTop() + 'px',
						left: leftPosition + 'px',
						width: '666px'
					});
					break;
				case 'YesNoMsg':
					this.controls.pointPopup.setStyle({
						position: 'absolute',
						top: this.map.GetTop() + 170 + 'px',
						left: leftPosition + 'px',
						width: '340px'
					});
					break;
				case 'cancelMsg':
					this.controls.pointPopup.setStyle({
						position: 'absolute',
						top: this.map.GetTop() + 170 + 'px',
						left: leftPosition + 'px',
						width: '340px'
					});
					break;
				case 'deleteMsg':
					this.controls.pointPopup.setStyle({
						position: 'absolute',
						top: this.map.GetTop() + 170 + 'px',
						left: leftPosition + 'px',
						width: '340px'
					});
					break;
				case 'OKMsg':
					this.controls.pointPopup.setStyle({
						position: 'absolute',
						top: this.map.GetTop() + 170 + 'px',
						left: leftPosition + 'px',
						width: '340px'
					});
					break;
			}
		}
	},
	addMapForm: function () {
		if (this.options.form && $(this.options.form)) {
			this.options.form = $(this.options.form);

			var change = function (event) {
				var latLon = this.map.GetCenter();
				var mapStyle = 'Road';

				if (event.mapStyle == 'a') {
					mapStyle = 'Aerial';
				} else if (event.mapStyle == 'h') {
					mapStyle = 'Hybrid';
				} else if (event.mapStyle == 'o') {
					mapStyle = 'Birdseye';
				} else if (event.mapStyle == 'b') {
					mapStyle = 'BirdseyeHybrid';
				}

				var formObj = {
					'param:latitude': latLon.Latitude,
					'param:longitude': latLon.Longitude,
					'param:zoom': event.zoomLevel,
					'param:baseMap': mapStyle,
					'map:latitude': latLon.Latitude,
					'map:longitude': latLon.Longitude,
					'map:zoom': event.zoomLevel,
					'map:baseMap': mapStyle
				};
				this.options.form.populate(formObj);

			}.bind(this);

			this.map.AttachEvent('onchangeview', change);
		}
	},
	findAddress: function (event, where) {
		var startIndex, numberOfResults, what, layer = null;
		var showResults, disambiguation = false;
		var id = this.id;
		this.searchBox = where;
		where = where.value;
		this.zoomLevel = this.map.GetZoomLevel();
		if (where !== '') {
			this.map.Find(what, where, null, layer, startIndex, numberOfResults, showResults, null, disambiguation, null, function (layer, resultsArray, places, hasMore, veErrorMessage) {

				if (this.searchBox.hasClassName('addMarker')) {
					if (this.searchLayer !== null) {
						this.map.DeleteShapeLayer(this.searchLayer);
					}
					this.searchLayer = new VEShapeLayer();
					this.map.AddShapeLayer(this.searchLayer);
					var latlng = new VELatLong(places[0].LatLong.Latitude, places[0].LatLong.Longitude);
					var marker = new VEShape(VEShapeType.Pushpin, latlng);
					marker.SetTitle('My Home');
					var veIcon = new VECustomIconSpecification();
					var iconHref = $context + '/images/mapping/mapIcons/homemarker.png';
					if (Prototype.Browser.IE && Prototype.Browser.version < 7) {
						veIcon.CustomHTML = '<div style="position:relative; top:-34px; width:53px; height: 57px; left:-5px; filter:Progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + iconHref +
								'\', sizingMethod=\'scale\'); display:inline-block;"></div>';
						veIcon.ImageOffset = new VEPixel(-134, -5);
						marker.SetCustomIcon(veIcon);
					} else {
						marker.SetCustomIcon('<div style="position:absolute; top:-34px; left:-5px; width:53px; height: 57px;"><img src="' + iconHref + '"/></div>');
					}
					this.searchLayer.AddShape(marker);
				}
				var resultDiv = $('resultsMap_' + id.substr(id.lastIndexOf('-') + 1, id.length));
				if (resultDiv) {
					resultDiv.update('');
					resultDiv.appendChild($N.ul({
						className: 'blockList'
					}));
					places.each(function (place, i) {
						resultDiv.down('ul').appendChild($N.li({}, $N.a({
							id: 'find-' + i + '-' + id,
							className: 'targetIcon',
							style: {
								cursor: 'pointer'
							}
						}, place.Name)));

						Event.observe($('find-' + i + '-' + id), 'click', function () {
							LHS.Map.store[id].map.Find(null, place.Name);
						});
					});
				}
				this.map.SetZoomLevel(this.zoomLevel);
			}.bind(this));
		}
		Event.element(event).blur();
		Event.stop(event);
	},
	/**
	 * Resets the map to its original view
	 */
	resetView: function (event, element) {
		// with no layer loaded there will be no lat or long
		if (this.options.latitude !== '' && this.options.longitude !== '') {
			var latLon = new VELatLong(this.options.latitude, this.options.longitude);
			var zoomLevel = (this.zoomLevel) ? this.zoomLevel : this.options.zoom;
			this.map.SetCenterAndZoom(latLon, zoomLevel);
			this.map.SetMapStyle(VEMapStyle[this.options.baseMap]);
		}
		if (typeof element !== 'undefined') {
			element.blur();
		}
		if (typeof event !== 'undefined') {
			Event.stop(event);
		}
	},
	mapResize: function (width, height) {
		this.map.Resize(width, height);
	},
	/**
	 * @param {String}
	 *            url - The url of the geoJSON feed, this calls a service which
	 *            returns KML then output xslt converts to JSON
	 * @return {VEShapeLayer} - the new layer
	 */
	loadJSON: function (url, zoom) {
		var map = this;
		var layer = new VEShapeLayer();
		// /AJAX request to get geoJSON object
		var kml2json = new Ajax.Request(url, {
			method: 'get',
			onSuccess: function (transport) {
				var obj = transport.responseText.evalJSON();
				obj.features.each(function (feature) {
					// call shape drawer
					if (feature.type == 'Point') {
						map.drawJSONPoint(feature, layer);
					} else if (feature.type == 'Polygon' || feature.type == 'LineString') {
						map.drawJSONPoly(feature, layer);
					}
				});

				// check for bounding box
				if (obj.bbox && zoom !== false) {
					// remove '0' from bounding box
					obj.bbox = obj.bbox.without(0);
					var topLeftLatLng = new VELatLong(obj.bbox[3], obj.bbox[0]);
					var bottomRightLatLng = new VELatLong(obj.bbox[1], obj.bbox[2]);

					var bestFit = new VELatLongRectangle(topLeftLatLng, bottomRightLatLng);
					// timeout prevents IE error
					if (bestFit) {
						setTimeout(function () {
							map.map.SetMapView(bestFit);
						}.bind(this), 1000);
					}
				}
			}
		});
		this.map.AddShapeLayer(layer);
		return layer;
	},
	drawJSONPoint: function (feature, layer) {
		if (feature) {
			var latlng = new VELatLong(feature.coordinates[1], feature.coordinates[0]);
			var marker = new VEShape(VEShapeType.Pushpin, latlng);
			var iconHref;

			if (feature.style.iconStyle) {
				iconHref = feature.style.iconStyle.iconHref;
			} else {
				if (feature.styleUrl.indexOf('-') != -1) {
					iconHref = $rootUrl + '/layer/' + feature.styleUrl.substringAfter('-') + '/marker.png';
				} else {
					iconHref = $rootUrl + '/' + feature.styleUrl;
				}
			}
			var veIcon = new VECustomIconSpecification();
			if (iconHref && iconHref.endsWith('.png')) {
				if (Prototype.Browser.IE && Prototype.Browser.version < 7) {
					veIcon.CustomHTML = '<div style="position:relative; top:-34px; width:53px; height: 57px; left:-5px; filter:Progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + iconHref +
							'\', sizingMethod=\'scale\'); display:inline-block;"></div>';
					veIcon.ImageOffset = new VEPixel(-134, -5);
					marker.SetCustomIcon(veIcon);
				} else {
					marker.SetCustomIcon('<div style="position:absolute; top:-34px; left:-5px; width:53px; height: 57px;"><img src="' + iconHref + '"/></div>');
				}
			}
			marker.SetTitle(feature.name);
			marker.SetDescription(feature.description);
			layer.AddShape(marker);
		}
	},
	drawJSONPoly: function (feature, layer) {
		if (feature) {
			var latlngs = [];

			feature.coordinates.each(function (coord) {
				latlngs.push(new VELatLong(coord[1], coord[0]));
			});

			var shapeType = feature.type == 'Polygon' ? VEShapeType.Polygon : VEShapeType.Polyline;
			var shape = new VEShape(shapeType, latlngs);

			var lineColour, fillColour, c, hex, fC, width;

			if (feature.style.lineStyle) {
				width = feature.style.lineStyle.width;
				fC = feature.style.lineStyle.color;
				fC = fC.substr(6, 2) + fC.substr(4, 2) + fC.substr(2, 2);
			} else {
				width = 2;
				fC = 'FF0000';
			}
			shape.SetLineWidth(width);
			c = new LHS.Colour({
				hex: fC
			});
			lineColour = new VEColor(c.red, c.green, c.blue, 0.8);
			fillColour = new VEColor(c.red, c.green, c.blue, 0.5);
			shape.SetLineColor(lineColour);
			shape.SetFillColor(fillColour);

			shape.HideIcon();
			layer.AddShape(shape);
		}
	},
	/**
	 * @param {String}
	 *            url - The url of the KML feed
	 * @param {Function}
	 *            callback - A function to be run when the KML is loaded
	 * @param {Boolean|Integer}
	 *            zoomTo - If true then the Map should move to the best view for
	 *            the new Layer - If an Integer then zoom to that idexed shape
	 * @return {VEShapeLayer} - the new layer
	 */
	loadKML: function (url, callback, zoomTo, options) {
		if (url.startsWith('/')) {
			url = document.location.protocol + '//' + document.location.host + url;
		} else if (!url.startsWith(document.location.protocol)) {
			url = document.location.href.substring(0, document.location.href.lastIndexOf('/') + 1) + url;
		}
		var layer = new VEShapeLayer();
		this.map.ImportShapeLayerData(new VEShapeSourceSpecification(VEDataType.ImportXML, url, layer), function () {
			if (callback) {
				callback();
			}
			if (this.options.portalMap && layer) {
				var count = layer.GetShapeCount();
				for ( var i = 0; i < count; i++) {
					var shape = layer.GetShapeByIndex(i);
					var currentDesc = shape.GetDescription();
					shape.SetTitle('');
					shape.SetDescription('<iframe src="' + currentDesc + '" frameborder="0" style="overflow: hidden; height: 139px; width: 100%;"></iframe>');
				}
				this.styleLayerFeatures(layer, options, true);
			} else {
				this.styleLayerFeatures(layer, options, false);
			}
			this.showLayer(layer, zoomTo);
		}.bind(this), zoomTo && $T.isBoolean(zoomTo));

		return layer;
	},
	/**
	 * @param {String}
	 *            url - The url of the Tile folder
	 * @return {VETileSourceSpecification} - the new tile layer
	 */
	loadTiles: function (value) {
		var tileLayers = value.evalJSON();
		tileLayers.each(function (tile) {
			this.tileLayers[tile.id] = new VETileSourceSpecification('layer-' + tile.id, tile.href + '/%4.png');
			this.tileLayers[tile.id].Opacity = tile.opacity;
			this.map.AddTileLayer(this.tileLayers[tile.id], true);
		}.bind(this));
	},
	hideTiles: function (layer) {
		this.map.HideTileLayer(layer.ID);
	},
	showTiles: function (layer) {
		this.map.ShowTileLayer(layer.ID);
	},
	unloadTiles: function (layer) {
		this.map.DeleteTileLayer(layer.ID);
	},
	/**
	 * @param {VEShapeLayer}
	 *            layer - The layer to be removed
	 */
	unloadLayer: function (layer) {
		this.map.DeleteShapeLayer(layer);
	},
	/**
	 * @param {VEShapeLayer}
	 *            layer - The layer to show
	 * @param {Boolean|Integer}
	 *            zoomTo - If true then the Map should move to the best view for
	 *            the new Layer - If an Integer then zoom to that idexed shape
	 */
	showLayer: function (layer, zoomTo) {
		if ($T.isNumber(zoomTo) && layer.GetShapeCount() > zoomTo) {
			var box = layer.GetShapeByIndex(zoomTo).GetBoundingBox();
			var rectangle = new VELatLongRectangle(new VELatLong(box.y2, box.x1), new VELatLong(box.y1, box.x2));
			this.selectedShapeBox = rectangle;
			this.map.SetMapView(rectangle);
		} else if (zoomTo) {
			this.selectedShapeBox = null;
			this.map.SetMapView(layer.GetBoundingRectangle());
		}
		layer.Show();
	},
	zoomTo: function (event, element) {
		// current shape
		// current layer
		// whole visible map
		var bestFit;
		if (this.layers.size() > 0) {
			if (element.hasClassName('zoomMap')) {
				var boundingBoxBottomRight = [];
				var boundingBoxTopLeft = [];
				this.layers.each(function (pair) {
					var layer = pair.value;
					if (layer.IsVisible() && layer.GetShapeCount() > 0) {
						boundingBoxTopLeft.push(layer.GetBoundingRectangle().TopLeftLatLong);
						boundingBoxBottomRight.push(layer.GetBoundingRectangle().BottomRightLatLong);
					}
				});
				var topLeftLatLng = new VELatLong(boundingBoxTopLeft.pluck('Latitude').max(), boundingBoxTopLeft.pluck('Longitude').min());
				var bottomRightLatLng = new VELatLong(boundingBoxBottomRight.pluck('Latitude').min(), boundingBoxBottomRight.pluck('Longitude').max());

				bestFit = new VELatLongRectangle(topLeftLatLng, bottomRightLatLng);

			} else if (element.hasClassName('zoomLayer')) {
				bestFit = this.selectedLayer.GetBoundingRectangle();
			} else if (element.hasClassName('zoomFeature') && this.selectedShapeBox) {
				bestFit = this.selectedShapeBox;
			}
			if (bestFit) {
				setTimeout(function () {
					this.map.SetMapView(bestFit);
				}.bind(this), 1000);
			}
		}
		if (element) {
			element.blur();
			Event.stop(event);
		}

	},
	/**
	 * @param {VEShapeLayer}
	 *            layer - The layer to hide
	 */
	hideLayer: function (layer) {
		layer.Hide();
	},
	/**
	 * Hide all layers
	 */
	hideAllLayer: function () {
		this.layers.values().invoke('Hide');
	},
	/**
	 * @param {String}
	 *            url - The url of the RSS feed
	 * @param {Function}
	 *            callback - A function to be run when the RSS is loaded
	 * @param {Boolean}
	 *            zoomTo - If true then the Map should move to the best view for
	 *            the new Layer
	 */
	loadRSS: function (url, callback, zoomTo, options) {
		if (url.startsWith('/')) {
			url = document.location.protocol + '//' + document.location.host + url;
		}
		var layer = new VEShapeLayer();
		this.map.ImportShapeLayerData(new VEShapeSourceSpecification(VEDataType.GeoRSS, url, layer), function () {
			if (callback) {
				callback();
			}
			this.styleLayerFeatures(layer, options, false);

		}.bind(this), zoomTo);
		return layer;
	},
	styleLayerFeatures: function (layer, options, keepIcon) {
		var count = layer.GetShapeCount();
		// need to make sure the right option.? are set
		for ( var i = 0; i < count; i++) {
			var shape = layer.GetShapeByIndex(i);
			if (!keepIcon) {
				shape.HideIcon();
			}

			// correct the icon position
			if (keepIcon || shape.GetType() === VEShapeType.Pushpin) {
				var icon = shape.IconUrl;
				if (Prototype.Browser.IE && Prototype.Browser.version < 7) {
					shape.SetCustomIcon('<div style="position:absolute; top:-34px; left:-5px; width:53px; height: 57px; filter:Progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + icon +
							'\', sizingMethod=\'scale\'); display:inline-block;"></div>');
				} else {
					shape.SetCustomIcon('<div style="position:absolute; top:-34px; left:-5px; width:53px; height: 57px"><img src="' + icon + '"/></div>');
				}
			}
		}
	},
	selectGeometry: function () {
		var me = this;
		var grabShape = function (e) {
			if (e.elementID !== null) {
				var overlay = me.map.GetShapeByID(e.elementID);

				for ( var i = 0; i < me.layer.GetShapeCount(); i++) {
					// console.log(me.layer.GetShapeByIndex(i));
				}
				if (me.overlay !== null) {
					if (me.overlay != overlay) {
						// me.pointsToFeature(me.overlay, 'remove');
					}
				}
				if (me.editMode === false) {
					me.overlay = overlay;
				}
				var id = overlay.GetDescription();
				id = id.substringBefore(' ');
				var node = $('treeNode-' + id);
				var nodeInsert = $('insert_treeNode-' + id);

				if (nodeInsert) {
					node = nodeInsert;
				}

				if (node) {
					node.tree.openToItem(node);
					me.clearLayers();
					me.loadKML(node.down('a.featureIcon').href, null, false);
					me.loadKML(node.up('li.layer').down('a.layerIcon').href, null, false);
				}
			}
		};
		var listener = me.map.AttachEvent('onclick', grabShape);
	},
	removePointsFromFeature: function () {
		var me = this;
		me.map.DeleteShape(me.featurePoints);
		me.featurePoints = null;
		/*
		 * me.map.DetachEvent('onmousemove',me.changeShape);
		 * me.map.DetachEvent('onmousedown',me.changeShape);
		 * me.map.DetachEvent('onmouseup',me.changeShape);
		 */
	},
	/**
	 * Adds points onto all the vertices and side of a Polygon/Line so that it
	 * can be modified
	 * 
	 * @param {VEShape}
	 *            geometry - The shape we are going to edit
	 * @param {Boolean}
	 *            end - If true then the points will be removed
	 */
	pointsToFeature: function (geometry, end) {
		var me = this;
		me.editMode = true;
		var dragShape = null;
		if (geometry.GetType() == 'Point') {
			var movePoint = function (e) {
				var x = e.mapX;
				var y = e.mapY;
				var pixel = new VEPixel(x, y);
				// var me = this;
				var LL = me.map.PixelToLatLong(pixel);

				if (e.eventName == 'onmousedown') {
					var point = me.map.GetShapeByID(e.elementID);
					if (point == geometry) {
						dragShape = geometry;
						return true;
					}
				} else if (e.eventName == 'onmouseup' || e.eventName == 'onmousemove') {
					if (e.eventName == 'onmouseup') {
						dragShape = null;
					}
					if (dragShape !== null) {
						me.overlay.SetPoints(LL);
						return true;
					}
				}
			};
			if (end) {
				me.map.AttachEvent('onmousedown', movePoint);
				me.map.AttachEvent('onmouseup', movePoint);
				me.map.AttachEvent('onmousemove', movePoint);
			} else {
				me.editMode = false;
				dragShape = null;
				me.overlay = null;
				me.map.DetachEvent('onmousedown', movePoint);
				me.map.DetachEvent('onmouseup', movePoint);
				me.map.DetachEvent('onmousemove', movePoint);
			}
		} else {
			if (end) {
				me.featurePoints = [];
				var newLayer = new VEShapeLayer();
				me.map.AddShapeLayer(newLayer);
				geometry.GetPoints().each(function (point, i) {
					if (i != geometry.GetPoints().length - 1) {
						var latlng = new VELatLong(point.Latitude, point.Longitude);
						var shape = new VEShape(VEShapeType.Pushpin, latlng);
						shape.SetDescription(geometry.GetID() + '-' + i);
						me.featurePoints.push(shape);
					}
				});

				newLayer.AddShape(me.featurePoints);
			}
			var point = null;
			var point2 = null;
			var changeShape = function (e) {
				var x = e.mapX;
				var y = e.mapY;
				pixel = new VEPixel(x, y);
				var me2 = this;
				var LL = me.map.PixelToLatLong(pixel);

				if (e.eventName == 'onmousedown') {
					point = me.map.GetShapeByID(e.elementID);
					if (e.elementID !== null) {
						if (point.GetType() == 'Point') {
							if (me.featurePoints.indexOf(point) == -1) {
								point = null;
							} else {
								point2 = me.featurePoints[me.featurePoints.indexOf(point) + 1];
								me.map.AttachEvent('onmousemove', changeShape);
							}
						}
						return true;
					}
				} else if (e.eventName == 'onmouseup' || e.eventName == 'onmousemove') {
					if (point !== null) {
						point.SetPoints(LL);

						/*
						 * var lat1 = point.Latitude; var lat2 =
						 * point2.Latitude; var lon1 = point.Longitude; var lon2 =
						 * point2.Longitude; var degrees = (lat2-lat1); var dLat =
						 * degrees * Math.PI / 180; degrees = (lon2-lon1); var
						 * dLon = degrees * Math.PI / 180; var Bx =
						 * Math.cos(lat2) * Math.cos(dLon); var By =
						 * Math.cos(lat2) * Math.sin(dLon); lat3 =
						 * Math.atan2(Math.sin(lat1)+Math.sin(lat2),
						 * Math.sqrt((Math.cos(lat1)+Bx)*(Math.cos(lat1)+Bx) +
						 * By*By ) ); degrees = (lon1 * Math.PI / 180); lon3 =
						 * degrees + Math.atan2(By, Math.cos(lat1) + Bx);
						 * 
						 * newLayer.AddShape(new VEShape(VEShapeType.Pushpin,
						 * new VELatLong(lat3, lon3)));
						 * 
						 * console.log(lat3, lon3);
						 */

						// var points = me.overlay.GetPoints();
						var points = [];
						me.featurePoints.each(function (point) {
							points.push(point.GetPoints()[0]);
						});

						points.push(points.last());
						me.overlay.SetPoints(points);
						// me.overlay.SetPoints(points);
						/*
						 * points[point.GetDescription().substringAfter] = LL;
						 * me.overlay.SetPoints(points);
						 */
						if (e.eventName == 'onmouseup') {
							point = null;

							polygon = '<feature><Polygon><outerBoundaryIs><LinearRing><tessellate>1</tessellate><coordinates>';
							me.featurePoints.each(function (marker) {
								var latlng = marker.GetPoints()[0];
								polygon += latlng.Longitude + ',' + latlng.Latitude + ',0.000000 ';
							});
							polygon += '</coordinates></LinearRing></outerBoundaryIs></Polygon></feature>';

							me.map.DetachEvent('onmousemove', changeShape);
						}
						return true;
					}

				}
			};
			if (end) {
				me.map.AttachEvent('onmousedown', changeShape);
				me.map.AttachEvent('onmouseup', changeShape);
			} else {
				me.editMode = false;
				me.overlay = null;
				me.map.DetachEvent('onmousedown', changeShape);
				me.map.DetachEvent('onmouseup', changeShape);
			}
		}
	},
	mapDrag: function (mapDiv, event) {
		if (event.eventName == 'onmousedown') {
			this.startX = event.mapX;
			this.startY = event.mapY;
		}
	},
	mapScroll: function (mapDiv, event) {
		if (mapDiv) {
			var d = 100;
			var x = event.mapX;
			var y = event.mapY;
			var w = mapDiv.getWidth();
			var h = mapDiv.getHeight();
			var t = (y < d) ? y - d : 0;
			var b = (y > h - d) ? y - h + d : 0;
			var l = (x < d) ? x - d : 0;
			var r = (x > w - d) ? x - w + d : 0;
			if (l < 0 || r > 0 || t < 0 || b > 0) {
				var now = new Date().getTime();
				if (this.scrollStart !== null && this.scrollStart < now) {
					var xRate = Math.round(20 * (l + r) / d);
					var yRate = Math.round(20 * (t + b) / d);
					this.map.StartContinuousPan(xRate, yRate);
					return;
				} else if (this.scrollStart === null) {
					this.scrollStart = now + 500;
				}
				return;
			}
		}
		this.scrollStart = null;
		this.map.EndContinuousPan();
	}
});

/**
 * The Line Drawer extends the Base Drawer
 */
LHS.MapVE.PointDrawer = Class.create();
LHS.MapVE.PointDrawer.prototype = Object.extend(new LHS.Map.Drawer(), {
	pointCount: 1,
	shapeCount: 0,
	pointPopup: null,
	/**
	 * @constructor
	 * @param {LHS.MapVE}
	 *            Map - the map implementation
	 * @param {String}
	 *            icon - the url to the icon used on the start point
	 * @param {String}
	 *            colour - the colour to be used to the stroke and fill
	 * @param {Integer}
	 *            width - the width of the stroke
	 * @param {String}
	 *            title - the title of the layer of the stroke
	 */
	initialize: function (Map, options) {
		if (Map) {
			this.map = Map.map;
			this.options = Object.extend(Map.drawOptions, options);

			var c = new LHS.Colour({
				hex: this.options.colour
			});
			this.options.lineColour = new VEColor(c.red, c.green, c.blue, 0.8);
			this.options.fillColour = new VEColor(c.red, c.green, c.blue, 0.5);

			this.markers = [];
			this.MapObj = Map;
			this.layer = new VEShapeLayer();
			this.map.AddShapeLayer(this.layer);
			this.shape = null;
			this.mapEvent = this.makePoint.bind(this);
			this.map.AttachEvent('onclick', this.mapEvent);
		}
	},
	/**
	 * Create a point for the shape here
	 * 
	 * @param {VEEvent}
	 *            event
	 */
	makePoint: function (event) {
		// if (this.MapObj.startX == event.mapX && this.MapObj.startY ==
		// event.mapY) {
		if ((Math.abs(event.mapX - this.MapObj.startX) < 8) && (Math.abs(event.mapY - this.MapObj.startY) < 8)) {
			if (this.pointCount === 0 || this.pointCount > this.markers.length) {
				var latlng = this.map.PixelToLatLong(new VEPixel(event.mapX, event.mapY));
				var marker = new VEShape(VEShapeType.Pushpin, latlng);

				if (this.options.icon && this.options.icon.endsWith('.png')) {
					if (Prototype.Browser.IE && Prototype.Browser.version < 7) {
						marker.SetCustomIcon('<div style="position:absolute; top:-34px; width:53px; height: 57px; left:-5px; filter:Progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + this.options.marker +
								'\', sizingMethod=\'scale\'); display:inline-block;"></div>');
					} else {
						marker.SetCustomIcon('<div style="position:absolute; top:-34px; left:-5px; width:53px; height: 57px"><img src="' + this.options.marker + '"/></div>');
					}
				}

				this.layer.AddShape(marker);
				this.markers.push(marker);
				this.redraw();

			} else {
				if (this.MapObj.controls.pointPopup) {
					this.MapObj.togglePopup(true, 'OKMsg');
					this.MapObj.positionPopup('OKMsg');
				}
			}
		}
	},
	/**
	 * Remove the last drawn marker
	 */
	stepBack: function () {
		if (this.markers.length !== 0) {
			var lastMarker = this.markers.pop();
			this.layer.DeleteShape(lastMarker);
			this.redraw();
		}

		this.MapObj.togglePopup(false, 'OKMsg');

		return this.markers.length > 0;
	},
	/**
	 * Cancel the current shape completely
	 */
	clear: function () {
		this.layer.DeleteAllShapes();
		this.map.DetachEvent('onclick', this.mapEvent);
		this.MapObj.togglePopup(false, 'OKMsg');
	},
	/**
	 * Remove the endpoint marker, click event and return the KML
	 * 
	 * @param {VEShapeLayer}
	 *            outputLayer - the layer to add to new shape too
	 */
	finish: function (outputLayer) {
		var finishButton = $('finish-' + this.MapObj.id.substringAfter('-'));
		if (finishButton && this.shape) {
			finishButton.href = finishButton.href.updateQueryString({
				'output:shapeId': this.shape.GetID()
			});
		}
		this.redraw(true);
		this.map.DetachEvent('onclick', this.mapEvent);
		this.shapeCount += 1;
		var kml = this.serialize();
		if (finishButton || kml === null) {
			this.map.DeleteShapeLayer(this.layer);
		}
		return kml;
	},
	/**
	 * Uses the collection of markers that have been created to produce the KML
	 * for the points
	 * 
	 * @return {String|null} return the KML String or null if there are not
	 *         sufficient markers
	 */
	serialize: function (geometryDetails) {
		if (this.markers.length === 0) {
			return null;
		}
		var kml = this.markers.collect(function (marker) {
			var latlng = marker.GetPoints()[0];
			return '<Point xmlns="http://earth.google.com/kml/2.2"><coordinates>' + latlng.Longitude + ',' + latlng.Latitude + '</coordinates></Point>';
		});
		return kml.join('');
	}
});

/**
 * The Line Drawer extends the Point Drawer
 */
LHS.MapVE.LineDrawer = Class.create();
LHS.MapVE.LineDrawer.prototype = Object.extend(new LHS.MapVE.PointDrawer(), {
	// shapeType: VEShapeType.Polyline, // The shape is set to be a line
	pointCount: 0,
	getDistance: function () {
		var markers = this.markers;
		var j = markers.length;
		var distance = 0;
		for ( var i = 0; i < j; i++) {
			if (i + 1 < j && j > 1) {
				var p1Lat = markers[i].Latitude * Math.PI / 180;
				var p1Lon = markers[i].Longitude * Math.PI / 180;

				var p2Lat = markers[i + 1].Latitude * Math.PI / 180;
				var p2Lon = markers[i + 1].Longitude * Math.PI / 180;

				var R = 6371; // earth's mean radius in km
				var dLat = p2Lat - p1Lat;
				var dLong = p2Lon - p1Lon;
				var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(p1Lat) * Math.cos(p2Lat) * Math.sin(dLong / 2) * Math.sin(dLong / 2);
				var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
				var disKm = R * c;
				// var disMiles = disKm * 0.6214;
				distance += disKm;
			}
		}
		return distance;
	},
	/**
	 * Redraws the shape to update any new/removed markers
	 * 
	 * @param {Boolean}
	 *            finish - If true the shape is drawn without the start and end
	 *            markers
	 */
	redraw: function (finish) {
		if (this.markers.length >= 2) {
			var markersLength = this.markers.length;
			var points = this.markers.collect(function (marker, i) {
				if (i !== 0 && (i !== markersLength - 1 || finish)) {
					marker.SetCustomIcon('<span></span>');
				} else if (i === markersLength - 1) {
					if (Prototype.Browser.IE && Prototype.Browser.version < 7) {
						marker.SetCustomIcon('<div style="position:absolute; top:-18px; left:6px; width:40px; height:40px; filter:Progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + $context +
								'/images/mapping/pencil.png\', sizingMethod=\'scale\'); display:inline-block;"></div>');
					} else {
						marker.SetCustomIcon('<div style="position:absolute; top:-18px; left:6px;"><img src="' + $context + '/images/mapping/pencil.png"/></div>');
					}
				}
				return marker.GetPoints()[0];
			});
			if (this.shapeType !== 'Polygon') {
				/*
				 * $('distance').disabled = false; $('distance').value =
				 * Math.round(this.getDistance()) + 'km';
				 */
			}
			// if we dont have a shape or need to change its type
			if (this.shape === null || (this.markers.length === 3 && this.shapeType === VEShapeType.Polygon) || (this.markers.length === 2 && this.shapeType === VEShapeType.Polygon)) {
				if (this.shape !== null && this.shapeType === 'Polygon') {
					this.layer.DeleteShape(this.shape);
				}
				var shapeType = (this.markers.length === 2) ? VEShapeType.Polyline : this.shapeType;
				this.shape = new VEShape(shapeType, points);
				this.shape.SetLineWidth(this.options.width);
				this.shape.SetLineColor(this.options.lineColour);
				this.shape.SetFillColor(this.options.fillColour);
				this.shape.HideIcon();
				this.layer.AddShape(this.shape);
			} else {
				this.shape.SetPoints(points);
			}
			/*
			 * if (this.shapeType == VEShapeType.Polyline) {
			 * this.map.SetCenter(points.last()); }
			 */
		} else if (this.shape) {
			this.layer.DeleteShape(this.shape);
			this.shape = null;
		}
	},
	/**
	 * Uses the collection of markers that have been created to produce the KML
	 * for the line
	 * 
	 * @return {String|null} return the KML String or null if there are not
	 *         sufficient markers
	 */
	serialize: function () {
		if (this.markers.length <= 1) {
			return null;
		}
		var kml = this.markers.collect(function (marker) {
			var latlng = marker.GetPoints()[0];
			return latlng.Longitude + ',' + latlng.Latitude + ',0.000000';
		});
		// $('distance').value= '';

		return '<LineString xmlns="http://earth.google.com/kml/2.2"><tessellate>1</tessellate><coordinates>' + kml.join(' ') + '</coordinates></LineString>';
	}
});

/**
 * The Polygon Drawer extends the Line Drawer
 */
LHS.MapVE.PolygonDrawer = Class.create();
LHS.MapVE.PolygonDrawer.prototype = Object.extend(new LHS.MapVE.LineDrawer(), {
	shapeType: VEShapeType.Polygon, // The shape is set to be a polygon

	/**
	 * Uses the collection of markers that have been created to produce the KML
	 * for the polygon
	 * 
	 * @return {String|null} return the KML String or null if there are not
	 *         sufficient markers
	 */
	serialize: function () {
		if (this.markers.length <= 2) {
			return null;
		}
		// we also want the first point at the end to complete the poly
		var points = this.markers.concat(this.markers[0]);
		var kml = points.collect(function (marker) {
			var latlng = marker.GetPoints()[0];
			return latlng.Longitude + ',' + latlng.Latitude + ',0.000000';
		});
		return '<Polygon xmlns="http://earth.google.com/kml/2.2"><outerBoundaryIs><LinearRing><tessellate>1</tessellate><coordinates>' + kml.join(' ') + '</coordinates></LinearRing></outerBoundaryIs></Polygon>';
	}
});

