HYSMaps.GeoCoords = function (latitude, longitude) {
	
	this.latitude = latitude;
	this.longitude = longitude;
};

HYSMaps.GeoCoords.prototype.calculateVectorDistance = function (coords, wrapLatitude, wrapLongitude) {
	
	var result = {};
	
	for (var i = 0; i < 2; ++i) {
		
		var direction = i ? 'latitude' : 'longitude';
		var length = i ? 'y' : 'x';
		var noWrap = i ? wrapLatitude === false : wrapLongitude === false;
		var distance1 = Math.abs(this[direction] - coords[direction]);
		var distance2 = Math.abs(distance1 - 360);
		result[length] = (distance1 < distance2 || noWrap) ? distance1 : distance2;
	}
	
	return result;
};

HYSMaps.GeoCoords.prototype.flip = function () {
	
	return new HYSMaps.GeoCoords(Math.abs(this.latitude - 360), Math.abs(this.longitude - 360));
};

HYSMaps.GeoCoords.prototype.toString = function () {
	
	return this.latitude + ',' + this.longitude;
};


HYSMaps.ScreenCoords = function (x, y) {
	
	this.x = x;
	this.y = y;
};


HYSMaps.Dimensions = function (width, height) {
	
	this.width = width;
	this.height = height;
};


HYSMaps.Bounds = function (nw, se) {
	
	this.nw = nw;
	this.se = se
};

HYSMaps.Bounds.prototype.contains = function (coords, extent) {
	
	var latitudinallyBounded = true;
	var longitudinallyBounded = true;
	
	if (extent == undefined || extent == 'latitude') {
		
		// check if point is south of north boundary
		var isSoutherly = (coords.latitude <= this.nw.latitude);
		// check if point is north of south boundary 
		var isNortherly = (coords.latitude >= this.se.latitude);
		// check if point is within latitude bounds (flipping logic if bounds wrap the equator)
		latitudinallyBounded = this.nw.latitude < this.se.latitude ? isSoutherly || isNortherly : isSoutherly && isNortherly;
	}
	if (extent == undefined || extent == 'longitude') {
		
		// check if point is east of west boundary
		var isEasterly = (coords.longitude >= this.nw.longitude);
		// check if point is west of east boundary
		var isWesterly = (coords.longitude <= this.se.longitude);
		// check if point is within longitude bounds (flipping logic if bounds wrap the prime meridian)
		longitudinallyBounded = this.nw.longitude > this.se.longitude ? isEasterly || isWesterly : isEasterly && isWesterly;
	}
	
	return latitudinallyBounded && longitudinallyBounded;
};

HYSMaps.Bounds.prototype.extend = function (coords) {
	
	if (!this.nw) {
		this.nw = new HYSMaps.GeoCoords(coords.latitude, coords.longitude);
		return;
	}
	
	if (!this.se) {
		this.se = new HYSMaps.GeoCoords(coords.latitude, coords.longitude);
		return;
	}
	
	var latitudinallyBounded = this.contains(coords, 'latitude');
	var longitudinallyBounded = this.contains(coords, 'longitude');
	if (latitudinallyBounded && longitudinallyBounded) {
		return;
	}
	
	var nwExtend = this.nw.calculateVectorDistance(coords);
	var seExtend = this.se.calculateVectorDistance(coords);
	
	// latitude
	if (!latitudinallyBounded) {
		var target = nwExtend.y < seExtend.y ? this.nw : this.se;
		target.latitude = coords.latitude;
	}
	
	// longitude
	if (!longitudinallyBounded) {
		var target = nwExtend.x < seExtend.x ? this.nw : this.se;
		target.longitude = coords.longitude;
	}
};

HYSMaps.Bounds.prototype.get = function () {
	
	if (!this.nw) return false;
	if (!this.se) return { nw: this.nw, se: this.nw };
	else return { nw: this.nw, se: this.se };
};


