
// URL rewriting
var markIndex = location.href.indexOf('?');
if(markIndex == -1) markIndex = false;
var hashIndex = location.href.indexOf('#');
if(hashIndex == -1) hashIndex = false;
if(markIndex && !hashIndex)
	location.href = location.href.replace(/\?/,'#?');
else if(markIndex && hashIndex && hashIndex > markIndex)
	location.href = location.href.substring(0, markIndex) + location.href.substring(hashIndex);

// Load Microsoft Virtual Earth API
document.write('<script src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.1"></script>');

// Globals
//var startFeedURL = "/EventsPortal/feed.aspx";
var startFeedURL = "/Applications/EventsPortal/feed.aspx";
var feedURL = startFeedURL;
var NULL_FUNC = function(){};
var debugOutput = false;
var previousTime = false;
var dataLoaded = false;
var mapLoaded = false;
var eventsByID = {};
var content = null;
var fullCalendar = null;
var isFirstLoad = true;
var reSearchOnReturn = false;
var searchWasLimited = false;
var searchLimit = 50;

// construct date cache
var dateEventsCache = {'1158278400000': 1, '1217289600000': 141, '1217376000000': 148, '1217462400000': 120, '1217548800000': 122, '1217635200000': 24, '1217721600000': 14, '1217808000000': 155, '1217894400000': 148, '1217980800000': 157, '1218067200000': 123, '1218153600000': 119, '1218240000000': 18, '1218326400000': 14, '1218412800000': 145, '1218499200000': 136, '1218585600000': 143, '1218672000000': 111, '1218758400000': 110, '1218844800000': 22, '1218931200000': 14, '1219017600000': 128, '1219104000000': 112, '1219190400000': 121, '1219276800000': 87, '1219363200000': 94, '1219449600000': 28, '1219536000000': 19, '1219622400000': 40, '1219708800000': 81, '1219795200000': 90, '1219881600000': 58, '1219968000000': 67, '1220054400000': 27, '1220140800000': 19, '1220227200000': 99, '1220313600000': 93, '1220400000000': 102, '1220486400000': 73, '1220572800000': 80, '1220659200000': 38, '1220745600000': 21, '1220832000000': 114, '1220918400000': 94, '1221004800000': 99, '1221091200000': 69, '1221177600000': 80, '1221264000000': 43, '1221350400000': 20, '1221436800000': 425, '1221523200000': 153, '1221609600000': 175, '1221696000000': 139, '1221782400000': 118, '1221868800000': 62, '1221955200000': 24, '1222041600000': 425, '1222128000000': 153, '1222214400000': 167, '1222300800000': 136, '1222387200000': 117, '1222473600000': 56, '1222560000000': 25, '1222646400000': 420, '1222732800000': 152, '1222819200000': 171, '1222905600000': 140, '1222992000000': 124, '1223078400000': 61, '1223164800000': 32, '1223251200000': 432, '1223337600000': 166, '1223424000000': 180, '1223510400000': 149, '1223596800000': 136, '1223683200000': 86, '1223769600000': 49, '1223856000000': 445, '1223942400000': 180, '1224028800000': 196, '1224115200000': 162, '1224201600000': 145, '1224288000000': 82, '1224374400000': 52, '1224460800000': 446, '1224547200000': 181, '1224633600000': 197, '1224720000000': 161, '1224806400000': 143, '1224892800000': 82, '1224979200000': 45, '1225065600000': 459, '1225152000000': 196, '1225238400000': 216, '1225324800000': 178, '1225411200000': 163, '1225497600000': 80, '1225584000000': 45, '1225670400000': 436, '1225756800000': 177, '1225843200000': 191, '1225929600000': 158, '1226016000000': 139, '1226102400000': 85, '1226188800000': 51, '1226275200000': 435, '1226361600000': 176, '1226448000000': 173, '1226534400000': 140, '1226620800000': 118, '1226707200000': 78, '1226793600000': 48, '1226880000000': 416, '1226966400000': 155, '1227052800000': 171, '1227139200000': 137, '1227225600000': 117, '1227312000000': 81, '1227398400000': 47, '1227484800000': 410, '1227571200000': 154, '1227657600000': 171, '1227744000000': 134, '1227830400000': 114, '1227916800000': 79, '1228003200000': 48, '1228089600000': 390, '1228176000000': 140, '1228262400000': 145, '1228348800000': 118, '1228435200000': 104, '1228521600000': 74, '1228608000000': 45, '1228694400000': 370, '1228780800000': 124, '1228867200000': 130, '1228953600000': 110, '1229040000000': 95, '1229126400000': 65, '1229212800000': 41, '1229299200000': 339, '1229385600000': 107, '1229472000000': 114, '1229558400000': 95, '1229644800000': 85, '1229731200000': 64, '1229817600000': 41, '1229904000000': 316, '1229990400000': 105, '1230076800000': 109, '1230163200000': 28, '1230249600000': 32, '1230336000000': 60, '1230422400000': 40, '1230508800000': 315, '1230595200000': 100, '1230681600000': 106, '1230768000000': 27, '1230854400000': 82, '1230940800000': 68, '1231027200000': 41, '1231113600000': 318, '1231200000000': 107, '1231286400000': 116, '1231372800000': 97, '1231459200000': 83, '1231545600000': 64, '1231632000000': 44, '1231718400000': 316, '1231804800000': 105, '1231891200000': 109, '1231977600000': 92, '1232064000000': 83, '1232150400000': 65, '1232236800000': 42, '1232323200000': 323, '1232409600000': 109, '1232496000000': 117, '1232582400000': 87, '1232668800000': 78, '1232755200000': 55, '1232841600000': 38, '1232928000000': 312, '1233014400000': 97, '1233100800000': 105, '1233187200000': 87, '1233273600000': 77, '1233360000000': 58, '1233446400000': 32, '1233532800000': 276, '1233619200000': 61, '1233705600000': 63, '1233792000000': 63, '1233878400000': 49, '1233964800000': 52, '1234051200000': 31, '1234137600000': 265, '1234224000000': 61, '1234310400000': 62, '1234396800000': 63, '1234483200000': 51, '1234569600000': 54, '1234656000000': 31, '1234742400000': 258, '1234828800000': 55, '1234915200000': 56, '1235001600000': 56, '1235088000000': 43, '1235174400000': 50, '1235260800000': 31, '1235347200000': 257, '1235433600000': 51, '1235520000000': 52, '1235606400000': 51, '1235692800000': 37, '1235779200000': 50, '1235865600000': 7, '1235952000000': 231, '1236038400000': 28, '1236124800000': 30, '1236211200000': 30, '1236297600000': 18, '1236384000000': 23, '1236470400000': 5, '1236556800000': 229, '1236643200000': 28, '1236729600000': 29, '1236816000000': 29, '1236902400000': 19, '1236988800000': 25, '1237075200000': 5, '1237161600000': 229, '1237248000000': 28, '1237334400000': 30, '1237420800000': 30, '1237507200000': 16, '1237593600000': 23, '1237680000000': 5, '1237766400000': 226, '1237852800000': 28, '1237939200000': 29, '1238025600000': 28, '1238112000000': 16, '1238198400000': 26, '1238284800000': 5, '1238371200000': 225, '1238457600000': 28, '1238544000000': 31, '1238630400000': 26, '1238716800000': 16, '1238803200000': 23, '1238889600000': 5, '1238976000000': 224, '1239062400000': 28, '1239148800000': 27, '1239235200000': 25, '1239321600000': 2, '1239408000000': 25, '1239494400000': 5, '1239580800000': 74, '1239667200000': 22, '1239753600000': 24, '1239840000000': 25, '1239926400000': 16, '1240012800000': 22, '1240099200000': 5, '1240185600000': 222, '1240272000000': 22, '1240358400000': 25, '1240444800000': 27, '1240531200000': 16, '1240617600000': 24, '1240704000000': 6, '1240790400000': 223, '1240876800000': 23, '1240963200000': 26, '1241049600000': 21, '1241136000000': 15, '1241222400000': 22, '1241308800000': 5, '1241395200000': 71, '1241481600000': 22, '1241568000000': 25, '1241654400000': 21, '1241740800000': 14, '1241827200000': 26, '1241913600000': 5, '1242000000000': 223, '1242086400000': 22, '1242172800000': 27, '1242259200000': 21, '1242345600000': 14, '1242432000000': 22, '1242518400000': 5, '1242604800000': 223, '1242691200000': 22, '1242777600000': 24, '1242864000000': 21, '1242950400000': 14, '1243036800000': 25, '1243123200000': 5, '1243209600000': 71, '1243296000000': 23, '1243382400000': 25, '1243468800000': 22, '1243555200000': 14, '1243641600000': 21, '1243728000000': 5, '1243814400000': 224, '1243900800000': 22, '1243987200000': 24, '1244073600000': 21, '1244160000000': 14, '1244246400000': 26, '1244332800000': 5, '1244419200000': 223, '1244505600000': 22, '1244592000000': 25, '1244678400000': 21, '1244764800000': 14, '1244851200000': 23, '1244937600000': 5, '1245024000000': 223, '1245110400000': 22, '1245196800000': 24, '1245283200000': 21, '1245369600000': 14, '1245456000000': 24, '1245542400000': 4, '1245628800000': 223, '1245715200000': 22, '1245801600000': 25, '1245888000000': 21, '1245974400000': 15, '1246060800000': 20, '1246147200000': 5, '1246233600000': 223, '1246320000000': 21, '1246406400000': 25, '1246492800000': 21, '1246579200000': 14, '1246665600000': 24, '1246752000000': 4, '1246838400000': 223, '1246924800000': 21, '1247011200000': 25, '1247097600000': 21, '1247184000000': 13, '1247270400000': 21, '1247356800000': 4, '1247443200000': 223, '1247529600000': 21, '1247616000000': 23, '1247702400000': 21, '1247788800000': 13, '1247875200000': 24, '1247961600000': 4, '1248048000000': 222, '1248134400000': 21, '1248220800000': 24, '1248307200000': 22, '1248393600000': 13, '1248480000000': 20, '1248566400000': 5, '1248652800000': 222, '1248739200000': 22, '1248825600000': 23, '1248912000000': 21, '1248998400000': 13, '1249084800000': 24, '1249171200000': 4, '1249257600000': 221, '1249344000000': 13, '1249430400000': 17, '1249516800000': 20, '1249603200000': 13, '1249689600000': 18, '1249776000000': 4, '1249862400000': 161, '1249948800000': 8, '1250035200000': 13, '1250121600000': 19, '1250208000000': 12, '1250294400000': 14, '1250380800000': 2, '1250467200000': 161, '1250553600000': 1, '1250640000000': 4, '1250726400000': 8, '1250812800000': 10, '1250899200000': 11, '1250985600000': 1, '1251072000000': 45, '1251158400000': 1, '1251244800000': 4, '1251331200000': 7, '1251417600000': 4, '1251504000000': 14, '1251676800000': 12, '1251763200000': 1, '1251849600000': 3, '1251936000000': 1, '1252022400000': 1, '1252108800000': 2, '1252281600000': 10, '1252454400000': 2, '1252627200000': 1, '1252713600000': 5, '1252800000000': 1, '1252886400000': 10, '1253059200000': 3, '1253318400000': 2, '1253491200000': 3, '1253664000000': 3, '1253923200000': 5, '1254096000000': 2, '1254268800000': 3, '1254355200000': 1, '1254528000000': 2, '1254873600000': 2, '1255132800000': 4, '1255392000000': 1, '1255478400000': 3, '1255737600000': 1, '1256256000000': 1, '1256342400000': 3, '1256515200000': 1, '1256688000000': 1, '1256947200000': 1, '1257033600000': 1, '1257552000000': 3, '1257897600000': 1, '1258070400000': 1, '1258156800000': 1, '1258761600000': 3, '1258934400000': 1, '1259107200000': 1, '1259193600000': 1, '1259366400000': 1, '1259625600000': 1, '1259971200000': 3, '1260316800000': 1, '1260576000000': 1, '1260662400000': 1, '1261180800000': 3, '1261526400000': 2, '1261785600000': 1, '1262390400000': 3, '1262736000000': 1, '1262995200000': 1, '1263340800000': 1, '1263600000000': 3, '1263945600000': 1, '1264204800000': 2, '1264809600000': 3, '1264982400000': 1, '1265155200000': 1, '1265414400000': 1, '1266019200000': 4, '1266364800000': 1, '1266624000000': 1, '1266883200000': 1, '1267228800000': 3, '1267401600000': 1, '1267574400000': 1, '1267833600000': 1, '1268438400000': 3, '1268784000000': 1, '1269043200000': 1, '1269302400000': 1, '1269648000000': 3, '1269993600000': 1, '1270080000000': 1, '1270252800000': 1, '1270857600000': 3, '1271203200000': 1, '1271462400000': 1, '1271980800000': 1, '1272067200000': 3, '1272412800000': 1, '1272672000000': 2, '1273276800000': 3, '1273622400000': 1, '1273881600000': 1, '1274486400000': 3, '1274572800000': 1, '1274832000000': 1, '1275091200000': 1, '1275350400000': 1, '1275696000000': 3, '1276041600000': 1, '1276300800000': 1, '1276905600000': 3, '1277251200000': 2, '1277510400000': 1, '1277942400000': 1, '1278115200000': 3, '1278460800000': 1, '1278720000000': 1, '1279324800000': 3, '1279670400000': 1, '1279843200000': 1, '1279929600000': 1, '1280534400000': 3, '1280620800000': 1, '1281139200000': 1, '1281744000000': 3, '1282348800000': 1, '1282521600000': 1, '1282953600000': 3, '1283299200000': 1, '1283558400000': 1, '1285200000000': 1, '1285891200000': 1, '1287792000000': 1, '1288569600000': 1, '1290470400000': 1, '1291161600000': 1, '1293062400000': 1, '1296518400000': 1, '1298937600000': 1, '1301616000000': 1, '1304208000000': 1, '1306886400000': 1, '1309478400000': 1, '1312156800000': 1, '1314835200000': 1, '1317427200000': 1, '1320105600000': 1, '1322697600000': 1, '1328054400000': 1, '1330560000000': 1, '1333238400000': 1, '1335830400000': 1, '1338508800000': 1, '1341100800000': 1, '1343779200000': 1, '1346457600000': 1, '1349049600000': 1, '1351728000000': 1, '1354320000000': 1, '1356998400000': 1, '1359676800000': 1, '1362096000000': 1, '1364774400000': 1, '1367366400000': 1, '1370044800000': 1, '1372636800000': 1};

