var HYSMaps = {};

HYSMaps.MapHandler = function (element) {
	
	// check for Google Maps compatibility
	if (GBrowserIsCompatible()) {
		
		var map = document.createElement('div');
		map.style.width = '100%';
		map.style.height = '100%';
		
		var wrapper = document.createElement('div');
		wrapper.className = 'map-container';
		wrapper.appendChild(map);
		
		this.element = element;
		this.element.appendChild(wrapper);
		
		// create Google Map
		this.gMap = new GMap2(map);
		this.gMap.addControl(new GSmallMapControl(), new GControlPosition(G_ANCHOR_BOTTOM_LEFT, new GSize(5, 40)));
		
		// get array of map types 
		var mapTypes = this.gMap.getMapTypes();
		// overwrite the resolution methods for each map type 
		for (var i=0; i<mapTypes.length; i++) { 
			mapTypes[i].getMinimumResolution = HYSMaps.MapHandler.prototype.minZoom;
			mapTypes[i].getMaximumResolution = HYSMaps.MapHandler.prototype.maxZoom;
		}
	
		// add unload handler to clean up objects
		window.onunload = this.unload.bind(this, window.onunload);
	}
};

HYSMaps.MapHandler.prototype.minZoom = function () { return 1; }; // minimum Google Maps zoom level
HYSMaps.MapHandler.prototype.maxZoom = function () { return 12; }; // maximum Google Maps zoom level

HYSMaps.MapHandler.prototype.input = function (coords, location, clear) {
	
	// create an input map for locating a use
	this.map = new HYSMaps.InputMap(this.gMap, coords, location, clear);
	this.map.initialise();
};

HYSMaps.MapHandler.prototype.output = function (input, title, assets) {
	
	// create an output map for displaying generated content
	HYSMaps.assets = assets || '';
	this.map = new HYSMaps.OutputMap(this.gMap, input, this.element, title, HYSMaps.assets);
	this.map.initialise();
	return this.map;
};

HYSMaps.MapHandler.prototype.unload = function (chain) {
	
	GUnload(); // unload Google Maps
	if (this.map) this.map.unload();
	if (chain) chain(); // call any existing unload handlers
};


HYSMaps.InputMap = function (map, coords, location, clear) {
	
	this.map = map; // Google Map
	this.coords = coords; // HTML form element to contain user given coordinates
	this.location = location; // HTML form element to contain user given locality
	this.clear = clear; // HTML form button to clear location information
	
	this.geo = new GReverseGeocoder(this.map); // class to lookup locality from lat / long
};

HYSMaps.InputMap.prototype.initialise = function () {
	
	// defaults
	var lat = 51.5001;
	var lon = -0.1262;
	var zoom = 2;
	
	// if geo-location code has been included, use the data
	if (typeof(geoData) != 'undefined' && geoData.lat && geoData.lon) {
		
		var lat = geoData.lat;
		var lon = geoData.lon;
		var zoom = HYSMaps.MapHandler.prototype.maxZoom();
	}
	
	this.map.setCenter(new GLatLng(lat, lon), zoom); // set the centre of the map
	
	// location alert (DP)
	this.message = document.createElement('span');
	this.message.className = 'location-alert';
	this.location.parentNode.insertBefore(this.message, this.location.nextSibling);
	
	// bind event handlers
	this.mapBinding = GEvent.bind(this.map, 'click', this, this.onAddMarker); // bind map click
	GEvent.bind(this.geo, 'load', this, this.onGeocode); // bind reverse geocode load
	this.clear.onclick = this.onClear.bind(this); // bind clear button click
};

HYSMaps.InputMap.prototype.onAddMarker = function (overlay, point) {

	// create marker and add to map
	this.marker = new GMarker(point, {draggable: true});
	this.markerBinding = GEvent.bind(this.marker, 'dragend', this, this.onPoint); // bind marker drag
	this.map.addOverlay(this.marker);
	
	GEvent.removeListener(this.mapBinding); // unbind map click
	
	this.onPoint(); // calculate and output location
};

HYSMaps.InputMap.prototype.onPoint = function () { // called when marker is created or drag/dropped
	
	// output coordinates
	var point = this.marker.getLatLng();
	this.coords.value = point.lng().toFixed(4) + ',' + point.lat().toFixed(4);
	
	// do reverse geocode lookup for location
	this.geo.reverseGeocode(point);
};

HYSMaps.InputMap.prototype.onClear = function () { // called when user clicks clear button
	
	if (this.marker) {
		this.map.removeOverlay(this.marker);
		this.mapBinding = GEvent.bind(this.map, 'click', this, this.onAddMarker); // bind map click
		GEvent.removeListener(this.markerBinding);
		this.marker = null;
	}
	this.location.value = '';
	this.message.innerHTML = '';
};

HYSMaps.InputMap.prototype.onGeocode = function (placemark) { // called when geocode data is received
	
	// output location
	var locality = this.geo.getPlacemarkProperty(placemark, 'LocalityName');
	var subadminarea = this.geo.getPlacemarkProperty(placemark, 'SubAdministrativeAreaName');
	this.location.value = locality ? locality : subadminarea ? subadminarea : '';
	this.message.innerHTML = 'Not what you expected? Type your location here.';
};

HYSMaps.InputMap.prototype.unload = function () {
	
	this.clear.onclick(Function.prototype.unbind); // prevent memory leaks
};


