/**
 * based on www.dynarch.com/projects/calendar, heavily modified
 * This script is distributed under the GNU Lesser General Public License.
 * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
 */

// The Calendar object constructor.
Calendar = function (init_date, onSelected, onClose) {
	// member variables
	this.activeDiv = null;
	this.onSelected = onSelected || null;
	this.onClose = onClose || null;
	this.hidden = false;
	this.maxDate = new Date();
	this.isPopup = true;
	this.date = init_date ? new Date(init_date) : new Date();
	this.hiliteToday = true;
	this.numMonths = 2;
	this.offset = { x: 0, y: 1 }
	// HTML elements
	this.tables = new Array();
	this.element = null;
	this.tbodies = new Array();
	this.titles = new Array();
	this.prev = null;
	this.next = null;
	// Information
	this.dateClicked = false;
};

// Event handlers are static and need a ref to the calendar involved in the event. _C is that ref.
Calendar._C = null;

// detect a special case of "web browser"
Calendar.is_ie = (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent));
Calendar.is_ie5 = (Calendar.is_ie && /msie 5\.0/i.test(navigator.userAgent));
Calendar.is_opera = /opera/i.test(navigator.userAgent);
Calendar.is_khtml = /Konqueror|Safari|KHTML/i.test(navigator.userAgent);


// BEGIN: UTILITY FUNCTIONS

Calendar.getAbsolutePos = function(el) {
	var SL = 0, ST = 0;
	var is_div = /^div$/i.test(el.tagName);
	if (is_div && el.scrollLeft)
		SL = el.scrollLeft;
	if (is_div && el.scrollTop)
		ST = el.scrollTop;
	var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST };
	if (el.offsetParent) {
		var tmp = this.getAbsolutePos(el.offsetParent);
		r.x += tmp.x;
		r.y += tmp.y;
	}
	return r;
};

Calendar.removeClass = function(el, className) {
	if (!(el && el.className)) {
		return;
	}
	var cls = el.className.split(" ");
	var ar = new Array();
	for (var i = cls.length; i > 0;) {
		if (cls[--i] != className) {
			ar[ar.length] = cls[i];
		}
	}
	el.className = ar.join(" ");
};

Calendar.addClass = function(el, className) {
	Calendar.removeClass(el, className);
	el.className += " " + className;
};

// Get the current (non-DIV, element-node) target of an event
Calendar.getElement = function(ev) {
	var f = Calendar.is_ie ? window.event.srcElement : ev.currentTarget;
	while (f.nodeType != 1 || /^div$/i.test(f.tagName)) {
		f = f.parentNode;
	}
	return f;
};

// Get the active element-node target of an event
Calendar.getTargetElement = function(ev) {
	var f = Calendar.is_ie ? window.event.srcElement : ev.target;
	while (f.nodeType != 1) {
		f = f.parentNode;
	}
	return f;
};

// Stop an event from bubbling/propagating up the DOM
Calendar.stopEvent = function(ev) {
	ev || (ev = window.event);
	if (Calendar.is_ie) {
		ev.cancelBubble = true;
		ev.returnValue = false;
	} else {
		ev.preventDefault();
		ev.stopPropagation();
	}
	return false;
};

Calendar.addEvent = function(el, evname, func) {
	if (el.attachEvent) { // IE
		el.attachEvent("on" + evname, func);
	} else if (el.addEventListener) { // Gecko / W3C
		el.addEventListener(evname, func, true);
	} else {
		el["on" + evname] = func;
	}
};

Calendar.removeEvent = function(el, evname, func) {
	if (el.detachEvent) { // IE
		el.detachEvent("on" + evname, func);
	} else if (el.removeEventListener) { // Gecko / W3C
		el.removeEventListener(evname, func, true);
	} else {
		el["on" + evname] = null;
	}
};

Calendar.createElement = function(type, parent) {
	var el = null;
	if (document.createElementNS) {
		// use the XHTML namespace; IE won't normally get here unless
		// _they_ "fix" the DOM2 implementation.
		el = document.createElementNS("http://www.w3.org/1999/xhtml", type);
	} else {
		el = document.createElement(type);
	}
	if (typeof parent != "undefined") {
		parent.appendChild(el);
	}
	return el;
};

