var CalendarMonthNames = [
	{short: 'Jan', full: 'January'},
	{short: 'Feb', full: 'February'},
	{short: 'Mar', full: 'March'},
	{short: 'Apr', full: 'April'},
	{short: 'May', full: 'May'},
	{short: 'Jun', full: 'June'},
	{short: 'Jul', full: 'July'},
	{short: 'Aug', full: 'August'},
	{short: 'Sep', full: 'September'},
	{short: 'Oct', full: 'October'},
	{short: 'Nov', full: 'November'},
	{short: 'Dec', full: 'December'},
];
var CalendarWeekNames = [
	{tiny: 'Mo', short: 'Mon', full: 'Monday'},
	{tiny: 'Tu', short: 'Tue', full: 'Tuesday'},
	{tiny: 'We', short: 'Wed', full: 'Wednesday'},
	{tiny: 'Th', short: 'Thu', full: 'Thursday'},
	{tiny: 'Fr', short: 'Fri', full: 'Friday'},
	{tiny: 'Sa', short: 'Sat', full: 'Saturday'},
	{tiny: 'Su', short: 'Sun', full: 'Sunday'},
];

var CalendarStates = [
	'unavailable',
	'available',
	'pending',
	'booked'
];
var CalendarStatesReadable = [
	'Unavailable',
	'Available',
	'Pending',
	'Booked'
];
var CalendarDefaultState = 0;
var CalendarDefaultStatePolar = 1;
var CalendarInitialState = 1;
var CalendarSelectableStates = [1];

var CalendarHoverLock = {
	locked: false,
	element: null
};

if (Notifier != undefined) {
	Notifier.addEvent('calendar_pager_previous');
	Notifier.addEvent('calendar_pager_next');
	Notifier.addEvent('calendar_statechange');
	Notifier.addEvent('calendar_statechange_end');
	Notifier.addEvent('calendar_selectmode');
	Notifier.addEvent('calendar_select');
}