HYSMaps.OutputMap = function (map, input, element, title, assets) {
	
	this.map = map; // Google map
	this.input = input; // path to XML data
	this.series = new Array(); // to contain data series
	this.clicks = new Array(); // to contain click event bindings
	this.controls = new Array(); // to contain control elements
	this.types = new Array(); // to contain point type elements
	this.filters = new Object(); // to contain types to filter by
	this.zooms = new Array(); // to contant zoom events
	this.icons = new Array(); // to contain GIcons
	this.colourIndex = 0; // stores how far through colour array
	this.assets = assets;
	
	// DOM setup	
	var controls = document.createElement('ul');
	controls.className = 'controls';
	element.insertBefore(controls, element.firstChild);
	
	var types = document.createElement('ul');
	types.className = 'types';
	
	var typeWrapper = document.createElement('div');
	typeWrapper.className = 'types';
	typeWrapper.appendChild(types);
	element.appendChild(typeWrapper);
	
	var overlayClose = document.createElement('a');
	overlayClose.setAttribute('href', '#');
	overlayClose.className = 'close';
	var text = document.createTextNode('Close');
	overlayClose.appendChild(text);
	
	var overlayContent = document.createElement('div');
	overlayContent.className = 'content';
	
	var overlayBg = document.createElement('div');
	overlayBg.className = 'bg';
	
	var bugFix1 = document.createElement('div');
	bugFix1.appendChild(overlayContent);
	
	var bugFix2 = document.createElement('div');
	bugFix2.appendChild(overlayBg);
	
	var overlay = document.createElement('div');
	var clickEvent = new HYSMaps.CloseClick(overlay);
	overlayClose.onclick = clickEvent.action;
	overlay.setAttribute('id', 'map-overlay');
	overlay.appendChild(overlayClose);
	overlay.appendChild(bugFix1);
	overlay.appendChild(bugFix2);
	
	var overlayWrapper = element.getElementsByTagName('div')[0];
	overlayWrapper.insertBefore(overlay, overlayWrapper.firstChild);	
	
	if (title) {
		var header = document.createElement('h2');
		header.className = 'header';
		var text = document.createTextNode(title);
		header.appendChild(text);
		element.insertBefore(header, element.firstChild);
	}
	
	this.dom = {		
		controls: controls,
		types: types,
		overlay: overlay,
		overlayClose: overlayClose,
		overlayWrapper: overlayWrapper,
		content: overlayContent
	};
};

HYSMaps.OutputMap.prototype.initialise = function () {
	
	// XML data parser
	var parser = new HYSMaps.InputParser;
	parser.parse(this.input, this, this.onData);
};

HYSMaps.OutputMap.prototype.onData = function (data) {
	
	this.series = data;
	
	for (var i = 0; i < this.series.length; ++i) {
		this.series[i].initMap(this); // initialise series specific functionality
	}
	
	this.setControls() // initialise the series / types controls
	this.setMap(); // initialise the Google map
	this.initPoints();
};

HYSMaps.OutputMap.prototype.initPoints = function (series) {
	
	for (var i = 0; i < this.series.length; ++i) {
		if (series == undefined || series == this.series[i]) {
			this.series[i].updatePoints(this, this.filters); // add points to map
		}
	}
};

HYSMaps.OutputMap.prototype.toggleSeries = function (series) {
	
	series.toggle(this);
	this.initPoints(series);
};

HYSMaps.OutputMap.prototype.toggleFilter = function (type) {
	
	if (this.filters[type] == 'filter') {
		delete this.filters[type];
	} else {
		this.filters[type] = 'filter';
	}
	this.initPoints();
};

HYSMaps.OutputMap.prototype.setControls = function () {
	
	// setup series controls
	for (var i = 0; i < this.controls.length; ++i) {
		this.dom.controls.appendChild(this.controls[i]);
	}
	
	// setup type controls
	/*for (i in this.types) {
		this.dom.types.appendChild(this.types[i]);
	}*/
	// POLITICAL PARTY EDIT:
	
	if (this.types['Conservative: Johnson'] != undefined) this.dom.types.appendChild(this.types['Conservative: Johnson']);
	if (this.types['Labour: Livingstone'] != undefined) this.dom.types.appendChild(this.types['Labour: Livingstone']);
	if (this.types['Lib Dem: Paddick'] != undefined) this.dom.types.appendChild(this.types['Lib Dem: Paddick']);
	if (this.types['Other'] != undefined) this.dom.types.appendChild(this.types['Other']);
	
	// cluster key
	var span = document.createElement('span');
	var text = document.createTextNode('Cluster of comments');
	span.appendChild(text);

	var label = document.createElement('label');
	label.appendChild(span);
	label.className = 'cluster';
	label.style.backgroundImage = 'url("' + this.assets + 'icon-cluster.gif")';

	var control = document.createElement('li');
	control.appendChild(label);
	
	this.dom.types.appendChild(control);
};

HYSMaps.OutputMap.prototype.setMap = function () {
	
	// defaults
	var lat = 51.5001;
	var lon = -0.1262;
	var zoom = 12;
	
	if (this.series.length) {
	
		// find total lengths and midpoints
		var lon = this.xMin + (this.xMax - this.xMin) / 2;
		var lat = this.yMin + (this.yMax - this.yMin) / 2;
		var xLen = Math.abs((this.xMax - this.xMin) + 0.0000001); // prevent divide-by-zero error
		var yLen = Math.abs((this.yMax - this.yMin) + 0.0000001);
		
		// find zoom level
		var size = this.map.getSize();
		var xZoom = Math.log(360 / xLen * size.width / 256) / Math.log(2);
	 	var yZoom = Math.log(200 / yLen * size.height / 256) / Math.log(2);
		var zoom = Math.floor(Math.min(xZoom, yZoom));
		if (zoom > HYSMaps.MapHandler.prototype.maxZoom()) zoom = HYSMaps.MapHandler.prototype.maxZoom();
		if (zoom < HYSMaps.MapHandler.prototype.minZoom()) zoom = HYSMaps.MapHandler.prototype.minZoom();
	}
	
	// set GMap centre
   	this.map.setCenter(new GLatLng(lat, lon), zoom);
};

HYSMaps.OutputMap.prototype.updateLimits = function (xMin, xMax, yMin, yMax) {
	
	// get max and min markers
	if (this.xMin == undefined || xMin < this.xMin) this.xMin = xMin;
	if (this.xMax == undefined || xMax > this.xMax) this.xMax = xMax;
	if (this.yMin == undefined || yMin < this.yMin) this.yMin = yMin;
	if (this.yMax == undefined || yMax > this.yMax) this.yMax = yMax;
};

