
Site.Calendar = {

	create : function(container, titleContainer, todayButton, editable, year, month, ambigEventsCallback) {
		return new this._Calendar(container, titleContainer, todayButton, editable, year, month, ambigEventsCallback);
	},
	
	_Calendar : function(container, titleContainer, todayButton, editable, year, month, ambigEventsCallback) {
	
		this.element = null;
		this.contentElement = null;
		this.titleContainer = titleContainer;
		this.todayButton = todayButton;
		this.eventPopup = null;
		this.quickAdd = null;
		
		this.year = year;
		this.month = month;
		this.firstDate = null;
		this.lastDate = null;
		
		this.monthStart = null;
		this.currentDate = null;
		this.weeks = null;
		
		this.editable = editable;
		this.ambigEventsCallback = ambigEventsCallback;
		this.inMini = ambigEventsCallback ? false : true; // temporary, kinda sketchy
		
		this.build(container);
	},
	
	_Week : function(calendar, row) {
		this.calendar = calendar;
		this.row = row;
		this.firstDayInnerContent = null;
		this.segments = null;
		this.days = new Array();
	},
	
	_Day : function(week, date, cell) {
		this.week = week;
		this.date = date;
		this.cell = cell;
	},
	
	_Date : function(year, month, date, hours, minutes) {
		if (year) {
			this.year = year;
			this.month = month;
			this.date = date;
			this.hours = hours ? hours : 0;
			this.minutes = minutes ? minutes : 0;
			this.hasTime = typeof(hours)!="undefined" && typeof(minutes)!="undefined";
			this.clean();
		}else{
			var d = new Date();
			this.year = d.getYear();
			if (this.year < 1900) this.year += 1900;
			this.month = d.getMonth() + 1;
			this.date = d.getDate();
			this.hours = d.getHours();
			this.minutes = d.getMinutes();
			this.hasTime = true;
		}
	},
	
	_Segment : function(start, end, event, isBeginning, isEnd) {
		this.start = start;
		this.end = end;
		this.event = event;
		this.isBeginning = isBeginning;
		this.isEnd = isEnd;
		this.element = null;
	},
	
	_Event : function(id, title, startDate, endDate, description, location, url, startDateStr, adminUrl, type) {
		this.id = id;
		this.title = title;
		this.startDate = startDate;
		this.big = startDate && (!(!endDate || endDate.dayCompareTo(startDate)==0) || (!endDate && !startDate.hasTime));
		this.endDate = endDate ? endDate : startDate;
		this.description = description;
		this.location = location;
		this.url = url;
		this.startDateStr = startDateStr;
		this.adminUrl = adminUrl;
		this.type = type;
	},
	
	_parseDateTime : function(dateStr, timeStr) {
		if (!dateStr) return null;
		var dateParts = dateStr.split('-');
		if (dateParts[0] == '0000' || dateParts[1] == '00' || dateParts[2] == '00') return null;
		if (timeStr) {
			var timeParts = timeStr.split(':');
			return new this._Date(
				parseInt(dateParts[0]),
				parseInt(this._removeLeadingZero(dateParts[1])),
				parseInt(this._removeLeadingZero(dateParts[2])),
				parseInt(this._removeLeadingZero(timeParts[0])),
				parseInt(this._removeLeadingZero(timeParts[1]))
			);
		}else{
			return new this._Date(
				parseInt(dateParts[0]),
				parseInt(this._removeLeadingZero(dateParts[1])),
				parseInt(this._removeLeadingZero(dateParts[2]))
			);
		}
		return d;
	},
	
	_removeLeadingZero : function(str) {
		if (str.charAt(0)=='0') return str.substring(1,str.length);
		return str;
	},
	
	_MONTH_NAMES : new Array(
		"January",
		"February",
		"March",
		"April",
		"May",
		"June",
		"July",
		"August",
		"September",
		"October",
		"November",
		"December"
	),
	
	_MONTH_NAME_ABBREVS : new Array(
		"Jan",
		"Feb",
		"Mar",
		"Apr",
		"May",
		"Jun",
		"Jul",
		"Aug",
		"Sep",
		"Oct",
		"Nov",
		"Dec"
	),
	
	_DAY_NAMES : new Array(
		"Sunday",
		"Monday",
		"Tuesday",
		"Wednesday",
		"Thursday",
		"Friday",
		"Saturday"
	),
	
	_DAY_NAME_ABBREVS : new Array(
		"Sun",
		"Mon",
		"Tue",
		"Wed",
		"Thu",
		"Fri",
		"Sat"
	)

}