var searchText;
var searchCategory;
var searchTimeScaleMin;
var searchTimeScaleMax;
var searchAbsoluteDate;
var pageNumber;
var perPage = 5;
var qs = new Querystring();

var map = null;
var RSS;

var html_enc = /<&"'/g;
var html_ent = {'<':'<', '&':'&amp;', '"':'&amp;', '\'':'&apos;'};
var js_enc = /<&"'\\/g;
var js_ent = {'\'':'\\x27', '"':'\\x22', '&': '\\x28', '<': '\\x3C', '\\':'\\\\'};
var month_names = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
var monthnames = ['January','February','March','April','May','June','July','August','September','October','November','December'];
var daynames = ['M','T','W','T','F','S','S'];
var fulldaynames = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'];
var previouslyUsedVenues = {};
var lastdate = new Date();

// Debug output function
var debugWrite = function(msg) {
	var newTime = new Date().getTime();
	if(previousTime) var timediff = newTime-previousTime;
	else var timediff = 0;
	previousTime = newTime;
	debugOutput.innerHTML = '[+' + timediff + 'ms] ' + msg + '<br/>' + debugOutput.innerHTML;
}
var debug = function(msg) {
	if(!debugOutput) {
		debugOutput = document.getElementById('debugOutput')
		if(!debugOutput) {
			debug = NULL_FUNC;
		} else {
			debug = debugWrite;
		}
	}
}

// Querystring parsing function
function Querystring(qs) {
	this.params = {};
	if (qs == null) qs = location.href.substring(location.href.indexOf('?')+1);
	if (qs.length == 0) return;
	qs = qs.replace(/\+/g, ' ');
	var args = qs.split('&');
	for (var i = 0; i < args.length; i++) {
		var pair = args[i].split('=');
		var name = decodeURIComponent(pair[0]);
		var value = (pair.length==2)
			? decodeURIComponent(pair[1])
			: name;
		this.params[name.toLowerCase()] = value;
	}
}
Querystring.prototype.get = function(key, default_) {
	var value = this.params[key.toLowerCase()];
	return (value != null) ? value : default_;
}
Querystring.prototype.set = function(key, value) {
	this.params[key.toLowerCase()] = value;
}
Querystring.prototype.contains = function(key) {
	var value = this.params[key.toLowerCase()];
	return (value != null);
}

// Helper class for generating large blocks of web page content
function HTMLBuffer() {
	this.buf = [];
}

function html_encode(text) {
	return text.replace(html_enc, function(v) { return html_ent[v]; });
}

function js_encode(text) {
	return text.replace(js_enc, function(v) { return js_ent[v]; });
}

