/**
 * 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.minDate = (typeof BookingBuddy != 'undefined') ? new Date(BookingBuddy.Strings.ServerTime) : new Date();
	this.maxDate = (typeof BookingBuddy != 'undefined') ? new Date(BookingBuddy.Strings.ServerTime) : new Date();
	this.isPopup = true;
	this.date = init_date ? new Date(init_date) : (typeof BookingBuddy != 'undefined') ? new Date(BookingBuddy.Strings.ServerTime) : 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;

//Initializing the first day to 0 (Sunday) in case the locale specific file does not contain it
Calendar._FD = 0;

// 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;

	// default day length display - SUN, MON, etc
	if (typeof Calendar._SDN_len == 'undefined') {
		Calendar._SDN_len = 3;
	}

	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";

			if (text == '&laquo;') {
				cell.className += " left";
			} else if (text == '&raquo;') {
				cell.className += " right";
			}

			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 + Calendar._FD) % 7].substr(0, Calendar._SDN_len);
		}

		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);
};

Calendar.prototype.toggleIFrameMask= function(action) {
	if (action == 'hide') {
		var iframe = document.getElementById('cal_hide_iframe');
		if (iframe) {
			iframe.parentNode.removeChild(iframe);
		}
	} else if (action == 'show') {
		iframe = document.createElement('iframe');
		iframe.src = '';
		iframe.id = 'cal_hide_iframe';
		iframe.frameBorder = '0';
		iframe.style.position = 'absolute';
		iframe.style.height = this.element.offsetHeight + 'px';
		iframe.style.top = parseInt(this.element.style.top) + 'px';
		iframe.style.left = this.element.style.left;
		iframe.style.width = this.element.offsetWidth + 'px';
		iframe.style.border = 'none';
		iframe.style.zIndex = 1001;
		iframe.scrolling = 'no';
		iframe.tabIndex = -1;
		iframe.style.display = 'block';
		this.element.style.zIndex = 1002;
		this.element.parentNode.appendChild(iframe);
	}
}

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

	if (typeof BookingBuddy !== 'undefined'  && this.params.showAt) {
		var today =  new Date(BookingBuddy.Strings.ServerTime)

		// get id of the element referred to by the calendar
		var showAtID = this.params.showAt.id;
		var show_parts = showAtID.split('_');
		var date_input = show_parts[0];

		if ($(date_input + '_month')) {
			var parts = $F(date_input + '_month').split(' ');
			var month = parts[0];
			var year = parts[1];
			var day = $F(date_input + '_day');

			var cal_selected_date = new Date();
			cal_selected_date.setFullYear(year, month-1, day);
		}
	} else {
		var today = new Date();
	}

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

	if (base_date <= this.minDate) {
		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 = '<div class="cal_arrow">&laquo;</div>';
	}

	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() - Calendar._FD) % 7;
		if(offset < 0) {
			offset += 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 (typeof cal_selected_date !== 'undefined') {
					if (tmp_date.getFullYear() == cal_selected_date.getFullYear() && tmp_date.getMonth() == cal_selected_date.getMonth() && iday == cal_selected_date.getDate()) {
						cell.className += " cal_selected_date";
					}
				}

				if (tmp_date.getFullYear() == today.getFullYear() && tmp_date.getMonth() == today.getMonth() && iday == today.getDate()) {
					cell.className += " today";
				} else if (tmp_date.getTime() < this.minDate.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 = '<div class="cal_arrow">&raquo;</div>';
			}
		}
	}
};

// 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 min date that the calendar can show.
Calendar.prototype.setMinDate = function (date) {
	this.minDate = 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);
	}
};

// 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.toggleIFrameMask('show');
};

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

// 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;
	}

	Calendar.continuation_for_khtml_browser = function() {
		_self.element.style.display = "block";
		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;
		_self.showAt(p.x, p.y);
	};
	if (Calendar.is_khtml) {
		setTimeout("Calendar.continuation_for_khtml_browser()", 10);
	} else {
		Calendar.continuation_for_khtml_browser();
	}
};

// 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;