// END: UTILITY FUNCTIONS


// BEGIN: CALENDAR EVENT FUNCTIONS

// Add a set of events to make an element behave like a button.
Calendar._add_evs = function(el) {
	Calendar.addEvent(el, "mouseover", Calendar.buttonMouseOver);
	Calendar.addEvent(el, "mousedown", Calendar.buttonMouseDown);
	Calendar.addEvent(el, "mouseout", Calendar.buttonMouseOut);
	if (Calendar.is_ie) {
		el.setAttribute("unselectable", true);
	}
};

// Mouse button is down and then released (up)
Calendar.mouseDownUp = function(ev) {
	var cal = Calendar._C;
	if (!cal) {
		return false;
	}
	var el = cal.activeDiv;
	if (!el) {
		return false;
	}
	var target = Calendar.getTargetElement(ev);
	ev || (ev = window.event);
	Calendar.removeClass(el, "active");
	if (target == el || target.parentNode == el) {
		Calendar.cellClick(el, ev);
	}
	with (Calendar) {
		removeEvent(document, "mouseup", mouseDownUp);
		removeEvent(document, is_ie5 ? "mousemove" : "mouseover", mouseDownOver);
		_C = null;
		return stopEvent(ev);
	}
};

// Mouse button is down and cursor moves over an element
Calendar.mouseDownOver = function (ev) {
	var cal = Calendar._C;
	if (!cal) {
		return;
	}
	var el = cal.activeDiv;
	var target = Calendar.getTargetElement(ev);
	if (target == el) {
		Calendar.addClass(el, "hilite active");
	} else {
		Calendar.removeClass(el, "hilite active");
	}
	return Calendar.stopEvent(ev);
};

// Mouse button is depressed (down)
Calendar.buttonMouseDown = function(ev) {
	with (Calendar) {
		var el = getElement(ev);
		if (el.disabled) {
			return false;
		}
		var cal = el.calendar;
		cal.activeDiv = el;
		_C = cal;
		addClass(el, "hilite active");
		addEvent(document, Calendar.is_ie5 ? "mousemove" : "mouseover", mouseDownOver);
		addEvent(document, "mouseup", mouseDownUp);
		return stopEvent(ev);
	}
};

// Mouse button is not depressed and moves over an element
Calendar.buttonMouseOver = function(ev) {
	with (Calendar) {
		var el = getElement(ev);
		if (el.disabled) {
			return false;
		}
		addClass(el, "hilite");
		return stopEvent(ev);
	}
};

// Mouse button is not depressed and moves out of an element
Calendar.buttonMouseOut = function(ev) {
	with (Calendar) {
		var el = getElement(ev);
		if (el.disabled) {
			return false;
		}
		removeClass(el, "hilite");
		return stopEvent(ev);
	}
};

// Generic click handler
Calendar.cellClick = function(el, ev) {
	var cal = el.calendar;
	var closing = false;
	var newdate = false;
	var date = null;
	var call_handler = true;
	if (typeof el.navtype == "undefined") {
		cal.date.setDateOnly(el.caldate);
		date = cal.date;
		newdate = !el.disabled;
		cal.dateClicked = true;
	} else {
		date = new Date(cal.date);
		cal.dateClicked = false;
		var mon = date.getMonth();
		function setMonth(m) {
			var day = date.getDate();
			var max = date.getMonthDays(m);
			if (day > max) {
				date.setDate(max);
			}
			date.setMonth(m);
		};
		switch (el.navtype) {
		case -1:
			setMonth(mon - 1);
			call_handler = false;
			break;
		case 1:
			setMonth(mon + 1);
			call_handler = false;
			break;
		}
		if (!date.equalsTo(cal.date)) {
			cal.setDate(date);
			newdate = true;
		}
	}
	if (newdate && call_handler) {
		ev && cal.callHandler();
	}
	if (closing) {
		Calendar.removeClass(el, "hilite");
		ev && cal.callCloseHandler();
	}
};

// END: CALENDAR EVENT FUNCTIONS


// BEGIN: CALENDAR INSTANCE METHODS