Site.Calendar._Calendar.prototype = {

	today : function() {
		this.year = null;
		this.build();
	},

	prevMonth : function() {
		this.month--;
		if (this.month < 1) {
			this.year--;
			this.month = 12;
		}
		this.build();
	},
	
	nextMonth : function() {
		this.month++;
		if (this.month > 12) {
			this.year++;
			this.month = 1;
		}
		this.build();
	},

	build : function(container) {
	
		if (!this.element) {
			this.element = document.createElement('div');
			this.element.style.position = 'relative';
			this.element.className = 'calendar-element';
			this.eventPopup = this.buildEventPopup();
			this.eventPopup.style.zIndex = '3';
			this.quickAdd = this.buildQuickAdd();
			this.quickAdd.style.zIndex = '2';
			this.element.appendChild(this.eventPopup);
			this.element.appendChild(this.quickAdd);
			container.appendChild(this.element);
			var thisObj = this;
			ToolMan.events().register(window, 'resize', function() { if (thisObj.lastRenderTime) thisObj.renderMonth(); });
		}else{
			this.element.removeChild(this.contentElement);
			this.hideEventPopup();
			this.hideQuickAdd();
		}
		
		this.contentElement = this.buildMonth();
		this.contentElement.style.zIndex = '1';
		this.element.appendChild(this.contentElement);
		
		this.updateTitle();
		this.fetchEvents();
	},

	buildMonth : function() {
	
		var currentDate = this.createDate();
		if (this.year == null) {
			this.year = currentDate.year;
			this.month = currentDate.month;
		}
		
		if (this.todayButton) {
			if (this.year == currentDate.year && this.month == currentDate.month) {
				this.todayButton.style.visibility = 'hidden';
			}else{
				this.todayButton.style.visibility = 'visible';
			}
		}
		
		this.firstDate = this.createDate(this.year, this.month, 1);
		var monthStart = (new Date(this.year,this.month-1,1)).getDay();
		if (monthStart > 0) this.firstDate.incrementDate(-monthStart);
		var daysInMonth = this.daysInMonth(this.year, this.month);
		var numWeeks = Math.ceil((daysInMonth + monthStart)/7);
		this.lastDate = this.firstDate.clone();
		this.lastDate.incrementDate(numWeeks * 7 - 1);
		this.monthStart = monthStart;
		this.currentDate = currentDate;
		var monthStartDate = this.createDate(this.year, this.month, 1);
		var monthEndDate = this.createDate(this.year, this.month, daysInMonth);
		
		var wrapper = document.createElement('div');
		wrapper.style.position = 'relative';
		var table = document.createElement('table');
		table.className = 'month';
		table.style.width = "100%";
		//table.style.borderCollapse = 'collapse';
		//table.style.borderSpacing = '0px';
		var tableBody = document.createElement("tbody");
		var row = document.createElement('tr');
		for (var i=0; i<7; i++) {
			var cell = document.createElement('th');
			cell.className = 'day-of-week' + (i==0 ? ' sunday-of-week' : '') + (i==6 ? ' saturday-of-week' : '');
			cell.style.width = '14.28%';
			cell.appendChild(document.createTextNode(Site.Calendar._DAY_NAME_ABBREVS[i]));
			row.appendChild(cell);
		}
		tableBody.appendChild(row);
		this.weeks = new Array();
		var Events = ToolMan.events();
		var date = this.firstDate.clone();
		for (var i=0; i<numWeeks; i++) {
			var row = document.createElement('tr');
			row.className = 'week';
			var week = this.createWeek(row);
			for (var j=0; j<7; j++) {
			
				var cell = document.createElement('td');
				var cellClassName;
				if (date.dayCompareTo(monthStartDate) < 0 || date.dayCompareTo(monthEndDate) > 0) {
					cellClassName = 'day inactive-day';
				}else{
					cellClassName = 'day active-day';
				}
				if (date.dayCompareTo(currentDate) == 0) {
					cellClassName += ' today';
				}
				cell.className = cellClassName +
					(j==0 ? ' sunday' : (j==6 ? ' saturday' : '')) +
					(i==0 ? ' first-week' : (i==numWeeks-1 ? ' last-week' : ''));
				cell.style.verticalAlign = 'top';
				cell.style.padding = '0px';
				
				var heading = document.createElement('div');
				heading.className = 'day-number';
				heading.appendChild(document.createTextNode(date.date));
				cell.appendChild(heading);
				
				var contentWrapper = document.createElement('div');
				contentWrapper.style.position = 'relative';
				var content = document.createElement('div');
				content.className = 'day-content';
				var innerContent = document.createElement('div');
				innerContent.style.position = 'relative';
				content.appendChild(innerContent);
				if (j==0) week.firstDayInnerContent = innerContent;
				contentWrapper.appendChild(content);
				cell.appendChild(contentWrapper);
				
				var day = this.createDay(week, date, cell);
				cell._day = day;
				
				Events.register(cell, 'click', this.dayClick);
				
				row.appendChild(cell);
				week.days.push(day);
				date.incrementDate(1);
			}
			tableBody.appendChild(row);
			this.weeks.push(week);
		}
		table.appendChild(tableBody);
		wrapper.appendChild(table);
		
		wrapper._calendar = this;
		Events.register(wrapper, 'mousemove', this.monthMouseMove);
		Events.register(wrapper, 'mouseout', this.monthMouseOut);
		
		this._segmentOverUID = 0;
		
		return wrapper;
	},
	
	buildEventPopup : function() {
		var e = document.createElement('div');
		e.className = 'popup event-info';
		e.style.position = 'absolute';
		//e.style.display = 'none';
		e.style.left = '-10000px'; // ie7 sucks
		e.style.top = '0px';
		var dateHeader = document.createElement('h2');
		dateHeader.className = 'date';
		e.appendChild(dateHeader);
		e._dateHeader = dateHeader;
		var postTitle = document.createElement('h3');
		postTitle.className = 'title';
		e._postTitle = postTitle;
		e.appendChild(postTitle);
		var postLocation = document.createElement('p');
		postLocation.className = 'location';
		e._postLocation = postLocation;
		e.appendChild(postLocation);
		var postBody = document.createElement('p');
		postBody.className = 'description';
		e._postBody = postBody;
		e.appendChild(postBody);
		return e;
	},
	
	buildQuickAdd : function() {
		var calendar = this;
		var e = document.createElement('div');
		e.className = 'popup quick-add Blog';
		e.style.position = 'absolute';
		e.style.display = 'none';
		var closeLink = document.createElement('a');
		var closeLinkText = document.createElement('span');
		closeLinkText.appendChild(document.createTextNode('[close]'));
		closeLink.appendChild(closeLinkText);
		closeLink.className = 'close';
		closeLink.href = 'javascript:;';
		closeLink.onclick = function() { calendar.hideQuickAdd(); };
		e.appendChild(closeLink);
		var dateHeader = document.createElement('h2');
		dateHeader.className = 'date';
		e._dateHeader = dateHeader;
		e.appendChild(dateHeader);
		var form = document.createElement('form');
		form.className = 'field';
		form.onsubmit = function() { calendar.quickAddClickAdd(e); return false; };
		var field = document.createElement('input');
		field.className = 'text quick-add-title';
		field.setAttribute('type', 'text');
		e._titleField = field;
		form.appendChild(field);
		var addButton = document.createElement('input');
		addButton.className = 'button1';
		addButton.setAttribute('type', 'submit');
		addButton.value = 'Add';
		form.appendChild(addButton);
		form.appendChild(document.createElement('br'));
		var moreLink = document.createElement('a');
		moreLink.href = _eventEditUrl;
		moreLink.onclick = function() { calendar.quickAddClickMore(e); return false };
		moreLink.appendChild(document.createTextNode('edit event details...'));
		form.appendChild(moreLink);
		e.appendChild(form);
		return e;
	},
	
	renderMonth : function() {
	
		if (this.lastRenderTime) {
			if ((new Date()).getTime()-this.lastRenderTime.getTime() < 100) {
				return;
			}
		}
		this.lastRenderTime = new Date();
		
		var weeks = this.weeks;
		for (var i=0; i<weeks.length; i++) {
			weeks[i].clearSegments();
		}
		
		var events = this.events;
		for (var i=0; i<events.length; i++) {
			this.partitianMonthEvent(events[i]);
		}
		
		for (var i=0; i<weeks.length; i++) weeks[i].render();
		
		this.makeMonthDayBounds();
		
	},
	
	makeMonthDayBounds : function() {
		var Coordinates = ToolMan.coordinates();
		var weeks = this.weeks;
		this.daysX = new Array(7);
		this.daysY = new Array(weeks.length);
		for (var i=0; i<7; i++) this.daysX[i] = Coordinates.topLeftOffset(weeks[0].days[i].cell).x;
		for (var i=0; i<weeks.length; i++) this.daysY[i] = Coordinates.topLeftOffset(weeks[i].days[0].cell).y;
		this.maxDayX = Coordinates.bottomRightOffset(weeks[0].days[6].cell).x;
		this.maxDayY = Coordinates.bottomRightOffset(weeks[weeks.length-1].days[0].cell).y;
	},
	
	partitianMonthEvent : function(event) {
		event.segments = new Array();
		if (this.eventInBounds(event)) {
		
			var sd, ed;
			var sv, ev;
			if (event.startDate.dayCompareTo(this.firstDate) < 0) {
				sd = this.firstDate;
				sv = false;
			}else{
				sd = event.startDate;
				sv = true;
			}
			if (event.endDate.dayCompareTo(this.lastDate) > 0) {
				ed = this.lastDate;
				ev = false;
			}else{
				ed = event.endDate;
				ev = true;
			}
			
			var endWeek = this.weekOf(ed);
			var monthStart = this.monthStart;
			var weeks = this.weeks;
			var first = true;
			while (true) {
				var startWeek = this.weekOf(sd);
				if (startWeek < endWeek) {
					var weekEndDate = sd.clone();
					weekEndDate.incrementDate(6 - sd.dayOfWeek());
					var segment = this.createSegment(sd, weekEndDate, event, first && sv, false);
					event.segments.push(segment);
					this.weeks[startWeek].segments.push(segment);
					sd = weekEndDate.clone();
					sd.incrementDate(1);
				}else{
					var segment = this.createSegment(sd, ed, event, first && sv, ev);
					event.segments.push(segment);
					this.weeks[startWeek].segments.push(segment);
					break;
				}
				first = false;
			}
		}
	},
	
	weekOf : function(date) {
		if (date.year < this.year) return 0;
		if (date.year > this.year) return this.weeks.length-1;
		if (date.month < this.month) return 0;
		if (date.month > this.month) return this.weeks.length-1;
		return Math.floor((date.date-1+this.monthStart) / 7);
	},
	
	eventInBounds : function(event) {
		return event.endDate.dayCompareTo(this.firstDate) >= 0 && event.startDate.dayCompareTo(this.lastDate) <= 0;
	},
	
	segmentDragStart : function(dragEvent) {
		var element = dragEvent.group.element;
		var event = element._event;
		var calendar = element._week.calendar;
		
		calendar.dragging = true;
		calendar.savedSegmentWidth = element.style.width;
		calendar.savedSegmentPosition = ToolMan.coordinates().topLeftPosition(element);
		calendar.savedSegmentClass = element.firstChild.className;
		element.style.width = '100%';
		if (event.big) {
			element.firstChild.className = 'event event-dragging big-event big-event-start big-event-end big-event-dragging';
		}else{
			element.firstChild.className = 'event event-dragging small-event small-event-dragging';
		}
		
		var textNodes = element.firstChild.firstChild.firstChild.childNodes;
		for (var i=0; i<textNodes.length; i++) {
			if (textNodes[i].className == 'date') textNodes[i].style.display = 'none';
		}
		
		var segments = event.segments;
		for (var i=0; i<segments.length; i++) {
			var e = segments[i].element;
			if (e != element) e.style.visibility = 'hidden';
		}
		calendar.hideEventPopup();
		calendar.hideQuickAdd();
	},
	
	segmentDragTransform : function(coordinate, dragEvent) {
		var Coordinates = ToolMan.coordinates();
		var element = dragEvent.group.element;
		var bounds = element._week.calendar.element;
		var c1 = Coordinates.topLeftOffset(bounds);
		var c2 = Coordinates.bottomRightOffset(bounds).minus(Coordinates.create(element.offsetWidth,0));
		return dragEvent.mouseOffset.plus(ToolMan.coordinates().create(10,10)).constrainTo(c1, c2);
	},
	
	segmentDragEnd : function(dragEvent) {
		var element = dragEvent.group.element;
		var event = element._event;
		var segments = event.segments;
		var week = element._week;
		var calendar = week.calendar;
		
		if (calendar.savedSegmentWidth) {
			if (!calendar.activeDay || event.startDate.dayCompareTo(calendar.activeDay.date)==0) {
			
				element.style.width = calendar.savedSegmentWidth;
				calendar.savedSegmentPosition.reposition(element);
				element.firstChild.className = calendar.savedSegmentClass;
				for (var i=0; i<segments.length; i++) {
					var e = segments[i].element;
					if (e != element) e.style.visibility = 'visible';
				}
				
				var textNodes = element.firstChild.firstChild.firstChild.childNodes;
				for (var i=0; i<textNodes.length; i++) {
					if (textNodes[i].className == 'date') textNodes[i].style.display = 'inline';
				}
				
			}else{
			
				var updateWeeks = new Array();
				
				for (var i=0; i<segments.length; i++) {
					var e = segments[i].element;
					e.parentNode.removeChild(e);
				}
				
				var startWeek = calendar.weekOf(event.startDate);
				var endWeek = calendar.weekOf(event.endDate);
				for (var i=startWeek; i<=endWeek; i++) {
					var weekSegments = calendar.weeks[i].segments;
					for (var j=0; j<weekSegments.length;) {
						if (weekSegments[j].event == event) {
							weekSegments.splice(j, 1);
						}else{
							j++;
						}
					}
					updateWeeks[i] = true;
				}
				
				var dayDifference = calendar.activeDay.date.dayDifference(event.startDate);
				event.startDate.incrementDate(dayDifference);
				if (event.endDate != event.startDate) event.endDate.incrementDate(dayDifference);
				calendar.partitianMonthEvent(event);
				
				startWeek = calendar.weekOf(event.startDate);
				endWeek = calendar.weekOf(event.endDate);
				for (var i=startWeek; i<=endWeek; i++) {
					updateWeeks[i] = true;
				}
				
				for (var i=0; i<updateWeeks.length; i++) {
					if (updateWeeks[i]) {
						calendar.weeks[i].clearSegmentElements();
						calendar.weeks[i].render();
					}
				}
				
				calendar.makeMonthDayBounds();
				calendar.updateEventDatabase(event);
			}
			
		}else{
		
			var href = location.href;
			var slashIndex = href.substring(0,7)=="http://" ? href.indexOf('/',7) : href.indexOf('/');
			if (href.substring(slashIndex+1,slashIndex+6)=='admin') {
				location.href = _eventEditUrl+'/'+event.id + '?calendar=' + calendar.year + '-' + calendar.month + "&si=" + site_id;
			}else{
				location.href = event.url;
			}
			calendar._followingLink = true;
		
		}
		
		if (calendar.activeDay && calendar.savedDayCellClass) calendar.activeDay.cell.className = calendar.savedDayCellClass;
		calendar.dragging = false;
		calendar.activeDay = null;
		calendar.savedSegmentWidth = null;
		calendar.savedDayCellClass = null;
	},
	
	dayClick : function(event) {
		var Coordinates = ToolMan.coordinates();
		var day = this._day;
		var calendar = day.week.calendar;
		if (!calendar._followingLink) {
			if (calendar.editable) calendar.showQuickAdd(day, Coordinates.mouseOffset(event));
		}else{
			calendar._followingLink = false;
		}
	},
	
	monthMouseMove : function(event) {
		event = ToolMan.events().fix(event);
		var mouseOffset = ToolMan.coordinates().mouseOffset(event);
		var calendar = this._calendar;
		if (calendar.dragging) {
			var newActiveDay = null;
			if (mouseOffset.x <= calendar.maxDayX && mouseOffset.y <= calendar.maxDayY) {
				var daysY = calendar.daysY;
				var daysX = calendar.daysX;
				var week, day;
				for (week = 0; week<calendar.weeks.length && mouseOffset.y >= daysY[week]; week++) ;
				for (day = 0; day<7 && mouseOffset.x >= daysX[day]; day++) ;
				if (week>0 && day>0) newActiveDay = calendar.weeks[week-1].days[day-1];
			}
			if (newActiveDay) {
				if (newActiveDay != calendar.activeDay) {
					if (calendar.activeDay) calendar.activeDay.cell.className = calendar.savedDayCellClass;
					calendar.activeDay = newActiveDay;
					calendar.savedDayCellClass = newActiveDay.cell.className;
					newActiveDay.cell.className += ' selected-day' + (calendar.currentDate.dayCompareTo(newActiveDay.date)==0 ? ' selected-current-day' : '');
				}
			}else{
				if (calendar.activeDay) calendar.activeDay.cell.className = calendar.savedDayCellClass;
				calendar.activeDay = null;
				calendar.savedDayCellClas = null;
			}
		}
	},
	
	monthMouseOut : function() {
		var calendar = this._calendar;
		if (calendar.activeDay) calendar.activeDay.cell.className = calendar.savedDayCellClass;
		calendar.activeDay = null;
		calendar.savedDayCellClas = null;
	},
	
	createEvent : function(id, title, startDate, endDate, description, location, url, startDateStr, adminUrl, type) {
		return new Site.Calendar._Event(id, title, startDate, endDate, description, location, url, startDateStr, adminUrl, type);
	},
	
	createWeek : function(row) {
		return new Site.Calendar._Week(this, row);
	},
	
	createDay : function(week, date, cell) {
		return  new Site.Calendar._Day(week, date.clone(), cell);
	},
	
	createDate : function(year, month, date, hours, minutes) {
		return new Site.Calendar._Date(year, month, date, hours, minutes);
	},
	
	createSegment : function(start, end, event, isBeginning, isEnd) {
		return new Site.Calendar._Segment(start, end, event, isBeginning, isEnd);
	},
	
	daysInMonth : function(year, month) {
		switch(month) {
			case 1: case 3: case 5: case 7: case 8: case 10: case 12:
				return 31;
			case 4: case 6: case 9: case 11:
				return 30;
			case 2:
				var y = new String(year);
				if (y.substr(2) == "00") {
					if ((new String(year/400)).indexOf('.') != -1) return 28;
					else return 29;
				}
				if ((new String(year/4)).indexOf('.') != -1) return 28;
				else return 29;
		}
	},
	
	updateTitle : function() {
		if (this.titleContainer) {
			this.titleContainer.innerHTML = Site.Calendar._MONTH_NAMES[this.month-1] + " " + this.year;
		}
	},
	
	showEventPopup : function(event, mouseOffset) {
		var Coordinates = ToolMan.coordinates();
		var e = this.eventPopup;		
		e._dateHeader.innerHTML = event.dateTimeString();
		e._postTitle.innerHTML = event.title;
		e._postLocation.innerHTML = event.location ? 'at '+event.location : '';
		e._postLocation.style.display = event.location ? 'block' : 'none';
		e._postBody.innerHTML = event.description ? event.description : '';
		e._postBody.style.display = event.description ? 'block' : 'none';
		var scrollOffset = Coordinates.scrollOffset();
		var min = Coordinates.create(0,scrollOffset.y);
		var max = Coordinates.create(Coordinates.clientSize().x-300, scrollOffset.y+Coordinates.clientSize().y); // shouldnt use hard value here
		mouseOffset.plus(Coordinates.create(20,20)).constrainTo(min,max).minus(Coordinates.topLeftOffset(this.element)).reposition(e);
		//e.style.display = 'block';
	},
	
	hideEventPopup : function() {
		// ie7 sucks
		this.eventPopup.style.left = '-10000px';
		this.eventPopup.style.top = '0px';
	},
	
	showQuickAdd : function(day, mouseOffset) {
		var Coordinates = ToolMan.coordinates();
		var date = day.date;
		var cell = day.cell;
		var quickAdd = this.quickAdd;
		
		quickAdd._titleField.value = '';
		quickAdd._dateHeader.innerHTML = date.toString();
		var scrollOffset = Coordinates.scrollOffset();
		var min = Coordinates.create(0,scrollOffset.y);
		var max = Coordinates.create(Coordinates.clientSize().x-245, scrollOffset.y+Coordinates.clientSize().y);
		mouseOffset.plus(Coordinates.create(20,-100)).constrainTo(min,max).minus(Coordinates.topLeftOffset(this.element)).reposition(quickAdd);
		quickAdd.style.display = 'block';
		quickAdd._titleField.focus();
		
		if (quickAdd._cell) quickAdd._cell.className = quickAdd._savedCellClass;
		quickAdd._date = date;
		quickAdd._cell = cell;
		quickAdd._savedCellClass = cell.className;
		cell.className += ' selected-day'; // todo: see if its the current date, if so, make selected-current-date
	},
	
	hideQuickAdd : function() {
		var quickAdd = this.quickAdd;
		quickAdd.style.display = 'none';
		if (quickAdd._cell) quickAdd._cell.className = quickAdd._savedCellClass;
		quickAdd._cell = null;
	},
	
	quickAddClickMore : function(quickAdd) {
		if (this.inMini) {
			Site.Mini.open(_eventEditUrl+'?date='+quickAdd._date.sqlDate(true)+
				'&title='+escape(quickAdd._titleField.value.trim()) + '&si='+site_id);
			this.hideQuickAdd();
		}else{
			window.location.href = _eventEditUrl+'?date='+quickAdd._date.sqlDate(true)+
				'&title='+escape(quickAdd._titleField.value.trim())+
				'&calendar='+this.year+"-"+this.month + '&si='+site_id;
		}
	},
	
	quickAddClickAdd : function(quickAdd) {
		var title = quickAdd._titleField.value.trim();
		if (!title) return;
		this.hideQuickAdd();
		var calendar = this;
		var request = Site.Util.makeXmlHttpRequest();
		request.onreadystatechange = function() {
			if (request.readyState == 4) {
				Site.Util.hideLoading();
				var doc = request.responseXML;
				if (doc) {
					calendar.events.push(calendar.createEvent(
						doc.getElementsByTagName('id')[0].firstChild.data,
						title,
						quickAdd._date,
						null,
						null,
						null,
						doc.getElementsByTagName('url')[0].firstChild.data
					));
					calendar.renderMonth();
					if (showQuickAddAlert) {
						alert("Your event has been submitted but it must be okayed by an administrator before it appears on the site calendar. If you would like to make changes to the event, you will see a link on the top bar of the page when you are logged in.");
						Site.Mini.redirectParent(null, null, true);
					}
				}else{
					alert('error!');
				}
			}
		}
		Site.Util.showLoading();
		request.open('GET', '/ajax_events.php?add='+quickAdd._date.sqlDate()+'&title='+escape(title)+'&si='+site_id, true);
		request.send(null);
	},
	
	fetchEvents : function() {
		var request = Site.Util.makeXmlHttpRequest();
		var calendar = this;
		request.onreadystatechange = function() {
			if(request.readyState == 4) {
				Site.Util.hideLoading();
				var doc = request.responseXML;
				var eventNodes = doc.getElementsByTagName('event');
				var events = new Array();
				var ambigEvents = new Array();
				for (var i=0; i<eventNodes.length; i++) {
					var n = eventNodes[i];
					var startDateNodes = n.getElementsByTagName('date');
					var startTimeNodes = n.getElementsByTagName('time');
					var endDateNodes = n.getElementsByTagName('end-date');
					var endTimeNodes = n.getElementsByTagName('end-time');
					var descNodes = n.getElementsByTagName('description');
					var locNodes = n.getElementsByTagName('location');
					var typeNodes = n.getElementsByTagName('type');
					var urlElement = n.getElementsByTagName('url')[0];
					var startDate = Site.Calendar._parseDateTime(
							startDateNodes[0].firstChild.data,
							startTimeNodes.length>0 ? startTimeNodes[0].firstChild.data : null);
					var event = calendar.createEvent(
						n.getElementsByTagName('id')[0].firstChild.data,
						n.getElementsByTagName('title')[0].firstChild.data,
						startDate,
						Site.Calendar._parseDateTime(
							endDateNodes.length>0 ? endDateNodes[0].firstChild.data : (endTimeNodes.length>0 ? startDateNodes[0].firstChild.data : null),
							endTimeNodes.length>0 ? endTimeNodes[0].firstChild.data : null
						),
						descNodes.length>0 ? descNodes[0].firstChild.data : null,
						locNodes.length>0 ? locNodes[0].firstChild.data : null,
						urlElement.firstChild ? urlElement.firstChild.data : null,
						startDateNodes[0].firstChild.data,
						n.getElementsByTagName('admin-url')[0].firstChild.data,
						typeNodes.length>0 ? typeNodes[0].firstChild.data : null
						);
					if (startDate) {
						events.push(event);
					}else{
						ambigEvents.push(event);
					}
				}
				calendar.events = events;
				calendar.renderMonth();
				if (calendar.ambigEventsCallback) {
					calendar.ambigEventsCallback(ambigEvents);
				}
			}
		}
		Site.Util.showLoading();
		request.open('GET', '/ajax_events.php?s='+this.firstDate.sqlDate(true)+'&e='+this.lastDate.sqlDate(true)+'&si='+site_id, true);
		request.send(null);
	},
	
	updateEventDatabase : function(event) {
		var request = Site.Util.makeXmlHttpRequest();
		var calendar = this;
		request.onreadystatechange = function() {
			if (request.readyState == 4) {
				Site.Util.hideLoading();
				if (request.responseText!='1') alert('error');
			}
		}
		Site.Util.showLoading();
		request.open('GET',
			'/ajax_events.php?edit='+event.id+'&date='+event.startDate.sqlDate()+
			(event.startDate!=event.endDate ? '&end_date=' + event.endDate.sqlDate() : '') + '&si='+site_id
		,true);
		request.send(null);
	}

}