HYSMaps.OutputMap.prototype.addControls = function (series, name, types, icon) {
	
	// series control
	var input = document.createElement('input');
	input.setAttribute('type', 'checkbox');
	input.setAttribute('checked', 'checked');
	input.setAttribute('defaultChecked', 'defaultChecked');
	input.onclick = this.toggleSeries.bind(this, series);
	
	var label = document.createElement('label');
	var text = document.createTextNode(name);
	label.appendChild(text);
	label.appendChild(input);

	var control = document.createElement('li');
	control.appendChild(label);
	
	this.controls.push(control);
	
	var gIcon = null;
		
	// if a series has no icon, assume it has 'types' and use custom colours
	if (icon == '') {
		
		// types controls
		for (type in types) {
		
			if (this.types[type] == undefined) {		
				
				// POLITICAL PARTY EDIT:
				switch (type) {
					
					case 'Conservative: Johnson':
						this.colourIndex = 2;
						break;
					case 'Labour: Livingstone':
						this.colourIndex = 3;
						break;
					case 'Lib Dem: Paddick':
						this.colourIndex = 1;
						break;
					case 'Other':
						this.colourIndex = 8;
						break;
					default:
						this.colourIndex = 4;
				}
				
				gIcon = this.createCustomIconColour(this.colourIndex);				
				this.icons[type] = gIcon;
				// POLITICAL PARTY EDIT: this.colourIndex++
						
				var span = document.createElement('span');
				var text = document.createTextNode(type);
				span.appendChild(text);
			
				var input = document.createElement('input');
				input.setAttribute('type', 'checkbox');
				input.onclick = this.toggleFilter.bind(this, type);
			
				var label = document.createElement('label');
				label.appendChild(span);
				label.appendChild(input);
				input.setAttribute('checked', 'checked');
				input.setAttribute('defaultChecked', 'defaultChecked');
				label.style.backgroundImage = 'url(' + gIcon.image + ')';
			
				var control = document.createElement('li');
				control.appendChild(label);
			
				this.types[type] = control;			
				this.filters[type] = 'filter';
			}
		}
	
	// otherwise assume it is reports/audio/video etc and use custom icon image
	} else {
		
		// if an assets folder is defined
		if (this.assets != '') {
								
			gIcon = new GIcon();
			gIcon.image = this.assets + icon;
			gIcon.iconSize = new GSize(20, 34);
			gIcon.shadow = this.assets + 'icon-shadow.png';
			gIcon.shadowSize = new GSize(36, 31);		
			gIcon.infoShadowAnchor = new GPoint(0, 0);		
			gIcon.iconAnchor = new GPoint(15, 15);
		
		// if an assets folder is not defined, use next colour in array
		} else {
			
			gIcon = this.createCustomIconColour(this.colourIndex); 
			this.colourIndex++;			
		}

		series.icon = gIcon;
	}
	
};

HYSMaps.OutputMap.prototype.createCustomIconColour = function(i) {
	return HYSMaps.MapIconMaker.createMarkerIcon({
		width: 25,
		height: 30,
		primaryColor: HYSMaps.MapIconMaker.colourPrimary[this.colourIndex],
		strokeColor: HYSMaps.MapIconMaker.colourStroke[this.colourIndex]
	});
};

HYSMaps.OutputMap.prototype.addPoint = function (point, coords, type, icon) {

	var gIcon = null;
	// if point belongs to a 'type' use that type's icon colour
	if (type) {
		gIcon = this.icons[type]
	}
	// otherwise use the series' icon
	else {
		gIcon = icon;
	}
	
	var markerOptions = { icon: gIcon, clickable: true };
	var marker = new GMarker(new GLatLng(coords[1], coords[0]), markerOptions);
	//this.map.addOverlay(marker);
	
	var clickEvent = point.makeClickEvent(this.dom.overlay, this.dom.content);
	marker.clickEvent = GEvent.bind(marker, 'click', clickEvent, clickEvent.action);
	
	return marker;
};

HYSMaps.OutputMap.prototype.removePoints = function (markers) {
		
	for (var i = 0, count = markers.length; i < count; ++i) {
		
		GEvent.removeListener(markers[i].clickEvent);
	}	
};

HYSMaps.OutputMap.prototype.addZoomLevel = function(name, zoom, lat, lng) {
	
	var mapZoomUl = this.dom.mapZoomUl;
	
	if (mapZoomUl == undefined) {
		var mapZoom = document.createElement('div');
		mapZoom.className = 'map-zoom';	
	
		var mapZoomHeader = document.createElement('h3');
		mapZoomHeader.appendChild(document.createTextNode('Zoom level'));
		mapZoom.appendChild(mapZoomHeader);
	
		mapZoomUl = document.createElement('ul');
		mapZoom.appendChild(mapZoomUl);
	
		this.dom.overlayWrapper.insertBefore(mapZoom, this.dom.overlayWrapper.firstChild);
		this.dom.mapZoomUl = mapZoomUl;
	}
	
	// create a zoom level link
	var zoomLevel = document.createElement('li');
	var zoomLevelLink = document.createElement('a');
	zoomLevelLink.setAttribute('href', '#');
	zoomLevelLink.appendChild(document.createTextNode(name));
	
	zoomLevelLink.onclick = this.onZoomLevel.bind(this, zoom, lat, lng);
	this.zooms.push(zoomLevelLink);
	
	zoomLevel.appendChild(zoomLevelLink);
	mapZoomUl.appendChild(zoomLevel);
}

HYSMaps.OutputMap.prototype.onZoomLevel = function (zoom, lat, lng) {
	
	this.map.setCenter(new GLatLng(lat, lng), zoom);
	return false;
};

HYSMaps.OutputMap.prototype.unload = function () {
	
	for (var i = 0; i < this.controls.length; ++i) {
		var input = this.controls[i].getElementsByTagName('input')[0];
		input.onclick(Function.prototype.unbind);
	}
	for (i in this.types) {
		var input = this.types[i].getElementsByTagName('input')[0];
		input.onclick(Function.prototype.unbind);
	}
	for (var i = 0; i < this.zooms.length; ++i) {
		this.zooms[i].onclick(Function.prototype.unbind);
	}
	this.dom.overlayClose.onclick = null;
};


HYSMaps.InputParser = function () {};