HTMLBuffer.prototype = {
	push: function() {
		for (var i=0, ilen=arguments.length; i < ilen; i++)
			this.buf.push(arguments[i].toString());
		return this;
	},
	pushEncoded: function() {
		for (var i=0, ilen=arguments.length; i < ilen; i++)
			if(arguments[i])
				this.buf.push(html_encode(arguments[i].toString()));
		return this;
	},
	pushFormatted: function() {
		for (var i=0, ilen=arguments.length; i < ilen; i++)
			this.buf.push(html_encode(arguments[i].toString()).replace(/(\r\n|\r|\n)/g,'<br/>'));
		return this;
	},
	pushJSString: function() {
		for (var i=0, ilen=arguments.length; i < ilen; i++)
			this.buf.push(js_encode(arguments[i].toString()));
		return this;
	},
	pushEscaped: function() {
		for (var i=0, ilen=arguments.length; i < ilen; i++)
			this.buf.push(escape(arguments[i].toString()));
		return this;
	},
	pushURIComponent: function() {
		for (var i=0, ilen=arguments.length; i < ilen; i++)
			this.buf.push(encodeURIComponent(arguments[i].toString()));
		return this;
	},
	pushFriendlyDate: function(i) {
		var d = new Date();
		d.setTime(i);
		this.buf.push(d.getDate().toString());
		switch(d.getDate()){
			case 11: this.buf.push('th'); break;
			case 12: this.buf.push('th'); break;
			case 13: this.buf.push('th'); break;
			default: switch(d.getDate() % 10) {
				case 1: this.buf.push('st'); break;
				case 2: this.buf.push('nd'); break;
				case 3: this.buf.push('rd'); break;
				default: this.buf.push('th'); break;
			}
		}
		this.buf.push(' ', month_names[d.getMonth()], ' ', d.getFullYear().toString());
		return this;
	},
	toString: function() {
		if (this.buf.length == 1)
			return this.buf[0];
		var concatenated = this.buf.join('');
		this.buf = [concatenated];
		return concatenated;
	}
};

// Unified interface for AJAX
function AJAXRequest(url, method, onResponse) {
	var xmlhttp = null;
	if (window.XMLHttpRequest)
		xmlhttp = new XMLHttpRequest(); // IE7 + Mozilla model
	else if (window.ActiveXObject)
		xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); // IE5 + IE6
	else {
		alert('Your browser does not support AJAX.');
		return false;
	};
	this.send = function() {
		xmlhttp.onreadystatechange = function() {
			debug('xmlhttp readystatechange: '+xmlhttp.readyState);
			if (xmlhttp.readyState == 4)
				onResponse(xmlhttp);
		};
		xmlhttp.open(method, url+(url.indexOf('?')>-1?'&':'?')+'no-cache='+Math.random());
		debug('RSS request opened. Sending...');
		xmlhttp.send(null);
	}
}

// Objects to represent a location
function RSSLocation(input) {
	if (!input) {
		this.x = 0;
		this.y = 0;
	}
	else {
		var split = input.indexOf(',');
		this.x = input.substr(0,split);
		this.y = input.substr(split+1);
	}
	this.toString = input;
}

// Object to represent a venue
function RSSVenue(input) {
	if (!input)
		this.Location = new RSSLocation('0,0');
	else
		for (var z=0, zlen=input.length; z<zlen; z++) {
			if (input[z].nodeType == 1) {
				if (input[z].childNodes.length == 0)
					continue;
				switch(input[z].nodeName) {
					case 'name':
						this.Name = input[z].childNodes[0].nodeValue;
						break;
					case 'location':
						this.Location = new RSSLocation(input[z].childNodes[0].nodeValue);
						break;
					case 'postcode':
						this.Postcode = input[z].childNodes[0].nodeValue;
						break;
					case 'guid':
						this.VenueID = input[z].childNodes[0].nodeValue;
						break;
				}
			}
		}
	this.Location.toString = this.Location.x+','+this.Location.y;
	return true;
}
RSSVenue.prototype = {
	Name: '',
	Postcode: '',
	VenueID: '',
	getEvents: function() {
		var evs = RSS.items;
		var returnEvs = [];
		for (var i=0, ilen=evs.length; i<ilen; i++) {
			if (evs[i].Venue.VenueID == this.VenueID)
				returnEvs.push(evs[i]);
		}
		return returnEvs;
	},
	addToMap: function(map) {
		if (!previouslyUsedVenues[this.VenueID]) {
//			debug(' - Adding venue to map <a href="#" onClick="map.SetCenter(new VELatLong('+this.Location.x+','+this.Location.y+')); return false;">'+this.Name+'</a>');
			previouslyUsedVenues[this.VenueID] = true;
			if (this.Location.x && this.Location.y) {
				var point = new VELatLong(this.Location.x, this.Location.y);
				map.AddPushpin(point);
				
				var shape = new VEShape(VEShapeType.Pushpin, point);
				shape.SetTitle(this.Name);
				
				var evs = this.getEvents();
				var buffer = new HTMLBuffer();
				buffer.push('<div><p>The following events are on at this venue: </p><table class="venueevents">');
				for (var i=0, ilen=evs.length; i<ilen; i++) {
					buffer.push('<tr><td>');
					buffer.push('<a href="#" onclick="EventDetailsFromID(\'', evs[i].EventID, '\'); return false;">');
					buffer.pushEncoded(evs[i].Name);
					buffer.push('</a>');
					buffer.push('</td><td>');
					if (evs[i].Dates.length == 1)
						buffer.pushFriendlyDate(evs[i].Dates[0]);
					else if (evs[i].Dates.length > 1)
						buffer.push('Various Dates');
					else
						buffer.push('Dates to be arranged');
					buffer.push('</td></tr>');
				}
				buffer.push('</table></div>');
				var HTML = buffer.toString();
				shape.SetDescription(HTML);
				map.AddShape(shape);
			}
		}
	}
};

// Object to represent an organisation
function RSSOrganisation(input) {
	if (input) {
		var match = /(.+) \((.+)\)/.exec(input);
		if (match) {
			this.Email = match[1];
			if (match[2])
				this.Name = match[2];
		}
	}
}
RSSOrganisation.prototype = {
	Name: '',
	Email: ''
};