HYSMaps.Series = function () {
	
	this.active = true; // toggle on/off
	this.points = []; // the point objects
};

HYSMaps.Series.prototype.addPoint = function (point) {
	
	this.points.push(point);
};

HYSMaps.Series.prototype.initialise = function (name, image, grouped) { // to be called after adding points
	
	if (!name || !image || !this.points.length) return false; // validate
	
	this.name = name; // display name
	this.grouped = grouped; // grouping technique flag
	this.icon = new HYSMaps.Icon(image); //  series icon
	return true;
};

HYSMaps.Series.prototype.initMap = function (map) {
	
	map.addControl(this, this.name, this.icon);
};

HYSMaps.Series.prototype.updatePoints = function (map) {
	
	for (var i = 0, c = this.points.length; i < c; ++i) {
		this.points[i].plot(map, this.icon, this.active, this.grouped);
	}
};

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


HYSMaps.TypedSeries = function () {
	
	HYSMaps.Series.call(this);
	this.types = {}; // array of point types
	this.filtering = true;
};
HYSMaps.TypedSeries.inherits(HYSMaps.Series);

HYSMaps.TypedSeries.prototype.addPoint = function (point, type) {
	
	HYSMaps.Series.prototype.addPoint.call(this, point);
	
	if (type && !(type in this.types)) {
		this.types[type] = new HYSMaps.Type(type);
	}
};

HYSMaps.TypedSeries.prototype.initMap = function (map) {
	
	HYSMaps.Series.prototype.initMap.call(this, map);
	HYSMaps.TypeIcon.colourIndex = 0;
	
	map.addDateFilter(this, HYSMaps.filtering.cutoff, '', '', true);
	map.addDateFilter(this, HYSMaps.filtering.maximum, "May slow your computer down");
	
	var indices = [];
	for (var i in this.types) {
		indices.push(i);
	}
	indices.sort();
	for (var i = 0; i < indices.length; ++i) {
		this.types[indices[i]].icon = new HYSMaps.TypeIcon();
		map.addType(this, indices[i], this.types[indices[i]].icon);
	}
};

HYSMaps.TypedSeries.prototype.toggleType = function (type) {
	
	this.types[type].active = !this.types[type].active;
};

HYSMaps.TypedSeries.prototype.toggleFiltering = function (limit) {
	
	this.filtering = limit;
};

HYSMaps.TypedSeries.prototype.updatePoints = function (map) {
	
	var filter = {};
	for (var i in this.types) {
		if (this.types[i].active) filter[i] = this.types[i];
	}
	
	for (var i = 0; i < this.points.length; ++i) {

		if (this.filtering && i > this.filtering) break;
		this.points[i].plot(map, this.icon, this.active, this.grouped, filter);
	}
};


HYSMaps.Type = function (name) {
	
	this.name = name;
	this.active = true;
	
	this.icon = new HYSMaps.TypeIcon();
};


HYSMaps.Point = function () {};

HYSMaps.Point.prototype.initialise = function (title, content, coords, type) {
	
	this.title = title;
	this.content = content;
	this.type = type; // TODO: validate type
	this.coords = coords;
	
	if (type == 'null') return false;
	
	return !(isNaN(this.coords.longitude) || isNaN(this.coords.latitude)); // validate
};

HYSMaps.Point.prototype.plot = function (map, icon, active, grouped, filter) {
	
	if (active && HYSMaps.isEmpty(filter)) {
		map.addPoint(this, this.coords, icon, this.date, grouped);
	}
	else if (active && !HYSMaps.isEmpty(filter) && this.type in filter) {
		map.addPoint(this, this.coords, filter[this.type].icon, this.date, grouped);
	}
};


HYSMaps.CommentPoint = function () {};
HYSMaps.CommentPoint.inherits(HYSMaps.Point);

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

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


HYSMaps.AudioPoint = function () {};
HYSMaps.AudioPoint.inherits(HYSMaps.Point);