// Create the calendar's DOM structure.
Calendar.prototype.create = function (_par) {
	var parent = null;
	if (!_par) {
		// default parent is the document body, in which case we create
		// a popup calendar.
		parent = document.getElementsByTagName("body")[0];
		this.isPopup = true;
	} else {
		parent = _par;
		this.isPopup = false;
	}

	var div = Calendar.createElement("div");
	this.element = div;
	div.className = "calendar";
	if (this.isPopup) {
		div.style.position = "absolute";
		div.style.display = "none";
	}

	for (var m = 0; m < this.numMonths; ++m) {
		var table = Calendar.createElement("table");
		this.tables[m] = table;
		table.cellSpacing = 0;
		table.cellPadding = 0;
		table.calendar = this;

		div.appendChild(table);

		var thead = Calendar.createElement("thead", table);
		var cell = null;
		var row = null;

		var cal = this;
		var hh = function (text, cs, navtype) {
			cell = Calendar.createElement("td", row);
			cell.colSpan = cs;
			cell.className = "button";
			if (navtype != 0 && Math.abs(navtype) <= 2)
				cell.className += " nav";
			Calendar._add_evs(cell);
			cell.calendar = cal;
			cell.navtype = navtype;
			cell.innerHTML = "<div unselectable='on'>" + text + "</div>";
			return cell;
		};

		row = Calendar.createElement("tr", thead);
		(m == 0) ? this.prev = hh("&laquo;", 1, -1) : hh("&nbsp;", 1);
		this.titles[m] = hh("", 5, 300);
		this.titles[m].className = "title";
		(m == this.numMonths - 1) ? this.next = hh("&raquo;", 1, 1) : hh("&nbsp;", 1);

		// day names
		row = Calendar.createElement("tr", thead);
		row.className = "daynames";
		for (var i = 0; i < 7; ++i) {
			cell = Calendar.createElement("td", row);
			cell.className = "day name";
			cell.innerHTML = Calendar._SDN[i % 7];
		}

		var tbody = Calendar.createElement("tbody", table);
		this.tbodies[m] = tbody;

		for (i = 6; i > 0; --i) {
			row = Calendar.createElement("tr", tbody);
			for (var j = 7; j > 0; --j) {
				cell = Calendar.createElement("td", row);
				cell.calendar = this;
				Calendar._add_evs(cell);
			}
		}
	}
	parent.appendChild(this.element);
	this._init(this.date);
};

// Initializes the calendar to the given date
Calendar.prototype._init = function (date) {
	this.date = date;
	var year = date.getFullYear();
	var mday = date.getDate();

	var today = new Date();
	var base_date = new Date(date);
	base_date.setDate(1);

	if (base_date <= today) {
		this.prev.disabled = true;
		this.prev.className += ' navdisabled';
		this.prev.innerHTML = '&nbsp;';
	} else {
		this.prev.disabled = false;
		Calendar.removeClass(this.prev, 'navdisabled');
		this.prev.innerHTML = '&laquo;';
	}

	for (var m = 0; m < this.numMonths; ++m) {
		this.tables[m].style.visibility = "hidden";

		var tmp_date = new Date(base_date);
		tmp_date.setMonth(tmp_date.getMonth() + m);
		var tmp_month = tmp_date.getMonth();
		var tmp_year = tmp_date.getFullYear();
		var iday = 0;

		// set tmp_date to either the 1st of the month we want
		// or the last sunday of the previous month.
		var offset = tmp_date.getDay() % 7;
		tmp_date.setDate(1 - offset);

		var row = this.tbodies[m].firstChild;
		for (var i = 0; i < 6; ++i, row = row.nextSibling) {
			var cell = row.firstChild;
			row.className = "daysrow";
			for (var j = 0; j < 7; ++j, cell = cell.nextSibling, tmp_date.setDate(iday + 1)) {
				iday = tmp_date.getDate();
				cell.className = "day";
				if (tmp_date.getMonth() != tmp_month) {
					cell.className = "emptycell";
					cell.innerHTML = "&nbsp;";
					cell.disabled = true;
					row.className = "emptyrow";
					continue;
				}
				cell.disabled = false;
				cell.innerHTML = iday;
				cell.caldate = new Date(tmp_date);
				if (tmp_date.getFullYear() == today.getFullYear()
					&& tmp_date.getMonth() == today.getMonth()
					&& iday == today.getDate()) {
					cell.className += " today";
				} else if (tmp_date.getTime() < today.getTime() || tmp_date.getTime() > this.maxDate.getTime())	{
					cell.className += " invalid";
					cell.disabled = true;
				}
			}
		}
		this.titles[m].innerHTML = Calendar._MN[tmp_month] + " " + tmp_year;
		this.tables[m].style.visibility = "visible";

		if (m == this.numMonths - 1) {
			if (this.maxDate.getTime() < tmp_date.getTime()) {
				this.next.disabled = true;
				this.next.className += ' navdisabled';
				this.next.innerHTML = '&nbsp;';
			} else {
				this.next.disabled = false;
				Calendar.removeClass(this.prev, 'navdisabled');
				this.next.innerHTML = '&raquo;';
			}
		}
	}
};

