jQuery.fn.calendar = function(date, options) {

	var dayNames = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
	var template = "<table><caption/><thead><tr><th/><th/><th/><th/><th/><th/><th/></tr></thead><tbody><tr><td/><td/><td/><td/><td/><td/><td/></tr><tr><td/><td/><td/><td/><td/><td/><td/></tr><tr><td/><td/><td/><td/><td/><td/><td/></tr><tr><td/><td/><td/><td/><td/><td/><td/></tr><tr><td/><td/><td/><td/><td/><td/><td/></tr><tr><td/><td/><td/><td/><td/><td/><td/></tr></tbody></table>";

	return this.each(function () {
		var table = createTable(jQuery(this), date, options);		
		
		populateHeader(table);
		populateBody(table);
		populateCaption(table)
		
		decorate(table);
	});
	
	function getFirstDateInMonth(date, firstDayOfWeek) {
		var firstDate = new Date(date.getTime());
		firstDate.setDate(1);
		
		return getFirstDateInWeek(firstDate, firstDayOfWeek);
	}
	
	function getFirstDateInWeek(date, firstDayOfWeek) {
		var dayOfWeek = date.getUTCDay();
		
		if (dayOfWeek != firstDayOfWeek) {
			date.setUTCDate(date.getUTCDate() - 1);
			date = getFirstDateInWeek(date, firstDayOfWeek);
		}
		
		return date;
	}
	
	function createTable(predecessor, date, options) {
		predecessor.after(template);
		var table = predecessor.next();
		
		table.data("date", new Date(date.getTime()));
		
		if (options && options.className)
			table.addClass(options.className);
			
		table.data("firstDay", (options && options.firstDayOfWeek) ? options.firstDayOfWeek : 0);
		table.data("days", (options && options.days) ? options.days : dayNames);
		table.data("alwaysShowLast", (options && options.alwaysShowLastRow == true) ? true : false);
		
		return table;
	}
	
	function populateHeader(table) {
		var days = table.data("days");
		var firstDay = table.data("firstDay");
		
		var headerIndex = firstDay;
		
		jQuery("th", table).each(function() {
			var cell = jQuery(this);
			cell.text(days[headerIndex++]);
			
			if (headerIndex > 6)
				headerIndex = 0;
		});
	}
	
	function populateCaption(table) {
		jQuery("caption", table).text(table.data("date").toUTCString());	
	}
	
	function populateBody(table) {
		var date = table.data("date");
		var firstDay = table.data("firstDay");
		var currentDate = getFirstDateInMonth(date, firstDay)
		
		jQuery("td", table).each(function() {
			jQuery(this)
				.removeClass()
				.data("date", new Date(currentDate.getTime()))
				.text(currentDate.getUTCDate())
				.addClass("dw" + currentDate.getUTCDay())
				.addClass("d" + currentDate.getUTCDate())
				.addClass("m" + currentDate.getUTCMonth())
				.addClass("y" + currentDate.getUTCFullYear());
					
			currentDate.setUTCDate(currentDate.getUTCDate() + 1);
		});	
	}
	
	function decorate(table) {
		var date = table.data("date");
		var currentDayStyle = ".d" + date.getUTCDate();
		var currentMonthStyle = ".m" + date.getUTCMonth();
		var currentYearStyle = ".y" + date.getUTCFullYear();
		
		// Mark all months which are not part of the current month.
		jQuery("td:not(" + currentMonthStyle + ")", table)
			.addClass("other");
		
		// Mark the current date.
		jQuery("td" + currentDayStyle, table)
			.filter(currentMonthStyle)
			.filter(currentYearStyle)
			.addClass("selected");
		
		
		// If the last row is not being used, hide it unless instructed otherwise.
		// The last row is unused if its first cell is empty.
		var lastRow = jQuery("tr:last", table);	
		if (table.data("alwaysShowLast") || !jQuery("td:first", lastRow).is(".other"))
			lastRow.show();	
		else
			lastRow.hide();	
	}
};