var Calendar = Class.create({
	// Constructor
	initialize: function(properties) {
		if (properties == undefined) {
			properties = {};
		}
		if (properties.availability == undefined) {
			properties.availability = {};
		}
		if (properties.editable == undefined) {
			properties.editable = true;
		}
		
		if (properties.selectable == undefined) {
			properties.selectable = false;
		}
		
		if (properties.property == undefined) {
			properties.property = {};
		}
		if (properties.room_number == undefined) {
			properties.room_number = 0;
		}
		
		if (properties.states == undefined) {
			properties.states = {};
		}
		if (properties.states.defaultstate == undefined) {
			properties.states.defaultstate = CalendarDefaultState;
		}
		if (properties.states.defaultpolar == undefined) {
			properties.states.defaultpolar = CalendarDefaultStatePolar;
		}
		if (properties.states.selectable == undefined) {
			properties.states.selectable = CalendarSelectableStates;
		} else if (!isNaN(properties.states.selectable)) {
			properties.states.selectable = [properties.states.selectable];
		}
		if (properties.states.states == undefined) {
			properties.states.states = CalendarStates;
		}
		if (properties.states.readable == undefined) {
			properties.states.readable = CalendarStatesReadable;
		}
		
		if (properties.selectable) {
			properties.editable = false;
			if (properties.states.initial == undefined) {
				var found = false;
				for (var i = 0; i < properties.states.states.length; i++) {
					if (properties.states.states[i] == 'selected') {
						found = true;
						break;
					}
				}
				if (!found) {
					properties.states.states.push('selected');
					properties.states.readable.push('Selected');
				}
				properties.states.initial = properties.states.states.length - 1;
			}
		}
		if (properties.selected == undefined) {
			properties.selected = [];
		}
		
		if (properties.states.initial == undefined) {
			properties.states.initial = CalendarInitialState;
		}
		
		if (properties.extensions == undefined) {
			properties.extensions = {};
		}
		if (properties.extensions.notifier == undefined) {
			properties.extensions.notifier = false;
		}
		if (properties.extensions.tooltips == undefined) {
			properties.extensions.tooltips = false;
		}
		if (properties.extensions.protomenu == undefined) {
			properties.extensions.protomenu = false;
		}
		if (properties.extensions.threads == undefined) {
			properties.extensions.threads = false;
		}
		
		// Initialize Members
		this.tempDate = new Date();
		
		this.editable = properties.editable;
		this.selectable = properties.selectable;
		this.states = properties.states;
		
		this.property = properties.property;
		this.room_number = properties.room_number;
		
		this.dates = {};
		this.lookupYears = {};
		this.lookupMonths = {};
		this.lookupDates = {};
		this.currentState = this.states.initial;
		
		this.selectModeContainers = [];
		this.monthContainers = {};
		this.visibleMonths = [];
		
		this.dragging = false;
		this.dragmode = 0;
		
		this.extensions = {};
		this.extensions.notifier = properties.extensions.notifier;
		this.extensions.tooltips = properties.extensions.tooltips;
		this.extensions.protomenu = properties.extensions.protomenu;
		this.extensions.threads = properties.extensions.threads;
		
		if (properties.extensions.tooltips) {
			if (properties.tooltips == undefined) {
				properties.tooltips = {};
			}
			if (properties.tooltips.date == undefined) {
				properties.tooltips.date = {
					callback: function() {
						return this.calendar.states.readable[this.getState()];
					}
				};
			}
			this.tooltips = properties.tooltips;
		}
		
		if (properties.extensions.protomenu) {
			if (properties.protomenu == undefined) {
				properties.protomenu = {};
			}
			if (properties.protomenu.menuitems == undefined) {
				properties.menuItems = [];
			}
			this._protomenu = new Proto.Menu({
				selector: false,
				className: 'menu desktop',
				menuItems: properties.protomenu.menuItems,
				beforeShow: function(event, protomenu) {
					Calendar.unlockHover();
					
					Tooltips.disable();
					var cCalendarDate = Event.element(event).cCalendarDate;
					cCalendarDate.hoverOn();
					cCalendarDate.lockHover();
					
					var state = cCalendarDate.getState();
					
					for (var i = 0; i < this.menuItems.length; i++) {
						var disabled = false;
						if (this.menuItems[i].activeStates != undefined) {
							disabled = true;
							for (var j = 0; j < this.menuItems[i].activeStates.length; j++) {
								if (this.menuItems[i].activeStates[j] == state) {
									disabled = false;
									break;
								}
							}
						}
						if (this.menuItems[i].inactiveStates != undefined) {
							for (var j = 0; j < this.menuItems[i].inactiveStates.length; j++) {
								if (this.menuItems[i].inactiveStates[j] == state) {
									disabled = true;
									break;
								}
							}
						}
						this.menuItems[i].disabled = disabled;
					}
					protomenu.setMenuItems(this.menuItems);
				},
				beforeHide: function(event) {
					Calendar.unlockHover();
					Tooltips.enable();
				},
				beforeSelect: function() {
					Calendar.unlockHover();
					Tooltips.enable();
				}
			});
		}
		
		// Set up bound events
		if (properties.editable) {
			this.hoverOnMonthEventBind = this.hoverOnMonthEvent.bindAsEventListener(this);
			this.hoverOffMonthEventBind = this.hoverOffMonthEvent.bindAsEventListener(this);
			this.toggleMonthStateEventBind = this.toggleMonthStateEvent.bindAsEventListener(this);
			this.hoverOnWeekdayEventBind = this.hoverOnWeekdayEvent.bindAsEventListener(this);
			this.hoverOffWeekdayEventBind = this.hoverOffWeekdayEvent.bindAsEventListener(this);
			this.toggleWeekdayStateEventBind = this.toggleWeekdayStateEvent.bindAsEventListener(this);
		}
		
		// Create dates for supplied availability
		for (var key in properties.availability) {
			var bookinginfo = {};
			for (var bi_key in properties.availability[key]) {
				bookinginfo[bi_key] = properties.availability[key][bi_key];
			}
			
			if (bookinginfo.date == undefined) {
				continue;
			}
			
			var selected = false;
			if (properties.selected.length) {
				if (bookinginfo.state > 1) {
					for (var i = 0; i < properties.selected.length; i++) {
						if (bookinginfo.booking_ref == properties.selected[i]) {
							selected = true;
						}
					}
				}
			}
			
			var dateParts = bookinginfo.date.split('-');
			var cDate = new Date();
			cDate.setDate(1);
			cDate.setFullYear(dateParts[0]);
			cDate.setMonth(dateParts[1] - 1);
			cDate.setDate(dateParts[2]);
			
			if (bookinginfo.date_end === undefined) {
				bookinginfo.date_end = bookinginfo.date;
			}
			
			var dateParts = bookinginfo.date_end.split('-');
			var cDateEnd = new Date();
			cDateEnd.setDate(1);
			cDateEnd.setFullYear(dateParts[0]);
			cDateEnd.setMonth(dateParts[1] - 1);
			cDateEnd.setDate(dateParts[2]);
			
			if (cDateEnd <= cDate) {
				this.addDate(null, {
					date: cDate,
					state: bookinginfo.state,
					selected: selected,
					bookinginfo: bookinginfo
				});
			} else {
				do {
					var bookinginfo = {};
					for (var bi_key in properties.availability[key]) {
						bookinginfo[bi_key] = properties.availability[key][bi_key];
					}
					this.addDate(null, {
						date: cDate,
						state: bookinginfo.state,
						selected: selected,
						bookinginfo: bookinginfo
					});
					cDate.setDate(cDate.getDate() + 1);
				} while (cDate <= cDateEnd);
			}
		}
	},
	
	_sort_dates: function() {
		// Sort Dates by Key
		var keys = [];
		for (var key in this.dates) {
			keys.push(key);
		}
		keys.sort(function(a, b) {
			a = a.split('-');
			var cDateA = new Date();
			cDateA.setDate(1);
			cDateA.setFullYear(a[0]);
			cDateA.setMonth(a[1] - 1);
			cDateA.setDate(a[2]);
			
			b = b.split('-');
			var cDateB = new Date();
			cDateB.setDate(1);
			cDateB.setFullYear(b[0]);
			cDateB.setMonth(b[1] - 1);
			cDateB.setDate(b[2]);
			
			return (cDateA > cDateB) - (cDateA < cDateB);
		});
		var dates_sorted = {};
		for (var i = 0; i < keys.length; i++) {
			dates_sorted[keys[i]] = this.dates[keys[i]];
		}
		this.dates = dates_sorted;
	},
	
	serialize: function(options) {
		if (options == undefined) {
			options = {};
		}
		if (options.modified == undefined) {
			options.modified = false;
		}
		if (options.states == undefined) {
			options.states = {
				available: true,
				pending: true,
				booked: true
			};
		}
		
		this._sort_dates();
		
		var data = {};
		for (var key in this.dates) {
			if (options.modified) {
				var modified = this.dates[key].modified();
			}
			var date = this.dates[key];
			var state = date.getState();
			if (options.states[this.states.states[state]] != undefined) {
				if (options.states[this.states.states[state]]) {
					// Check for consecutive dates
					var consecutive_date = false;
					var cDate = new Date();
					cDate.setDate(1);
					cDate.setFullYear(date.getYear());
					cDate.setMonth(date.getMonth() - 1);
					cDate.setDate(date.getDay() - 1);
					do {
						var check_date = cDate.getFullYear() + '-' + (cDate.getMonth() + 1) + '-' + cDate.getDate();
						//alert (key + "\n" + check_date);
						if (this.dates[check_date] == undefined) {
							break;
						}
						if (this.dates[check_date].getState() == state && this.dates[check_date].getBookingReference() == date.getBookingReference()) {
							consecutive_date = check_date;
						} else {
							break;
						}
						cDate.setDate(cDate.getDate() - 1);
					} while (1);
					
					if (!consecutive_date || data[consecutive_date] == undefined) {
						data[key] = {
							date: date.getYear() + '-' + date.getMonth() + '-' + date.getDay(),
							date_end: date.getYear() + '-' + date.getMonth() + '-' + date.getDay(),
							state: state
						};
						if (options.modified) {
							data[key].modified = modified;
							data[key].bookinginfo = date.getBookingInfo();
						} else if (date.getBookingReference()) {
							data[key].bookinginfo = date.getBookingInfo();
						}
					} else {
						data[consecutive_date].date_end = key;
						if (modified) {
							data[consecutive_date].modified = true;
						}
					}
				}
			}
		}
		
		if (options.modified) {
			for (var key in data) {
				var cDate = new Date();
				
				var bi_date = data[key].bookinginfo.date.split('-');
				cDate.setDate(1);
				cDate.setFullYear(bi_date[0]);
				cDate.setMonth(bi_date[1] - 1);
				cDate.setDate(bi_date[2]);
				bi_date = cDate.getFullYear() + '-' + (cDate.getMonth() + 1) + '-' + cDate.getDate();
				
				if (data[key].bookinginfo.date_end == undefined) {
					var bi_date_end = data[key].bookinginfo.date.split('-');
				} else {
					var bi_date_end = data[key].bookinginfo.date_end.split('-');
				}
				cDate.setDate(1);
				cDate.setFullYear(bi_date_end[0]);
				cDate.setMonth(bi_date_end[1] - 1);
				cDate.setDate(bi_date_end[2]);
				bi_date_end = cDate.getFullYear() + '-' + (cDate.getMonth() + 1) + '-' + cDate.getDate();
				
				if (bi_date != data[key].date || bi_date_end != data[key].date_end) {
					data[key].modified = true;
				}
				if (data[key].state < 2) {
					delete data[key].bookinginfo;
				}
				if (!data[key].modified) {
					delete data[key];
				}
			}
		}
		
		return data;
	},
	
	modified: function() {
		for (var key in this.dates) {
			if (this.dates[key].modified()) {
				return true;
			}
		}
		return false;
	},
	resetModified: function() {
		for (var key in this.dates) {
			this.dates[key].modified(false);
		}
	},
	
	getState: function() {
		return this.currentState;
	},
	setState: function(state) {
		this.currentState = state;
		for (var containerIndex = 0; containerIndex < this.selectModeContainers.length; containerIndex++) {
			var container = this.selectModeContainers[containerIndex];
			$(container).select('div.selectmode').each(function(item) {
				if (item.hasClassName(this.states.states[state])) {
					item.addClassName('current');
				} else {
					if (item.hasClassName('current')) {
						item.removeClassName('current');
					}
				}
			}.bind(this));
		}
	},
	
	isDragging: function() {
		return this.dragging;
	},
	setDragging: function(dragging) {
		this.dragging = dragging;
	},
	getDragMode: function() {
		return this.dragmode;
	},
	setDragMode: function(dragmode) {
		this.dragmode = dragmode;
	},
	
	getWeekday: function(year, month, day) {
		// Gets the weekday where the first day of the week is Monday
		if (year.getDay != undefined) {
			var weekday = year.getDay();
		} else {
			this.tempDate.setDate(1);
			this.tempDate.setFullYear(year);
			this.tempDate.setMonth(month - 1);
			this.tempDate.setDate(day);
			var weekday = this.tempDate.getDay();
		}
		return weekday == 0 ? 6 : weekday - 1;
	},
	
	getMonthLength: function(year, month) {
		return new Date(year, month, 0).getDate();
	},
	
	setContainer: function(container) {
		if (!$(container)) {
			return;
		}
		$(container).appendChild(this.calendarContainer);
	},
	
	renderRange: function(container, options) {
		if (!$(container)) {
			return;
		}
		
		this.calendarContainer = $(createExtendedElement('div', {
			parent: container
		}));
		
		container = this.calendarContainer;
		
		var cDate = new Date();
		if (options == undefined) {
			options = {};
		}
		
		if (options.from == undefined) {
			option.from = {};
		}
		if (options.from.year == undefined) {
			options.from.year = cDate.getFullYear();
		}
		if (options.from.month == undefined) {
			options.from.month = cDate.getMonth() + 1;
		}
		if (options.from.date == undefined) {
			options.from.date = 1;
		}
		
		if (options.to == undefined) {
			option.to = {};
		}
		if (options.to.year == undefined) {
			options.to.year = cDate.getFullYear();
		} else {
			cDate.setFullYear(options.to.year);
		}
		if (options.to.month == undefined) {
			options.to.month = cDate.getMonth() + 1;
		} else {
			cDate.setMonth(options.to.month - 1);
		}
		if (options.to.date == undefined) {
			options.to.date = this.getMonthLength(cDate.getFullYear(), cDate.getMonth() + 1);
		}
		
		var cCurrentDate = new Date();
		cCurrentDate.setFullYear(options.from.year);
		cCurrentDate.setMonth(options.from.month - 1);
		cCurrentDate.setDate(options.from.date);
		
		options.year = cCurrentDate.getFullYear();
		options.month = cCurrentDate.getMonth() + 1;
		options.date = 1;
		
		var rangeContainer = container;
		if (options.pager != undefined) {
			if (options.pager.display == undefined) {
				options.pager.display = 'both';
			}
			if (options.pager.display == 'top' || options.pager.display == 'both' || options.pager.display == 'none') {
				var rangeContainer = this.renderPager(container, options);
			} else if (options.pager.display == 'bottom') {
				var rangeContainer = this.renderRangeContainer(container, options);
			}
		}
		this.rangeContainer = rangeContainer;
		
		var queueFunction = function() {
			while (cCurrentDate.getFullYear() <= options.to.year && (cCurrentDate.getMonth() + 1) <= options.to.month) {
				options.year = cCurrentDate.getFullYear();
				options.month = cCurrentDate.getMonth() + 1;
				options.date = 1;
				this.renderMonth(rangeContainer, options);
				cCurrentDate.setMonth(cCurrentDate.getMonth() + 1);
			}
		}.bind(this)
		
		if (this.extensions.threads) {
			Threads.queue(queueFunction);
		} else {
			queueFunction();
		}
		
		if (options.pager != undefined) {
			if (options.pager.display == 'bottom' || options.pager.display == 'both') {
				options.rangeContainer = rangeContainer;
				if (options.selectMode == undefined) {
					options.selectMode = {};
				}
				if (options.pager.display == 'both') {
					options.selectMode.pager = false;
				}
				this.renderPager(container, options);
			}
		}
	},
	
	renderRangeContainer: function(container, options) {
		return $(createExtendedElement('div', {
			className: 'range',
			parent: container
		}));
	},
	
	renderPager: function(container, options) {
		if (!$(container)) {
			return;
		}
		if (options == undefined) {
			options = {};
		}
		if (options.pager == undefined) {
			return;
		}
		if (options.selectMode == undefined) {
			options.selectMode = {
				pager: true
			};
		}
		if (options.selectMode.pager == undefined) {
			options.selectMode.pager = false;
		}
		if (options.selectMode.year == undefined) {
			options.selectMode.year = false;
		}
		if (options.selectMode.month == undefined) {
			options.selectMode.month = false;
		}
		
		if (options.pager.steps == undefined) {
			options.pager.steps = 1;
		}
		
		if (options.pager.display != 'none') {
			var pagerContainer = $(createExtendedElement('div', {
				className: 'pager',
				parent: container
			}));
		}
		
		if (options.rangeContainer == undefined) {
			var rangeContainer = this.renderRangeContainer(container, options);
		} else {
			rangeContainer = options.rangeContainer;
		}
		
		if (this.pager == undefined) {
			this.pager = {
				options: options
			};
		}
		
		if (options.pager.display == 'none') {
			return rangeContainer;
		}
		
		var pagerPrevious = $(createExtendedElement('div', {
			className: 'button previous',
			innerHTML: 'previous',
			parent: pagerContainer
		}));
		pagerPrevious.observe('click', function(event) {
			this.pagePrevious();
			if (this.extensions.notifier) {
				Notifier.raise('calendar_pager_previous', {
					calendar: this
				});
			}
		}.bindAsEventListener(this));
		pagerPrevious.observe('mouseover', function(event) {
			pagerPrevious.addClassName('hover');
		});
		pagerPrevious.observe('mouseout', function(event) {
			pagerPrevious.removeClassName('hover');
		});
		pagerPrevious.observe('mousedown', function(event) {
			Event.stop(event);
		});
		pagerPrevious.observe('selectstart', function(event) {
			Event.stop(event);
		});
		pagerPrevious.observe('dblclick', function(event) {
			Event.stop(event);
		});
		
		var pagerNext = $(createExtendedElement('div', {
			className: 'button next',
			innerHTML: 'next',
			parent: pagerContainer
		}));
		pagerNext.observe('click', function(event) {
			this.pageNext();
			if (this.extensions.notifier) {
				Notifier.raise('calendar_pager_next', {
					calendar: this
				});
			}
		}.bindAsEventListener(this));
		pagerNext.observe('mouseover', function(event) {
			pagerNext.addClassName('hover');
		});
		pagerNext.observe('mouseout', function(event) {
			pagerNext.removeClassName('hover');
		});
		pagerNext.observe('mousedown', function(event) {
			Event.stop(event);
		});
		pagerNext.observe('selectstart', function(event) {
			Event.stop(event);
		});
		pagerNext.observe('dblclick', function(event) {
			Event.stop(event);
		});
		
		createExtendedElement('div', {
			className: 'clear',
			parent: pagerContainer
		});
		
		if (options.selectMode.pager) {
			this.addSelectMode(pagerContainer, options);
		}
		
		return rangeContainer;
	},
	
	pagePrevious: function() {
		var cCurrentDate = new Date();
		cCurrentDate.setDate(1);
		cCurrentDate.setFullYear(this.pager.options.year);
		
		for (var i = 0; i < this.pager.options.pager.steps; i++) {
			var visibleMonth = this.visibleMonths.pop();
			if (visibleMonth != undefined) {
				this.pager.options.year = visibleMonth.year;
				this.pager.options.month = visibleMonth.month;
			}
			this.hideMonth(this.rangeContainer, this.pager.options);
		}
		
		for (var i = 0; i < this.pager.options.pager.steps; i++) {
			if (this.visibleMonths.length) {
				var visibleMonth = this.visibleMonths[0];
			} else {
				var visibleMonth = {
					year: this.pager.options.year,
					month: this.pager.options.month,
					made: 1
				}
			}
			cCurrentDate.setFullYear(visibleMonth.year);
			cCurrentDate.setMonth(visibleMonth.month - 2);
			this.pager.options.year = cCurrentDate.getFullYear();
			this.pager.options.month = cCurrentDate.getMonth() + 1;
			this.showMonth(this.rangeContainer, this.pager.options);
		}
	},
	
	pageNext: function() {
		var cCurrentDate = new Date();
		cCurrentDate.setDate(1);
		cCurrentDate.setFullYear(this.pager.options.year);
		
		for (var i = 0; i < this.pager.options.pager.steps; i++) {
			var visibleMonth = this.visibleMonths.shift();
			this.pager.options.year = visibleMonth.year;
			this.pager.options.month = visibleMonth.month;
			this.hideMonth(this.rangeContainer, this.pager.options);
		}
		
		for (var i = 0; i < this.pager.options.pager.steps; i++) {
			if (this.visibleMonths.length) {
				var visibleMonth = this.visibleMonths[this.visibleMonths.length - 1];
			} else {
				var visibleMonth = {
					year: this.pager.options.year,
					month: this.pager.options.month
				}
			}
			cCurrentDate.setFullYear(visibleMonth.year);
			cCurrentDate.setMonth(visibleMonth.month);
			this.pager.options.year = cCurrentDate.getFullYear();
			this.pager.options.month = cCurrentDate.getMonth() + 1;
			this.showMonth(this.rangeContainer, this.pager.options);
		}
	},
	
	hideMonth: function(container, options) {
		if (this.monthContainers[options.year + '-' + options.month] != undefined) {
			$(this.monthContainers[options.year + '-' + options.month]).hide();
		}
		for (var i = 0; i < this.visibleMonths.length; i++) {
			if (this.visibleMonths[i].year == options.year && this.visibleMonths[i].month == options.month) {
				this.visibleMonths.splice(i, 1);
				break;
			}
		}
	},
	
	showMonth: function(container, options) {
		this.renderMonth(container, options);
	},
	
	renderYear: function(container, options) {
		if (!$(container)) {
			return;
		}
		if (options == undefined) {
			options = {};
		}
		
		var cDate = new Date();
		cDate.setDate(1);
		if (options.year == undefined) {
			options.year = cDate.getFullYear();
		} else {
			cDate.setFullYear(options.year);
		}
		
		for (var month = 1; month <= 12; month++) {
			options.month = month;
			this.renderMonth(container, options);
		}
	},
	
	renderMonth: function(container, options) {
		if (!$(container)) {
			return;
		}
		if (options == undefined) {
			options = {};
		}
		if (options.showMonthNames == undefined) {
			options.showMonthNames = true;
		}
		if (options.showWeekdays == undefined) {
			options.showWeekdays = true;
		}
		if (options.showOffDates == undefined) {
			options.showOffDates = true;
		}
		if (options.showDateNumbers == undefined) {
			options.showDateNumbers = true;
		}
		if (options.selectMode == undefined) {
			options.selectMode = {
				month: true
			};
		}
		if (options.selectMode.pager == undefined) {
			options.selectMode.pager = false;
		}
		if (options.selectMode.year == undefined) {
			options.selectMode.year = false;
		}
		if (options.selectMode.month == undefined) {
			options.selectMode.month = false;
		}
		
		var cDate = new Date();
		cDate.setDate(1);
		if (options.year == undefined) {
			options.year = cDate.getFullYear();
		} else {
			cDate.setFullYear(options.year);
		}
		if (options.month == undefined) {
			options.month = cDate.getMonth() + 1;
		} else {
			cDate.setMonth(options.month - 1);
		}
		
		var monthVisible = false;
		for (var i = 0; i < this.visibleMonths.length; i++) {
			if (this.visibleMonths[i].year ==  cDate.getFullYear() && this.visibleMonths[i].month == cDate.getMonth() + 1) {
				monthVisible = true;
				break;
			}
		}
		if (!monthVisible) {
			this.visibleMonths.push({
				year: cDate.getFullYear(),
				month: cDate.getMonth() + 1
			});
			this.visibleMonths.sort(function(a, b) {
				if (a.year < b.year) {
					return -1;
				} else if (a.year > b.year) {
					return 1;
				} else if (a.month < b.month) {
					return -1;
				} else if (a.month > b.month) {
					return 1;
				} else {
					return 0;
				}
			});
		}
		
		if (this.monthContainers[cDate.getFullYear() + '-' + (cDate.getMonth() + 1)] != undefined) {
			$(this.monthContainers[cDate.getFullYear() + '-' + (cDate.getMonth() + 1)]).show();
			return;
		}
		
		var monthContainer = $(createExtendedElement('div', {
			className: 'monthcontainer'
		}));
		this.monthContainers[cDate.getFullYear() + '-' + (cDate.getMonth() + 1)] = monthContainer;
		
		if (options.selectMode.month) {
			this.addSelectMode(monthContainer, options);
		}
		
		var month = $(createExtendedElement('div', {
			className: 'month',
			parent: monthContainer
		}));
		
		if (options.showMonthNames) {
			var monthName = $(createExtendedElement('h2', {
				innerHTML: CalendarMonthNames[options.month - 1].full + ' ' + options.year,
				cYear: options.year,
				cMonth: options.month,
				parent: month
			}));
			if (this.editable) {
				monthName.observe('mouseover', this.hoverOnMonthEventBind);
				monthName.observe('mouseout', this.hoverOffMonthEventBind);
				monthName.observe('mousedown', this.toggleMonthStateEventBind);
				monthName.observe('selectstart', function(event) {
					Event.stop(event);
				});
			}
			createExtendedElement('div', {
				className: 'clear',
				parent: month
			});
		}
		
		var weekDates = $(createExtendedElement('div', {
			className: 'days',
			parent: month
		}));
		
		if (options.showWeekdays) {
			var weekDayHeaders = $(createExtendedElement('ul', {
				className: 'weekdays',
				parent: weekDates
			}));
			for (var weekdayIndex = 0; weekdayIndex < 7; weekdayIndex++) {
				var weekday = CalendarWeekNames[weekdayIndex];
				var className = 'weekday';
				if (weekdayIndex == 0) {
					className += ' first';
				}
				if (weekdayIndex == CalendarWeekNames.length - 1) {
					className += ' last';
				}
				var weekHeader = $(createExtendedElement('li', {
					className: className,
					innerHTML: weekday.tiny,
					cYear: options.year,
					cMonth: options.month,
					cWeekday: weekdayIndex,
					parent: weekDayHeaders
				}));
				if (this.editable) {
					weekHeader.observe('mouseover', this.hoverOnWeekdayEventBind);
					weekHeader.observe('mouseout', this.hoverOffWeekdayEventBind);
					weekHeader.observe('mousedown', this.toggleWeekdayStateEventBind);
					weekHeader.observe('selectstart', function(event) {
						Event.stop(event);
					});
				}
			};
		}
		
		var totalDays = 42;
		var monthLength = this.getMonthLength(options.year, options.month);
		
		var dateContainer = $(createExtendedElement('div', {
			parent: weekDates
		}));
		if (options.showWeekdays) {
			var overlapStart = this.getWeekday(options.year, options.month, 1);
			var overlapEnd = totalDays - overlapStart - monthLength;
			var dateRow;
		} else {
			var overlapStart = 0;
			var overlapEnd = 0;
			var dateRow = this.newDateRow(dateContainer);
		}
		
		var rowCount = 0;
		for (var backdays = overlapStart; backdays > 0; backdays--) {
			var cCurrentDate = new Date(options.year, options.month - 1, 1 - backdays);
			var firstCol = false;
			if (cCurrentDate.getDay() == 1) {
				dateRow = this.newDateRow(dateContainer);
				rowCount++;
				firstCol = true;
			}
			if (options.showOffDates) {
				this.addDate(dateRow, {
					date: cCurrentDate,
					showdate: options.showDateNumbers,
					off: true,
					toprow: rowCount == 1 ? true : false,
					bottomrow: rowCount == 6 ? true : false,
					first: firstCol,
					last: backdays == 1 ? true : false
				});
			} else {
				this.addEmptyDate(dateRow, {
					toprow: rowCount == 1 ? true : false,
					bottomrow: rowCount == 6 ? true : false,
					first: firstCol,
					last: backdays == 1 ? true : false
				});
			}
		}
		for (var day = 1; day <= monthLength; day++) {
			var cCurrentDate = new Date(options.year, options.month - 1, day);
			
			var firstCol = false;
			if (cCurrentDate.getDay() == 1 && options.showWeekdays) {
				dateRow = this.newDateRow(dateContainer);
				rowCount++;
				firstCol = true;
			}
			if (day == 1) {
				firstCol = true;
			}
			
			var lastCol = false;
			if (cCurrentDate.getDay() == 0 && options.showWeekdays) {
				lastCol = true;
			}
			if (day == monthLength) {
				lastCol = true;
			}
			
			var showDate = true;
			if (options.from != undefined) {
				if (cCurrentDate.getFullYear() == options.from.year && (cCurrentDate.getMonth() + 1) == options.from.month && cCurrentDate.getDate() < options.from.date) {
					showDate = false;
				}
			}
			if (options.to != undefined) {
				if (cCurrentDate.getFullYear() == options.to.year && (cCurrentDate.getMonth() + 1) == options.to.month && cCurrentDate.getDate() > options.to.date) {
					showDate = false;
				}
			}
			if (showDate) {
				this.addDate(dateRow, {
					date: cCurrentDate,
					showdate: options.showDateNumbers,
					toprow: rowCount == 1 ? true : false,
					bottomrow: rowCount == 6 ? true : false,
					first: firstCol,
					last: lastCol
				});
			} else {
				this.addEmptyDate(dateRow, {
					toprow: rowCount == 1 ? true : false,
					bottomrow: rowCount == 6 ? true : false,
					first: firstCol,
					last: lastCol
				});
			}
		}
		for (var extradays = 1; extradays <= overlapEnd; extradays++) {
			var cCurrentDate = new Date(options.year, options.month, extradays);
			var firstCol = false;
			if (cCurrentDate.getDay() == 1) {
				dateRow = this.newDateRow(dateContainer);
				rowCount++;
				firstCol = true;
			}
			if (options.showOffDates) {
				this.addDate(dateRow, {
					date: cCurrentDate,
					showdate: options.showDateNumbers,
					off: true,
					toprow: rowCount == 1 ? true : false,
					bottomrow: rowCount == 6 ? true : false,
					first: firstCol,
					last: cCurrentDate.getDay() == 0 ? true : false
				});
			} else {
				this.addEmptyDate(dateRow, {
					toprow: rowCount == 1 ? true : false,
					bottomrow: rowCount == 6 ? true : false,
					first: firstCol,
					last: cCurrentDate.getDay() == 0 ? true : false
				});
			}
		}
		
		createExtendedElement('div', {
			className: 'clear',
			parent: weekDates
		});
		
		var cCurrentDate = new Date();
		cCurrentDate.setDate(1);
		cCurrentDate.setFullYear(options.year);
		cCurrentDate.setMonth(options.month);
		if (options.beforemonth != undefined) {
			$(container).insertBefore(monthContainer, options.beforemonth);
		} else if (this.monthContainers[cCurrentDate.getFullYear() + '-' + (cCurrentDate.getMonth() + 1)]) {
			$(container).insertBefore(monthContainer, this.monthContainers[cCurrentDate.getFullYear() + '-' + (cCurrentDate.getMonth() + 1)]);
		} else {
			$(container).appendChild(monthContainer);
		}
	},
	
	newDateRow: function(container) {
		return createExtendedElement('ul', {
			className: 'dayrow',
			parent: container
		});
	},
	
	addSelectMode: function(container, options) {
		var selectModes = $(createExtendedElement('div', {
			className: 'selectmodes',
			parent: container
		}));
		this.selectModeContainers.push(selectModes);
		for (var stateIndex = 0; stateIndex < this.states.states.length; stateIndex++) {
			var selectMode = $(createExtendedElement('div', {
				className: 'selectmode ' + this.states.states[stateIndex],
				cState: stateIndex,
				parent: selectModes
			}));
			if (stateIndex == this.getState()) {
				selectMode.addClassName('current');
			}
			selectMode.observe('click', function(event) {
				var element = Event.element(event);
				this.setState(element.cState);
				Notifier.raise('calendar_selectmode', {
					calendar: this,
					selectmode: element.cState
				});
			}.bindAsEventListener(this));
			if (this.extensions.tooltips) {
				selectMode.addTooltip(this.states.readable[stateIndex]);
			}
		}
		createExtendedElement('div', {
			className: 'clear',
			parent: selectModes
		});
	},
	
	getDate: function(dateID) {
		if (this.dates[dateID] != undefined) {
			return this.dates[dateID];
		}
		return false;
	},
	addDate: function(container, options) {
		if (options == undefined) {
			return;
		}
		if (options.date == undefined) {
			return;
		}
		if (options.state == undefined) {
			options.state = this.states.defaultstate;
		}
		if (options.selected == undefined) {
			options.selected = false;
		}
		
		var dateID = options.date.getFullYear() + '-' + (options.date.getMonth() + 1) + '-' + options.date.getDate();
		
		if (options.bookinginfo == undefined) {
			options.bookinginfo = {
				date: dateID,
				state: options.state
			};
		}
		
		if (this.dates[dateID] == undefined) {
			cCalendarDate = new CalendarDate({
				calendar: this,
				year: options.date.getFullYear(),
				month: options.date.getMonth() + 1,
				day: options.date.getDate(),
				state: options.state,
				selected: options.selected,
				bookinginfo: options.bookinginfo
			});
			if ($(container)) {
				cCalendarDate.render(container, options);
			}
			this.dates[dateID] = cCalendarDate;
		} else {
			if ($(container)) {
				this.dates[dateID].render(container, options);
			}
		}
	},
	addEmptyDate: function(container, options) {
		if (!$(container)) {
			return;
		}
		if (options == undefined) {
			options = {};
		}
		if (options.toprow == undefined) {
			options.toprow = false;
		}
		if (options.bottomrow == undefined) {
			options.bottomrow = false;
		}
		if (options.first == undefined) {
			options.first = false;
		}
		if (options.last == undefined) {
			options.last = false;
		}
		
		var className = 'empty';
		if (options.toprow) {
			className += ' toprow';
		}
		if (options.bottomrow) {
			className += ' bottomrow';
		}
		if (options.first) {
			className += ' first';
		}
		if (options.last) {
			className += ' last';
		}
		
		return createExtendedElement('li', {
			className: className,
			parent: container
		});
	},
	
	getDatesByWeekday: function(year, month, weekday) {
		var date = 1;
		while ((this.getWeekday(year, month, date) != weekday) && date <= 7) {
			date++;
		}
		
		var dates = [];
		while (date <= this.getMonthLength(year, month)) {
			if (this.getDate(year + '-' + month + '-' + date) != undefined) {
				dates[dates.length] = this.getDate(year + '-' + month + '-' + date)
			}
			date += 7;
		}
		return dates;
	},
	
	getDatesByMonth: function(year, month) {
		var dates = [];
		var date = 1;
		while (date <= this.getMonthLength(year, month)) {
			if (this.getDate(year + '-' + month + '-' + date) != undefined) {
				dates[dates.length] = this.getDate(year + '-' + month + '-' + date)
			}
			date++;
		}
		return dates;
	},
	
	toggleDates: function(dates, hover) {
		if (hover == undefined) {
			hover = false;
		}
		var toggleOn = false;
		for (dateIndex = 0; dateIndex < dates.length; dateIndex++) {
			var date = dates[dateIndex];
			if (date.getState() >= 2) {
				continue;
			}
			if (date.getState() != this.getState()) {
				toggleOn = true;
				break;
			}
		}
		
		for (dateIndex = 0; dateIndex < dates.length; dateIndex++) {
			var date = dates[dateIndex];
			if (date.getState() >= 2) {
				continue;
			}
			if (toggleOn) {
				if (hover) {
					date.hoverOn(null, {
						onlyOn: true,
						noInversion: true
					});
				} else {
					date.setState(this.getState());
				}
			} else {
				if (hover) {
					date.hoverOn(null, {
						onlyOn: true,
						noInversion: false
					});
				} else {
					if (this.getState() == this.states.defaultstate) {
						date.setState(this.states.defaultpolar);
					} else {
						date.setState(this.states.defaultstate);
					}
				}
			}
		}
	},
	
	hoverOnMonth: function(element) {
		if (this.getState() >= 2) {
			return;
		}
		this.toggleDates(this.getDatesByMonth(element.cYear, element.cMonth), true);
		return;
	},
	hoverOnMonthEvent: function(event) {
		return this.hoverOnMonth(Event.element(event));
	},
	
	hoverOffMonth: function(element) {
		if (this.getState() >= 2) {
			return;
		}
		var dates = this.getDatesByMonth(element.cYear, element.cMonth);
		for (var dateIndex = 0; dateIndex < dates.length; dateIndex++) {
			var date = dates[dateIndex];
			date.hoverOff();
		}
	},
	hoverOffMonthEvent: function(event) {
		return this.hoverOffMonth(Event.element(event));
	},
	
	toggleMonthState: function(element) {
		if (this.getState() >= 2) {
			return;
		}
		this.toggleDates(this.getDatesByMonth(element.cYear, element.cMonth));
	},
	toggleMonthStateEvent: function(event) {
		this.toggleMonthState(Event.element(event));
		event.stop();
	},
	
	hoverOnWeekday: function(element) {
		if (this.getState() >= 2) {
			return;
		}
		this.toggleDates(this.getDatesByWeekday(element.cYear, element.cMonth, element.cWeekday), true);
		return;
	},
	hoverOnWeekdayEvent: function(event) {
		return this.hoverOnWeekday(Event.element(event));
	},
	
	hoverOffWeekday: function(element) {
		if (this.getState() >= 2) {
			return;
		}
		var dates = this.getDatesByWeekday(element.cYear, element.cMonth, element.cWeekday);
		for (var dateIndex = 0; dateIndex < dates.length; dateIndex++) {
			var date = dates[dateIndex];
			date.hoverOff();
		}
	},
	hoverOffWeekdayEvent: function(event) {
		return this.hoverOffWeekday(Event.element(event));
	},
	
	toggleWeekdayState: function(element) {
		if (this.getState() >= 2) {
			return;
		}
		this.toggleDates(this.getDatesByWeekday(element.cYear, element.cMonth, element.cWeekday));
	},
	toggleWeekdayStateEvent: function(event) {
		this.toggleWeekdayState(Event.element(event));
		event.stop();
	}
});

Calendar.unlockHover = function() {
	if (CalendarHoverLock.locked) {
		CalendarHoverLock.locked = false;
		CalendarHoverLock.element.hoverOff();
	}
}