HYSMaps.InputParser.prototype.parse = function (url, context, callback) {
	
	// do ajax request to post back address
	this.ajax = new XMLHttpRequest();
	this.ajax.open('GET', url, true);
	this.ajax.onreadystatechange = this.onLoad.bind(this, context, callback);
	this.ajax.send(null);
	
	return new Array;
};

HYSMaps.InputParser.prototype.onLoad = function (context, callback) {
	
	if (this.ajax.readyState == 4) {
		
		var data = new Array();
		
		var comments = new HYSMaps.Series('Comments', '');
		var audio = new HYSMaps.Series('Audio', 'icon-audio.png');
		var reports = new HYSMaps.Series('Reports', 'icon-report.png');
		var video = new HYSMaps.Series('Video', 'icon-video.png');
		
		var xml = this.ajax.responseXML.getElementsByTagName('entry');
		for (var i = 0, count = xml.length; i < count; ++i) {
			
			switch (xml[i].parentNode.nodeName) {
				
				case 'comments':
					
					var date = this.getText(xml[i].getElementsByTagName('date')[0]);
					var name = this.getText(xml[i].getElementsByTagName('name')[0]);
					var content = this.getText(xml[i].getElementsByTagName('content')[0]);
					var coords = this.getText(xml[i].getElementsByTagName('coords')[0]);
					var type = this.getText(xml[i].getElementsByTagName('type')[0]);
					var link = this.getText(xml[i].getElementsByTagName('link')[0]);
					var alert = this.getText(xml[i].getElementsByTagName('alert')[0]);
				
					if (type != 'null' && type != undefined && coords != undefined) {
						var point = new HYSMaps.CommentPoint(date, name, content, coords, type, link, alert);
						comments.addPoint(point, type);
					}
					
					break;
				case 'audio':
				
					var title = this.getText(xml[i].getElementsByTagName('title')[0]);
					var content = this.getText(xml[i].getElementsByTagName('content')[0]);
					var audioLink = this.getText(xml[i].getElementsByTagName('audio-link')[0]);
					var coords = this.getText(xml[i].getElementsByTagName('geo-coordinates')[0]);
					
					var point = new HYSMaps.AudioPoint(title, content, coords, null, audioLink);
					audio.addPoint(point, null);
					
					break;
				case 'reports':
				
					var title = this.getText(xml[i].getElementsByTagName('title')[0]);
					var content = this.getText(xml[i].getElementsByTagName('content')[0]);
					var imageLink = this.getText(xml[i].getElementsByTagName('image-link')[0]);
					var link = this.getText(xml[i].getElementsByTagName('more-info-link')[0]);
					var coords = this.getText(xml[i].getElementsByTagName('geo-coordinates')[0]);
					
					var point = new HYSMaps.ReportPoint(title, content, coords, null, imageLink, link);
					reports.addPoint(point, null);
					
					break;
				case 'video':
				
					var title = this.getText(xml[i].getElementsByTagName('title')[0]);
					var content = this.getText(xml[i].getElementsByTagName('content')[0]);
					var playlistLink = this.getText(xml[i].getElementsByTagName('playlist-xml-link')[0]);
					var imageLink = this.getText(xml[i].getElementsByTagName('holding-image-link')[0]);
					var coords = this.getText(xml[i].getElementsByTagName('geo-coordinates')[0]);
					
					var point = new HYSMaps.VideoPoint(title, content, coords, null, playlistLink, imageLink);
					video.addPoint(point, null);
					
					break;
			}
		}
		
		if (comments.length) data.push(comments);
		if (reports.length) data.push(reports);
		if (video.length) data.push(video);
		if (audio.length) data.push(audio);
		
		callback.call(context, data);
	}
};

HYSMaps.InputParser.prototype.getText = function (element) {
	
	if (element == undefined) {
		return;
	}
	if (element.nodeType != 3 && element.childNodes.length) {
		return this.getText(element.firstChild);
	}
	else if (element.nodeType == 3) {
		return element.nodeValue;
	}
};


(function () {
	
// bind event handlers with object context
Function.prototype.bind = function (object) {
	
	var method = this;
	var oldArguments = toArray(arguments).slice(1);
	return function (argument) {
		if (argument == Function.prototype.unbind) {
			method = null;
			oldArguments = null;
		} else if (method) {
			var newArguments = toArray(arguments);
			return method.apply(object, oldArguments.concat(newArguments));
		}
	};
};

Function.prototype.unbind = new Object;

// add inheritance
Function.prototype.inherits = function (superclass) { 
	
	var x = function () {}; 
	x.prototype = superclass.prototype; 
	this.prototype = new x(); 
};

function toArray(pseudoArray) {
	
	var result = [];
	for (var i = 0; i < pseudoArray.length; i++) {
		result.push(pseudoArray[i]);
	}
	return result;
};

})();


HYSMaps.Series = function (name, icon) {
	
	this.name = name; // display name
	this.points = new Array(); // the point objects
	this.types = new Object(); // array of point types
	this.markers = new Array(); // array of GMarkers
	this.length = 0; // number of points
	this.active = true; // toggle on/off
	this.icon = icon;
	
	this.clusterIcon = new GIcon();
	this.clusterIcon.image = HYSMaps.assets + 'icon-cluster.png';
	this.clusterIcon.iconSize = new GSize(39, 34);
	this.clusterIcon.iconAnchor = new GPoint(9, 31);
	this.clusterIcon.infoWindowAnchor = new GPoint(9, 31);
	this.clusterIcon.shadow = 'http://www.google.com/intl/en_us/mapfiles/arrowshadow.png';
	this.clusterIcon.shadowSize = new GSize(39, 34);
};

HYSMaps.Series.prototype.addPoint = function (point, type) {
	
	this.points.push(point);
	if (type != 'null') this.types[type] = '';
	this.length++;
	
	// get max and min points
	if (this.xMin == undefined || point.x < this.xMin) this.xMin = point.x;
	if (this.xMax == undefined || point.x > this.xMax) this.xMax = point.x;
	if (this.yMin == undefined || point.y < this.yMin) this.yMin = point.y;
	if (this.yMax == undefined || point.y > this.yMax) this.yMax = point.y;	
};