HYSMaps.AudioPoint.prototype.initialise = function (title, content, coords, audio) {
	
	this.audio = audio;
	
	return HYSMaps.Point.prototype.initialise.call(this, title, content, coords);
};

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


HYSMaps.ReportPoint = function () {};
HYSMaps.ReportPoint.inherits(HYSMaps.Point);

HYSMaps.ReportPoint.prototype.initialise = function (title, content, coords, image, link) {
	
	this.image = image;
	this.link = link;
	
	return HYSMaps.Point.prototype.initialise.call(this, title, content, coords);
};

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


HYSMaps.VideoPoint = function () {};
HYSMaps.VideoPoint.inherits(HYSMaps.Point);

HYSMaps.VideoPoint.prototype.initialise = function (title, content, coords, video, image) {
	
	this.video = video;
	this.image = image;
	
	return HYSMaps.Point.prototype.initialise.call(this, title, content, coords);
};

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 += '<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">';
		html += '<object id="bbc_emp_fmtj_embed_obj" width="512" height="106" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000">';
		html += '<param value="http://news.bbc.co.uk/player/emp/2_6_5222/player.swf" name="movie"/>';
		html += '<param value="default" name="wmode"/>';
		html += '<param value="true" name="allowFullScreen"/>';
		html += '<param value="embeddedPlayer" 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.audio + '&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="106" flashvars="config=http://news.bbc.co.uk/player/emp/config/default.xml&companionSize=300x30&companionType=adi&preroll=&config_settings_autoPlay=false&playlist=' + data.audio + '&config_plugin_fmtjLiveStats_pageType=eav2&embedReferer=&config_plugin_fmtjLiveStats_edition=Domestic&embedPageUrl=/1/hi/world/americas/7351742.stm&" name="embeddedPlayer" allowfullscreen="true" wmode="default" src="http://news.bbc.co.uk/player/emp/2_6_5222/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';
		
	};
};


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">';
		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" 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=' + data.video + '&config_plugin_fmtjLiveStats_pageType=eav2&embedReferer=&config_plugin_fmtjLiveStats_edition=Domestic&embedPageUrl=/1/hi/world/americas/7351742.stm&" name="embeddedPlayer" 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';

	};
	
	
};


HYSMaps.Icon = function (image, inputIcon) {
	
	this.image = (inputIcon) ? image : HYSMaps.assets + image;
	this.imageSize = { x:25, y:30 };
	this.shadow = HYSMaps.assets + 'icon-shadow.gif';
	this.shadowSize = { x:36, y:31 };
	this.iconAnchor = { x:12, y:30 };
};


HYSMaps.ClusterIcon = function () {
	
	this.image = HYSMaps.assets + 'icon-cluster.gif';
	this.imageSize = { x:39, y:34 };
	this.shadow = HYSMaps.assets + 'icon-shadow.gif';
	this.shadowSize = { x:39, y:34 };
	this.iconAnchor = { x:19, y:34 };
};


HYSMaps.TypeIcon = function () {
	
	var image = 'icon-type.' + this.getColour() + '.gif';
	HYSMaps.Icon.call(this, image);
	
	this.imageSize = { x:25, y:30 };
	this.iconAnchor = { x:12.5, y:30 };
};
HYSMaps.TypeIcon.inherits(HYSMaps.Icon);

HYSMaps.TypeIcon.prototype.getColour = function () {
	
	if (++HYSMaps.TypeIcon.colourIndex >= HYSMaps.TypeIcon.colours.length) HYSMaps.TypeIcon.colourIndex = 1;
	return HYSMaps.TypeIcon.colours[HYSMaps.TypeIcon.colourIndex - 1];
};

HYSMaps.TypeIcon.colours = ['green', 'gold', 'blue', 'red', 'purple', 'turquoise', 'yellow', 'mint', 'grey', 'black'];
HYSMaps.TypeIcon.colourIndex = 0;