var Cathmhaol = window.Cathmhaol || {};

/**
* Creates a popup calendar that will allow the user to select a date. The constructor allows the user to set the form input, the default date, and the helper object.
*
* @argument	{node|string} formInput    An HTML form input
* @argument	{date|string} defaultDate  A value that is a date or can be evaluated as a date
* @argument	{node|string} helper       A DOM node or HTML string that can be clicked.
* @argument	{string} iso_639_1_code    An ISO 639-1 Alpha-3 language code.
* @argument	{integer} dwstart          An integer between 0 (Sunday) and 6 (Saturday) representing the first day of the week.
*
* @requires	Cathmhaol.Calendar http://js.cathmhaol.com/cjl-calendar.js
*
* @author	Robert King (hrobertking@cathmhaol.com)
*
* @example	var oBirthdayHelp = new Cathmhaol.DateInput("birthday");
* @example	var oAnniversaryHelp = new Cathmhaol.DateInput("anniversary", "1/1/1970");
* @example	var oDateHelp = new Cathmhaol.DateInput("birthday", "", '<img src="http://www.cathmhaol.com/images/calendar.gif">', "it");
*/
Cathmhaol.DateInput = function(formInput, defaultDate, helper, iso_639_1_code, dwstart) {
	/**
	* @property	Day of the week that is first: Cathmhaol.Calendar.SUNDAY, Cathmhaol.Calendar.MONDAY, Cathmhaol.Calendar.TUESDAY, Cathmhaol.Calendar.WEDNESDAY, Cathmhaol.Calendar.THURSDAY, Cathmhaol.Calendar.FRIDAY, Cathmhaol.Calendar.SATURDAY
	* @type	{integer}
	*/
	this.firstDay = dwstart || Cathmhaol.Calendar.SUNDAY;

	/**
	* @method	Closes and destroys the calendar popup. If a date was selected, that date is set as the value of the input.
	* @returns	{void}
	* @argument	{event} evt
	*/
	this.close = function(evt) {
		// We get the target of the event that is being handled (the click event of a date in the calendar) and
		// determine the date based on the title attribute of the element. Using that date, we set the private
		// variable _value and set the _input if it's been bound.

		evt = evt || window.event;
		var target = evt.target || evt.srcElement;
		_destroy();
		if (target && target.title) {
			var d = target.title.split("-");
			if (d.length == 3) {
				var yy = parseFloat(d[0]);
				var mm = parseFloat(d[1])-1;
				var dd = parseFloat(d[2]);
				if (mm < 0) { yy--; mm = 12+mm; }
				_value = new Date(yy, mm, dd);
				if (_input) {
					_input.value = (_value.getMonth()+1) + "/" + _value.getDate() + "/" + _value.getFullYear();
				}
			}
		}
		return;
	};

	/**
	* @method	Returns the date selected
	* @returns	{date}
	*/
	this.getDate = function() {
		// Just return the date value.

		return _value;
	};

	/**
	* @method	Returns the ISO 639-1 Alpha-3 language code being used
	* @returns	{string}
	*/
	this.getLanguageCode = function() {
		// Just return the language code that's being used.

		return _lang;
	};

	/**
	* @method	Returns an array of ISO 639-1 Alpha-3 language codes available
	* @returns	{string[]}
	*/
	this.getLanguageCodes = function() {
		// Return an array of all the language codes supported.

		return Cathmhaol.Calendar.supportedLanguages();
	};

	/**
	* @method	Sets the ISO 639-1 Alpha-3 language code being used. Returns true if set, false if not.
	* @returns	{boolean}
	* @argument	{string} languageCode
	*/
	this.setLanguageCode = function(languageCode) {
		// Set the language code as long as it's a supported language.

		if (Cathmhaol.Calendar.isSupportedLanguage(languageCode)) {
			_lang = languageCode;
			return true;
		}
		return false;
	};

	/**
	* @method	Displays the calendar popup at the specified position.
	* @returns	{void}
	* @argument	{integer} top
	* @argument	{integer} left
	*/
	this.show = function(top, left) {
		// We can't show a calendar if we don't know what date to start with, so check to make sure we have a value
		// before we start. If we have a value, set the top and left to provided values, using the default values if
		// a top or left is not specified, then make sure that the calendar stub is rendered. Once the stub is
		// rendered, generate a calendar based on the currently selected day. Finally, make sure that the calendar is
		// inside the viewport close to the bound input.

		if (!_value) {
			return;
		}
		_top = top || _top;
		_left = left || _left;

		if (!_panel) { _open(_top, _left); } else { _panel.style.width = "200px"; }

		_value = _getValue();

		_yy.innerHTML = _value.getFullYear();
		_mo.innerHTML = Cathmhaol.Calendar.MONTHS[me.getLanguageCode()][_value.getMonth()].longName;

		while (_cal.hasChildNodes()) {
			_cal.removeChild(_cal.firstChild);
		}

		var iWidth = 0;
		var oHeader = _cal.insertRow(-1);
		var c = 0;
		while (c < 7) {
			var i = me.firstDay + c;
			i = (i < 7 ? i : i - 7);
			var oDay = _formatHeader(oHeader.insertCell(-1));
			var sShort = Cathmhaol.Calendar.DAYS[me.getLanguageCode()][i].shortName;
			var sLong = Cathmhaol.Calendar.DAYS[me.getLanguageCode()][i].longName;
			oDay.innerHTML = (sShort ? sShort : sLong);
			oDay.title = sLong;
			c++;
		}

		var dValue = new Date(_value.getFullYear(), _value.getMonth(), 1);
		var oWeek = _cal.insertRow(-1);
		for (var c = 0; c < (Cathmhaol.Calendar.dayOfTheWeek(dValue) - me.firstDay); c++) {
			var oDay = _formatBoundary(oWeek.insertCell(-1));
		}

		for (var c = 1; c <= Cathmhaol.Calendar.daysInMonth(_value); c++) {
			if (oWeek.cells.length == 7) { oWeek = _cal.insertRow(-1); }
			var dValue = new Date(_value.getFullYear(), _value.getMonth(), c);
			var oDay = _formatWeekday(oWeek.insertCell(-1));
			oDay.title = dValue.getFullYear() + "-" + (dValue.getMonth() < 9 ? "0" : "") + (dValue.getMonth() + 1) + "-" + (dValue.getDate() < 10 ? "0" : "") + dValue.getDate();
			oDay.onclick = function(e) { me.close.apply(me, [e]); }
			oDay.innerHTML = c.toString();
		}

		for (var c = 7 - oWeek.cells.length; c > 0; c--) {
			var oDay = _formatBoundary(oWeek.insertCell(-1));
		}

		_setHeight(_setWidth());
		_moveToReadable();
		return;
	};

	/**
	* @private
	* @method	Go to the previous month
	* @returns	{void}
	* @argument	{event} evt
	*/
	function _back(evt) {
		_value = Cathmhaol.Calendar.dateAdd(Cathmhaol.Calendar.PERIOD.MONTH, -1, _value);
		me.show();
		return;
	}

	/**
	* @private
	* @method	Creates a button
	* @returns	{node}
	*/
	function _button() {
		var oBtn = document.createElement("input");
		oBtn.style.background = "transparent";
		oBtn.style.border = "1px solid #000";
		oBtn.style.cursor = "pointer";
		oBtn.style.fontFamily = "Verdana"
		oBtn.style.fontSize = ".7em"
		oBtn.style.fontStyle = "normal";
		oBtn.style.fontVariant = "small-caps";
		oBtn.style.fontWeight = "bold";
		oBtn.style.margin = "0";
		oBtn.style.width = "auto";
		oBtn.type = "button";
		return oBtn;
	}

	/**
	* @private
	* @method	Destroys the popup panel
	* @returns	{void}
	* @argument	{event} evt
	*/
	function _destroy(evt) {
		if (_panel) {
			var oDomParent = _panel.parentNode;
			if (oDomParent) { oDomParent.removeChild(_panel); }
			_panel = null;
		}
		return;
	}

	/**
	* @private
	* @method	Formats the table cells that lie outside of the month.
	* @returns	{node}
	* @argument	{node} oTableCell
	*/
	function _formatBoundary(oTableCell) {
		oTableCell.className = "calendar boundary";
		oTableCell.style.backgroundColor = "#ccc";
		oTableCell.style.border = "1px solid #000";
		oTableCell.style.fontFamily = "Verdana";
		oTableCell.style.fontSize = "10px";
		oTableCell.style.height = "3em";
	}

	/**
	* @private
	* @method	Formats the table cells that are the headers for the weekday columns.
	* @returns	{node}
	* @argument	{node} oTableCell
	*/
	function _formatHeader(oTableCell) {
		oTableCell.className = "calendar dow header"
		oTableCell.style.cursor = "default";
		oTableCell.style.fontSize = "10px";
		oTableCell.style.overflow = "hidden";
		oTableCell.style.padding = "0";
		oTableCell.style.textAlign = "center";
		oTableCell.style.verticalAlign = "bottom";
		return oTableCell;
	}

	/**
	* @private
	* @method	Formats the table cells that contain days
	* @returns	{node}
	* @argument	{node} oTableCell
	*/
	function _formatWeekday(oTableCell) {
		oTableCell.className = "calendar dow day";
		oTableCell.style.border = "1px solid #000";
		oTableCell.style.cursor = "pointer";
		oTableCell.style.fontFamily = "Verdana";
		oTableCell.style.fontSize = "10px";
		oTableCell.style.height = "3em";
		oTableCell.style.textAlign = "right";
		oTableCell.style.verticalAlign = "top";
		oTableCell.style.width = "14.3%";
		return oTableCell;
	}

	/**
	* @private
	* @method	Go to the next month
	* @returns	{void}
	* @argument	{event} evt
	*/
	function _forward(evt) {
		_value = Cathmhaol.Calendar.dateAdd(Cathmhaol.Calendar.PERIOD.MONTH, 1, _value);
		me.show();
		return;
	}

	/**
	* @private
	* @method	Inserts a DOM node after another.
	* @returns	{void}
	* @argument	{node} nodeToInsert
	* @argument	{node} nodeToInsertAfter
	*/
	function _insertAfter(nodeToInsert, nodeToInsertAfter) {
		// To insert after a node, we go to the next sibling and insert it before that node. If the node does
		// not have a next sibling (it's the last child), then we simply append the node to the parent, making
		// it the new 'last child'.

		var parent = nodeToInsertAfter.parentNode;
		var nextSibling = nodeToInsertAfter.nextSibiling;
		if (nextSibling) {
			nextSibling.insertBefore(nodeToInsert);
		} else if (parent) {
			parent.appendChild(nodeToInsert);
		}
		return;
	}

	/**
	* @private
	* @method	Gets the value associated with the calendar
	* @returns	{date}
	*/
	function _getValue() {
		// Return the first value we can find.

		if (_value) { return _value; }
		if (_input.value && _input.value != "") { return new Date(_input.value); }
		return new Date();
	}

	/**
	* @private
	* @method	Moves the popup to a readable section.
	* @returns	{void}
	*/
	function _moveToReadable() {
		// Calculate the size of the viewport and the size of the calendar that's displayed.
		// If the top/left corner to the bottom/right corner extends outside of the viewport
		// then move the top/left corner to a place where it won't force the calendar out of
		// the viewport by subtracting the height of the calendar from the height of the viewport
		// and the width of the calendar from the width of the viewport.

		var heightOfScreen = parseFloat(window.innerHeight ? window.innerHeight : (document.body.clientHeight ? document.body.clientHeight : 0));
		var widthOfScreen = parseFloat(window.innerWidth ? window.innerWidth : (document.body.clientWidth ? document.body.clientWidth : 0));

		var heightOfPanel = parseFloat(_panel.clientHeight);
		var leftOfPanel = parseFloat(_panel.style.left.replace(/px/, ""));
		var topOfPanel = parseFloat(_panel.style.top.replace(/px/, ""));
		var widthOfPanel = parseFloat(_panel.clientWidth);

		if ((topOfPanel + heightOfPanel) > heightOfScreen) {
			_top = (heightOfScreen - heightOfPanel - 3);
			_panel.style.top = _top+"px";
		}
		if ((leftOfPanel + widthOfPanel) > widthOfScreen) {
			_left = (widthOfScreen - widthOfPanel - 3);
			_panel.style.left = _left+"px";
		}
		return;
	}

	/**
	* @private
	* @method	Initializes the popup and places it at the coordinates indicated by the top and left arguments.
	* @returns	{void}
	* @argument	{integer} top
	* @argument	{integer} left
	*/
	function _open(top, left) {
		_top = top || _top;
		_left = left || _left;

		var _dnoCalendar = document.createElement("div");
		_dnoCalendar.className = "calendar panel";
		_dnoCalendar.style.backgroundColor = "#fff";
		_dnoCalendar.style.border = "1px solid #000";
		_dnoCalendar.style.filter = "alpha(opacity=100)";
		_dnoCalendar.style.opacity = 1;
		_dnoCalendar.style.marginLeft = "-8px";
		_dnoCalendar.style.marginTop = "-8px";
		_dnoCalendar.style.padding = "3px";
		_dnoCalendar.style.textAlign = "center";
		_dnoCalendar.style.width = "100%";
		_dnoCalendar.style.zIndex = document.body.style.zIndex + 10;

		var oTitle = document.createElement("table");
		oTitle.cellPadding = "0";
		oTitle.cellSpacing = "0";
		oTitle.style.margin = "0";
		oTitle.style.padding = "0";
		oTitle.style.width = "100%";
		oTitle.className = "calendar title";
		var oRow = oTitle.insertRow(-1);

		_yy = oRow.insertCell(-1);
		_yy.className = "year";
		_yy.style.cursor = "default";
		_yy.style.fontFamily = "Verdana";
		_yy.style.fontSize = ".7em";
		_yy.style.fontStyle = "normal";
		_yy.style.fontVariant = "small-caps";
		_yy.style.fontWeight = "bold";
		_yy.style.textAlign = "left";
		_yy.style.verticalAlign = "bottom";
		_yy.style.width = "80%";

		var oWinCtrl = oRow.insertCell(-1);
		oWinCtrl.className = "button";
		oWinCtrl.style.textAlign = "right";
		oWinCtrl.style.verticalAlign = "bottom";
		oWinCtrl.style.width = "20%";

		var _panelBtnClose = _button();
		_panelBtnClose.className = "close";
		_panelBtnClose.style.fontSize = ".5em";
		_panelBtnClose.style.fontWeight = "bold";
		_panelBtnClose.style.padding = "0";
		_panelBtnClose.value = "X";
		oWinCtrl.appendChild(_panelBtnClose);

		_dnoCalendar.appendChild(oTitle);

		_mo = document.createElement("div");
		_mo.className = "month";
		_mo.style.clear = "both";
		_mo.style.cursor = "default";
		_mo.style.cssFloat = "none";
		_mo.style.fontFamily = "Verdana";
		_mo.style.fontSize = "1.2em";
		_mo.style.styleFloat = "none";
		_mo.style.width = "100%";

		_dnoCalendar.appendChild(_mo);

		var oControl = document.createElement("table");
		oControl.className = "navigation";
		oControl.cellPadding = "0";
		oControl.cellSpacing = "0";
		oControl.style.margin = "0";
		oControl.style.padding = "0";
		oControl.style.width = "100%";
		var oRow = oControl.insertRow(-1);

		var oCtrl_Prev = oRow.insertCell(-1);
		oCtrl_Prev.className = "button";
		oCtrl_Prev.style.textAlign = "left";
		oCtrl_Prev.style.verticalAlign = "bottom";
		oCtrl_Prev.style.width = "50%";

		_panelBtnPrev = _button();
		_panelBtnPrev.className = "previous";
		_panelBtnPrev.value = "<";
		oCtrl_Prev.appendChild(_panelBtnPrev);

		var oCtrl_Next = oRow.insertCell(-1);
		oCtrl_Next.className = "button";
		oCtrl_Next.style.textAlign = "right";
		oCtrl_Next.style.verticalAlign = "bottom";
		oCtrl_Next.style.width = "50%";

		_panelBtnNext = _button();
		_panelBtnNext.className = "next";
		_panelBtnNext.value = ">";
		oCtrl_Next.appendChild(_panelBtnNext);

		_dnoCalendar.appendChild(oControl);

		_cal = document.createElement("table");
		_cal.className = "calendar";
		_cal.cellPadding = "1px";
		_cal.cellSpacing = "2px";
		_cal.style.clear = "both";
		_cal.style.cssFloat = "none";
		_cal.style.styleFloat = "none";
		_cal.style.width = "100%";

		_dnoCalendar.appendChild(_cal);

		_panel = document.createElement("div");
		_panel.className = "calendar shadow";
		_panel.style.backgroundColor = "#ccc";
		_panel.style.filter = "alpha(opacity=100)";
		_panel.style.left = _left+"px";
		_panel.style.opacity = 1;
		_panel.style.padding = "5px";
		_panel.style.position = "absolute";
		_panel.style.textAlign = "center";
		_panel.style.top = _top+"px";
		_panel.style.width = "200px";
		_panel.style.zIndex = document.body.style.zIndex + 1;
		_panel.appendChild(_dnoCalendar);

		_panelBtnClose.onclick = function(e) { _destroy.apply(me, [e]); }
		_panelBtnNext.onclick = function(e) { _forward.apply(me, [e]); }
		_panelBtnPrev.onclick = function(e) { _back.apply(me, [e]); }

		document.body.insertBefore(_panel, document.body.firstChild);

		return;
	}

	/**
	* @private
	* @method	Displays the calendar popup
	* @returns	{void}
	* @argument	{event} evt
	*/
	function _popup(evt) {
		// Calculate the coordinates of the event that triggered the popup (e.g. mouseclick)
		// and display the calendar at that position.

		var evt = evt || window.event;
		_left = (evt && evt.clientX ? evt.clientX : _left && _left > 0 ? _left : 0);
		_top  = (evt && evt.clientY ? evt.clientY : _top  && _top  > 0 ? _top  : 0);
		me.show(_top, _left);
	}

	/**
	* @private
	* @method	Sets the height of the popup based on the width of the cells.
	* @returns	{void}
	* @argument	{integer} h
	*/
	function _setHeight(h) {
		// Because fonts don't typically have a 1:1 height/width ratio, we set the height
		// of the table cell based on the width of the table cell, giving us a square.

		for (var r = 1; r < _cal.rows.length; r++) {
			for (var c = 0; c < _cal.rows[r].cells.length; c++) {
				_cal.rows[r].cells[c].style.height = h+"px";
			}
		}
		return;
	}

	/**
	* @private
	* @method	Sets the width of the popup based on the content width of the header.
	* @returns	{integer}
	*/
	function _setWidth() {
		// We set the width of the panel to the width of the 7-column table (calendar) plus
		// a 10px margin.

		var iWidth = 0;
		for (var c = 0; c < _cal.rows[0].cells.length; c++) {
			if (_cal.rows[0].cells[c].clientWidth > iWidth) { iWidth = _cal.rows[0].cells[c].clientWidth; }
		}
		_panel.style.width = ((iWidth * 7) + (parseFloat(_cal.cellSpacing.replace(/px/, "")) * 6) + (parseFloat(_cal.cellSpacing.replace(/px/, "")) * 7)+10)+"px";
		return iWidth;
	}

	//Constructor
	/**
	* _lang	The ISO language code to use.
	* _input	The form element to populate with the date.
	* _value	Date value to use.
	*/
	var _input = (typeof(formInput) == "string") ? document.getElementById(formInput) : formInput;
	var _value = (!defaultDate) ? new Date() : (typeof(defaultDate) == "string" || typeof(defaultDate) == "number") ? new Date(defaultDate) : (defaultDate.getMonth.call && defaultDate.getFullYear.call) ? defaultDate : new Date();
	if (helper) {
		if (typeof helper == "string") {
			var oDiv = document.createElement("div");
			oDiv.innerHTML = helper;
			helper = oDiv.childNodes[0];
		}
	} else {
		helper = document.createElement("input");
		helper.type = "button";
		helper.value = "?";
	}
	helper.onclick = function(e) { _popup.apply(me, [e]); }
	if (_input && helper) {
		_insertAfter(helper, _input);
	}
	var _lang = (Cathmhaol.Calendar.isSupportedLanguage(iso_639_1_code) ? iso_639_1_code : "en");

	/**
	* _cal           The table that contains weeks/days.
	* _left          The pixel position of the left edge of the popup.
	* _mo            The node containing the month name displayed in the calendar.
	* _panel         The calendar popup with drop shadow.
	* _top           The pixel position of the top edge of the popup.
	* _yy            The table cell containing the year.
	*/
	var _cal, _left, _mo, _panel, _top, _yy, me = this;
};