HYSMaps.Series.prototype.initMap = function (map) {
	
	map.updateLimits(this.xMin, this.xMax, this.yMin, this.yMax);
	map.addControls(this, this.name, this.types, this.icon);
};

HYSMaps.Series.prototype.updatePoints = function (map, filters) {
	
	if (this.markers.length != 0) {
		this.cluster.removeMarkers();
		map.removePoints(this.markers);
		this.markers = new Array();
	}
	
	if (this.active) {
		this.cluster = new HYSMaps.ClusterMarker(map.map, {clusterMarkerIcon: this.clusterIcon, intersectPadding: -7});
		for (var i = 0, count = this.points.length; i < count; ++i) {
			var marker = this.points[i].plot(map, filters, this.icon);
			if (marker) {
				this.markers.push(marker);
			}
		}
		this.cluster.removeMarkers();
		this.cluster.addMarkers(this.markers);
		this.cluster.refresh();
	}
};

HYSMaps.Series.prototype.toggle = function (map) {
	
	this.active = ! this.active;
};


HYSMaps.Point = function (title, content, coords, type) {
	
	this.title = title;
	this.content = content;
	this.type = type;
	
	var split = coords.split(',');
	this.x = parseFloat(split[0]);
	this.y = parseFloat(split[1]);
};

HYSMaps.Point.prototype.plot = function (map, filters, icon) {
	
	if (this.type == null || this.type in filters) {
		return map.addPoint(this, [this.x, this.y], this.type, icon);
	}
};


HYSMaps.CommentPoint = function (date, name, content, coords, type, link, alert) {
	
	HYSMaps.Point.call(this, name, content, coords, type);
	
	this.date = date;
	this.link = link;
	this.alert = alert;
};
HYSMaps.CommentPoint.inherits(HYSMaps.Point);

HYSMaps.CommentPoint.prototype.makeClickEvent = function (element, content) {
	
	return new HYSMaps.CommentClick(element, content, this);
};


HYSMaps.AudioPoint = function (title, content, coords, type, audio) {
	
	HYSMaps.Point.call(this, title, content, coords, type);
	this.audio = audio;
};
HYSMaps.AudioPoint.inherits(HYSMaps.Point);

HYSMaps.AudioPoint.prototype.makeClickEvent = function (element, content) {
	
	return new HYSMaps.AudioClick(element, content, this);
};


HYSMaps.ImagePoint = function (title, content, coords, type, image) {
	
	HYSMaps.Point.call(this, title, content, coords, type);
	this.image = image;
};
HYSMaps.ImagePoint.inherits(HYSMaps.Point);

HYSMaps.ImagePoint.prototype.makeClickEvent = function (element, content) {
	
	return new HYSMaps.ImageClick(element, content, this);
};


HYSMaps.ReportPoint = function (title, content, coords, type, image, link) {
	
	HYSMaps.Point.call(this, title, content, coords, type);
	this.image = image;
	this.link = link;
};
HYSMaps.ReportPoint.inherits(HYSMaps.Point);

HYSMaps.ReportPoint.prototype.makeClickEvent = function (element, content) {
	
	return new HYSMaps.ReportClick(element, content, this);
};


HYSMaps.VideoPoint = function (title, content, coords, type, video, image) {
	
	HYSMaps.Point.call(this, title, content, coords, type);
	this.video = video;
	this.image = image;
};
HYSMaps.VideoPoint.inherits(HYSMaps.Point);

HYSMaps.VideoPoint.prototype.makeClickEvent = function (element, content) {
	
	return new HYSMaps.VideoClick(element, content, this);
};


HYSMaps.CloseClick = function (output, content, data) {
	
	this.action = function () {
		output.style.display = 'none';
		return false;
	};
};


HYSMaps.CommentClick = function (output, content, data) {
	
	this.action = function () {
		
		var html = '<h3 class="map-title">User comment</h3>';
		html += '<h2 class="map-user">' + data.title + '</h2>';
		html += '<p class="map-date">' + data.date + '</p>';
		html += '<p class="map-content">' + data.content + '</p>';
		html += '<p class="map-link"><a href="'+data.link+'">More on this discussion</a></p>';
		html += '<p class="map-moderate"><a href="'+data.alert+'">Alert a moderator</a></p>';

		content.innerHTML = html;
		
		output.className = 'type-comment';
		output.style.display = 'block';
	};
};

HYSMaps.AudioClick = function (output, content, data) {
	
	this.action = function () {
	
		var html = '<h3 class="map-title">Audio</h3>';
		html += '<h2 class="map-heading">' + data.title + '</h2>';
		html += '<p class="map-content">' + data.content + '</p>';
		html += '<p class="map-link"><a onclick="javascript:newsi.utils.av.launch({el:this});return false;" href="'+data.audio+'">Listen</a></p>';
		content.innerHTML = html;
		
		output.className = 'type-audio';
		output.style.display = 'block';
		
	};
};


HYSMaps.ReportClick = function (output, content, data) {
	
	this.action = function () {
		
		var html = '<h3 class="map-title">Report</h3>';
		html += '<h2 class="map-heading">' + data.title + '</h2>';
		html += '<div class="map-content-holder">';
		html += '<div class="map-content-report">' + data.content + '</div>';
		if(data.image) {
			html += '<div class="map-content-images">';
			html += '<img width="226" height="170" src="' + data.image + '" alt="' + data.title + '" />';
			html += '</div>';
		}
		html += '</div>';
		if(data.link) {
			html += '<p class="map-link"><a href="'+data.link+'">Full story (you will leave this page)</a></p>';
		}
		content.innerHTML = html;
		
		if(data.image) {
			output.className = 'type-report';
		} else {
			output.className = 'type-report-basic';
		}
		
		output.style.display = 'block';
	};
};