Site.Calendar._Week.prototype = {

	render : function() {
	
		var segments = this.segments;
		for (var i=segments.length-1; i>=0; i--) {
			for (var j=0; j<i; j++) {
				var flip = false;
				var compare = segments[j].start.dayCompareTo(segments[j+1].start);
				if (compare == 0) {
					compare = segments[j].end.dayCompareTo(segments[j+1].end);
					if (compare == 0) {
						flip = segments[j].start.compareTo(segments[j+1].start) > 0;
					}else{
						flip = compare < 0;
					}
				}else{
					flip = compare > 0;
				}
				if (flip) {
					var temp = segments[j];
					segments[j] = segments[j+1];
					segments[j+1] = temp;
				}
			}
		}
		
		var levels = new Array();
		for (var i=0; i<segments.length; i++) {
			var j;
			for (j=0; j<levels.length; j++) {
				var fits = true;
				for (var k=0; k<levels[j].length; k++) {
					if (segments[i].dayCollidesWith(levels[j][k])) {
						fits = false;
						break;
					}
				}
				if (fits) break;
			}
			if (!levels[j]) levels[j] = new Array();
			levels[j].push(segments[i]);
		}
		
		var Coordinates = ToolMan.coordinates();
		var Events = ToolMan.events();
		var Drag = ToolMan.drag();
		var origin = Coordinates.topLeftOffset(this.calendar.contentElement);
		
		var y = 0;
		for (var i=0; i<levels.length; i++) {
			var tallest = 0;
			for (var j=0; j<levels[i].length; j++) {
				
				var segment = levels[i][j];
				var event = segment.event;
				
				//alert('render event ' + event.title);
				
				var main = document.createElement('div');
				var wrap1 = document.createElement('div');
				wrap1.className = 'text-wrap';
				var wrap2 = document.createElement('div');
				var element = document.createElement('div');
				element.className = 'text';
				element.style.overflow = 'hidden';
				
				if (event.startDate.hasTime && (!event.big || segment.start.dayOfWeek()!=6 && segment.end.dayOfWeek()!=0)) {
					var date = document.createElement('span');
					date.className = 'date';
					if (event.big) {
						date.appendChild(document.createTextNode('('+event.startDate.timeString()+') '));
					}else{
						date.appendChild(document.createTextNode(event.startDate.timeString(1)+' '));
					}
					element.appendChild(date);
				}
				
				var title = document.createElement('span');
				title.className = 'title';
				title.appendChild(document.createTextNode(event.title));
				element.appendChild(title);
				
				wrap1.appendChild(element);
				wrap2.appendChild(wrap1);
				main.appendChild(wrap2);
				
				var bigClassName = 'big-event';
				var startDayOfWeek = segment.start.dayOfWeek();
				var endDayOfWeek = segment.end.dayOfWeek();
				var contentWrapper = this.days[startDayOfWeek].cell.childNodes[1];
				var left, right;
				if (segment.isBeginning) {
					left = Coordinates.topLeftOffset(contentWrapper.firstChild.firstChild).minus(origin).x;
					bigClassName += ' big-event-start';
				}else{
					left = Coordinates.topLeftOffset(contentWrapper).minus(origin).x;
				}
				if (segment.isEnd) {
					right = Coordinates.bottomRightOffset(this.days[endDayOfWeek].cell.childNodes[1].firstChild.firstChild).minus(origin).x;
					bigClassName += ' big-event-end';
				}else{
					right = Coordinates.bottomRightOffset(this.days[endDayOfWeek].cell.childNodes[1]).minus(origin).x;
				}
				
				wrap2.className = 'event ' + (event.big ? bigClassName : 'small-event') + (event.type ? ' '+event.type.replace(/\W+/g,'-')+'-event' : '');
				
				main.style.cursor = 'pointer';
				main.style.position = 'absolute';
				main.style.top = y+'px';
				main.style.width = (right-left)+'px';
				main._week = this;
				main._event = event;
				Events.register(main, 'mouseover', this.eventMouseOver);
				Events.register(main, 'mouseout', this.eventMouseOut);
				
				if (segment.isBeginning) {
					main.style.left = '0px';
				}else{
					var left = -Coordinates.topLeftOffset(contentWrapper.firstChild.firstChild).minus(Coordinates.topLeftOffset(contentWrapper)).x;
					main.style.left = left+'px';
				}
				contentWrapper.firstChild.firstChild.appendChild(main);
				
				if (this.calendar.editable) {
					var dragGroup = Drag.createSimpleGroup(main);
					dragGroup.register('dragstart', this.calendar.segmentDragStart);
					dragGroup.register('dragend', this.calendar.segmentDragEnd);
					dragGroup.addTransform(this.calendar.segmentDragTransform);
					dragGroup.setThreshold(4);
				}else{
					Events.register(main, 'click', this.eventUneditableClick);
				}
				
				segment.element = main;
				
				tallest = Math.max(tallest, main.offsetHeight);
			}
			
			y += tallest;
		}
		
		this.firstDayInnerContent.style.height = y+'px';
	},
	
	eventMouseOver : function(jsEvent) {
		var Coordinates = ToolMan.coordinates();
		var calendar = this._week.calendar;
		if (!calendar.dragging) {
			if (calendar._activeSegmentDiv!=this) {
				if (calendar._activeSegmentDiv) calendar._activeSegmentDiv.firstChild.className = calendar.savedEventClass;
				calendar.savedEventClass = this.firstChild.className;
				this.firstChild.className += ' event-hover ' + (this._event.big ? 'big-event-hover' : 'small-event-hover');
				calendar._activeSegmentDiv = this;
				calendar.showEventPopup(this._event, Coordinates.mouseOffset(jsEvent));
			}
			calendar._segmentOverUID++;
		}
	},
	
	eventMouseOut : function(jsEvent) {
		var Coordinates = ToolMan.coordinates();
		var calendar = this._week.calendar;
		if (!calendar.dragging) {
			var thisObj = this;
			var id = calendar._segmentOverUID;
			setTimeout(function() {
				if (calendar._activeSegmentDiv == thisObj && calendar._segmentOverUID == id) {
					thisObj.firstChild.className = calendar.savedEventClass;
					calendar.hideEventPopup();
					calendar._activeSegmentDiv = null;
					calendar._segmentOverUID = 0;
				}
			}, 50);
		}
	},
	
	eventUneditableClick : function(jsEvent) {
		window.location.href = this._event.url;
	},
	
	clearSegments : function() {
		this.clearSegmentElements();
		this.segments = new Array();
	},
	
	clearSegmentElements : function() {
		if (!this.segments) return;
		for (var i=0; i<this.segments.length; i++) {
			var segment = this.segments[i];
			if (segment.element) {
				segment.element.parentNode.removeChild(segment.element);
			}
		}
	}
}

