/**
 * Returns the number of days the month of this date has.
 */
Date.prototype._datesPerMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
Date.prototype.getNumberOfDays = function() {
	var month = this.getMonth();
	var ret = this._datesPerMonth[month];
	if(ret == 28) {
		var year = this.getFullYear();
		if(!(year%4) && year%400) { // "schaltjahr"?
			ret += 1;
		}
	}
	return ret;
}

Date.prototype.dayTranslationTable = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag",
		"Samstag", "Sonntag"];
Date.prototype.dayAsString = function() {
	return this.dayTranslationTable[this.getIsoDay()];
}

/**
 * Returns the day as a zero indexed array where "0" means Monday and "6" stands for Sunday.
 */
Date.prototype.getIsoDay = function() {
	return (this.getDay()+6)%7;
}

Date.prototype.monthTranslationTable = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August",
		"September", "Oktober", "November", "Dezember"];
Date.prototype.monthAsString = function() {
	// the month is indexed by "1"... this is just mad, if I may say that.
	return this.monthTranslationTable[this.getMonth()];
}

var Calendar = function(id, date) {
	this.dateCursor = date ? date : new Date();
	this.table = $(id);
	this.selectionEnabled = true;

	this.createUI();
	this.table.addClassName("calendar-body");

	this.selection = [];
	this.selection.add = function(date) {
		// check if this date already exists
		if(!this.find(function(o){return o.equals(date)})) {
			this.push(date);
			return true;
		}
		return false;
	}

	this.selection.remove = function(date) {
		for(var i = 0; i < this.length; i++) {
			if(this[i].equals(date)) {
				this.splice(i, 1);
				return true;
			}
		}
		return false;
	}

	this.selection.datesOfMonthCache = [];
	this.selection.cachedDate = "";
	var self = this;
	this.selection.getDatesOfMonth = function() {
		if(this.cachedDate != self.dateCursor.getFullYear()+"-"+self.dateCursor.getMonth()) {
			// not yet cached
			this.cachedDate = self.dateCursor.getFullYear()+"-"+self.dateCursor.getMonth();
			this.datesOfMonthCache = this.findAll(function(i) {
				return i.year == self.dateCursor.getFullYear() && i.month == self.dateCursor.getMonth();
			});
		}
		return this.datesOfMonthCache;
	}
}

var SelectedDate = function(date, month, year) {
	this.date = date;
	this.month = month;
	this.year = year;
}

SelectedDate.prototype.equals = function(date) {
	return this.month == date.month && this.date == date.date && this.year == date.year;
}

Calendar.prototype._prepareEvents = function(node, self) {
	Event.observe(node, "click", function(e) {
		self.dateClicked(Event.element(e));
	}, false);
	Event.observe(node, "mouseover", function(e) {
		e = Event.element(e);
		if(e.innerHTML == " ") {
			return;
		}
		e.addClassName("hover");
	});
	Event.observe(node, "mouseout", function(e) {
		e = Event.element(e);
		if(e.innerHTML == " ") {
			return;
		}
		e.removeClassName("hover");
	});
}

Calendar.prototype.createUI = function() {
	var self = this;
	// first the caption (not as difficult...)
	var prevLink, nextLink;
	var tempRow = this.table.childNodes[0];
	prevLink = tempRow.childNodes[0];
	nextLink = tempRow.childNodes[2];
	this.caption = tempRow.childNodes[1];
	this.caption.appendChild(document.createTextNode(this.dateCursor.getFullYear()
					+ " "
					+ this.dateCursor.monthAsString()));
	Event.observe(prevLink, "click", function() {
		// for some unknown reason this is done automaticly when going backwards
		/*if(self.dateCursor.getMonth() == 0) {
			self.dateCursor.setYear(self.dateCursor.getFullYear()-1);
		}*/
		self.dateCursor.setMonth((self.dateCursor.getMonth()-1)%12);
		self.updateUI();
	}, false);
	Event.observe(nextLink, "click", function() {
		if(self.dateCursor.getMonth() == 11) {
			self.dateCursor.setYear(self.dateCursor.getFullYear()+1);
		}
		self.dateCursor.setMonth((self.dateCursor.getMonth()+1)%12);
		self.updateUI();
	}, false);

	var handleMouseOver = function(e) {
		e = Event.element(e);
		e.style.cursor = "pointer";
	}

	var handleMouseOut = function(e) {
		e = Event.element(e);
		e.style.cursor = "normal";
	}

	Event.observe(prevLink, "mouseover", handleMouseOver);
	Event.observe(nextLink, "mouseover", handleMouseOver);
	Event.observe(prevLink, "mouseout", handleMouseOut);
	Event.observe(nextLink, "mouseout", handleMouseOut);
	// now it's going to be funny!
	var headLine = Builder.node("tr");
	var days = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
	for(var i = 0; i < 7; i++) {
		headLine.appendChild(Builder.node("th", {className:"dayline"}, days[i]));
	}
	this.table.appendChild(headLine);
	// add spacing (days of week that are not days of month
	var tempDate = new Date(this.dateCursor.getFullYear(), this.dateCursor.getMonth(), 1);
	var row = Builder.node("tr");
	var rowCount = 1;
	var node;
	for(var i = 1; i < tempDate.getIsoDay()+1; i++) {
		row.appendChild(node = Builder.node("td", {}, " "));
		node.month = -1;
		node.year = -1;
		node.date = -1;
		this._prepareEvents(node, self);
	}
	// build calendar
	for(var i = 1; i <= tempDate.getNumberOfDays(); i++) {
		tempDate.setDate(i);
		if(i != 1 && tempDate.getIsoDay() == 0) {
			// day is monday, start a new row
			this.table.appendChild(row);
			row = Builder.node("tr");
			rowCount++;
		}
		row.appendChild(node = Builder.node("td", {}, tempDate.getDate()));
		node.month = tempDate.getMonth();
		node.date = tempDate.getDate();
		node.year = tempDate.getFullYear();
		this._prepareEvents(node, self);
	}
	// pad until the end
	var day = tempDate.getIsoDay();
	var table = this.table;
	if(day != 6) {
		(6-day).times(function() {
			row.appendChild(node = Builder.node("td", {}, " "));
			node.month = -1;
			node.year = -1;
			node.date = -1;
			self._prepareEvents(node, self);
		});
	}
	table.appendChild(row);
	if(rowCount < 6) {
		(6-rowCount).times(function() {
			row = Builder.node("tr");
			(7).times(function() {
				row.appendChild(node = Builder.node("td", {}, " "));
				node.month = -1;
				node.year = -1;
				node.date = -1;
				self._prepareEvents(node, self);
			});
			table.appendChild(row);
		});
	}
}