HYSMaps.VideoClick = function (output, content, data) {
	
	this.action = function () {
	
		var html = '<h3 class="map-title">Video</h3>';
		html += '<div class="map-content-holder-video">';
		html += '<div class="map-content-video-text"><h2 class="map-heading">' + data.title + '</h2>' + data.content + '</div>';
		html += '<div class="map-content-video">';
		
		html += '<div id="emp_7351844">';
		html += '<object id="bbc_emp_fmtj_embed_obj" width="512" height="288" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000">';
		html += '<param value="http://news.bbc.co.uk/player/emp/2_2_1773/player.swf" name="movie"/>';
		html += '<param value="default" name="wmode"/>';
		html += '<param value="true" name="allowFullScreen"/>';
		html += '<param value="embeddedPlayer_7351844" name="name"/>';
		html += '<param value="config=http://news.bbc.co.uk/player/emp/config/default.xml&companionSize=300x30&companionType=adi&preroll=&config_settings_autoPlay=false&playlist=' + data.video + '&config_plugin_fmtjLiveStats_pageType=eav2&embedReferer=&config_plugin_fmtjLiveStats_edition=Domestic&embedPageUrl=/1/hi/world/americas/7351742.stm&" name="flashvars"/>';
		html += '<embed id="bbc_emp_fmtj_embed_emb" width="512" height="288" flashvars="config=http://news.bbc.co.uk/player/emp/config/default.xml&companionSize=300x30&companionType=adi&preroll=&config_settings_autoPlay=false&playlist=http%3A%2F%2Fnews.bbc.co.uk%2Fmedia%2Femp%2F7350000%2F7351800%2F7351844.xml&config_plugin_fmtjLiveStats_pageType=eav2&embedReferer=&config_plugin_fmtjLiveStats_edition=Domestic&embedPageUrl=/1/hi/world/americas/7351742.stm&" name="embeddedPlayer_7351844" allowfullscreen="true" wmode="default" src="http://news.bbc.co.uk/player/emp/2_2_1773/player.swf" type="application/x-shockwave-flash"/>';
		html += '</object>';
		html += '</div>';
		
		html += '</div>';
		html += '</div>';
		
		content.innerHTML = html;
		
		output.className = 'type-video';
		output.style.display = 'block';

	};
	
	
};


// create AJAX wrapper for IE
if (typeof XMLHttpRequest == 'undefined') {
	
	XMLHttpRequest = function () {
		
		return new ActiveXObject(
			//IE 5 uses a different XMLHTTP object from IE 6
			navigator.userAgent.indexOf('MSIE 5') >= 0 ? 'Microsoft.XMLHTTP' : 'Msxml2.XMLHTTP'
		);
	};
}

/* MapIconMaker */
/* http://gmaps-utility-library.googlecode.com/svn/trunk/mapiconmaker */
HYSMaps.MapIconMaker = {};
HYSMaps.MapIconMaker.createMarkerIcon=function(a){var b=a.width||32;var c=a.height||32;var d=a.primaryColor||"#ff0000";var e=a.strokeColor||"#000000";var f=a.cornerColor||"#ffffff";var g="http://chart.apis.google.com/chart?cht=mm";var h=g+"&chs="+b+"x"+c+"&chco="+f.replace("#","")+","+d.replace("#","")+","+e.replace("#","")+"&ext=.png";var j=new GIcon(G_DEFAULT_ICON);j.image=h;j.iconSize=new GSize(b,c);j.shadowSize=new GSize(Math.floor(b*1.6),c);j.iconAnchor=new GPoint(b/2,c);j.infoWindowAnchor=new GPoint(b/2,Math.floor(c/12));j.printImage=h+"&chof=gif";j.mozPrintImage=h+"&chf=bg,s,ECECD8"+"&chof=gif";var h=g+"&chs="+b+"x"+c+"&chco="+f.replace("#","")+","+d.replace("#","")+","+e.replace("#","");j.transparent=h+"&chf=a,s,ffffff11&ext=.png";j.imageMap=[b/2,c,(7/16)*b,(5/8)*c,(5/16)*b,(7/16)*c,(7/32)*b,(5/16)*c,(5/16)*b,(1/8)*c,(1/2)*b,0,(11/16)*b,(1/8)*c,(25/32)*b,(5/16)*c,(11/16)*b,(7/16)*c,(9/16)*b,(5/8)*c];for(var i=0;i<j.imageMap.length;i++){j.imageMap[i]=parseInt(j.imageMap[i])}return j}
HYSMaps.MapIconMaker.colourPrimary = ['#229b48','#ffcd34','#32689b','#ee383a','#9a679a','#67c8ca','#f6ef3b','#99cc99','#cdcccc','#333366'];
HYSMaps.MapIconMaker.colourStroke = ['#136734','#e9bb4a','#1b3664','#cd2027','#663366','#329a9a','#cbc32c','#669a66','#a09f9f','#21205f']


/*
	ClusterMarker Version 1.3.0
	
	A marker manager for the Google Maps API
	http://googlemapsapi.martinpearman.co.uk/clustermarker
	
	Copyright Martin Pearman 2008
	Last updated 5th March 2008

	This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

	You should have received a copy of the GNU General Public License along with this program.  If not, see <http://www.gnu.org/licenses/>.
	
*/

HYSMaps.ClusterMarker = function ($map, $options){
	this._map=$map;
	this._mapMarkers=[];
	this._iconBounds=[];
	this._clusterMarkers=[];
	this._eventListeners=[];
	if(typeof($options)==='undefined'){
		$options={};
	}
	this.borderPadding=($options.borderPadding)?$options.borderPadding:256;
	this.clusteringEnabled=($options.clusteringEnabled===false)?false:true;
	if($options.clusterMarkerClick){
		this.clusterMarkerClick=$options.clusterMarkerClick;
	}
	if($options.clusterMarkerIcon){
		this.clusterMarkerIcon=$options.clusterMarkerIcon;
	}else{
		this.clusterMarkerIcon=new GIcon();
		this.clusterMarkerIcon.image='http://maps.google.com/mapfiles/arrow.png';
		this.clusterMarkerIcon.iconSize=new GSize(39, 34);
		this.clusterMarkerIcon.iconAnchor=new GPoint(9, 31);
		this.clusterMarkerIcon.infoWindowAnchor=new GPoint(9, 31);
		this.clusterMarkerIcon.shadow='http://www.google.com/intl/en_us/mapfiles/arrowshadow.png';
		this.clusterMarkerIcon.shadowSize=new GSize(39, 34);
	}
	this.clusterMarkerTitle=($options.clusterMarkerTitle)?$options.clusterMarkerTitle:'Click to zoom in and see %count markers';
	if($options.fitMapMaxZoom){
		this.fitMapMaxZoom=$options.fitMapMaxZoom;
	}
	this.intersectPadding=($options.intersectPadding)?$options.intersectPadding:0;
	if($options.markers){
		this.addMarkers($options.markers);
	}
	GEvent.bind(this._map, 'moveend', this, this._moveEnd);
	GEvent.bind(this._map, 'zoomend', this, this._zoomEnd);
	GEvent.bind(this._map, 'maptypechanged', this, this._mapTypeChanged);
}

