var timetable = {
	items: [],
	ampm: true,
	isoffset: ["float", "offset", "utc"][0],
	
	getEnd: function(start) {
		for (var i = 0, j = this.items.length; i < j; i++) {
			if (this.items[i].start == start) {
				return this.items[i].end;
			}
		}
		return null;
	},
	getStart: function(end) {
		for (var i = 0, j = this.items.length; i < j; i++) {
			if (this.items[i].end == end) {
				return this.items[i].start;
			}
		}
		return null;
	},
	getSessionByTime: function(start, end) {
		for (var i = 0, j = this.items.length; i < j; i++) {
			if (this.items[i].start.getTime() == start.getTime() && this.items[i].end.getTime() == end.getTime()) {
				return this.items[i];
			}
		}
		return null;
	},
	isValidTime: function(time1, time2) {
		for (var i = 0, j = this.items.length; i < j; i++) {
			if (this.items[i].start == time1 && this.items[i].end == time2) {
				return true;
			}
		}
		return false;
	},
	isValidLocation: function(time1, time2, location) {
		var session = this.getSessionByTime(time1, time2);
		if (session) {
			for (var i = 0, j = session.events.length; i < j; i++) {
				if (session.events[i].location == location) {
					return false;
				}
			}
		}
		return true;
	},
	getLocations: function() {
		var res = [];
		for (var i = 0, j = this.items.length; i < j; i++) {
			for (var k = 0, l = this.items[i].events.length; k < l; k++) {
				if (!res.inside(this.items[i].events[k].location)) {
					res.push(this.items[i].events[k].location);
				}
			}
		}
		return res;
	},
	getLocations4Output: function() {
		var res = [];
		for (var i = 0, j = this.items.length; i < j; i++) {
			for (var k = 0, l = this.items[i].events.length; k < l; k++) {
				if (!this.items[i].events[k].isglobal && !res.inside(this.items[i].events[k].location)) {
					res.push(this.items[i].events[k].location);
				}
			}
		}
		return res;
	},
	addEvent: function(start, end, location, isglobal, description) {
		var session;
		if (isglobal || !(session = this.getSessionByTime(start, end))) {
			session = new Session(start, end);
			this.items.push(session);
		}
		session.events.push({location: location, isglobal: isglobal, desc: description});
	},
	sort: function() {
		this.items.sort(function(a, b) {
			if (a.start < b.start) {
				return -1;
			}
			if (a.start > b.start) {
				return 1;
			}
			return 0;
		});
		var loc = this.getLocations4Output();
		for (var i = 0, j = this.items.length; i < j; i++) {
			this.items[i].events.sort(function(a, b) {
				if (loc.inside(a.location) < loc.inside(b.location)) {
					return -1;
				}
				if (loc.inside(a.location) > loc.inside(b.location)) {
					return 1;
				}
				return 0;
			});
		}
	},
	toString: function(hook) {
		this.sort();
		var res = '<p><a href="http://suda.co.uk/projects/X2V/get-vcal.php?uri=<Your page URL>">Download this calendar (Don&#8217;t forget to fix the URL)</a></p>\n';
		res += '<table summary="Timetable" class="vcalendar">\n';
		res += '	<thead>\n';
		res += '		<tr>\n';
		res += '			<td>&#160;</td>\n';
		var loc = this.getLocations4Output();
		for (var i = 0, k = loc.length; i < k; i++) {
			res += '			<th id="location-' + (i + 1) + '" axis="location"><span class="location">' + loc[i] + '</span></th>\n';
		}
		res += '		</tr>\n';
		res += '	</thead>\n';
		res += '	<tbody>\n';
		for (var i = 0, j = this.items.length; i < j; i++) {
			res += '		<tr>\n';
			res += '			<th id="time-' + (i + 1) + '" axis="time" class="time">';
			res += this.convertTime(i);
			res += "</th>\n";
			for (var k = 0, l = this.items[i].events.length; k < l; k++) {
				var n = k;
				if (k == l - 1) {
					while (n + 1 < loc.inside(this.items[i].events[k].location)) {
						res += '			<td class="empty">&#160;</td>\n';
						n++;
					}
				}
				res += '			<td class="' + (this.items[i].events[k].isglobal?'global ':'') + 'vevent" headers="time-' + (i + 1);
				if (!this.items[i].events[k].isglobal) {
					res += ' location-' + loc.inside(this.items[i].events[k].location) + '"';
				} else {
					res += '" colspan="' + loc.length + '"';
				}
				res += hook?' id="event-' + i + '-' + k + '"':'';
				res +='><div class="summary">' + this.items[i].events[k].desc + '</div>';
				if (this.items[i].events[k].isglobal) {
					res += ' <span class="location">(<span class="value">' + this.items[i].events[k].location + '</span>)</span>';
				}
				res += "</td>\n";
				if (k == l - 1 && !this.items[i].events[k].isglobal) {
					for (var a = loc.inside(this.items[i].events[k].location), b = loc.length; a < b; a++) {
						res += '			<td class="empty">&#160;</td>\n';
					}
				}
			}
			res += '		</tr>\n';
		}
		res += '	</tbody>\n</table>\n';
		return res;
	},	
	convertTime: function(i) {
		var S = {};
		var tt = this;
		S.val = tt.items[i].start.iso();
		S.hours = tt.items[i].start.getHours();
		S.mins = '<span class="minutes">' + tt.items[i].start.getMinutes().zero() + '</span>';
		S.separator = '<span class="separator">:</span>';
		S.postfix = "";
		if (this.ampm) {
			S.postfix = '<span class="postfix"> am</span>';
			if (S.hours > 12) {
				S.postfix = '<span class="postfix"> pm</span>';
				S.hours = S.hours - 12;
			}
		}
		S.toString = function() {
			return '<abbr title="' + tt.items[i].start.iso(tt.isoffset) + '" class="dtstart"><span class="hours">' + this.hours + '</span>' + this.separator + this.mins + this.postfix + '</abbr>';
		};
		var E = {};
		E.val = tt.items[i].end.iso();
		E.hours = tt.items[i].end.getHours();
		E.mins = '<span class="minutes">' + tt.items[i].end.getMinutes().zero() + '</span>';
		E.separator = '<span class="separator">:</span>';
		E.postfix = "";
		if (this.ampm) {
			E.postfix = '<span class="postfix"> am</span>';
			if (E.hours > 12) {
				E.postfix = '<span class="postfix"> pm</span>';
				E.hours = E.hours - 12;
			}
		}
		E.toString = function() {
			return '<abbr title="' + tt.items[i].end.iso(tt.isoffset) + '" class="dtend"><span class="hours">' + this.hours + '</span>' + this.separator + this.mins + this.postfix + '</abbr>';
		};
		if (S.postfix == E.postfix) {
			S.postfix = S.postfix.replace(/\"postfix\"/g, '"hidden postfix"');
		}
		return S + "&#8211;" + E;
	}
};


function Session (start, end) {
	this.start = start;
	this.end = end;
	this.events = [];

	this.removeEvent = function(ev) {
		this.events.remove(ev);
		if (this.events.length == 0) {
			timetable.items.remove(this);
		}
	};
}


String.prototype.ISO2date = function() {
	var val = this.toString();
	val = val.match(/^(\d{4})\-?(\d{2})\-?(\d{2})T(\d{2}):?(\d{2}):?(\d{2}).*$/);
	var dt = new Date();
	if (val.length == 7) {
		dt.setYear(val[1]);
		dt.setMonth(val[2] - 1);
		dt.setDate(val[3]);
		dt.setHours(val[4]);
		dt.setMinutes(val[5]);
		dt.setSeconds(val[6]);
		dt.setMilliseconds(0);
	}
	return dt;
};


Array.prototype.inside = function(value) {
	for (var i = 0; i < this.length; i++) {
		if (this[i] == value) {
			return i + 1;
		}
	}
	return false;
};


Array.prototype.remove = function(value) {
	var flag = false;
	for (var i = 0, j = this.length; i < j; i++) {
		if (this[i] == value) {
			flag = true;
		}
		if (flag && i < j - 1) {
			this[i] = this[i + 1];
		}
	}
	if (flag) {
		this.pop();
	}
};


Date.prototype.iso = function(utc) {
	var temp = new Date(this);
	var soffset = "";
	if (utc && (utc == "offset" || utc == "utc")) {
		var offset = (new Date()).getTimezoneOffset();
		if (utc == "offset") {
			soffset = ((offset > 0)?"+":"-") + (Math.abs(offset) / 60).zero() + (Math.abs(offset) % 60).zero();
		} else {
			soffset = "Z";
		}
		temp.setMinutes(temp.getMinutes() + offset);
	}
	return temp.getFullYear() + "-" + 
		(temp.getMonth() + 1).zero() + "-" + 
		temp.getDate().zero() + 
		"T" + temp.getHours().zero() + ":" +
			  temp.getMinutes().zero() + ":" +
			  temp.getSeconds().zero() + soffset;
};


Number.prototype.zero = function() {
	if (this < 10) {
		return "0" + this;
	}
	return this;
};