// Object to represent an event
function RSSEvent() {
	this.Categories = [];
	this.Dates = [];
	this.Venue = new RSSVenue();
	this.Organisation = new RSSOrganisation();
}
RSSEvent.prototype = {
	Name: '',
	Description: '',
	WebPageURL: '',
	Image: null,
	EventID: '',
	Audience: '',
	Status: '',
	PhoneNumber: '',
	Organiser: '',
	StartDate: null,
	EndDate: null,
	addToMap: function(map) {
		debug('Adding event to map <a href="#" onclick="map.SetCenter(new VELatLong('
				+this.Venue.Location.x+','+this.Venue.Location.y+'));">'+this.Name+'</a>');
		if (!(this.Venue.Location.x && this.Venue.Location.y))
			return;
		// Add positional marker
		var point = new VELatLong(this.Venue.Location.x, this.Venue.Location.y);
		var shape = new VEShape(VEShapeType.Pushpin, point);
		var buffer = new HTMLBuffer();
		shape.SetTitle(this.name);
		//if (this.Image && this.Image.src!="")
		//	buffer.push('<img src="').pushEncoded(this.Image.src).push('" align="left" />'); 
		buffer.push('<p><b>Venue:</b> ').pushEncoded(this.Venue.Name).push('</p>');
		if (this.Dates.length > 0) {
			buffer.push('<h4>Dates:</h4><ul>');
			for (var f=0, flen=this.Dates.length; f<flen; f++) {
				buffer.push('<li>').pushFriendlyDate(this.Dates[f]).push('</li>');
			}
			buffer.push('</ul>');
		}
		else
			buffer.push('<p>Dates to be arranged</p>');
		buffer.push('<p><b>Categories:</b> ');
		if (this.Categories.length > 0) {
			for (var f=0, flen=this.Categories.length; f<flen; f++) {
				var cat=this.Categories[f];
				if (f != 0)
					buffer.push(', ');
				buffer.push('<a href="#" title="Click here for more information about the ').pushEncoded(cat);
				buffer.push(' category">').pushEncoded(cat).push('</a>');
			}
		}
		else
			buffer.push('<i>None</i>');
		buffer.push('</p>');
		var HTML = buffer.toString();
		shape.SetDescription(HTML);
		map.AddShape(shape);
	},
	addToList: function(list) {
		var output = new HTMLBuffer();
		output.push('<div class="eventResult">');
		//if(this.Image && this.Image.src!='')
		//	output.push('<a href="#" onclick="EventDetailsFromID('+this.EventID+')"><img src="').pushEncoded(this.Image.src).push('" style="max-width:70px; max-height:70px;" /></a>');
		output.push('<h3><a href="#" onclick="EventDetailsFromID('+this.EventID+'); return false;">').pushEncoded(this.Name).push('</a></h3>');
		if(this.Categories.length > 0) {
			output.push('<p class="eventCategory">');
			for (var f=0, flen=this.Categories.length; f<flen; f++) {
				if (f != 0)
					output.push(', ');
				output.pushEncoded(this.Categories[f]);
			}
			output.push('</p>');
		}
		output.push('<p class="eventDate">');
		if (this.Dates.length > 0) {
			if(this.Dates.length > 1) {
				output.push('Until ').pushFriendlyDate(this.Dates[this.Dates.length-1]);
			} else {
				output.pushFriendlyDate(this.Dates[0]);
			}
		} else {
			output.push('Dates to be arranged');
		}
		output.push(' <span class="venue">').pushEncoded(this.Venue.Name).push('</span></p><p class="eventDescription">');
		var description = this.Description.replace(/<\s*?[^>]+\s*?>/gi, '');
		if(description.length > 200)
			output.pushEncoded(description.substr(0, 200)).push(' <a href="#" onclick="EventDetailsFromID('+this.EventID+'); return false;">&hellip;more</a>');
		else
			output.pushEncoded(description);
		output.push('</p>');
		output.push('</div>');
		return output;
	},
	setDetailsBox: function() {
		location.href = location.href.split('?')[0].replace(/#/,'') + '#?EventID=' + this.EventID;
		var db = document.getElementById('detailsBody');
		var sdb = document.getElementById('sideDetailsBody');
		sdb.style.display = 'block';
		var mainDetails = new HTMLBuffer();
		var sideDetails = new HTMLBuffer();
		mainDetails.push('<h1>').pushEncoded(this.Name).push('</h1><div class="eventItemMainDetails"><p class="date">');
		if (this.Dates.length > 0) {
			if(this.Dates.length > 1) {
				mainDetails.push('Until ').pushFriendlyDate(this.Dates[this.Dates.length-1]);
			} else {
				mainDetails.pushFriendlyDate(this.Dates[0]);
			}
		} else {
			mainDetails.push('Dates to be arranged');
		}
		mainDetails.push('</p><p><a href="#" onclick="if(reSearchOnReturn) { searchControlSubmit(1); reSearchOnReturn = false; } else { PopulateList(pageNumber || 1); ClearDetailsBox(); } return false">&laquo; back to Find An Event</a>');
		mainDetails.push('</p><p>').pushEncoded(this.Venue.Name).push('</p></div><div class="eventItemMain">');
		if (this.Image && this.Image.src!="")
			mainDetails.push('<img src="').pushEncoded(this.Image.src).push('" style="margin:2px;" onerror="this.style.display=\'none\'" alt="').pushEncoded(this.name).push('" />');
		
		// Search description for accessibility info + venue info (this is a filthy hack)
		var descriptionText = this.Description;
		var venueInfo;
		var accessibilityInfo;
		var accessibilityIndex = descriptionText.indexOf('<br><b>Accessibility Info:</b>');
		if(accessibilityIndex > -1) {
			accessibilityInfo = descriptionText.substr(accessibilityIndex + 30).replace(/\r?\n/g, '<br />');
			descriptionText = descriptionText.substr(0, accessibilityIndex);
		}
		var venueIndex = descriptionText.indexOf('<b>Venue Info:</b>') ;
		if(venueIndex > -1) {
			venueInfo = descriptionText.substr(venueIndex+ 18).replace(/\r?\n/g, '<br />');
			descriptionText = descriptionText.substr(0, venueIndex);
		}
		mainDetails.push(descriptionText);
		
		sideDetails.push('<h3 class="eventTitle">DETAILS</h3><p class="venue"><strong>Venue:</strong> ').pushEncoded(this.Venue.Name);
		if(venueInfo)
			sideDetails.push(', ').push(venueInfo);
		sideDetails.push('</p>');
		if(this.Organisation.Name.length > 0) {
			if(this.Organisation.Email.length > 0)
				sideDetails.push('<p class="venue"><strong>Contact:</strong> <a href="mailto:').pushEncoded(this.Organisation.Email).push('">').pushEncoded(this.Organisation.Name).push('</a></p>');
			else
				sideDetails.push('<p class="venue"><strong>Contact:</strong> ').pushEncoded(this.Organisation.Name).push('</p>');
		}
		if(this.PhoneNumber.length > 0)
			sideDetails.push('<p class="venue"><strong>Phone:</strong> ').pushEncoded(this.PhoneNumber).push('</p>');
		if(this.Organiser.length > 0 && (this.Organisation.Name.length == 0 || this.Organisation.Name != this.Organiser))
			sideDetails.push('<p class="venue"><strong>Organiser:</strong> ').pushEncoded(this.Organiser).push('</p>');
		if(this.Categories.length > 0) {
			sideDetails.push('<p class="venue"><strong>Categories:</strong> ');
			for (var f=0, flen=this.Categories.length; f<flen; f++) {
				if (f != 0)
					sideDetails.push(', ');
				var cat = this.Categories[f];
				sideDetails.push('<a href="javascript:selectCategory(\'').pushJSString(cat).push('\');" ');
				sideDetails.push('title="Click here for more information about the ').pushEncoded(cat).push(' category">');
				sideDetails.pushEncoded(this.Categories[f]).push('</a>');
			}
			sideDetails.push('</p>');
		}
		if (accessibilityInfo)
			sideDetails.push('<p class="venue"><strong>Accessibility:</strong> ').push(accessibilityInfo).push('</p>');
		if (this.Dates.length > 0) {
			sideDetails.push('<div class="buttons">');
			var dates = this.Dates.sort();
			for (var f=0, flen=this.Dates.length; f<flen; f++) {
				var googled = googleDate(dates[f]);
				if(f==5) sideDetails.push('<a href="#" onclick="document.getElementById(\'hiddenDates\').style.display=\'block\'; this.style.display=\'none\'; return false;" style="line-height:20px; vertical-align:top;" id="showMoreDates">&hellip;show more dates</a><div id="hiddenDates" style="display: none">');
				sideDetails.push('<ul><li>');
				sideDetails.push('<span class="addtocal">');
				
				// Google calendar
				sideDetails.push('<a href="http://www.google.com/calendar/event?action=TEMPLATE');
				sideDetails.push('&text=').pushURIComponent(this.Name);
				sideDetails.push('&dates=').pushURIComponent(encodeURIComponent(googled)).push("/").pushURIComponent(googled);
				if (this.WebPageURL.length > 0)
					sideDetails.push('&sprop=website:').pushURIComponent(this.WebPageURL);
				if (this.Organisation.Name.length > 0)
					sideDetails.push('&sprop=name:').pushURIComponent(this.Organisation.Name);
				if (this.Description.length > 1200)
					sideDetails.push('&details=').pushURIComponent(this.Description.substr(0,1195) + "(...)");
				else
					sideDetails.push('&details=').pushURIComponent(this.Description);
				if (this.Venue && this.Venue.Location)
					sideDetails.push('&location=', this.Venue.Location.x, ',', this.Venue.Location.y); 
				sideDetails.push('" target="_blank"><img src="/Applications/EventsPortal/images/google.gif" alt="Add to my Google Calendar" /></a>');
				
				// vCalendar
				sideDetails.push('<a href="/Applications/EventsPortal/ical.aspx?title=').pushURIComponent(this.Name);
				sideDetails.push('&start=').pushEscaped(googled).push('T000000&end=').pushEscaped(googled).push('T235959');
				sideDetails.push('&desc=').pushURIComponent(this.Description.replace(/<[^>]*>/gi, ''));
				sideDetails.push('&loc=').pushURIComponent(this.Venue.Name);
				sideDetails.push('"><img src="/Applications/EventsPortal/images/outlook.gif" alt="Add to my Outlook Calendar (vCal file)" /></a>');
				
				// Yahoo!
				sideDetails.push('<a href="http://calendar.yahoo.com/?v=60&view=d&type=20&title=').pushEscaped(this.Name);
				sideDetails.push('&st=').push(googled);
				sideDetails.push('&desc=').pushEscaped(this.Description.replace(/\u2019/g,"'"));
				sideDetails.push('&in_loc=').pushEscaped(this.Venue.Name);
				sideDetails.push('" target="_blank"><img src="/Applications/EventsPortal/images/yahoo.gif" alt="Add to my Yahoo! Calendar" /></a>');
				
				sideDetails.push('</span>');

				sideDetails.pushFriendlyDate(dates[f]).push('</li></ul><br />');
			}
			if(this.Dates.length > 5) sideDetails.push('<a href="#" onclick="document.getElementById(\'hiddenDates\').style.display=\'none\'; document.getElementById(\'showMoreDates\').style.display=\'block\'; return false;" style="line-height:20px; vertical-align:top;">show fewer dates</a></div>');
			sideDetails.push('</div>');
		}
		
		if (this.WebPageURL.length > 0) {
			mainDetails.push('<p><a target="blank" href="');
			mainDetails.pushEncoded(this.WebPageURL);
			mainDetails.push('">Click here for further information (will open in a new window)</a></p>');
		}
		var permalinkURL = location.href.split('?')[0].replace(/#/,'') + '?EventID=' +this.EventID;
		mainDetails.push('<p><a href="').push(permalinkURL).push('"');
		mainDetails.push(' onclick="try { window.clipboardData.setData(\'Text\', \''+permalinkURL+'\'); alert(\'A link to this page has been copied to your clipboard.\'); return false } catch(e) { return true }"');
		mainDetails.push('>Link to this page</a></p>');
		if (fullCalendar) {
			mainDetails.push(
				'<p><a href="#" onclick="document.getElementById(\'detailsBody\').innerHTML = fullCalendar; return false;">',
				'Return to calendar view</a></p>',
				'<p><a href="#" onclick="hideCalendar(); EventDetailsFromID(', this.EventID, '); return false;">Show on map view</a></p>'
			);
		}
		db.innerHTML = mainDetails.toString();
		sdb.innerHTML = sideDetails.toString();
		// move map
		map.DeleteAllShapes();
		previouslyUsedVenues = {};
		this.Venue.addToMap(map);
		map.SetCenter(new VELatLong(this.Venue.Location.x,this.Venue.Location.y))
	}
};

// Hashtable functions for RSS 'parsing'
function RSSPTitle(node, returnItem) { 
	returnItem.Name = node.text || node.textValue; 
	return returnItem; 
}
function RSSPDescription(node, returnItem) { 
	returnItem.Description = node.text || node.textValue; 
	return returnItem; 
}
function RSSPLink(node, returnItem) { 
	returnItem.WebPageURL = node.text || node.textValue; 
	return returnItem; 
}
function RSSPAuthor(node, returnItem) { 
	returnItem.Organisation = new RSSOrganisation(node.text || node.textValue); 
	return returnItem; 
}
function RSSPGuid(node, returnItem) { 
	returnItem.EventID = node.text || node.textValue; 
	eventsByID[returnItem.EventID] = returnItem; 
	return returnItem; 
}
function RSSPCategory(node, returnItem) { 
	var nodeValue = node.text || node.textValue;
	if(nodeValue) { 
		var idx = nodeValue.indexOf('#');
		if(idx >= 0) nodeValue = nodeValue.substr(0, idx);
		returnItem.Categories.push(nodeValue); 
	}
	return returnItem; 
}
function RSSPEnclosure(node, returnItem) { 
	returnItem.Image = new Image(); 
	returnItem.Image.src = node.getAttribute('url');
	return returnItem; 
}
function RSSPBWSVenue(node, returnItem) { 
	returnItem.Venue = new RSSVenue(node.childNodes); 
	return returnItem; 
}
function RSSPBWSStatus(node, returnItem) { 
	returnItem.Status = node.text || node.textValue; 
	return returnItem; 
}
function RSSPBWSAudience(node, returnItem) { 
	returnItem.Audience = node.text || node.textValue; 
	return returnItem; 
}
function RSSPBWSDate(node, returnItem) { 
	returnItem.Dates.push(Date.parse(node.text || node.textValue)); 
	return returnItem; 
}
function RSSPBWSTelephone(node, returnItem) { 
	returnItem.PhoneNumber = node.getAttribute('number'); 
	return returnItem; 
}
function RSSPBWSOrganiser(node, returnItem) {
	returnItem.Organiser = node.getAttribute('name');
	return returnItem;
}
var RSSP = {
	'title': RSSPTitle,
	'description': RSSPDescription,
	'link': RSSPLink,
	'author': RSSPAuthor,
	'guid': RSSPGuid,
	'category': RSSPCategory,
	'enclosure': RSSPEnclosure,
	'bws:venue': RSSPBWSVenue,
	'bws:status': RSSPBWSStatus,
	'bws:audience': RSSPBWSAudience,
	'bws:date': RSSPBWSDate,
	'bws:telephone': RSSPBWSTelephone,
	'bws:organiser': RSSPBWSOrganiser
}

// Object to read RSS feed of events
function RSSFeedReader(url) {
	this.items = [];
	debug('RSS Feed Reader: '+url);
	this.AJAX = new AJAXRequest(url, 'GET', this.loadFromXML);
}
RSSFeedReader.prototype = {
	update: function() {
		dataLoaded = false;
		this.AJAX.send();
	},
	loadFromXML: function(xmlhttp) {
		if (xmlhttp.status==200) {
			searchWasLimited = false;
			debug('RSS Update Response Received');
			var items = xmlhttp.responseXML.getElementsByTagName('item');
			var returnArray = [];
			//dateEventsCache = {}; // Clear date cache for calendar filtering
			// Loop through feed items
			if(isFirstLoad) {
				var evtID = parseInt(qs.get("EventID"));
			}
			debug('Processing XML data...');
			var count = 0;
			for (i=0, ilen=items.length; i<ilen; i++) {
				var item = items[i];
				if(item.nodeName != 'item') continue;
				count++;
				if(count==searchLimit && !(isFirstLoad && !isNaN(evtID))) {
					searchWasLimited = true;
					break;
				}
				var returnItem = new RSSEvent();
				for (var node = item.firstChild; node; node=node.nextSibling) {
					if (node.nodeType==1) 
						if(RSSP[node.nodeName]) returnItem = RSSP[node.nodeName](node, returnItem);
				}
				returnArray.push(returnItem);
			}
			debug('Data processed.');
			dataLoaded = true;
			RSS.items = returnArray.sort(function(a,b) { return a.Name.toLowerCase() > b.Name.toLowerCase() ? 1 : -1 });
			debug('Data sorted alphabetically.');
			if(isFirstLoad) {
				mapLoad();
				LoadCalendar();
				// Show an event if an event ID has been passed in.
				if(!isNaN(evtID) && eventsByID[evtID]) {
					debug('Event ID is valid');
					EventDetailsFromID(evtID);
					qs.set("EventID", "");
					isFirstLoad = false;
					reSearchOnReturn = true;
					return;
				} else {
					debug('Event ID is invalid');
					searchControlSubmit(1);
				}
			}
			pageNumber = 1;
			PopulateList(pageNumber);
			if(mapLoaded) PopulateMap();
			else map.onLoadMap = PopulateMap;
			isFirstLoad = false;
		}
		else {
			alert('An error has occurred. Please refresh and try again.\n\nUnexpected response: '+xmlhttp.status+':'+xmlhttp.statusText);
			debug('RSS: Unexpected response: '+xmlhttp.status+':'+xmlhttp.statusText);
			return false;
		}
	}
};

// Called when the map loads
function MapLoadHandler() {
	debug('Map loaded.');
	mapLoaded = true;
}

// Populate the map with items
function PopulateMap() {
	if(!dataLoaded) { debug('Data not yet loaded for map, waiting 200ms and trying again'); setTimeout('PopulateMap()', 200); }
	debug('Populating map');
	map.DeleteAllShapes();
	if(RSS.items.length > 0) {
		previouslyUsedVenues = {};
		if(searchText && searchText.length > 0) {
			var pageCount = PageCount();
			var startOfPage = (pageNumber-1) * perPage;
			var endOfPage = startOfPage + perPage;
			for(var x=startOfPage; x<endOfPage; x++) {
				if(RSS.items[x]) RSS.items[x].Venue.addToMap(map);
			}
		} else {
			for (var x=0, xlen=RSS.items.length; x<xlen; x++) {
				if(RSS.items[x]) RSS.items[x].Venue.addToMap(map);
			}
		}
	}
	debug('Map populated');
}

// Populate the search results with items
function PopulateList(page) {
	//if(!dataLoaded) { debug('Data not yet loaded, for result list, waiting 200ms and trying again'); setTimeout('PopulateList()', 200); return false; }
	if(!page) page = 1;
	debug('Populating search results');
	document.getElementById('detailsBody').innerHTML = '';
	document.getElementById('sideDetailsBody').innerHTML = '';
	document.getElementById('sideDetailsBody').style.display = 'none';
	document.getElementById('backLinkBox').style.display = 'none';
	document.getElementById('backLinkBox').innerHTML = '';
	var resultList = document.getElementById('eventResultsList');
	var pageCount = PageCount();
	if(page > pageCount) page = pageCount;
	if(!resultList) return false;
	var output = new HTMLBuffer();
	output.push('<h1>Find An Event</h1>');
	output.push('<p>You searched <strong>');
	if(searchCategory && searchCategory.length > 0)
		output.pushEncoded(searchCategory);
	else
		output.pushEncoded('All Of What\'s On');
	output.push('</strong> ');
	if(searchText && searchText.length > 0 && searchText != ' ')
		output.push('for <strong>').pushEncoded(searchText).push('</strong> ');
	if(searchAbsoluteDate) {
		output.push('on <strong>').push(searchAbsoluteDate).push('</strong> ');
	} else {
		var timeRange = document.getElementById('MaxDaysAhead');
		var chosenTimeRange = timeRange.options[timeRange.selectedIndex].text;
		switch(chosenTimeRange) {
			case 'Today': output.push('<strong>Today</strong>'); break;
			case 'Tomorrow': output.push('<strong>Tomorrow</strong>'); break;
			default: output.push('in the <strong>').pushEncoded(chosenTimeRange).push('</strong>');
		}
	}
	output.push('</p>');
	if(searchWasLimited)
		output.push('<p>Your search was limited to <strong>'+searchLimit+'</strong> events. Please narrow down your criteria for better results.</p>');
	debug(RSS.items.length + ' items');
	if(RSS.items && RSS.items.length > 0) {
		var startOfPage = (page-1) * perPage;
		var endOfPage = startOfPage + perPage - 1;
		if(endOfPage > RSS.items.length) endOfPage = RSS.items.length - 1;
		debug('Displaying page: '+page+'/'+pageCount);
		debug('Displaying items: '+startOfPage+'-'+endOfPage);
		for(var x=startOfPage; x<=endOfPage; x++) {
			if(RSS.items[x])
				output.push(RSS.items[x].addToList(resultList));
			else
				debug('Item '+x+' not found');
		}
		output.push('<div class="sres-info"><div class="spag"><ul id="searchPaginationAfter"><li>Loading...</li></ul></div></div>');
		resultList.innerHTML = output;
		var pager = document.getElementById('searchPaginationAfter');
		if(!pager) return false;
		output = new HTMLBuffer();
		output.push('<li>Pages:</li>');
		var firstPageToShow = 1;
		var lastPageToShow = pageCount;
		if(pageCount > 10) {
			if((page-firstPageToShow) > 5 && (lastPageToShow-page) > 5) {
				firstPageToShow = page - 5;
				lastPageToShow = page + 5;
			} else if((page-firstPageToShow) <= 5) {
				lastPageToShow = firstPageToShow + 10;
			} else if((lastPageToShow-page) >= 4) {
				firstPageToShow = lastPageToShow - 10;
			}
		}
		for(var x=firstPageToShow; x<=lastPageToShow; x++) {
			if(x!=firstPageToShow) output.push('<li style="list-style: none">,</li>');
			output.push('<li');
			if(x == page) output.push(' class="active"><strong>');
			else output.push('><a href="?search' + (searchText?'&SearchText='+encodeURIComponent(searchText):'') + (searchCategory?'&Categories='+encodeURIComponent(searchCategory):'') + (searchTimeScaleMin?'&MinDaysAhead='+encodeURIComponent(searchTimeScaleMin):'') + (searchTimeScaleMax?'&MaxDaysAhead='+encodeURIComponent(searchTimeScaleMax):'')+'&Page='+x+'" onclick="PopulateList('+x+'); PopulateMap(); return false">');
			output.push(x)
			if(x == page) output.push('</strong>');
			else output.push('</a>');
			output.push('</li>');
		}
		if(page <= (pageCount-1)) output.push('<li><a href="?search' + (searchText?'&SearchText='+encodeURIComponent(searchText):'') + (searchCategory?'&Categories='+encodeURIComponent(searchCategory):'') + (searchTimeScaleMin?'&MinDaysAhead='+encodeURIComponent(searchTimeScaleMin):'') + (searchTimeScaleMax?'&MaxDaysAhead='+encodeURIComponent(searchTimeScaleMax):'')+'&Page='+(parseInt(page)+1)+'" onclick="PopulateList('+(parseInt(page)+1)+'); PopulateMap(); return false">Next</a></li>');
		pager.innerHTML = output;
	} else if(dataLoaded) {
		output.push('<p class="noresults">There were no results for your search.</p>');
		resultList.innerHTML = output;
	}
	pageNumber = page;
	debug('Populated search results');
}

// Returns the number of pages from the current result set
function PageCount() {
	return Math.ceil(RSS.items.length/perPage);
}

// Call EventDetails for a given event ID
function EventDetailsFromID(id) {
	eventsByID[id].setDetailsBox();
	document.getElementById('eventResultsList').innerHTML= '';
}

// Clears the details box
function ClearDetailsBox() {
	document.getElementById('detailsBody').innerHTML = '';
	document.getElementById('sideDetailsBody').innerHTML = '';
	PopulateMap();
	map.SetCenter(VenuesCentre());
}

// Find the geographic centre of the events
function VenuesCentre() {
	var evs = RSS.items;
	if (evs.length == 0)
		return new VELatLong(51.507921,0.0265); 
	var xMax=0, xMin=0, yMax=0, yMin=0;
	previouslyUsedVenues = {};
	for (var i=0, ilen=evs.length; i<ilen; i++) {
		var v = evs[i].Venue;
		if (!previouslyUsedVenues[v.venueID]) {
			if (v.Location.x > xMax || xMax == 0) xMax = parseFloat(v.Location.x);
			if (v.Location.x < xMin || xMin == 0) xMin = parseFloat(v.Location.x);
			if (v.Location.y > yMax || yMax == 0) yMax = parseFloat(v.Location.y);
			if (v.Location.y < yMin || yMin == 0) yMin = parseFloat(v.Location.y);
			previouslyUsedVenues[v.VenueID] = true;
		}
	}
	previouslyUsedVenues = {};
	var x = (xMax + xMin)/2;
	var y = (yMax + yMin)/2;
	x = x.toFixed(6);
	y = y.toFixed(6);
	if (x==0)
		x = 51.507921;
	if (y==0)
		y = 0.0265;
	return new VELatLong(x,y);
}

// Converts a date to a format suitable for Google calendar
function googleDate(i) {
	var d = new Date();
	d.setTime(i);
	var year = d.getFullYear().toString();
	var month = (d.getMonth()+1).toString();
	if (month.length < 2)
		month = '0' + month;
	var date = d.getDate().toString();
	if (date.length < 2)
		date = '0' + date;
	return year+month+date;
}
	
// Quickly check if a date has events
function DateHasEvents(d) {
	var indexDate = ClearTime(d);
	return dateEventsCache[indexDate] || 0;
}

// Removes the time component of a date
function ClearTime(a) {
	r = new Date(a);
	return r.setUTCHours(0,0,0,0);
}

// get minimum and maximum dates
function GetMinMaxDate() {
	var evs = RSS.items;
	var minDate, maxDate;
	for (var i=0, ilen=evs.length; i<ilen; i++) {
		var ev = evs[i];
		for (var j=0, jlen=ev.Dates.length; j<jlen; j++) {
			var date = ev.Dates[j];
			if (!minDate || ev.Dates[j] < minDate) minDate = date;
			if (!maxDate || ev.Dates[j] > maxDate) maxDate = date;
		}
	}
	return {_mindate:minDate, _maxDate:maxDate};
}

// Get events for a date
function DateEvents(d) {
	var d = new Date(ClearTime(d));
	var evs = RSS.items;
	var returnEvs = [];
	for (var i=0, ilen=evs.length; i<ilen; i++) {
		var ev = evs[i];
		for (var j=0, jlen=ev.Dates.length; j<jlen; j++) {
			if (d.getTime() == ClearTime(ev.Dates[j])) 
				returnEvs.push(ev);
		}
	}
	return returnEvs;
}

// List the events for a date
function ListEventsForDate(d) {
	var d = new Date(d);
	document.getElementById('sideDetailsBody').innerHTML = '';
	document.getElementById('detailsBody').innerHTML = '';
	document.getElementById('eventResultsList').innerHTML = '<h1>Find an Event</h1><p>Please wait while the events are loaded&hellip;</p>';
	searchAbsoluteDate = d.getDate()+'-'+(d.getMonth()+1)+'-'+d.getFullYear();
	searchText = '';
	document.getElementById('SearchText').value = '';
	searchCategory = '';
	document.getElementById('Categories').selectedIndex = 0;
	searchTimeScale = '';
	document.getElementById('MaxDaysAhead').selectedIndex = 0;
	var appendage = '?Start=' + searchAbsoluteDate + '&End=' + searchAbsoluteDate;
	feedURL = startFeedURL + appendage;
	pageNumber = 1;
	location.href = location.href.split('?')[0].replace(/#/,'') + '#' + appendage;
	
	// Load the data in
	RSS = new RSSFeedReader(feedURL);
	RSS.update();
}

// Loads a calendar
function LoadCalendar(d) {
	lastdate = d = d || lastdate;
	document.getElementById('calendar').innerHTML = 'Loading calendar...';
	debug('Start calendar load');
	document.getElementById('calendar').innerHTML = GenerateCalendar(d);
	debug('Calendar loaded.');
}

// Generates the HTML for a calendar
function GenerateCalendar(i) {
	var d = new Date(i);
	var o = GetMinMaxDate();
	var cl='cal';
	var cledge=' class="'+cl+' '+cl+'edge"';
	var clwday=' class="'+cl+' '+cl+'wday"';
	var cldate=' class="'+cl+' '+cl+'date';
	var clother=cldate+' '+cl+'dateother"';
	var clerror=cldate+' '+cl+'error"';
	var cldatesel=cldate+' '+cl+'datesel"';
	cldate+='"';
	cl=' class="'+cl+'"';
	var startOfMonth = new Date(d.getFullYear(), d.getMonth(), 1);
	var lastMonth = new Date(d);
	lastMonth.setDate(startOfMonth.getDate()-20);
	var nextMonth = new Date(d);
	nextMonth.setDate(startOfMonth.getDate()+40);
	var lastYear = new Date(d);
	lastYear.setDate(startOfMonth.getDate()-350);
	var nextYear = new Date(d);
	nextYear.setDate(startOfMonth.getDate()+370);
	var buffer = new HTMLBuffer();
	d.setDate(1);
	
	buffer.push('<div class="dateNav"><a href="#" onclick="LoadCalendar('+lastMonth.getTime()+'); return false;"><<</a> ',monthnames[d.getMonth()],' <a href="#" onclick="LoadCalendar(', nextMonth.getTime(), '); return false;">>></a></div>');
	buffer.push('<table>');
	buffer.push('<thead><tr>');
			
	var dmonth=d.getMonth();
	buffer.push('<tr>');
	for (var i=0; i<7; i++)
		buffer.push('<th', clwday, '>', daynames[i], '</th>');
	var dow=d.getDay();
	dow=(dow+6)%7;
	d = new Date(d);
	var dyear=d.getFullYear();
	d.setDate(1-dow);
	var minday=1-dow;
	buffer.push('</thead></tr>');
	
	buffer.push('<tbody>');

	for (var i=0; i<6; i++) {
		buffer.push('<tr>');
		for (var j=0; j<7; j++) {
			var eventCount = DateHasEvents(d);
			if (eventCount == 0)
				buffer.push('<td', clerror, '>', d.getDate());
			else {
				buffer.push('<td', (d.getMonth()!=dmonth?clother:(d-0==o._lastdate-0&&!invaliddate)?cldatesel:cldate), '>',
					'<span title="', eventCount, ' event'+(eventCount>1?'s':''), '">',
					'<a title="', eventCount, ' event',(eventCount>1?'s':''),
					'" href="#" onclick="ListEventsForDate(', d.getTime(), '); return false;" class="calevents">',
					d.getDate(),
					'</a></span>'
				);
			}
			buffer.push('</td>');
			d.setDate(d.getDate()+1);
		}
		buffer.push('</tr>');
	}
	
	buffer.push('</tbody>');
	buffer.push('</table>');
	buffer.push('<div class="dateNav"><a href="#" onclick="LoadCalendar(', lastYear.getTime(), '); return false;"><<</a> ',dyear,' <a href="#" onclick="LoadCalendar(', nextYear.getTime(), '); return false;">>></a></div>');

	d.setDate(d.getDate()-20);
	
	return buffer.toString();
}

// Generates a month dropdown
function monthDropdown(obj, d) {
	obj.onclick = NULL_FUNC;
	var date = new Date(d);
	var month = date.getMonth();
	var buffer = new HTMLBuffer();
	buffer.push('<select onchange="enlargedCalendarMonth(new Date(', date.getFullYear(), ',this.value,1));">');
	for (var m=0; m<12; m++) {
		buffer.push('<option value="', m, '"');
		if (m == month)
			buffer.push(' selected="selected"');
		buffer.push('>', monthnames[m], '</option>');
	}
	buffer.push('</select>');
	obj.innerHTML = buffer.toString();
}

// Hides the calendar
function hideCalendar() {
	document.getElementById('mgContent').innerHTML = content;
	fullCalendar = null;
	mapLoad();
	
	// Update from RSS Data
	for (var x=0, xlen=RSS.items.length; x<xlen; x++)
		RSS.items[x].Venue.addToMap(map);
}

// Generates the HTML for an enlarged month calendar
function enlargedCalendarMonth(i) {
	if (!i) i = new Date();
	var d = new Date(i);
	var o = GetMinMaxDate();
	var cl='cal';
	var cledge=' class="'+cl+' '+cl+'edge"';
	var clwday=' class="'+cl+' '+cl+'wday"';
	var cldate=' class="'+cl+' '+cl+'date';
	var clother=cldate+' '+cl+'dateother"';
	var clerror=cldate+' '+cl+'error"';
	var cldatesel=cldate+' '+cl+'datesel"';
	cldate+='"';
	cl=' class="'+cl+'"';
	var startOfMonth = new Date(d.getFullYear(), d.getMonth(), 1);
	var lastMonth = new Date(d);
	lastMonth.setDate(startOfMonth.getDate()-20);
	var nextMonth = new Date(d);
	nextMonth.setDate(startOfMonth.getDate()+40);
	var lastYear = new Date(d);
	lastYear.setDate(startOfMonth.getDate()-350);
	var nextYear = new Date(d);
	nextYear.setDate(startOfMonth.getDate()+370);
	var buffer = new HTMLBuffer();
	d.setDate(1);
	buffer.push(
		'<div id="detailsBody"><table cellpadding=0 cellspacing=0 border=0 class="fullcal"><tr>',
		'<td', cledge, '><table style="width:100%" cellpadding=0 cellspacing=0 border=0><tr>',
		'<td', cledge, ' align="center"><a href="#" onclick="enlargedCalendarMonth(', lastMonth.getTime(),
		'); return false;"><</a> <span onclick="monthDropdown(this, ', d.getTime(), ');">', monthnames[d.getMonth()],
		'</span> <a href="#" onclick="enlargedCalendarMonth(', nextMonth.getTime(), '); return false;">></a></td>'
	);
	buffer.push('</tr></table></td>');
	buffer.push('</tr><tr>');
	buffer.push('<td', cl, '>');

	var dmonth=d.getMonth();
	buffer.push('<table cellpadding="2" cellspacing="0" border="0"><tr>');
	for (var i=0; i<7; i++)
		buffer.push('<td', clwday, '>', fulldaynames[i], '</td>');
	var dow=d.getDay();
	dow = (dow+6)%7;
	d = new Date(d);
	var dyear = d.getFullYear();
	d.setDate(1-dow);
	var minday = 1-dow;

	for (var i=0; i<6; i++) {
		buffer.push('<tr>');
		for (var j=0; j<7; j++) {
			if ((o._mindate&&d<o._mindate)||(o._maxdate&&d>o._maxdate))
				buffer.push('<td', clerror, '><div class="number">', d.getDate(), '</div>'); 
			else {
				buffer.push('<td', (d.getMonth()!=dmonth?clother:(d-0==o._lastdate-0&&!invaliddate)?cldatesel:cldate), '>');
				var eventlist = DateEvents(d);
				eventCount = eventlist.length;
				if (eventCount > 0 && content) {
					buffer.push('<div class="number">', d.getDate(), '</div>');
					buffer.push('<div class="fullcalevents">');
					for (var k=0; k<eventCount;k++) {
						var ev = eventlist[k];
						buffer.push(
							'<div class="fullcalevent"><a href="#" onclick="EventDetailsFromID(', ev.EventID, '); return false;"> ');
						buffer.pushEncoded(ev.Name).push('</div>');
					}
					buffer.push('</div>');
				}
				else buffer.push('<div class="number">', d.getDate(), '</div>');
			}
			buffer.push('</td>');
			d.setDate(d.getDate()+1);
		}
		buffer.push('</tr>');
	}
	buffer.push('</table>', '</td>', '</tr>', '<tr>');
	buffer.push('<td', cledge, ' align="center">');
	buffer.push('<a href="#" onclick="enlargedCalendarMonth(', lastYear.getTime(),'); return false;" show="-350"><</a>');
	buffer.push('<span>',dyear,'</span>');
	buffer.push('<a href="#" onclick="enlargedCalendarMonth(', nextYear.getTime(), '); return false;" show="370">></a></td>');
	buffer.push('</tr></table>');
	if (content)
	{
		buffer.push('<div id="enlargeCalendarMonth">');
		buffer.push('<a href="#" onclick="hideCalendar(); return false;">Close</a>');
		buffer.push(' | ');
		buffer.push('<a href="#" onclick="enlargeCalendarWeek(', startOfMonth.getTime(),'); return false;">Week View</a>');
		buffer.push('</div>');
	}
	var s = buffer.toString();
	fullCalendar = s;
	document.getElementById('mgContent').innerHTML = s;
}

// Loads the enlarged month calendar
function enlargeCalendarMonth(i) {
	i = i ? new Date(i) : new Date();
	content = content || document.getElementById('mgContent').innerHTML;
	enlargedCalendarMonth(i);
}

// Generates the HTML for an enlarged week calendar
function enlargedCalendarWeek(i) {
	if (!i) i = new Date();
	var d = new Date(i);
	var o = GetMinMaxDate();
	var cl='cal';
	var cledge=' class="'+cl+' '+cl+'edge"';
	var clwday=' class="'+cl+' '+cl+'wday"';
	var cldate=' class="'+cl+' '+cl+'date';
	var clother=cldate+' '+cl+'dateother"';
	var clerror=cldate+' '+cl+'error"';
	var cldatesel=cldate+' '+cl+'datesel"';
	cldate+='"';
	cl=' class="'+cl+'"';
	var startOfWeek = new Date(d.getFullYear(), d.getMonth(), d.getDate() + 1 - d.getDay());
	var lastWeek = new Date(d);
	lastWeek.setDate(startOfWeek.getDate()-7);
	var nextWeek = new Date(d);
	nextWeek.setDate(startOfWeek.getDate()+7);
	var lastYear = new Date(d);
	lastYear.setDate(startOfWeek.getDate()-350);
	var nextYear = new Date(d);
	nextYear.setDate(startOfWeek.getDate()+370);
	var buffer = new HTMLBuffer();
	buffer.push(
		'<div id="detailsBody"><table cellpadding=0 cellspacing=0 border=0 class="fullcalweek"><tr>',
		'<td', cledge, '><table style="width:100%" cellpadding=0 cellspacing=0 border=0><tr>',
		'<td', cledge, ' align="center"><a href="#" onclick="enlargedCalendarWeek(', lastWeek.getTime(),
		'); return false;"><</a>',
		'<span>', 'Week starting ', startOfWeek.getDate(), ' ', monthnames[startOfWeek.getMonth()],
		' ', startOfWeek.getFullYear(),
		'</span> <a href="#" onclick="enlargedCalendarWeek(', nextWeek.getTime(), '); return false;">></a>',
		'</td>'
	);
	buffer.push('</tr></table></td>');
	buffer.push('</tr><tr>');
	buffer.push('<td', cl, '>');

	buffer.push('<table cellpadding="2" cellspacing="0" border="0">');
	for (var i=0; i<7; i++)
	{
		var d = new Date(startOfWeek);
		d.setDate(d.getDate()+i);
		buffer.push('<tr>');
		buffer.push('<td', clwday, '>', fulldaynames[i], ' ', d.getDate(), '</td>');
		buffer.push('<td', cldate, '>');
		
		var eventlist = DateEvents(d);
		eventCount = eventlist.length;
		if (eventCount > 0 && content) {
			buffer.push('<div class="fullcalevents">');
			for (var k=0; k<eventCount;k++) {
				var ev = eventlist[k];
				buffer.push(
					'<div class="fullcalevent"><a href="#" onclick="EventDetailsFromID(', ev.EventID, '); return false;"> ');
				buffer.pushEncoded(ev.Name).push('</div>');
			}
			buffer.push('</div>');
		} else {
			buffer.pushEncoded(' ');
		}
		
		buffer.push('</td>');

		buffer.push('</tr>');
	}
	buffer.push('</tr>');
	buffer.push('</table>', '</td>', '</tr>', '</table>');
	var month = new Date(startOfWeek);
	month.setDate(0);
	if (content)
	{
		buffer.push('<div id="enlargeCalendarWeek">');
		buffer.push('<a href="#" onclick="hideCalendar(); return false;">Close</a>');
		buffer.push(' | ');
		buffer.push('<a href="#" onclick="enlargeCalendarMonth(', startOfWeek.getTime(),'); return false;">Month View</a>');
		buffer.push('</div>');
	}
	var s = buffer.toString();
	fullCalendar = s;
	document.getElementById('mgContent').innerHTML = s;
}

// Loads the enlarged week calendar
function enlargeCalendarWeek(i) {
	i = i ? new Date(i) : new Date();
	content = content || document.getElementById('mgContent').innerHTML;
	enlargedCalendarWeek(i);
}

// Loads the map
function mapLoad() {
	debug('Starting map load');
	try {
		map = new VEMap('map_canvas');
		debug('Map object created');
		map.onLoadMap = MapLoadHandler;
		map.SetDashboardSize(VEDashboardSize.Small);
		map.LoadMap(new VenuesCentre(), 12, VEMapStyle.Road, false, VEMapMode.Mode2D, true, 1);
	}
	catch(e) {
		debug('Error loading map. ' + e.name+': '+ e.message);
		document.getElementById('map_canvas').innerHTML = 'An error has occurred loading the map. (<a href="#" onclick="location.href=location.href; return false">retry</a>)';
	}
}

// Cross browser function to attach an event to an object
function addEventSimple(obj,evt,fn) {
	if (obj.attachEvent) {
		obj.attachEvent('on'+evt,fn);
	}
	else if (obj.addEventListener) {
		obj.addEventListener(evt,fn,false);
	}
}

// Adds a submission event handler for the search bar
function searchControlLoad() {
	addEventSimple(document.getElementById('searchControl'), 'submit', searchControlSubmit);
}

// Handles the submission of the search bar
function searchControlSubmit(ev) {
	ev = ev || event || 1;
	searchText = document.getElementById('SearchText').value;
	if(searchText == '') searchText = '';
	searchCategory = document.getElementById('Categories').value;
	searchTimeScale = document.getElementById('MaxDaysAhead').value;
	searchAbsoluteDate = '';
	var appendage = '?search' + (searchText?'&SearchText='+encodeURIComponent(searchText):'') + (searchCategory?'&Categories='+encodeURIComponent(searchCategory):'') + searchTimeScale;
	feedURL = startFeedURL + appendage;
	location.href = location.href.split('?')[0].replace(/#/,'') + '#' + appendage;
	pageNumber = 1;
	document.getElementById('detailsBody').innerHTML = '';
	document.getElementById('sideDetailsBody').innerHTML = '';
	document.getElementById('sideDetailsBody').style.display = 'none';
	document.getElementById('eventResultsList').innerHTML = '<h1>Find an Event</h1><p>Please wait while the events are loaded&hellip;</p>';
	RSS = new RSSFeedReader(feedURL);
	RSS.update();
	
	// block submission
	if(ev != 1) ev.returnValue = false;
	return false;
}

// Handles a category being clicked
function selectCategory(catName) {
	var appendage = '?Categories='+encodeURIComponent(catName)+'&MinDaysAhead=0&MaxDaysAhead=28';
	feedURL = startFeedURL + appendage;
	location.href = location.href.split('?')[0].replace(/#/,'') + '#' + appendage;
	searchCategory = catName;
	searchTimeScaleMin = 0;
	searchTimeScaleMax = 28;
	pageNumber = 1;
	searchText = '';
	document.getElementById('SearchText').value = '';
	var categoryDD = document.getElementById('Categories');
	for(var p=0,plen=categoryDD.options.length;p<plen;p++) {
		if(searchCategory == categoryDD.options[p].value) {
			categoryDD.selectedIndex = p;
			break;
		}
	}
	var timeRangeDD = document.getElementById('MaxDaysAhead');
	for(var o=0,olen=timeRangeDD.options.length;o<olen;o++) {
		if('&MinDaysAhead='+searchTimeScaleMin+'&MaxDaysAhead='+searchTimeScaleMax == timeRangeDD.options[o].value) {
			timeRangeDD.selectedIndex = o;
			break;
		}
	}
	searchTimeScale = '';
	searchAbsoluteDate = '';
	document.getElementById('detailsBody').innerHTML = '';
	document.getElementById('sideDetailsBody').innerHTML = '';
	document.getElementById('sideDetailsBody').style.display = 'none';
	document.getElementById('eventResultsList').innerHTML = '<h1>Newham Events Search Results</h1><p>Please wait while your search is performed&hellip;</p>';
	RSS = new RSSFeedReader(feedURL);
	RSS.update();
}

// Start everything off
window.onload = function() {
	document.getElementById('map_canvas').innerHTML = '<p class="loading">Loading map&hellip;</p>';
	document.getElementById('calendar').innerHTML = '<p class="loading">Loading event data&hellip;</p>';
	document.getElementById('eventResultsList').innerHTML = '<h1>Loading&hellip;</h1><p>Please wait, the event data is loading&hellip;</p>';
	RSS = new RSSFeedReader(feedURL);
	// Set up search control
	searchControlLoad();
	
	// Get query string parameters for searches
	searchText = qs.get("SearchText");
	if(searchText == '') searchText = false;
	searchCategory = qs.get("Categories");
	searchTimeScaleMin = qs.get("MinDaysAhead") || 0;
	searchTimeScaleMax = qs.get("MaxDaysAhead") || 0;
	pageNumber = qs.get("Page") || 1;
	
	// Check for an event ID being passed in
	var evtID = parseInt(qs.get("EventID"));
	if(!isNaN(evtID)) {
		feedURL = startFeedURL;
	}
	else
		feedURL = startFeedURL + '?search' + (searchText?'&SearchText='+encodeURIComponent(searchText):'') + (searchCategory?'&Categories='+encodeURIComponent(searchCategory):'') + ((searchTimeScaleMin || searchTimeScaleMin == 0)?'&MinDaysAhead='+encodeURIComponent(searchTimeScaleMin):'') + ((searchTimeScaleMax || searchTimeScaleMax == 0)?'&MaxDaysAhead='+encodeURIComponent(searchTimeScaleMax):'');
	RSS = new RSSFeedReader(feedURL);
	RSS.update();
	// Update search controls
	if(searchText)
		document.getElementById('SearchText').value = searchText;
	if(searchCategory){
		var categoryDD = document.getElementById('Categories');
		for(var p=0,plen=categoryDD.options.length;p<plen;p++) {
			if(searchCategory == categoryDD.options[p].value) {
				categoryDD.selectedIndex = p;
				break;
			}
		}
	}
	var timeRangeDD = document.getElementById('MaxDaysAhead');
	for(var o=0,olen=timeRangeDD.options.length;o<olen;o++) {
		if('&MinDaysAhead='+searchTimeScaleMin+'&MaxDaysAhead='+searchTimeScaleMax == timeRangeDD.options[o].value) {
			timeRangeDD.selectedIndex = o;
			break;
		}
	}
}