HYSMaps.ClusterMarker.prototype.addMarkers=function($markers){
	var i;
	if(!$markers[0]){
		//	assume $markers is an associative array and convert to a numerically indexed array
		var $numArray=[];
		for(i in $markers){
			$numArray.push($markers[i]);
		}
		$markers=$numArray;
	}
	for(i=$markers.length-1; i>=0; i--){
		$markers[i]._isVisible=false;
		$markers[i]._isActive=false;
		$markers[i]._makeVisible=false;
	}
	this._mapMarkers=this._mapMarkers.concat($markers);
};

HYSMaps.ClusterMarker.prototype._clusterMarker=function($clusterGroupIndexes){
	function $newClusterMarker($location, $icon, $title){
		return new GMarker($location, {icon:$icon, title:$title});
	}
	var $clusterGroupBounds=new GLatLngBounds(), i, $clusterMarker, $clusteredMarkers=[], $marker, $this=this;
	for(i=$clusterGroupIndexes.length-1; i>=0; i--){
		$marker=this._mapMarkers[$clusterGroupIndexes[i]];
		$marker.index=$clusterGroupIndexes[i];
		$clusterGroupBounds.extend($marker.getLatLng());
		$clusteredMarkers.push($marker);
	}
	$clusterMarker=$newClusterMarker($clusterGroupBounds.getCenter(), this.clusterMarkerIcon, this.clusterMarkerTitle.replace(/%count/gi, $clusterGroupIndexes.length));
	$clusterMarker.clusterGroupBounds=$clusterGroupBounds;	//	only req'd for default cluster marker click action
	this._eventListeners.push(GEvent.addListener($clusterMarker, 'click', function(){
		$this.clusterMarkerClick({clusterMarker:$clusterMarker, clusteredMarkers:$clusteredMarkers });
	}));
	return $clusterMarker;
};

HYSMaps.ClusterMarker.prototype.clusterMarkerClick=function($args){
	this._map.setCenter($args.clusterMarker.getLatLng(), this._map.getBoundsZoomLevel($args.clusterMarker.clusterGroupBounds));
};

HYSMaps.ClusterMarker.prototype._filterActiveMapMarkers=function(){
	var $borderPadding=this.borderPadding, $mapZoomLevel=this._map.getZoom(), $mapProjection=this._map.getCurrentMapType().getProjection(), $mapPointSw, $activeAreaPointSw, $activeAreaLatLngSw, $mapPointNe, $activeAreaPointNe, $activeAreaLatLngNe, $activeAreaBounds=this._map.getBounds(), i, $marker, $uncachedIconBoundsIndexes=[], $oldState;
	if($borderPadding){
		$mapPointSw=$mapProjection.fromLatLngToPixel($activeAreaBounds.getSouthWest(), $mapZoomLevel);
		$activeAreaPointSw=new GPoint($mapPointSw.x-$borderPadding, $mapPointSw.y+$borderPadding);
		$activeAreaLatLngSw=$mapProjection.fromPixelToLatLng($activeAreaPointSw, $mapZoomLevel);
		$mapPointNe=$mapProjection.fromLatLngToPixel($activeAreaBounds.getNorthEast(), $mapZoomLevel);
		$activeAreaPointNe=new GPoint($mapPointNe.x+$borderPadding, $mapPointNe.y-$borderPadding);
		$activeAreaLatLngNe=$mapProjection.fromPixelToLatLng($activeAreaPointNe, $mapZoomLevel);
		$activeAreaBounds.extend($activeAreaLatLngSw);
		$activeAreaBounds.extend($activeAreaLatLngNe);
	}
	this._activeMarkersChanged=false;
	if(typeof(this._iconBounds[$mapZoomLevel])==='undefined'){
		//	no iconBounds cached for this zoom level
		//	no need to check for existence of individual iconBounds elements
		this._iconBounds[$mapZoomLevel]=[];
		this._activeMarkersChanged=true;	//	force refresh(true) as zoomed to uncached zoom level
		for(i=this._mapMarkers.length-1; i>=0; i--){
			$marker=this._mapMarkers[i];
			$marker._isActive=$activeAreaBounds.containsLatLng($marker.getLatLng())?true:false;
			$marker._makeVisible=$marker._isActive;
			if($marker._isActive){
				$uncachedIconBoundsIndexes.push(i);
			}
		}
	}else{
		//	icondBounds array exists for this zoom level
		//	check for existence of individual iconBounds elements
		for(i=this._mapMarkers.length-1; i>=0; i--){
			$marker=this._mapMarkers[i];
			$oldState=$marker._isActive;
			$marker._isActive=$activeAreaBounds.containsLatLng($marker.getLatLng())?true:false;
			$marker._makeVisible=$marker._isActive;
			if(!this._activeMarkersChanged && $oldState!==$marker._isActive){
				this._activeMarkersChanged=true;
			}
			if($marker._isActive && typeof(this._iconBounds[$mapZoomLevel][i])==='undefined'){
				$uncachedIconBoundsIndexes.push(i);
			}
		}
	}
	return $uncachedIconBoundsIndexes;
};