// Set the date and initialize the calendar to start at that date.
Calendar.prototype.setDate = function (date) {
	if (!date.equalsTo(this.date)) {
		this._init(date);
	}
};

// Force a refresh of the calendar.
Calendar.prototype.refresh = function () {
	this._init(this.date);
}

// Set the max date that the calendar can show.
Calendar.prototype.setMaxDate = function (date) {
	this.maxDate = date;
};

// Calls the first user handler (selectedHandler).
Calendar.prototype.callHandler = function () {
	if (this.onSelected) {
		this.onSelected(this);
	}
};

// Calls the second user handler (closeHandler).
Calendar.prototype.callCloseHandler = function () {
	if (this.onClose) {
		this.onClose(this);
	}
	this.hideShowCovered();
};

// Removes the calendar object from the DOM tree and destroys it.
Calendar.prototype.destroy = function () {
	var el = this.element.parentNode;
	el.removeChild(this.element);
	Calendar._C = null;
	window._sl_calendar = null;
};

// This gets called when the user presses a mouse button anywhere in the
// document, if the calendar is shown.  If the click was outside the open
// calendar this function closes it.
Calendar._checkCalendar = function(ev) {
	var calendar = window._sl_calendar;
	if (!calendar) {
		return false;
	}
	var el = Calendar.is_ie ? Calendar.getElement(ev) : Calendar.getTargetElement(ev);
	for (; el != null && el != calendar.element; el = el.parentNode);
	if (el == null) {
		// calls closeHandler which should hide the calendar.
		window._sl_calendar.callCloseHandler();
		return Calendar.stopEvent(ev);
	}
};

// Shows the calendar.
Calendar.prototype.show = function () {
	this.element.style.display = "block";
	this.hidden = false;
	if (this.isPopup) {
		window._sl_calendar = this;
		Calendar.addEvent(document, "mousedown", Calendar._checkCalendar);
	}
	this.hideShowCovered();
};

// Hide the calendar.
Calendar.prototype.hide = function () {
	if (this.isPopup) {
		Calendar.removeEvent(document, "mousedown", Calendar._checkCalendar);
	}
	this.element.style.display = "none";
	this.hidden = true;
	this.hideShowCovered();
};

// Show the calendar at a given absolute position
Calendar.prototype.showAt = function (x, y) {
	var s = this.element.style;
	s.left = (x + this.offset['x']) + "px";
	s.top = (y + this.offset['y']) + "px";
	this.show();
};