Calendar.prototype.updateUI = function() {
	this.caption.innerHTML = this.dateCursor.getFullYear() +" "+ this.dateCursor.monthAsString();

	var tempDate = new Date(this.dateCursor.getFullYear(), this.dateCursor.getMonth(), 1);
	var row = 2;
	// clean prefix
	for(var i = 0; i < tempDate.getIsoDay(); i++) {
		var node = this.table.childNodes[row].childNodes[i];
		if(node) {
			node.innerHTML = " ";
			node.date = -1;
			node.month = -1;
			node.year = -1;
			this.updateCellUI(node);
		}
	}

	// update data
	for(var i = 1; i <= tempDate.getNumberOfDays(); i++) {
		tempDate.setDate(i);
		if(i != 1 && tempDate.getIsoDay() == 0) {
			row++;
		}
		var node = this.table.childNodes[row].childNodes[tempDate.getIsoDay()];
		node.innerHTML = tempDate.getDate();
		node.date = tempDate.getDate();
		node.month = tempDate.getMonth();
		node.year = tempDate.getFullYear();
		this.updateCellUI(node);
	}

	// clean suffix
	var day = tempDate.getIsoDay();
	var children = this.table.childNodes;
	var self = this;
	if(day != 6) {
		(6-day).times(function(i) {
			var node = children[row].childNodes[day+i+1];
			node.innerHTML = " ";
			node.date = -1;
			node.month = -1;
			node.year = -1;
			self.updateCellUI(node);
		});
	}
	if(children.length > row+1) {
		(children.length-row-1).times(function() {
			row++;
			(7).times(function(i) {
				var node = children[row].childNodes[i];
				node.innerHTML = " ";
				node.date = -1;
				node.month = -1;
				node.year = -1;
				self.updateCellUI(node);
			});
		});
	}
}

Calendar.prototype.updateCellUI = function(node) {
	// clean selection
	if(node.hasClassName("selected")) node.removeClassName("selected");

	// this task may be slow when many dates are selected
	this.selection.getDatesOfMonth().each(function(i) {
		if(i.date == node.date) {
			node.addClassName("selected");
		}
	});
}

Calendar.prototype.dateClicked = function(e) {
	if(e.date == -1 || !this.selectionEnabled) {
		return;
	}

	var date = new SelectedDate(e.date, e.month, e.year);

	// update display
	if(e.hasClassName("selected")) {
		e.removeClassName("selected");
		this.selection.remove(date);
	} else {
		e.addClassName("selected");
		this.selection.add(date);
	}
}

Calendar.prototype.selectionAsParameter = function() {
	var param = [];
	this.selection.each(function(i) {
		param.push([i.year, i.month+1, i.date].join("-"));
	});
	return param.join(",");
}

Calendar.prototype.importData = function(data) {
	// no input? nothing to do
	if(data.length <= 0) {
		return;
	}
	var self = this;
	// most basic "parser", if it turns out to be to slow we need to rewrite it
	data.split(",").each(function(date) {
		date = date.split("-");
		self.selection.add(new SelectedDate(date[2], date[1]-1, date[0]));
	});
	this.updateUI();
}