HYSMaps.ClusterMarker.prototype._filterIntersectingMapMarkers=function(){
	var $clusterGroup, i, j, $mapZoomLevel=this._map.getZoom();
	for(i=this._mapMarkers.length-1; i>0; i--)
	{
		if(this._mapMarkers[i]._makeVisible){
			$clusterGroup=[];
			for(j=i-1; j>=0; j--){
				if(this._mapMarkers[j]._makeVisible && this._iconBounds[$mapZoomLevel][i].intersects(this._iconBounds[$mapZoomLevel][j])){
					$clusterGroup.push(j);
				}
			}
			if($clusterGroup.length!==0){
				$clusterGroup.push(i);
				for(j=$clusterGroup.length-1; j>=0; j--){
					this._mapMarkers[$clusterGroup[j]]._makeVisible=false;
				}
				this._clusterMarkers.push(this._clusterMarker($clusterGroup));
			}
		}
	}
};

HYSMaps.ClusterMarker.prototype.fitMapToMarkers=function(){
	var $markers=this._mapMarkers, $markersBounds=new GLatLngBounds(), i;
	for(i=$markers.length-1; i>=0; i--){
		$markersBounds.extend($markers[i].getLatLng());
	}
	var $fitMapToMarkersZoom=this._map.getBoundsZoomLevel($markersBounds);
		
	if(this.fitMapMaxZoom && $fitMapToMarkersZoom>this.fitMapMaxZoom){
		$fitMapToMarkersZoom=this.fitMapMaxZoom;
	}
	this._map.setCenter($markersBounds.getCenter(), $fitMapToMarkersZoom);
	this.refresh();
};

HYSMaps.ClusterMarker.prototype._mapTypeChanged=function(){
	this.refresh(true);
};

HYSMaps.ClusterMarker.prototype._moveEnd=function(){
	if(!this._cancelMoveEnd){
		this.refresh();
	}else{
		this._cancelMoveEnd=false;
	}
};

HYSMaps.ClusterMarker.prototype._preCacheIconBounds=function($indexes){
	var $mapProjection=this._map.getCurrentMapType().getProjection(), $mapZoomLevel=this._map.getZoom(), i, $marker, $iconSize, $iconAnchorPoint, $iconAnchorPointOffset, $iconBoundsPointSw, $iconBoundsPointNe, $iconBoundsLatLngSw, $iconBoundsLatLngNe, $intersectPadding=this.intersectPadding;
	for(i=$indexes.length-1; i>=0; i--){
		$marker=this._mapMarkers[$indexes[i]];
		$iconSize=$marker.getIcon().iconSize;
		$iconAnchorPoint=$mapProjection.fromLatLngToPixel($marker.getLatLng(), $mapZoomLevel);
		$iconAnchorPointOffset=$marker.getIcon().iconAnchor;
		$iconBoundsPointSw=new GPoint($iconAnchorPoint.x-$iconAnchorPointOffset.x-$intersectPadding, $iconAnchorPoint.y-$iconAnchorPointOffset.y+$iconSize.height+$intersectPadding);
		$iconBoundsPointNe=new GPoint($iconAnchorPoint.x-$iconAnchorPointOffset.x+$iconSize.width+$intersectPadding, $iconAnchorPoint.y-$iconAnchorPointOffset.y-$intersectPadding);
		$iconBoundsLatLngSw=$mapProjection.fromPixelToLatLng($iconBoundsPointSw, $mapZoomLevel);
		$iconBoundsLatLngNe=$mapProjection.fromPixelToLatLng($iconBoundsPointNe, $mapZoomLevel);
		this._iconBounds[$mapZoomLevel][$indexes[i]]=new GLatLngBounds($iconBoundsLatLngSw, $iconBoundsLatLngNe);
	}
};

HYSMaps.ClusterMarker.prototype.refresh=function($forceFullRefresh){
	var i,$marker, $uncachedIconBoundsIndexes=this._filterActiveMapMarkers();
	if(this._activeMarkersChanged || $forceFullRefresh){
		this._removeClusterMarkers();
		if(this.clusteringEnabled && this._map.getZoom()<this._map.getCurrentMapType().getMaximumResolution()){
			if($uncachedIconBoundsIndexes.length>0){
				this._preCacheIconBounds($uncachedIconBoundsIndexes);
			}
			this._filterIntersectingMapMarkers();
		}
		for(i=this._clusterMarkers.length-1; i>=0; i--){
			this._map.addOverlay(this._clusterMarkers[i]);
		}
		for(i=this._mapMarkers.length-1; i>=0; i--){
			$marker=this._mapMarkers[i];
			if(!$marker._isVisible && $marker._makeVisible){
				this._map.addOverlay($marker);
				$marker._isVisible=true;
			}
			if($marker._isVisible && !$marker._makeVisible){
				this._map.removeOverlay($marker);
				$marker._isVisible=false;
			}
		}
	}
};

HYSMaps.ClusterMarker.prototype._removeClusterMarkers=function(){
	for(var i=this._clusterMarkers.length-1; i>=0; i--){
		this._map.removeOverlay(this._clusterMarkers[i]);
	}
	for(i=this._eventListeners.length-1; i>=0; i--){
		GEvent.removeListener(this._eventListeners[i]);
	}
	this._clusterMarkers=[];
	this._eventListeners=[];
};

HYSMaps.ClusterMarker.prototype.removeMarkers=function(){
	for(var i=this._mapMarkers.length-1; i>=0; i--){
		if(this._mapMarkers[i]. _isVisible){
			this._map.removeOverlay(this._mapMarkers[i]);
		}
		delete this._mapMarkers[i]._isVisible;
		delete this._mapMarkers[i]._isActive;
		delete this._mapMarkers[i]._makeVisible;
	}
	this._removeClusterMarkers();
	this._mapMarkers=[];
	this._iconBounds=[];
};


HYSMaps.ClusterMarker.prototype.triggerClick=function($index){
	var $marker=this._mapMarkers[$index];
	if($marker._isVisible){
		//	$marker is visible
		GEvent.trigger($marker, 'click');
	}
	else if($marker._isActive){
		//	$marker is clustered
		this._map.setCenter($marker.getLatLng());
		this._map.zoomIn();
		this.triggerClick($index);
	}else{
		// $marker is not within active area (map bounds + border padding)
		this._map.setCenter($marker.getLatLng());
		this.triggerClick($index);
	}
};

HYSMaps.ClusterMarker.prototype._zoomEnd=function(){
	this._cancelMoveEnd=true;
	this.refresh(true);
};