// Show the calendar near a given element.
Calendar.prototype.showAtElement = function (el, opts) {
	var self = this;
	var p = Calendar.getAbsolutePos(el);
	if (!opts || typeof opts != "string") {
		this.showAt(p.x, p.y + el.offsetHeight);
		return true;
	}
	function fixPosition(box) {
		if (box.x < 0) {
			box.x = 0;
		}
		if (box.y < 0) {
			box.y = 0;
		}
		var cp = document.createElement("div");
		var s = cp.style;
		s.position = "absolute";
		s.right = s.bottom = s.width = s.height = "0px";
		document.body.appendChild(cp);
		var br = Calendar.getAbsolutePos(cp);
		document.body.removeChild(cp);
		if (Calendar.is_ie) {
			br.y += document.body.scrollTop;
			br.x += document.body.scrollLeft;
		} else {
			br.y += window.scrollY;
			br.x += window.scrollX;
		}
		var tmp = box.x + box.width - br.x;
		if (tmp > 0) box.x -= tmp;
		tmp = box.y + box.height - br.y;
		if (tmp > 0) box.y -= tmp;
	};
	Calendar.continuation_for_khtml_browser = function() {
		var w = self.element.offsetWidth;
		var h = self.element.offsetHeight;
		self.element.style.display = "none";
		var valign = opts.substr(0, 1);
		var halign = "l";
		if (opts.length > 1) {
			halign = opts.substr(1, 1);
		}
		// vertical alignment
		switch (valign) {
		    case "T": p.y -= h; break;
		    case "B": p.y += el.offsetHeight; break;
		    case "C": p.y += (el.offsetHeight - h) / 2; break;
		    case "t": p.y += el.offsetHeight - h; break;
		    case "b": break; // already there
		}
		// horizontal alignment
		switch (halign) {
		    case "L": p.x -= w; break;
		    case "R": p.x += el.offsetWidth; break;
		    case "C": p.x += (el.offsetWidth - w) / 2; break;
		    case "l": p.x += el.offsetWidth - w; break;
		    case "r": break; // already there
		}
		p.width = w;
		p.height = h + 40;
		fixPosition(p);
		self.showAt(p.x, p.y);
	};
	if (Calendar.is_khtml) {
		setTimeout("Calendar.continuation_for_khtml_browser()", 10);
	} else {
		Calendar.continuation_for_khtml_browser();
	}
};

// IE and opera render applets, iframes, and selects on top of everything else regardless of z-index.
// This hides any of those elements that would cover the visible calendar.
Calendar.prototype.hideShowCovered = function () {
	if (!Calendar.is_ie && !Calendar.is_opera) {
		return;
	}
	
	function getVisib(obj){
		var value = obj.style.visibility;
		if (!value) {
			if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C
				if (!Calendar.is_khtml) {
					value = document.defaultView.getComputedStyle(obj, "").getPropertyValue("visibility");
				} else {
					value = '';
				}
			} else if (obj.currentStyle) { // IE
				value = obj.currentStyle.visibility;
			} else {
				value = '';
			}
		}
		return value;
	};

	var tags = new Array("applet", "iframe", "select");
	var el = this.element;

	var p = Calendar.getAbsolutePos(el);
	var EX1 = p.x;
	var EX2 = el.offsetWidth + EX1;
	var EY1 = p.y;
	var EY2 = el.offsetHeight + EY1;

	for (var k = tags.length; k > 0; ) {
		var ar = document.getElementsByTagName(tags[--k]);
		var cc = null;

		for (var i = ar.length; i > 0;) {
			cc = ar[--i];

			p = Calendar.getAbsolutePos(cc);
			var CX1 = p.x;
			var CX2 = cc.offsetWidth + CX1;
			var CY1 = p.y;
			var CY2 = cc.offsetHeight + CY1;

			if (this.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) {
				if (!cc.__sl_save_visibility) {
					cc.__sl_save_visibility = getVisib(cc);
				}
				cc.style.visibility = cc.__sl_save_visibility;
			} else {
				if (!cc.__sl_save_visibility) {
					cc.__sl_save_visibility = getVisib(cc);
				}
				cc.style.visibility = "hidden";
			}
		}
	}
};

// Extensions to the Date class

// Adds the number of days array to the Date object.
Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31);

// Return the number of days in the current month
Date.prototype.getMonthDays = function(month) {
	var year = this.getFullYear();
	if (typeof month == "undefined") {
		month = this.getMonth();
	}
	if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) {
		return 29;
	} else {
		return Date._MD[month];
	}
};

// Check date and time equality
Date.prototype.equalsTo = function(date) {
	return ((this.getFullYear() == date.getFullYear()) &&
		(this.getMonth() == date.getMonth()) &&
		(this.getDate() == date.getDate()) &&
		(this.getHours() == date.getHours()) &&
		(this.getMinutes() == date.getMinutes()));
};

