/**
 * calendar widget
 * @version 1.8.2 2009-07-03
 * @author Gregor Kofler
 * 
 * @param {Object} htmlElem element receiving data value
 *
 * @param {Object} configuration (optional)
 * 	trigger:		{Object} triggering element
 *	eType:			{String} event type
 *	dontHide:		{Boolean} show permanently
 *  initDate:		{Object} initial date value	
 *	noPast:			{Boolean|Date} allows only future dates | date after Date
 *	showCw:			{Boolean} have calendar weeks displayed
 *	inputLocale:	{String} input date format (date_de|date_us|date_iso),
 *	outputFormat:	{String} format string to format output inserted in htmlElem
 *	months:			{String} months name of months separated by spaces
 *
 * @param {Object} XHR Parameters for adding additional via XHR retrieved information
 * 
 * @return {Object} calendar widget
 * 
 * served events: "datePick", "monthChange", "yearChange", "show", "hide"
 * 
 * @todo make htmlElem optional
 * 
 */
/*global Coord, vxJS */

if(!vxJS) { throw new Error("widget.calendar: vxJS core missing."); }

vxJS.widget.calendar = function(htmlElem, config, xhrReq) {
	if(!htmlElem)	{ throw new Error("widget.calendar: Missing element."); }

	if(typeof config !== "object") {
		config = {};
	}

	var	layer, input, mNode, table, docFrag = document.createDocumentFragment(), dayCells, weekCells, that = {}, triggerListenerId, docListenerId, xhr,
		now = config.initDate && config.initDate.constructor === Date ? config.initDate : new Date(),
		day = now.getDate(), month = now.getMonth(), year = now.getFullYear(),
		currDay = day, currMon = month, currYear = year,
		elemDay = 0, elemMon = 0, elemYear = 0,
		locale = config.inputLocale || "date_de",
		format = config.outputFormat || (locale === "date_de" ? "%D.%M.%Y" : "%Y-%M-%D"),
		months = (config.months || "Jan Feb M\u00E4rz Apr Mai Juni Juli Aug Sept Okt Nov Dez").split(" "),
		showCw = config.showCw, noPast;

	if(config.noPast) {
		noPast = config.noPast.constructor === Date ? config.noPast : new Date(year, month, day);
	}

	// US style not implemented yet
	var getBeginOfCW = function(cw, year, usStyle) {
		year = year || new Date().getFullYear();
			
		var	b = new Date(year, 0, 1),
			c = (cw - 1) * 7 + 1,
			d = b.getDay() || 7;
		return new Date(year, 0, (c - d) + (d > 4 ? 8 : 1));
	};

	var initDates = function() {
		var elemD, d;

		elemD = htmlElem.value.trim();

		if(/^\d\d?[ \/\-\.]+(?:\d{2}|\d{4})$/.test(elemD)) {
			elemD =  elemD.split(/[ \/\-\.]+/);
			d = getBeginOfCW(elemD[0], (""+year).slice(0, 4-elemD[1].length) + elemD[1], locale === "date_us");
			day		= elemDay	= d.getDate();
			month	= elemMon	= d.getMonth();
			year	= elemYear	= d.getFullYear();
			return;
		}

		if((elemD = elemD.toDateTime(locale, true))) {
			day		= elemDay	= elemD.getDate();
			month	= elemMon	= elemD.getMonth();
			year	= elemYear	= elemD.getFullYear();
		}
	};

	var getFirstEnabledDay = function(row) {
		var i, cN, cells = row.childNodes;

		for(i = 0; i < cells.length; i++) {
			cN = cells[i].className;
			if(cN.indexOf("dateCell") === -1) {
				continue;
			}
			if(cN.indexOf("disabled") === -1) {
				return cells[i].firstChild.nodeValue;
			}
		}
	};

	var restoreCells = function() {
		var i, rex = /\s+disabled/;

		for(i = dayCells.length; i--; ) {
			if(dayCells[i] && !dayCells[i].disabled) {
				dayCells[i].elem.className = dayCells[i].elem.className.replace(rex, "");
			}
		}
		if(weekCells) {
			for(i = weekCells.length; i--; ) {
				if(getFirstEnabledDay(weekCells[i].parentNode)) {
					weekCells[i].className = weekCells[i].className.replace(rex, "");
				}
			}
		}
	};

	var handleXhrResponse = function() {
		var r, i, d, rex = /\s+disabled/;

		if((r = this.response.entries) && r.length) {
			for(i = r.length; i--; ) {
				if((d = +r[i].day) && dayCells[--d]) {
					if(!r[i].disabled && !dayCells[d].disabled) {
						dayCells[d].elem.className = dayCells[d].elem.className.replace(rex, "");
					}
					if(r[i].label) {
						dayCells[d].elem.appendChild("div".create(r[i].label));
					}
					delete dayCells[d];
				}
			}
			restoreCells();
		}
	};

	var doXhr = function() {
		if(!xhrReq) {
			return;
		}
		
		if(xhr) {
			xhr.abort();
			xhr.use(null, { date: year + "-" + ("0"+(month+1)).slice(-2) + "-01" });
		}
		else {
			xhr = vxJS.xhr(xhrReq, { date: year + "-" + ("0"+(month+1)).slice(-2) + "-01" });
			vxJS.event.addListener(xhr, "completed", handleXhrResponse);
			vxJS.event.addListener(xhr, "timeout", restoreCells);
		}
	};

	var fillCalendar = function() {
		var	d = new Date(year, month, 1),
			sDay = d.getDay(), eDay = d.getDaysOfMonth(),
			w, startCol = locale === "date_us" ? sDay : (sDay === 0 ? 6 : sDay-1),
			r, i, cN, trail = [], f, rows;

		mNode.nodeValue = months[month];
		input.value = year;

		dayCells = [];
		trail.fill("",startCol);
		r = "tr".create(trail.domWrapWithTag("td"));

		for(i = 1; i <= eDay; i++) {
			f = f || !noPast || !(new Date(year, month, i) < noPast);

			cN	= "dateCell";
			if(month === currMon && year === currYear && i === currDay) {
				cN += " today";
			}
			else if(month === elemMon && year === elemYear && i === elemDay) {
				cN += " marked";
			}
			if(!f || xhrReq) {
				cN += " disabled";
			}

			if(startCol++ % 7 === 0 && startCol > 1) {
				docFrag.appendChild(r);
				r = "tr".create();
			}

			r.appendChild("td".setProp("class", cN).create(i));

			dayCells[i-1] = {
				elem: r.lastChild,
				disabled: !f
			};
		}

		trail = [];
		trail.fill("td", 7 - (startCol % 7 !== 0 ? startCol % 7 : 7));
		for(i = 0; i < trail.length; i++) {
			r.appendChild(trail[i].create());
		}
		docFrag.appendChild(r);

		if(showCw) {
			weekCells = [];
			w = w || d.getCW(locale === "date_us");
			rows = docFrag.childNodes;

			for(i = 0; i < rows.length; ++i) {
				if(w > 52) {
					w = new Date(year, month, parseInt(vxJS.dom.getElementsByClassName("dateCell", rows[i])[0].firstChild.nodeValue, 10)).getCW(locale === "date_us");
				}
				rows[i].appendChild("td".create(w++));
				rows[i].lastChild.className = "weekCell" + (!getFirstEnabledDay(rows[i]) ? " disabled" : "");
				weekCells.push(rows[i].lastChild);
			}
		}

		table.replaceChild("tbody".create(docFrag), table.childNodes[1]);

		doXhr();
	};

	var insertDate = function() {
		htmlElem.value = new Date(year, month, day).format(format);
	};

	var createCalendar = function() {
		var d;

		table	= "table".create(["thead".create(), "tbody".create()]);

		mNode	= document.createTextNode("");
		input	= "input".setProp("maxLength", 4).create();

		layer	= "div".setProp("class", "vxJS_calendar").create("div".create([
			"table".setProp("class", "vxJS_dragBar").create("tbody".create("tr".create([
				"td".setProp("class", "prevMon").create("\u00AB"),
				"td".setProp("class", "mon").create(mNode),
				"td".setProp("class", "nextMon").create("\u00BB"),
				"td".create(),
				"td".setProp("class", "prevYear").create("\u00AB"),
				"td".setProp("class", "year").create(input),
				"td".setProp("class", "nextYear").create("\u00BB")
				]))),
			table]));

		switch(locale) {
			case "date_us":
				d = "S,M,T,W,T,F,S,CW";
				break;
			case "date_iso":
				d = "M,T,W,T,F,S,S,CW";
				break;
			default:
				d = "M,D,M,D,F,S,S,KW";
		}
		d = d.split(",");
		if(!showCw) {
			d.pop();
		}

		table.firstChild.appendChild("tr".create(d.domWrapWithTag("th")));

		vxJS.event.addListener(input, "blur", function() {
			var y;
			if(/^\d{2,}$/.test(this.value)) {
				y = parseInt((""+new Date().getFullYear()).slice(0, 4-this.value.length) + this.value, 10);
				if(y !== year) {
					year = y;
					fillCalendar();
					vxJS.event.serve(that, "yearChange");
				}
			}
			this.value = year;
		});

		vxJS.event.addListener(input, "keydown", function(e) {
			switch(e.keyCode) {
				case 27:
					this.value = year;
				case 13:
					this.blur();
			}
		});

		var mark = function(n) {
			var o;
			if((o = vxJS.dom.getElementsByClassName("marked", table)[0])) {
				o.className = "dateCell";
			}
			n.className = "dateCell marked";
		};

		vxJS.event.addListener(layer, "click", function(e) {
			var c = this.className, n, type;

			switch(c) {
				case "prevMon":
					if(--month < 0) { month = 11; year--; }
					fillCalendar();
					type = "monthChange";
					break;
				case "nextMon":
					if(++month > 11) { month = 0; year++; }
					fillCalendar();
					type = "monthChange";
					break;
				case "prevYear":
					year--;
					fillCalendar();
					type = "yearChange";
					break;
				case "nextYear":
					year++;
					fillCalendar();
					type = "yearChange";
					break;

				default:
					if(c.indexOf("weekCell") !== -1 && c.indexOf("disabled") === -1) {
						if((day = getFirstEnabledDay(this.parentNode))) {
							type = "datePick";
						}
					}
					else if(c.indexOf("dateCell") !== -1 && c.indexOf("disabled") === -1) {
						day = parseInt(this.firstChild.nodeValue, 10);
						type = "datePick";
						if(config.dontHide) {
							mark(this);
						}
					}
					else if((n = vxJS.dom.getParentElement(this, "td.dateCell")) && n.className.indexOf("disabled") === -1) {
						day = parseInt(n.firstChild.nodeValue, 10);
						type = "datePick";
						if(config.dontHide) {
							mark(n);
						}
					}
			}
			if(type) {
				vxJS.event.serve(that, type);

				if(type === "datePick") {
					insertDate();
					docListener();
				}
			}

			vxJS.event.cancelBubbling(e);
		});

		if(!config.dontHide) {
			layer.style.display = "none";
			layer.style.position = "absolute";
			document.body.appendChild(layer);
		}
	};

	var triggerListener = function(e) {
		if(htmlElem.disabled) {
			return;
		}

		vxJS.event.cancelBubbling(e);
		vxJS.event.removeListener(triggerListenerId);
		docListenerId = vxJS.event.addListener(document, "click", docListener);

		initDates();
		fillCalendar();
		vxJS.dom.setElementPosition(layer, vxJS.dom.getElementOffset(config.trigger));
		that.show();
		vxJS.event.serve(that, "show");
	};

	createCalendar();

	if(config.trigger) {
		triggerListenerId = vxJS.event.addListener(config.trigger, config.eType || "click", triggerListener);

		var docListener = function() {
			that.hide();
			vxJS.event.serve(that, "hide");
			vxJS.event.removeListener(docListenerId);
			triggerListenerId = vxJS.event.addListener(config.trigger, config.eType || "click", triggerListener);
		};
	}
	else {
		initDates();
		fillCalendar();

		docListener = function() {
			return;
		};
	}
	
	/**
	 * expose container element
	 */
	that.element = layer;

	/**
	 * expose hooks to attach fx
	 */
	that.show = function() { layer.style.display = ""; };
	that.hide = function() { layer.style.display = "none"; };

	/**
	 * get currently selected date from calendar as date object
	 */
	that.getDate = function() {
		return new Date(year, month, day);
	};

	/**
	 * set date of calendar explicitly
	 * either provide date object or get it from associated input element
	 */
	that.setDate = function(d) {
		if(d && d.constructor !== Date) {
			return;
		}
		if(d) {
			day = d.getDate();
			month = d.getMonth();
			year = d.getFullYear();
		}
		else {
			initDates();
		}

		fillCalendar();
	};

	return that;
};
