function debugMsg(msg) {
	var debug = $("#debug");
	var html = debug.html();
	debug.html(msg + "<br />" + html);
}


/*********************************************************
 *
 * Google maps Javascript called on page load
 *
**********************************************************/
var map_nav_display;
var uiImagePath = '/radio4/worldonthemove/assets/ui/';
var nav_glow_state;
var map_nav_page = 1;
var map_nav_pagemax;
var mi;
var global_sliderTimestamp;
var map_nav_display = false;
var toggleHeight;
var showPrompt = true;
var setSliderToCurrentDate = true;
var sliderInitialPosition = 0;
var earliest_timestamp_cache = 0;

var useMapOverride = true;
var totalMapPoints = 0;

var cache_mapdetails_species_link = "";
var cache_mapdetails_species_report = "";

var initialSpecies;



if (species_array) {
	var num_species = species_array.length - 2;
	initialSpecies = species_array[Math.round(Math.random() * num_species)];
}


$(document).ready(function(){

	if (! GBrowserIsCompatible()) {
		
		return;
	}
	
	// don't animate the very first (page load) since map might not be loaded fully
	var animateInitialSlider = false;
	
	var url = window.location.href;
	if (url.indexOf("/species/") != -1) {
		var urlArray = url.split("/species/");
		var sp = urlArray[1].replace(/\//g, "");
		initialSpecies = sp;
	}
	
	var page = $(document);
	var mapData = $('<div id="map-data"></div>').prependTo('#map');
	var mapVisual = $('<div id="map-visual"></div>').prependTo('#map');
	var mapControls = $('<div id="map-controls"></div>').prependTo('#map');
	var mapDetails = $('#map-details');
	var mapSlider = $('#map-slider');
	var mapNav = $('#map-nav');

	mapData.loadMigrationData = function (species, h, s) {
		mapData.onMigrationData(species, h, s);
		
	}
	
	mapData.onMigrationData = function (species, h, s) {
		
		if (! (this.map instanceof MigrationMap)) {
			this.map = new MigrationMap(mapVisual);
		}
		
		if (this.migration && mapControls) {
			this.migration.clearMap(this.map, mapControls);
		}
		
		mi = new Migration("", "");
		var that = this;
		$.get("/radio4/worldonthemove/mapdata/" + species + "/", function(data) {
		//$.get("data.js", function(data) {
				var data = data.replace(/<!--/, "");
				var data = data.replace(/<events \/>/, "");
				var data = data.replace(/-->/, "");
				
				useMapOverride = true;
				
				eval(data);
				
				cache_mapdetails_species_link = "";
				cache_mapdetails_species_report = "";
				
				that.migration = mi;
				that.migration.setMap(that.map);
				that.migration.setScale(mapSlider, that.map);
				that.migration.setControls(mapControls, that.map);
				
				if ($("div#map-controls p").length == 1) {
					$("div#map-controls p:first")
					.unbind("click")
					.css("cursor", "default")
					.addClass("disabled");
				}
				
				if ($("div#map-controls p#actual-route").length != 0) {
					$("div#map-controls p#predicted-route").removeClass("active");
				}
				
				$("dt:first", mapDetails).show();
				$("dd.title", mapDetails).text(mi.name);
				$("dd.description", mapDetails).html("<i>" + mi.summary + "</i>");
				$("dd.user", mapDetails).hide();
				$("dd:eq(3)", mapDetails).empty();
				
				cache_mapdetails_species_link = "<p class=\"link-arrow\"><a href=\"/radio4/worldonthemove/species/" + species + "/\">More on the " + mi.name+"</a></p>";
				
				$("dd:eq(3)", mapDetails).append(cache_mapdetails_species_link)
				$("dl", mapDetails).css("display", "block");
				
				if (h && s) {
					mapNav.animate({ height: h }, s );
					map_nav_display = false;
				}
				
				var report_id = "";
				var report_title = "";
				for (var i = 0; i < mi.series.length; ++i) {
					if (mi.series[i].id == "reports") {					
						for (var j=0; j < mi.series[i].points.length; j++) {
							if (mi.series[i].pointIds[j] != null && mi.series[i].pointIds[j] != "") {
								report_id = mi.series[i].pointIds[j];
								report_title = mi.series[i].pointIdTitles[j];
							}
						}
					}
				}
				if (report_id != "") {
					cache_mapdetails_species_report = "<p class=\"link-arrow\"><a href=\"/radio4/worldonthemove/reports/"+report_id+"/\">Latest report: " + report_title + "</a></p>";
					$("dd:eq(3)", mapDetails).append(cache_mapdetails_species_report);
				}
			})	
	}
	
	 
	mapSlider.initialise = function (plotFunction, plotter, map) {
		
		this.empty();
		
		$("body").append("<div id=\"debug\" style=\"position:absolute;top:0;right:0;color:white;font-size:11px;font-weight:normal;\" />");
		
		var scale = $('<table><tbody><tr></tr></tbody></table>').appendTo(this);
		
		// build an array of all times
		var allTimes = new Array();
		for (var i = 0; i < plotter.series.length; i++) {
			for (var j = 0; j < plotter.series[i].times.length; j++) {
				time = plotter.series[i].times[j];
				if ($("body").hasClass("community")) {
					allTimes.push(time);
				} else {
					if (!isNaN(time) && (plotter.series[i].id == "predicted-route" || plotter.series[i].id == "actual-route" || plotter.series[i].id == "sighting")) {
						allTimes.push(time);
					}
				}
			}
		}
		allTimes.sort();
		
		var earliest_miliseconds = allTimes[0];
		earliest_timestamp_cache = earliest_miliseconds;
		var latest_miliseconds = allTimes[allTimes.length - 1];
		timediff = latest_miliseconds - earliest_miliseconds;
		
		var earliest = new Date(earliest_miliseconds);
		var latest = new Date(latest_miliseconds);		

		months = Math.floor(timediff / (1000 * 60 * 60 * 24 * 7 * 4));
		weeks = Math.floor(timediff / (1000 * 60 * 60 * 24 * 7));
		days = Math.floor(timediff / (1000 * 60 * 60 * 24)); 

		var numDates = 0;
		var date = earliest;
		
		var monthNames = "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(",")
		var timeUnit = "";

		// use days
		if (days <= 14) {
			timeUnit = "days";
			numDates = days + 2;
			for (var i = 0; i < days + 2; i++) {
				$(this).find("table tbody tr").append("<td class=\"days\" id=\"" + date.getTime() + "\">" + date.getDate() + " " + monthNames[date.getMonth()] + "</td>");
				date.setDate(date.getDate() + 1);
			}
		}
		
		// use weeks
		else if (weeks <=11) {
			timeUnit = "weeks";
			numDates = weeks + 2;
			//alert(numDates);
			for (var i = 0; i < weeks + 2; i++) {
				$(this).find("table tbody tr").append("<td class=\"weeks\" id=\"" + date.getTime() + "\">" + date.getDate() + " " + monthNames[date.getMonth()] + "</td>");
				date.setDate(date.getDate() + 7);
			}
		}
		
		// use months
		else {
			timeUnit = "months";
			numDates = months + 3;
			date = new Date(date.getFullYear(), date.getMonth(), 1);
			for (var i = 0; i < months + 3; i++) {
				$(this).find("table tbody tr").append("<td class=\"months\" id=\"" + date.getTime() + "\">" + monthNames[date.getMonth()] + "</td>");
				date = addMonths(date, 1);
			}
		}
	
		function addDays(startDate, numDays) {
		    return new Date(startDate.getTime() + numDays*24*60*60*1000);
		}
		
		function addMonths(startDate, numMonths) {
		    var addYears = Math.floor(numMonths/12);
		    var addMonths = numMonths - (addYears * 12);
		    var newMonth = startDate.getMonth() + addMonths;
		    if (startDate.getMonth() + addMonths > 11) {
		      ++addYears;
		      newMonth = startDate.getMonth() + addMonths - 12;
		    }
		    var newDate = new Date(startDate.getFullYear()+addYears,newMonth,startDate.getDate(),startDate.getHours(),startDate.getMinutes(),startDate.getSeconds());
		    while (newDate.getMonth() != newMonth) {
		      newDate = addDaysToDate(newDate, -1);
		    }
		    return newDate;			
		}	
		
		var dateContainerWidth = this.width();
		var dateUnitWidth = this.width() / numDates;
		$("table tr td", this).width(Math.floor(dateUnitWidth));
		
		var slider = $('<div id="map-slider-handle"></div>').appendTo(this).css('width', dateUnitWidth);
		
		// cache dates in an array to save DOM inspection during slider movement
		slider.dates = new Array();
		slider.dateUnit = $("#map-slider table tbody td:eq(0)").attr("class");
		$("#map-slider table tbody td").each(function(i) {
			slider.dates.push($(this).attr("id"));
		});

		if (setSliderToCurrentDate) {
			sliderInitialPosition = 0;
			var currentDate = new Date();
			var currentTimestamp = currentDate.getTime();
			// if today is > than slider bar day, set it to match that day
			// overridden every iteration, so if today is off the scale, select
			// the latest date on the scale
			for (var i = 0; i < slider.dates.length; i++) {
				if (currentDate.getTime() > slider.dates[i] && setSliderToCurrentDate) {
					sliderInitialPosition = (dateUnitWidth * (i));		
					sliderInitialPosition = sliderInitialPosition - sliderInitialPosition * 0.005; // error margin of 0.5%
				}
			}
		}
		
		if ($("body").hasClass("community")) {
			showPrompt = false;
		}
		
		if (showPrompt) {
			var sliderWidth = parseInt($("#map-slider-handle").width()) - 53;
			$("#map").after("<div id=\"map-prompt\"></div>");
			showPrompt = false
		} else {
			$("#map-prompt").remove();
		}
		
		slider.pxWidth = parseInt(slider.width());
		slider.pxLeft = 0;
		slider.xMax = parseInt(this.width()) - slider.pxWidth;
		slider.frameRate = 40;
		slider.animDuration = 0.75;
		slider.animDelta = 1 / (slider.frameRate * slider.animDuration); // frame rate / animation duration
		
		// create clickable div behind slider
		$("#map-slider-handle").after("<div id=\"map-slider-clickable\"></div>");
		$("#map-slider-clickable")
		.click(function(e) {
			var offset = 0;
			if (!$('body').hasClass("homepage")) {
				offset = 144;
			}
			slider.animate((e.pageX - offset) - (slider.pxWidth/2));
		});
		
		slider.onMouseDown = function (e) {
			$("#map-prompt").remove();
			page.bind('mousemove', { offset: e.pageX - parseInt(slider.css('left')) }, slider.onMouseMove);
			page.bind('select', slider.negateEvent);
			page.one('mouseup', function (e) {
				document.onselectstart = function() { return true; }
				page.unbind('mousemove', slider.onMouseMove);
				page.unbind('select', slider.negateEvent);
			});
		}
		
		slider.negateEvent = function () {
			return false;
		}
		
		slider.onMouseMove = function (e) {
			document.onselectstart = function() { return false; }
			slider.move(e.pageX - e.data.offset);
		}
		
		slider.move = function (x) {			
			if (x < 0) x = 0;
			if (x > this.xMax) x = this.xMax;
			
			/*var scaleFactor = 5;
			var pos = (Math.floor(x / scaleFactor) * scaleFactor) / (this.pxWidth + 1);*/
			var pos = Math.floor((x / this.pxWidth + 1) / .1) * .1;
			if (this.pos != pos) {
				// currentDateAbove is the timestamp of the date unit directly above the slider
				var currentDateAbove = Math.floor(pos);
				var dateAbove= slider.dates[currentDateAbove - 1] * 1;
				var timestamp = 0;
				
				// if days, each unit is one day so use the timestamp itself
				if (slider.dateUnit == "days") {
					timestamp = dateAbove;
				} else {
					// if weeks or months, see how far between two known dates the slider is,
					// interpolate dates and calculate timestamp
					var fractionMovedAlong = pos - currentDateAbove;
					timestamp = dateAbove + ((((slider.dates[currentDateAbove]) * 1) - dateAbove) * fractionMovedAlong);
				}
				if (!isNaN(timestamp)) {
					if (timestamp < earliest_timestamp_cache) {
						timestamp = earliest_timestamp_cache + 1000;
					}
					plotFunction.call(plotter, map, timestamp);
					global_sliderTimestamp = timestamp;
				}
				
				this.pos = pos;
			}
			
			this.css({ left: x });
			this.pxLeft = x;
		}
		
		slider.animate = function (x, delay) {			
			if (x == undefined) return false;
			
			if (delay && x != undefined) {				
				var self = this;				
				setTimeout('self.animate(x)', delay);
			}
			
			slider.unbind('mousedown', slider.onMouseDown);			
			$("#map-slider-clickable").unbind("click");
			
			var progress = 0;
			var start = this.pxLeft;
			var diff = x - start;
			
			function process() {
	
				if (progress > 1 - slider.animDelta) {					
					clearInterval(animInterval);
					slider.bind('mousedown', slider.onMouseDown);
					$("#map-slider-clickable").bind("click", function(e) {
						var offset = 0;
						if (!$('body').hasClass("homepage")) {
							offset = 144;
						}
						slider.animate((e.pageX - offset) - (slider.pxWidth/2));
					});
				}
				
				progress += slider.animDelta;
				var eased = -1 *(progress/=1)*(progress-2) + 0;
				slider.move(start + diff * eased);
			}
			animInterval = setInterval(process, slider.frameRate);
		}
		
		slider.bind('mousedown', slider.onMouseDown);
		
		if ($('body.homepage').length > 0) {
			//slider.animate(388, 5000);
		} else {
		
			//slider.animate(313, 5000);
		}
		
		if (animateInitialSlider) {
			sliderInitialPosition += 1;
			if (sliderInitialPosition > 770) { sliderInitialPosition = 770; }
			slider.animate(sliderInitialPosition);
		} else {
			slider.move(sliderInitialPosition);
		}
		animateInitialSlider = true;
		
		return slider;
	}
	
		
	mapControls.addControl = function (series, map, id, name) {
		
		var className = "";
		if (series.isActive) {
			className = "active";
		}
		
		/*
		Migration.prototype.setScale = function (scale, map) {

			scale.initialise(this.plot, this, map);
		}
		*/
				
		$('<p></p>')
		 .appendTo(this)
		 .attr('id', id)
		 .attr("class", className)
		 .html("<span>" + name + "</span>")
		 .select(function () { return false; })
		 .mousedown(function () { return false; })
		 .click(function () {
			$(this).toggleClass("active");
			// collection of all active tabs
			var activeTabs = $("div#map-controls p.active");
			// if toggling .active on this tab has meant no tabs are active,
			// force current tab to remain active and don't re-scale map
			if (activeTabs.length == 0) {
				$(this).addClass("active");
				return false;
			}
						
			// clear map bounds to force update			
			mi.xMin = undefined;
			mi.xMax = undefined;
			mi.yMin = undefined;
			mi.yMax = undefined;	
			
			totalMapPoints = 0;
					
			// loop through each active tab
			$(activeTabs).each(function() {
				// loop through all series on the map
				for (var i=0; i < mi.series.length; i++) {
					// when a series matches the ID from a tab
					// don't use comments for resizing map
					if (mi.series[i].id == $(this).attr("id") && mi.series[i].id != "user-comments") {
						// loop through series points and add to the limits of the map
						for (var j = 0; j < mi.series[i].points.length; ++ j) {
							totalMapPoints++;
							mi.calculateLimits(mi.series[i].points[j], mi.series[i].points[++j]);
						}
					}
				}
			});
			// force the rescaling
			mi.setMap(mapData.map);
			
			series.activate();
			series.plot(map, global_sliderTimestamp);			
		});
	}
	
	
	mapData.loadMigrationData(initialSpecies);
	
	
	var mapNavToggle = $('p#link-toggle a');
	toggleHeight = (parseInt($(mapNavToggle).height()) + parseInt($(mapNavToggle).css('padding-top')) + parseInt($(mapNavToggle).css('padding-bottom')));
	var mapHeight = 283;
	var mapAnimateSpeedight = 800;
	
	
	/*
	
	var nav_glow;
	var nav_glow_speed = 500
	nav_glow_state = true;
	
	function do_glow(c) {
		
		$(mapNavToggle).animate({ color: c }, nav_glow_speed, function() { });
		
	}

	nav_glow = setInterval(function() {
	
		if(nav_glow_state == true) {
			
			do_glow('#4e820f');
			
			nav_glow_state = false;
		
		} else {
		
			do_glow('#aed97a');
			
			nav_glow_state = true;
		
		}
		
	}, nav_glow_speed);
	
	*/
	
	// set toggle height
	mapNav.css({height : toggleHeight});
		
	
	$('ul', mapNav).css({display : 'block'});
		
	// add shadows
	$('#map-slider').after('<div id="map-shadow-top">&nbsp;</div><div id="map-shadow-bottom">&nbsp;</div>');
	
	
	$('#link-toggle').after('<a href="#" class="show list-up">up</a> <a href="#" class="show list-down">down</a>');
	
	
	$('a.list-up').hide();
	
	
	// set list groups
	$("li", mapNav).each(function(i) {
		if (i >= 7 && i <= 13) {
			$(this).attr("class", "list-2").hide();
		}
		if (i >= 14 && i <= 20) {
			$(this).attr("class", "list-3").hide();
		}
		
		if (i >= 21 && i <= 27) {
			$(this).attr("class", "list-4").hide();
		}
	});
	
	map_nav_pagemax = Math.ceil($("li", mapNav).length / 7);
	
	// show only first list
	$("li:not(.list-1)", mapNav).hide();
	
	
	$("li", mapNav).each(function() {
		var className = $("a:first", this).attr("id");
		$("span", this).css("background-image", "url('/radio4/worldonthemove/assets/ui/icons/small-"+className+".gif')");
	});
	
	$("a.list-up", mapNav).click(function() {
		
		if(map_nav_page == 1) {
					
			return false;
		}
		
		map_nav_page --;
		
		$("li", mapNav).hide();
		$("li.list-" + map_nav_page, mapNav).show();
		
		
		$('a.list-down').show();
		
		if(map_nav_page == 1) {
					
			$('a.list-up').hide();
		}
		
		return false;
		
	});
	
	$("a.list-down", mapNav).click(function() {
				
		if(map_nav_pagemax == map_nav_page) {
					
			return false;
		}
		
		map_nav_page ++;
		
		$("li", mapNav).hide();
		$("li.list-" + map_nav_page, mapNav).show();
		
		$('a.list-up').show();
		
		if(map_nav_page == map_nav_pagemax) {
					
			$('a.list-down').hide();
		}
		
		return false;
		
	});
	
	// toggle click functionality
	mapNavToggle.click(function() {
	
		if($('body.homepage').length != 1) {		
			location.href = "/radio4/worldonthemove/species/"		
			return false;		
		}

		if(!map_nav_display) {		
			mapNav.animate({ height: mapHeight }, mapAnimateSpeedight );			
			$(this).addClass('active');			
			map_nav_display = true;		
		} else {
			
			mapNav.animate({ height: toggleHeight }, mapAnimateSpeedight );			
			$(this).removeClass('active');			
			map_nav_display = false;		
		}
	
		return false;
	
	});
	
	$('ul li a', mapNav).click(function() {
		$('li a', mapNav).removeClass('active');
		$(this).addClass('active');
		mapData.loadMigrationData($(this).attr("id"), toggleHeight, mapAnimateSpeedight);
		return false;	
	});

	if($('body.homepage').length > 0) {
		$("a:contains('Terms of Use')").parent().css("right", "225px");
	}	
	
	$("#map-holder").click(function() {
		$("#map-prompt").remove();
	})
	
});


$.elementReady('map-holder', function(){	
	
	var homepage = $('body.homepage');

	if(homepage.length > 0) {
	
		$('#map-holder').prependTo('.wotm');
		
	} else {
			$("#map-nav").hide();
			$("#map-details").hide();
			$("#map-details").prepend("<div id=\"map-details-close\">Close</div>");
			$("#map-details-close").click(function() { $("#map-details").hide(); });
	}

	
	$('#map').before('<div id="map-divider"></div>');
});


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


var MigrationMap = function (jqObject) {
	GMap2.call(this, jqObject.get(0));
	GMap2.prototype.addControl.call(this, new GSmallMapControl(), new GControlPosition(G_ANCHOR_BOTTOM_LEFT, new GSize(3,35)));
}

MigrationMap.inherits(GMap2);

MigrationMap.prototype.setCenter = function (midPoint, length) {

	var size = GMap2.prototype.getSize.call(this);
	var sf = 0.9;
	
	var xZoom = Math.log(360 / length.x * (size.width * sf) / 256) / Math.log(2);
 	var yZoom = Math.log(200 / length.y * (size.height * sf) / 256) / Math.log(2);
	var zoom = Math.floor(Math.min(xZoom, yZoom));
	// max-zoom is 17 for Google Maps. Limit zoom to prevent initial zoom onto exact location
	// when only one data point is visible.
	if (zoom > 10) {
		zoom = 10;
	}
	if (zoom < 0) {
		zoom = 0;
	}

	if (totalMapPoints == 1 && mi.override_centre != "" && mi.override_zoom != "") {
		useMapOverride = true;
	}

	if (useMapOverride) {
		if (mi.override_centre != "") {
			midPoint.x = mi.override_centre.split(",")[1].replace(/ /, "");
			midPoint.y = mi.override_centre.split(",")[0].replace(/ /, "");
		}
		if (mi.override_zoom != "") {
			zoom = mi.override_zoom * 1;
		}
		useMapOverride = false;
	}
	


   	GMap2.prototype.setCenter.call(this, new GLatLng(midPoint.y, midPoint.x), zoom);
	GEvent.bind(this, "addoverlay", this, this.onAddOverlay);
	
	$("#map-visual div:first div:first div img").livequery(function() {
		if ($(this).attr("id") == "mtgt_undefined" && $(this).css("width") == "46px") {
			$(this).css("z-index", "999999999");
		}
	});
}

MigrationMap.prototype.makePolyline = function (points, tooltip, colour) {
		
	if (points.length < 4) {
		return false;
	}
		
	var weight = 3;
	var opacity = 1;
	var style = 'solid';
	
	var gPoints = new Array();
	for (var i = 0; i < points.length; ++ i) {
		
		gPoints.push(new GLatLng(points[++ i], points[i - 1]));
	}
	
	return polyline = new GPolyline(gPoints, colour, weight, opacity);
}

MigrationMap.prototype.addPolyline = function (points, tooltip, colour) {

	var polyline = this.makePolyline(points, tooltip, colour);
	
	if (polyline) {
		
		GMap2.prototype.addOverlay.call(this, polyline);
	}
	
	return polyline;
}

MigrationMap.prototype.updatePolyline = function (existing, points, tooltip, colour) {

	var polyline = this.makePolyline(points, tooltip, colour);
	
	if (polyline) {
		
		polyline.existing = existing;
		GMap2.prototype.addOverlay.call(this, polyline);
	}
	else {
		
		this.removePolyline(existing);
	}
	
	return polyline;
}

MigrationMap.prototype.removePolyline = function (polyline) {
		
	GMap2.prototype.removeOverlay.call(this, polyline);
	delete polyline;
}

MigrationMap.prototype.onAddOverlay = function (overlay) {
		
	if ($.browser.msie) {
		
		if (overlay.existing) {
				
			this.removePolyline(overlay.existing);
			delete overlay.existing;
		}
	}
	else {
		
		var self = this;
		setTimeout(function () {
			
			if (overlay.existing) {
				
				self.removePolyline(overlay.existing);
				delete overlay.existing;
			}
		}, 1);
	}
}

MigrationMap.prototype.addMarkers = function (points, iconPath, iconSize, isClickable) {

	var icon = new GIcon();
	icon.image = uiImagePath + iconPath;
	icon.iconSize = new GSize(iconSize[0], iconSize[1]);
	icon.shadowSize = new GSize(22, 20);
	if (iconSize[0] < 40) {
		icon.iconAnchor = new GPoint(15, 26);
	} else {
		icon.iconAnchor = new GPoint(20, 20);
	}	
	icon.infoWindowAnchor = new GPoint(1, 1);
	
	var markers = new Array();
	
	for (var i = 0; i < points.length; ++i) {
			
		var markerOptions = { icon: icon, clickable: isClickable };
		var marker = new GMarker(new GLatLng(points[++i], points[i - 1]), markerOptions);
		GMap2.prototype.addOverlay.call(this, marker);
		markers.push(marker);
	}
	
	return markers;
}

MigrationMap.prototype.removeMarkers = function (markers) {
		
	for (var i = 0; i < markers.length; ++i) {
		
		GMap2.prototype.removeOverlay.call(this, markers[i]);
		delete markers[i];
	}
}

MigrationMap.prototype.addClickEvent = function (marker, callback) {

	GEvent.addListener(marker, "click", function () { callback.action(); });
}


var Migration = function (name, summary) {
	
	this.name = name;
	this.summary = summary;
	this.series = new Array();
}

Migration.prototype.calculateLimits = function (x, y) {
	if (this.xMin == undefined || x < this.xMin) this.xMin = x;
	if (this.xMax == undefined || x > this.xMax) this.xMax = x;
	if (this.yMin == undefined || y < this.yMin) this.yMin = y;
	if (this.yMax == undefined || y > this.yMax) this.yMax = y;	
}

Migration.prototype.addSeries = function (series, bound) {
	
	// HACK for attempted fix around line 90
 	//if (true) {
	if (bound) {	
		for (var i = 0; i < series.points.length; ++ i) {
			this.calculateLimits(series.points[i], series.points[++ i]);
		}
	}
		
	this.series.push(series);
}

Migration.prototype.setMap = function (map) {
	
	var xMid = this.xMin + (this.xMax - this.xMin) / 2;
	var yMid = this.yMin + (this.yMax - this.yMin) / 2;
	var xLen = Math.abs(this.xMax - this.xMin);
	var yLen = Math.abs(this.yMax - this.yMin);
	
	// setCenter(midpoint, length)
	map.setCenter({x: xMid, y: yMid}, {x: xLen, y: yLen});
}

Migration.prototype.setScale = function (scale, map) {
	
	scale.initialise(this.plot, this, map);
}

Migration.prototype.setControls = function (controls, map) {
	
	for (var i = 0; i < this.series.length; ++ i) {
		this.series[i].giveControl(controls, map);
	}
}

Migration.prototype.clearMap = function (map, controls) {
	
	for (var i = 0; i < this.series.length; ++ i) {
		
		this.series[i].plot(map, false);
	}
	controls.empty();
}

Migration.prototype.plot = function (map, pos) {

	for (var i = 0; i < this.series.length; ++ i) {
		
		this.series[i].plot(map, pos);
	}
}


var SERIES_ASO = false;
var SERIES_ISO = true;


var Series = function (id, name, icon, bound, active) {
	
	this.id = id;
	this.name = name;
	this.icon = icon;
	
	this.points = new Array();
	this.times = new Array();
	this.pointIds = new Array();
	this.pointIdDescriptions = new Array();
	this.pointIdTitles = new Array();
	
	this.isActive = active;
	this.time = 0;
	this.bound = bound;
}		
	
Series.prototype.addPoint = function (x, y, time, id, title, desc) {
		
	this.points.push(x, y);
	this.times.push(time);
	this.pointIds.push(id);
	this.pointIdTitles.push(title);
	this.pointIdDescriptions.push(desc);
}

Series.prototype.addToMigration = function (migration) {
	
	// series often created from more than one XML nodeset, so not all ordered by date
	// so we need to order the times array numerically, then re-order points, pointIds, pointIdDescriptions and pointIdTitles too
	
	var newOrder = [];
	for (i=0; i < this.times.length; i++) {
		newOrder.push({ time: this.times[i], point1: this.points[i * 2], point2: this.points[(i * 2) + 1], id: this.pointIds[i], title: this.pointIdTitles[i], description: this.pointIdDescriptions[i]});
	}
	
	// empty the original array containers
	this.times.length = 0;
	this.points.length = 0;
	this.pointIds.length = 0;
	this.pointIdDescriptions.length = 0;
	this.pointIdTitles.length = 0;
	
	// sort in ascending order
	function compareTimes(a, b) {
		return a.time - b.time;
	}	
	newOrder.sort(compareTimes)
	
	// push sorted values back into series properties
	for (i=0; i < newOrder.length; i++) {
		this.times.push(newOrder[i].time);
		this.points.push(newOrder[i].point1, newOrder[i].point2);
		this.pointIds.push(newOrder[i].id);
		this.pointIdDescriptions.push(newOrder[i].description);
		this.pointIdTitles.push(newOrder[i].title);
	}
		
	migration.addSeries(this, this.bound);
}

Series.prototype.activate = function (flag) {
	
	if (flag == undefined) flag = ! this.isActive;
	this.isActive = flag;
}

Series.prototype.giveControl = function (controls, map) {
	
	controls.addControl(this, map, this.id, this.name, this.isActive);
}

Series.prototype.plot = function (map, time) {

	if (time != undefined) this.time = time;

	/***TODO****/
	if (this.id == 'data-ugc' && time) return this.points;
	/*******/
	
	if (this.isActive) {
		
		var i = 0;
		while (this.times[i] <= time) ++i;
		return this.points.slice(0, i * 2);
	}
	else {
		
		return new Array();
	}
}

Series.prototype.click = function (map, markers, index) {

	for (var i = 0; i < markers.length; ++i) {

		var clickEvent = new MarkerClickEvent(this.id, this.pointIds[index + i], this.pointIdTitles[index + i], this.pointIdDescriptions[index + i]);
		map.addClickEvent(markers[i], clickEvent);
	}
}


var Route = function (id, name, icon, colour, bound, active) {
	
	Series.call(this, id, name, icon, bound, active);
	this.colour = colour;
}

Route.inherits(Series);

Route.prototype.plot = function (map, time) {
	
	if (this.marker) {
		
		map.removeMarkers(this.marker.splice(0, 1));
	}
	
	var points = Series.prototype.plot.call(this, map, time);
		
	if (points.length > 0 && points.length != this.points.length) {

		var lastTime = this.times[(points.length - 2) / 2];
		var nextTime = this.times[points.length / 2];
		var pc = (time - lastTime) / (nextTime - lastTime);

		var lastX = this.points[points.length - 2];
		var lastY = this.points[points.length - 1];
		var nextX = this.points[points.length];
		var nextY = this.points[points.length + 1];
		
		// if last/next is negative, we've crossed the date line, so make positive, negative
		// prevents crazy interpolation across
		if (lastX > 0 && nextX < 0) { nextX = nextX * -1; }
		if (lastX < 0 && nextX > 0) { lastX = lastX * -1; }
		
		var interX = lastX + (nextX - lastX) * pc;
		var interY = lastY + (nextY - lastY) * pc;
 
		points.push(interX, interY);
	}
	
	if (this.polyline) {
		
		this.polyline = map.updatePolyline(this.polyline, points, this.name, this.colour);
	}
	else {
		
		this.polyline = map.addPolyline(points, this.name, this.colour);
	}
	
	clearTimeout(this.markerTimeout);

	if (this.isActive) {
		
		var delay = (time == undefined) ? 1 : 400;
		var self = this;
		this.markerTimeout = setTimeout(function () {			
			self.addMarker(map, points.slice(-2));		
		}, delay);
	}
}

Route.prototype.addMarker = function (map, pos) {

	this.marker = map.addMarkers(pos, this.icon.file, this.icon.size, true);
	this.click(map, this.marker);
}

Route.prototype.giveControl = function (controls, map) {
	controls.addControl(this, map, this.id, this.name, this.isActive);
}


var Markers = function (id, name, icon, bound, active) {
	
	Series.call(this, id, name, icon, bound, active);
	this.markers = new Array();
}

Markers.inherits(Series);

Markers.prototype.plot = function (map, time) {
	
	var points = Series.prototype.plot.call(this, map, time);
	
	if (points.length / 2 > this.markers.length) {
		
		// add additional markers if number of x/y points to plot is greater than the number of markers we have stored
		var newMarkers = map.addMarkers(points.slice(this.markers.length * 2), this.icon.file, this.icon.size, true);
		this.click(map, newMarkers, this.markers.length);
		this.markers = this.markers.concat(newMarkers);
		
	}
	else if (points.length / 2 < this.markers.length) {
		
		map.removeMarkers(this.markers.splice(points.length / 2, this.markers.length - points.length / 2));
	}
}

var MarkerClickEvent = function (id, handle, title, description) {
	
	this.action = function () {
		
		$("#map-prompt").remove();
			
		switch (id) {
		
			case 'predicted-route':
			case 'actual-route':
			
				$("#map-details dt:first").show();
				$("#map-details dd.title").show().text(mi.name);
				$("#map-details dd.description").show().html("<i>" + mi.summary + "</i>");
				$("#map-details dd:eq(3)").empty();
				$("#map-details dd:eq(3)").show().append(cache_mapdetails_species_link);
				
				if (cache_mapdetails_species_report != "") {
					$("#map-details dd:eq(3)").append(cache_mapdetails_species_report);
				}
							
				$("#map-details dl").css("display", "block");
				$("#map-details").show();
	
				break;
			case 'sighting':
				if (title == "") {
					$("#map-details dt:first").hide();
					$("#map-details dd.user").hide();
					$("#map-details dd.title").show().text("Sighting");
					$("#map-details dd.description").show().html("" + mi.name + " was spotted here");
					$("#map-details dd:eq(3)").hide();
					$("#map-details").show();
				} else {
					$("#map-details dt:first").hide();
					$("#map-details dd.title").show().text('Sighting');
					$("#map-details dd.user").show().html(title);
					$("#map-details dd.description").show().html(description);
					$("#map-details dd:eq(3)").empty();
					$("#map-details dd:eq(3)").show().append("<p class=\"link-arrow\"><a href=\"" + handle + "\">More on this comment</a></p>");
					$("#map-details").show();
				}
							
				break;
			case 'user-comments':
			
				$("#map-details dt:first").hide();
				$("#map-details dd.title").show().text('User Comment');
				$("#map-details dd.user").show().html(title);
				$("#map-details dd.description").show().html(description);
				$("#map-details dd:eq(3)").empty();
				$("#map-details dd:eq(3)").show().append("<p class=\"link-arrow\"><a href=\"" + handle + "\">More on this comment</a></p>");
				$("#map-details").show();
				
				break;
			case 'reports':
				
				$("#map-details dt:first").hide();
				$("#map-details dd.title").show().text(title);
				$("#map-details dd.user").hide()
				$("#map-details dd.description").show().html(description);
				$("#map-details dd:eq(3)").empty();
				$("#map-details dd:eq(3)").show().append("<p class=\"link-arrow\"><a href=\"/radio4/worldonthemove/reports/" + handle + "/\">"+title+"</a></p>");
	
				$("#map-details").show();
			
				break;
		}
	}
}