// Set only the year, month, date parts (keep existing time)
Date.prototype.setDateOnly = function(date) {
	var tmp = new Date(date);
	this.setDate(1);
	this.setFullYear(tmp.getFullYear());
	this.setMonth(tmp.getMonth());
	this.setDate(tmp.getDate());
};

// global object that remembers the calendar
window._sl_calendar = null;





// ** I18N

// Calendar EN language
// Author: Mihai Bazon, <mihai_bazon@yahoo.com>
// Encoding: any
// Distributed under the same terms as the calendar itself.

// For translators: please use UTF-8 if possible.  We strongly believe that
// Unicode is the answer to a real internationalized world.  Also please
// include your contact information in the header, as can be seen above.

// full day names
Calendar._DN = new Array
("Sunday",
 "Monday",
 "Tuesday",
 "Wednesday",
 "Thursday",
 "Friday",
 "Saturday",
 "Sunday");

// Please note that the following array of short day names (and the same goes
// for short month names, _SMN) isn't absolutely necessary.  We give it here
// for exemplification on how one can customize the short day names, but if
// they are simply the first N letters of the full name you can simply say:
//
//   Calendar._SDN_len = N; // short day name length
//   Calendar._SMN_len = N; // short month name length
//
// If N = 3 then this is not needed either since we assume a value of 3 if not
// present, to be compatible with translation files that were written before
// this feature.

// short day names
Calendar._SDN = new Array
("Su",
 "Mo",
 "Tu",
 "We",
 "Th",
 "Fr",
 "Sa",
 "Su");

// First day of the week. "0" means display Sunday first, "1" means display
// Monday first, etc.
Calendar._FD = 0;

// full month names
Calendar._MN = new Array
("January",
 "February",
 "March",
 "April",
 "May",
 "June",
 "July",
 "August",
 "September",
 "October",
 "November",
 "December");

// short month names
Calendar._SMN = new Array
("Jan",
 "Feb",
 "Mar",
 "Apr",
 "May",
 "Jun",
 "Jul",
 "Aug",
 "Sep",
 "Oct",
 "Nov",
 "Dec");



/** 
 * based on http://dynarch.com/mishoo/calendar.epl
 * This script is distributed under the GNU Lesser General Public License.
 * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
 */
Calendar.savedHandlers = new Array();
Calendar.setup = function (params) {
	function param_default(pname, def) { if (typeof params[pname] == "undefined") { params[pname] = def; } };
	param_default("button",         null);
	param_default("eventName",      "click");
	param_default("align",          "Br");
	param_default("onSelect",       null);
	param_default("onClose",        null);
	param_default("onUpdate",       null);
	param_default("date",           new Date());
	param_default("position",       null);
	param_default("cache",          false);
	
	var target_date = new Date();
	target_date.setDate(1);
	target_date.setFullYear(target_date.getFullYear() + 1);
	param_default('maxDate', target_date);
	params.button = document.getElementById(params.button);

	function onSelect(cal) {
		var p = cal.params;
		if (cal.dateClicked && typeof p.onUpdate == "function") {
			p.onUpdate(cal);
		}
		if (cal.dateClicked && cal.dateClicked) {
			cal.callCloseHandler();
		}
	};

	var trigger = params.button;
	var handler_key = params.button.id + '_' + 'on' + params.eventName;
	Calendar.savedHandlers[handler_key] = trigger['on' + params.eventName];
	trigger["on" + params.eventName] = function() {
		var must_create = false;
		var cal = window._sl_calendar;
		params.date = params.getDate();
		if (!(cal && params.cache)) {
			must_create = true;
			window._sl_calendar = cal = new Calendar(params.date,
												params.onSelect || onSelect,
												params.onClose || function(cal) { cal.hide(); });
			cal.setMaxDate(params.maxDate);
			cal.params = params;
		} else {
			cal.hide();
		}

		cal.setDate(params.date);
		if (must_create) {
			cal.create();
		}
		if (!params.position) {
			cal.showAtElement(params.showAt || params.button, params.align);
		} else {
			cal.showAt(params.position[0], params.position[1]);
		}
		if (typeof Calendar.savedHandlers[handler_key] == 'function') {
			window.setTimeout('Calendar.savedHandlers["' + handler_key + '"]()', 10);
		}
		return false;
	};
};