Site.Calendar._Date.prototype = {

	incrementDate : function(days) {
		this.date += days;
		this.clean();
	},
	
	clone : function() {
		if (this.hasTime) {
			return new Site.Calendar._Date(this.year, this.month, this.date, this.hours, this.minutes);
		}else{
			return new Site.Calendar._Date(this.year, this.month, this.date);
		}
	},
	
	compareTo : function(that) {
		return this._val() - that._val();
	},
	
	dayCompareTo : function(that) {
		return this._dayVal() - that._dayVal();
	},
	
	_val : function() {
		return this.year*500*24*60+this.month*32*24*60+this.date*24*60+this.hours*60+this.minutes;
	},
	
	_dayVal : function() {
		return this.year*500+this.month*32+this.date;
	},
	
	dayOfWeek : function() {
		return (new Date(this.year, this.month-1, this.date)).getDay(); 
	},
	
	dayDifference : function(that) {
		return Math.round(((new Date(this.year, this.month-1, this.date)).getTime() - (new Date(that.year, that.month-1, that.date)).getTime()) / (24*60*60*1000.0));
	},
	
	clean : function() {
		var d = new Date(this.year, this.month-1, this.date, this.hours, this.minutes, 0);
		this.year = d.getYear();
		if (this.year < 1900) this.year += 1900;
		this.month = d.getMonth() + 1;
		this.date = d.getDate();
		this.hours = d.getHours();
		this.minutes = d.getMinutes();
	},
	
	toString : function() {
		return this.dateString(3) + (this.hasTime ? ' ' + this.timeString() : '');
	},
	
	dateString : function(format) {
		if (!format) format = 2;
		var dayName;
		if (format==4) dayName = Site.Calendar._DAY_NAMES[this.dayOfWeek()]+', ';
		else if (format==3) dayName = Site.Calendar._DAY_NAME_ABBREVS[this.dayOfWeek()]+', ';
		else dayName = '';
		return dayName+(format>1 ? Site.Calendar._MONTH_NAMES[this.month-1] : Site.Calendar._MONTH_NAME_ABBREVS[this.month-1])+' '+this.date+', '+this.year;
	},
	
	timeString : function(format) {
		if (!format) format = 2;
		var minutes = this.minutes;
		var hoursStr = this.hours%12;
		if (hoursStr==0) hoursStr = '12';
		return hoursStr+(minutes==0?'':(':'+(minutes<10 ? ('0'+minutes) : minutes)))+(this.hours>11?(format>1?'pm':'p'):(format>1?'am':''));
	},
	
	sqlDate : function(useDash) {
		return this.year + (useDash ? '-' : '') +
			(this.month<10 ? '0' : '') + this.month + (useDash ? '-' : '') +
			(this.date<10 ? '0' : '') + this.date;
	}

}

Site.Calendar._Segment.prototype = {

	dayCollidesWith : function(that) {
		return this.end.dayCompareTo(that.start) >= 0 && this.start.dayCompareTo(that.end) <= 0;
	},

	toString : function() {
		return '[' + this.start + ' , ' + this.end + ']';
	}

}

Site.Calendar._Event.prototype = {
	
	dateTimeString : function() {
		return this.startDate.dateString(3) + (this.startDate.hasTime ? ', ' + this.startDate.timeString() : '') +
			(this.endDate!=this.startDate ? ' - ' + (this.big ? this.endDate.dateString(3) : '') + (this.endDate.hasTime ? (this.big ? ', ' : '') + this.endDate.timeString() : '') : '');
	},
	
	toString : function() {
		return this.title + ' ' + this.startDate;
	}
	
}
