/** -----------------------------------------------------------------------------
	*	FILE:			Calendar.js		(Calendar version 1.0)
	*	PURPOSE:		Provides a client side (javascript) popup calendar control.
	*					Features include: Draggable, Default date, Selectable Dates
	*	AUTHOR:			J.Sietz  August 6,2008
	*
	*
	*	PUBLIC METHODS		DESCRIPTION
	*	--------------		------------------------------------------------
	*	show				Pops the calendar control up and activates for user selection (see show options below)
	*	hide				Hides and disconnects the behaviors (browser performance related)
	*	getSelectedDate		Returns the user selected date (usually used in the "Calendar:Selected" listener routine)
	*	getDayOfMonth		Returns the day of the month of the user selected date
	*	getDayName			Returns the day of the user selected date
	*	getMonthName		Returns the month name of the selected date
	*	getMonthAbbreviation Returns the month abbreviation of the selected date
	*	getYear				Returns the year of the user selected date
	*	isLeapYear			Determines if the year of the selected date is a leaper year
	*	getDaysInMonth		Returns the # of days in the user selected date
	*	getFirstDayOfMonth	Returns a day name index of the 1st of the selected month
	*
	*
	*	SHOW OPTIONS		DESCRIPTION
	*	--------------		------------------------------------------------
	*	className			Style skin to use
	*	selectableDates		selectable dates, see notes above
	*	selectedDate		Sets the selected/default date of the calendar control
	*	showToday			Displays/Hide the Today button
	*	showClose			Displays/Hide the Close button
	*	showCancel			Displays/Hide the Cancel button
	*	draggable			Determines if calendar is draggable
	*	top					absolute position of the top edge of the calendar
	*	left				absolute position of the left edge of the calendar
	*	extendedProperty	object to be passed with the custom events
	*
	*
	*	SELECTABLE DATES STRING
	*	--------------------------------------------------------------------
	*	+*						all dates are selectable (default)
	*	-*						no dates are selectable
	*	+mm/dd/yyyy				specific date is selectable
	*	-mm/dd/yyyy				specific date is not selectable
	*	+<mm/dd/yyyy			Dates prior to the specified date are selectable
	*	-<mm/dd/yyyy				Dates prior to the specified date are NOT selectable
	*	+>mm/dd/yyyy			Dates after the specified date are selectable
	*	->mm/dd/yyyy				Dates after the specified date are NOT selectable
	*	+mm/dd/yyyy:mm/dd/yyyy	date range selectable
	*	-mm/dd/yyyy:mm/dd/yyyy	date range not selectable
	*	+-dayname					makes all daynames (not)selectable (SUN,MON,TUE,WED,THU,FRI,SAT)
	*	+-1stDOM						1st Day Of the Month
	*	+-LastDOM					Last Day Of the Month
	*
	*	Examples of usage:
	*		STRING							DESCRIPTION
	*		-----------------				-----------------------------------------------------
	*		+*,-<01/01/2008					All dates are selectable except dates prior to 1/1/08
	*		-*,+FRI							No dates are selectable except fridays
	*		-*,+01/15/2008,+02/15/2008		No dates are selectable except 1/15/08 and 2/15/08
	*		-*,+06/01/2008:12/31/2008		No dates are selectable except dates in the range 6/1/08 to 12/31/08
	*
	*		Calendar.show({
	*			top:				"40px",			// absolute position of the top edge of the calendar
	*			left:				"750px",		// absolute position of the left edge of the calendar
	*			selectableDates:	"+*,-<01/01/2008,->12/31/2008,-08/04/2008,-08/06/2008,+01/11/2009",
	*			selectedDate:		$('hireDate').value,
	*			showToday:			true,			// Displays/Hide the Today button
	*			showClose:			false,			// Displays/Hide the Close button
	*			showCancel:			false,			// Displays/Hide the Cancel button
	*			draggable:			true			// Determines if calendar is draggable
	*			});
	*		$(document).observe("Calendar:Selected",populateHireDate);
	*		function populateHireDate(evnt)
	*		{
	*			$('hireDate').value = evnt.memo.selectedDate.format('MM/dd/yyyy');
	*		}
	*
	*
	*	DEPENDENCIES
	*	-----------------------------------
	*	prototype.js		version 1.6.0.3
	*	dateExtensions.js
	*
**/
/*-------------- Calendar Class --------------*/

var Calendar = function()
{	// Singleton class
	if (window.Calendar) return window.Calendar;


	/* -- Public Methods ----------------------------------------------------- */
	var _public =
	{
		// Initializes and configures the calendar control
		initialize: function()
		{	// Dynamically load the Date library and Calendar styles
			if (typeof(Date.parseString) != 'function')
				_private._loadJSFile( 'script/DateExtensions.js');
			_private._loadStyleFile('script/css/Calendar.css');

			_private._zIndex = _private._getMaxZIndex();
			_private._ValidDateAll = true;
			_private._DayExceptions = [true, true, true, true, true, true, true];
			_private._DateExceptions = null;
			_private._DateOutOfRange = null;
		},

		// Configures and displays the calendar control, closes existing displayed control
		show : function(optns)
		{
			_private._hideDropDowns();

			// Capture the calendar configuration parameters and set the selected date
			if (optns)
			{
				_private._options = Object.extend( _private._options, optns||{} );
				if ( _private._options.selectedDate == "" && typeof _private._options.selectedDate == "string" )
				{
					_private._Date = new Date();
					_private._SelectedDate = new Date(_private._Date);
				}
				else if ( _private._options.selectedDate && typeof _private._options.selectedDate == "string" && Date.isValid(_private._options.selectedDate) )
				{
					_private._Date = Date.parseString(_private._options.selectedDate);
					_private._SelectedDate = new Date(_private._Date);
				}
				else if ( _private._options.selectedDate && typeof _private._options.selectedDate == "object" && typeof _private._options.selectedDate.getDate == "function" && Date.isValid(_private._options.selectedDate) )
				{
					_private._Date = new Date( _private._options.selectedDate );
					_private._SelectedDate = new Date(_private._Date);
				}
			}

			// Establish the selectable dates
				if (!optns || !optns.selectableDates)
				{
					_private._ValidDateAll = true;
					_private._DayExceptions = [true, true, true, true, true, true, true];
					_private._DateExceptions = null;
					_private._DateOutOfRange = null;
				}
				_private._establishSelectableDates();

			// Establish working z-Index level to ensure calendar shows on top
			var current_zindex = _private._getMaxZIndex();
			if (current_zindex > _private._zIndex) _private._zIndex = current_zindex;

			// Establish base Calendar container
			_private._CalendarElement = $(_private._CalendarID);
			if (!_private._CalendarElement)
			{
				_private._CalendarElement = document.createElement("div");
				_private._CalendarElement = Object.extend( _private._CalendarElement, {id:_private._CalendarID, className:_private._options.className});
				_private._CalendarElement.style.zIndex = current_zindex+9;
				_private._CalendarElement.style.position = 'absolute';
				_private._CalendarElement.addClassName = "Calendar_draggable";
				document.body.insertBefore(_private._CalendarElement,document.body.firstChild);
				_private._CalendarElement = $(_private._CalendarID);
			}
			_private._CalendarElement.style.zIndex = current_zindex+9;

			// Establish the controls initial position on the page
				popupSize = _private._CalendarElement.getDimensions();
				var pageSize = _private._getPageSize();
				var pageCenter  = {y:Math.floor(pageSize.pageHeight/2), x:Math.floor(pageSize.pageWidth/2)};
				var top,left;
				switch (_private._options.left)
				{
					case 'left':
						left = 0;
						break;
					case 'right':
						left = Math.floor(pageSize.pageWidth - popupSize.width) ;
						break;
					case 'center':
						left = Math.ceil(pageCenter.x - popupSize.width / 2) ;
						break;
					default:
						left = Math.floor(parseFloat(_private._options.left)) ;
						break;
				}
				switch (_private._options.top)
				{
					case 'top':
						top = 0;
						break;
					case 'bottom':
						top = Math.floor(pageSize.pageHeight - popupSize.height);
						break;
					case 'center':
						top = Math.ceil(pageCenter.y - popupSize.height / 2) ;
						break;
					default:
						top = Math.floor(parseFloat(_private._options.top));
						break;
				}
				_private._options.left = left +'px';
				_private._options.top = top +'px';
				_private._CalendarElement.style.left = left+'px';
				_private._CalendarElement.style.top = top+'px';
				_private._CalendarElement.style.display = 'none';

			// Assemble the outer chrome
				var Chrome =
					'<table class="Calendar_table" ><tbody>' +
					'<tr><td class="Calendar_nw Calendar_draggable">&nbsp;</td>' +
						'<td class="Calendar_n Calendar_draggable">&nbsp;</td>' +
						'<td class="Calendar_ne Calendar_draggable">&nbsp;</td></tr>' +
					'<tr><td class="Calendar_tw Calendar_draggable">&nbsp;</td>' +
						'<td class="Calendar_t " id="_ClndrTitle">'+_private._renderTitle()+'</td>' +
						'<td class="Calendar_te Calendar_draggable">&nbsp;</td></tr>' +
					'<tr><td class="Calendar_w">&nbsp;</td>' +
						'<td class="Calendar_c" id="_ClndrContent">'+_private._renderContent()+'</td>' +
						'<td class="Calendar_e">&nbsp;</td></tr>' +
					'<tr><td class="Calendar_w">&nbsp;</td>' +
						'<td class="Calendar_b">&nbsp;</td>' +
						'<td class="Calendar_e">&nbsp;</td></tr>' +
					'<tr><td class="Calendar_bw">&nbsp;</td>' +
						'<td class="Calendar_b" id="_ClndrFooter">'+_private._renderFooter()+'</td>' +
						'<td class="Calendar_be">&nbsp;</td></tr>' +
					'<tr><td class="Calendar_sw">&nbsp;</td>' +
						'<td class="Calendar_s">&nbsp;</td>' +
						'<td class="Calendar_se">&nbsp;</td></tr>' +
					'</tbody></table>';
				_private._CalendarElement.innerHTML = Chrome;

			// Populate and set default selection of the year dropdown control
				var elm = $('_ClndrYr');
				while (elm.options.length > 0)	 elm.options[elm.options.length-1] = null;
				// Constructor: var newOpt = new Option("text", "value", isDefaultSelectedFlag, isSelectedFlag);
				var curYear = _private._Date.getFullYear();
				var validYears = _private._getValidYears();
				validYears[_private._SelectedDate.getFullYear()+''] = _private._SelectedDate.getFullYear();
				for (var yr in validYears )
					elm.options[elm.options.length] = new Option(yr, yr);
				_private._selectOption(elm, curYear);
				_private._CalendarElement.style.display = '';

			// Establish the Calendar control behavior
				$('_ClndrPM').observe('click',_private._navPrevMonth);
				$('_ClndrNM').observe('click',_private._navNextMonth);
				$('_ClndrYr').observe('change',_private._navYear);
				$('_ClndrClose').observe('click',_private._closeButton);
				$('_ClndrDates').observe('click',_private._dateClick);
				if ($('_ClndrToday'))	$('_ClndrToday').observe('click',_private._todayButton);
				if ($('_ClndrClear'))	$('_ClndrClear').observe('click',_private._clearButton);
				if ($('_ClndrClose2'))	$('_ClndrClose2').observe('click',_private._closeButton);
				if ($('_ClndrCancel'))	$('_ClndrCancel').observe('click',_private._cancelButton);
				if ( _private._options.draggable )
				{// Enable the draggable feature by adding listeners to the draggable areas
					var dragElms = $A(_private._CalendarElement.getElementsByClassName('Calendar_draggable'));
					for (i=0; i<dragElms.length; i++)
							Event.observe( dragElms[i], 'mousedown', _private._dragOBJ );
				}
				//$(document.body).observe('click',_private._autoClose);

			_private._zIndex = _private._getMaxZIndex();
		},

		// Hides the calendar control
		hide : function()
		{
			$(document.body).stopObserving('click',_private._autoClose);
			$('_ClndrPM').stopObserving('click',_private._navPrevMonth);
			$('_ClndrNM').stopObserving('click',_private._navNextMonth);
			$('_ClndrYr').stopObserving('click',_private._navYear);
			$('_ClndrClose').stopObserving('click',_private._closeButton);
			$('_ClndrDates').stopObserving('click',_private._dateClick);
			if ($('_ClndrClose2'))	$('_ClndrClose2').stopObserving('click',_private._closeButton);
			if ($('_ClndrCancel'))	$('_ClndrCancel').stopObserving('click',_private._cancelButton);
			if ($('_ClndrToday'))	$('_ClndrToday').stopObserving('click',_private._todayButton);
			if ( _private._options.draggable )
			{// Enable the draggable feature by adding listeners to the draggable areas
				var dragElms = $A(_private._CalendarElement.getElementsByClassName('Calendar_draggable'));
				if (dragElms && dragElms.each)
				{
					for (i=0; i<dragElms.length; i++)
						Event.stopObserving(dragElms[i],'mousedown', _private._dragOBJ);
				}
			}
			_private._CalendarElement = $(_private._CalendarID);
			_private._CalendarElement.style.display = 'none';

			_private._unhideDropDowns();
		},

		// Retrieves the user selected date as a date object
		getSelectedDate : function()
		{
			return(_private._Date);
		},

		// Standard Date function applied to selected date
        getDayOfMonth: function()
		{
                return _private._Date.getDate();
        },

		// Standard Date function applied to selected date
		getDayName: function()
		{
                return _private._Date.getDayName();
        },

		// Standard Date function applied to selected date
        getDayAbbreviation: function()
		{
                return _private._Date.getDayAbbreviation();
        },

		// Standard Date function applied to selected date
        getMonth: function()
		{
                return _private._Date.getMonth()+1;
        },

		// Standard Date function applied to selected date
        getMonthName: function()
		{
                return _private._Date.getMonthName();
        },

		// Standard Date function applied to selected date
        getMonthAbbreviation: function()
		{
                return _private._Date.getMonthAbbreviation();
        },

		// Standard Date function applied to selected date
        getYear: function()
		{
                return _private._Date.getFullYear();
        },

		// Extended function to determine if selected date is a leap year
		isLeapYear: function ()
		{
			var date = new Date(_private._Date.getFullYear(), 1, 29);
			return (date.getMonth() == 1);
		},

		// Extended function to determine the number of days in the month of the provided date or selected date
		getDaysInMonth : function (dte)
		{
			var date = (dte ? new Date(dte) : new Date(_private._Date.getFullYear(), _private._Date.getMonth(), 28) );
			var mnth = date.getMonth();
			var i = 28;
			for (i=28; i<=32; i++) {
				date.setDate(i);
				if (date.getMonth() != mnth)
					return (i-1);
				}
		},

		// Standard Date function applied to selected date
        getFirstDayOfMonth: function(dte)
		{
			var date = (dte ? new Date(dte) : new Date(_private._Date.getFullYear(), _private._Date.getMonth(), 1) );
			return date.getDay();
		}
	};

	/* -- Private Properties and Methods ------------------------------------- */
	var _private =
	{
		_Active	:			false,
		_SelectedDate :		new Date(),
		_Date :				new Date(),
		_CalendarID :		'popCalendar',
		_zIndex :			0,
		_CalendarElement :	null,
		_ValidDateAll :		true,										// (re)established by _private._establishSelectableDates()
		_DayExceptions :	[true, true, true, true, true, true, true],	// (re)established by _private._establishSelectableDates()
		_DateExceptions :	null,										// (re)established by _private._establishSelectableDates()
		_DateOutOfRange :	null,										// (re)established by _private._establishSelectableDates()
		_ValidYears :		{},
		_options :			{
							className:			"Calendar",		// Style skin to use
							selectableDates:	"+*",			// default all dates selectable, see notes above
							selectedDate:		new Date(),		// Sets the selected date
							showToday:			true,			// Displays/Hide the Today button
							showClose:			false,			// Displays/Hide the Close button
							showCancel:			false,			// Displays/Hide the Cancel button
							showClear:			false,			// Displays/Hide the Clear button
							draggable:			true,			// Determines if calendar is draggable
							top:				"0px",			// absolute position of the top edge of the calendar
							left:				"0px"			// absolute position of the left edge of the calendar
							},



		// Renders the HTML title area of the calendar
		_renderTitle: function ()
		{
			var html =
				"<table class='x-cal' width='100%'><tbody>" +
				"<tr><td colspan=4 class='Calendar_draggable' style='height:2px'><!--dnc--></td></tr>"+
				"<tr><td id='_ClndrPM' class='Calendar_pm' title='Previous Month'><!--dnc--></td>" +
					"<td>"+
						"<span class='Calendar_draggable' >"+_private._Date.getMonthName()+"</span>&nbsp;"+
						"<select id='_ClndrYr' title='Select Year' >"+
						"</select>"+
						"</td>"+
					"<td id='_ClndrNM' class='Calendar_nm' title='Next Month'><!--dnc--></td>" +
					"<td id='_ClndrClose' class='Calendar_rx' title='Close Calendar'><!--dnc--></td></tr>" +
				"<tr><td colspan=4 class='Calendar_draggable' style='height:2px'><!--dnc--></td></tr>"+
				"</tbody></table>";
			return (html);
		},

		// Renders HTML for the month calendar day content section of the calendar
		_renderContent: function()
		{
			var html = "";
			var FrstDy = _public.getFirstDayOfMonth();				// 0 - 6
			var DysInMnth = _public.getDaysInMonth();				// 1 - 31
			var ncalrows = Math.ceil( (DysInMnth + FrstDy) / 7 );	// 5 - 6
			var ShwDte = new Date( _private._Date.getFullYear(), _private._Date.getMonth(), 1 );
			ShwDte = ShwDte.add('d',-FrstDy);						// 1st date to be display in the monthly calendar

			html += "<table class='x-cal' id='_ClndrDates'><tbody>";

			// Week day heading names
			html += "<tr>";
			for (c=0; c<7; c++) {
				html += "<td id='x-days-name-" + Date.dayAbbreviations[c] + "-td' class='x-days-name-td' >&nbsp;" + Date.dayAbbreviations[c].substr(0,2) + "&nbsp;</td>";
				}
			html += "</tr>";

			// Individual days
			for (r=0; r<ncalrows; r++)	{
				html += "<tr>";
				for (c=0; c<7; c++) {
					var clss = (ShwDte.getDay() == 0 || ShwDte.getDay() == 6 || ShwDte.getMonth() != _private._Date.getMonth() ? " x-weekend" : "")	// weekend (Sat or Sun) style
								+ (_private._isSelectable(ShwDte) ? " x-selectable" : " x-outOfRange" )	// unselectable style
								+ (_private._SelectedDate.equalsIgnoreTime(ShwDte) ? " x-selected-day" : "");	// selected day style
					var styl = (_private._isSelectable(ShwDte) ? " style='cursor: pointer;'" : "" );
					var attr = ' date="'+ ShwDte.format('MM/dd/yyyy') +'"';
					var mseovr = ' onmouseover="$(this).addClassName(\'x-mouse-over\')" onmouseout="$(this).removeClassName(\'x-mouse-over\')"';
					html += "<td id='x-date-" + ShwDte.getFullYear() +"-" + (ShwDte.getMonth()+1) + "-" + ShwDte.getDate() + "' class='" + clss + "' " + styl + attr + mseovr + " >" + ShwDte.getDate() + "</td>";
					ShwDte.add("d",1);
					}
				html += "</tr>";
				}

			// Completes the HTML for base Calendar
			html += "</tbody></table>";
			return html;
		},

		_renderFooter: function()
		{
			var html='';
			// Add appropriate buttons under the Calendar
			if (_private._options.showToday)
				html += "<div><a href='#' class='x-button' id='_ClndrToday'>Today</a></div>";
			if (_private._options.showClear)
				html += "<div><a href='#' class='x-button' id='_ClndrClear'>Clear</a></div>";
			if (_private._options.showCancel)
				html += "<div><a href='#' class='x-button' id='_ClndrCancel'>Cancel</a></div>";
			if (_private._options.showClose)
				html += "<div><a href='#' class='x-button' id='_ClndrClose2'>Close</a></div>";
			return(html);
		},

		// Establishes which dates are selectable given optional selectableDates parameter
		//		selectableDates  ==>  "+*,-<01/01/2008,->01/01/2020"
		_establishSelectableDates: function()
		{
			if ( !_private._options.selectableDates )	return;

			var	str = _private._options.selectableDates,
					frag, frags = str.split(","),
					validndx = 1,
					f,fl, d, obj, ValidSwitch, strdate, ucstrdate, strtdate, enddate, daterange;

			// Assemble a list of selectable and unselectable dates
			for (f=0, fl=frags.length; f<fl; f++)
			{
				frag = frags[f];
				frag = frag.replace(/^\s+/,'').replace(/\s+$/,'');	// trim spaces on both ends
				if (frag.length < 1 ||  ( frag.substr(0,1) != "+"  &&  frag.substr(0,1) != "-" ) )
				{// skip any malformed date exceptions
					continue;
				}
				if (frag == "+*")
				{// Establish the point that all dates are valid, clear any exceptions
					_private._ValidDateAll = true;
					_private._DayExceptions = [true, true, true, true, true, true, true];
					_private._DateExceptions = null;
					_private._DateOutOfRange = null;
				}
				else if (frag == "-*")
				{// Establish the point that NO dates are valid, clear any exceptions
					_private._ValidDateAll = false;
					_private._DayExceptions = [false, false, false, false, false, false, false];
					_private._DateExceptions = null;
					_private._DateOutOfRange = null;
				}
				else
				{
					ValidSwitch =  ( frag.substr(0,1) == "+" ? true : false );
					strdate = frag.substr(1);
					ucstrdate = strdate.toUpperCase();
					if ( "SUN, MON, TUE, WED, THU, FRI, SAT".indexOf(ucstrdate) >= 0 )
						{
						for (d=0; d<7 && Date.dayAbbreviations[d].toUpperCase() != ucstrdate; d++) ;
						if (d < 7)	this._DayExceptions[d] = ValidSwitch;
						continue;
						}
					else if ( "1stDOM" == strdate )
						{
						obj = {ValidSwitch:ValidSwitch, ExceptionType:strdate};
						if (null == _private._DateOutOfRange) _private._DateOutOfRange = new Array();
						_private._DateOutOfRange[_private._DateOutOfRange.length] = obj;
						continue;
						}
					else if ( "LastDOM" == strdate )
						{
						obj = {ValidSwitch:ValidSwitch, ExceptionType:strdate};
						if (null == _private._DateOutOfRange) _private._DateOutOfRange = new Array();
						_private._DateOutOfRange[_private._DateOutOfRange.length] = obj;
						continue;
						}
					else if ( "<>".indexOf(frag.substr(1,1)) >= 0 )
						{// Greater than or lessthan Date
						obj = {ValidSwitch:ValidSwitch, ExceptionType:"Range", LessThan:(frag.substr(1,1) == "<" ? true:false)};
						strtdate = Date.parseString(frag.substr(2));
						obj.ValidDate = (new Date(strtdate)).clearTime();
						if (null == _private._DateOutOfRange) _private._DateOutOfRange = [];
						_private._DateOutOfRange[_private._DateOutOfRange.length] = obj;
						continue;
						}
					else if ( strdate.indexOf(":") >= 0 )
						{// Date Range
						daterange = strdate.split(":");
						strtdate = Date.parseString(daterange[0]);
						enddate = Date.parseString(daterange[1]);
						}
					else
						{// Single Date   Start equals End Date
						strtdate = Date.parseString(strdate);
						enddate = new Date(strtdate);
						}
					while ( strtdate.getTime() <= enddate.getTime() )
					{
						obj = {ValidSwitch:ValidSwitch, ExceptionType:"Date",ValidDate:(new Date(strtdate)).clearTime()};
						if (null == _private._DateExceptions) _private._DateExceptions = [];
						_private._DateExceptions[_private._DateExceptions.length] = obj;
						strtdate=strtdate.add("d",1);
					}
				}	// end if frag else

			}	// next for (frags)
		},

		// Determines if a date is selectable by the user.
		_isSelectable: function(dte)
		{
			dte.clearTime();
			var	obj, ndx, ndxlngth,
					selectable = _private._DayExceptions[dte.getDay()];

			if (_private._DateOutOfRange)
			{// OUT OF RANGE DATE CHECKS
				for (ndx=0, ndxlngth=_private._DateOutOfRange.length; ndx<ndxlngth; ndx++ )
				{// check for an exception date
					obj = _private._DateOutOfRange[ndx];
					switch (obj.ExceptionType)
					{
					case '1stDOM':
						selectable = (1 == dte.getDate()) ? obj.ValidSwitch : selectable;
						break;
					case 'LastDOM':
						selectable = (Date.daysInMonth[dte.getMonth()] ==  dte.getDate()) ? obj.ValidSwitch : selectable;
						break;
					default:
						if ( _private._DateOutOfRange[ndx].LessThan )		// <
						{
							selectable = dte.isBefore(_private._DateOutOfRange[ndx].ValidDate) ? obj.ValidSwitch : selectable;
						}
						else
						{													// >
							selectable = dte.isAfter(_private._DateOutOfRange[ndx].ValidDate) ? obj.ValidSwitch : selectable;
						}
						break;
					}
				}
			}
			if ( _private._DateExceptions )
			{// DATE RANGE CHECKS
				for (ndx=0, ndxlngth=_private._DateExceptions.length; ndx<ndxlngth; ndx++ )
				{// check for an exception dates
					obj = _private._DateExceptions[ndx];
					if (  obj.ValidDate.equals(dte) ) selectable = obj.ValidSwitch;
				}
			}
			return selectable;
		},

		// Determines the valid years to display in the year selection dropdown based on configured selectable dates
		_getValidYears : function()
		{
			// Build an object of valid years
			_private._ValidYears = {};

			var	dte = new Date(1990, 0, 1, 0, 0, 0, 0),
					yr = 1990;
			while(yr < 2025)
			{
				if ( _private._isSelectable(dte) )
				{
					_private._ValidYears[yr+''] = yr;
					yr++;
					dte = new Date(yr, 0, 1, 0, 0, 0);
					continue;
				}
				dte.setDate(dte.getDate()+1);	// Add a day
				yr=dte.getFullYear();
			}
			return (_private._ValidYears);
		},

		// Establish Current Selection For Dropdown Control
		_selectOption: function(elm, value)
		{
			elm=$(elm);
			var searchValue = (value+'').toUpperCase();
			for (var k=0; k<elm.options.length; k++)
			{
				if (elm.options[k].value.toUpperCase() == searchValue)
				{
					elm.selectedIndex = k;
					return;
				}
			}
		},

		// User clicked a date, validate that its selectable
		_dateClick: function(evnt)
		{
			if (evnt)	Event.stop(evnt);
			var SourceElement = evnt.target ? evnt.target : evnt.originalTarget ? evnt.originalTarget : this;
			if( SourceElement && (SourceElement.nodeType == 3 || SourceElement.nodeType == 4 ) )
			{// Note that Safari and Konqueror sometimes make the target point to the textNode inside the element. This is a patch for that
				SourceElement = SourceElement.parentNode;
			}
			var id = SourceElement.id;
			if ( SourceElement.className.indexOf("x-outOfRange") >= 0 || id.indexOf("x-date-") < -1)  return false;
			var dte = Date.parseString( SourceElement.readAttribute('date') );
			_private._SelectedDate = new Date(dte);
			$(document.body).fire("Calendar:Selected", {selectedDate:_private._SelectedDate, extendedProperty:_private._options.extendedProperty});
			_public.hide();
		},

		// User clicked the Today button
		_todayButton: function(evnt)
		{
			if (evnt)	Event.stop(evnt);
			_private._SelectedDate = new Date();
			$(document).fire("Calendar:Selected",{selectedDate:_private._SelectedDate, extendedProperty:_private._options.extendedProperty});
			_public.hide();
		},


		// User click somewhere on the page
		_autoClose: function(evnt)
		{

			if (!Prototype.Browser.IE)
			{// Firefox bubbles from the outer most container to the inner most unlike IE
			}

			// if click event happened outside of the calendar control, close the calendar control
			var elt = $(Event.element(evnt));
			if (elt && !elt.descendantOf(_private._CalendarElement))
				{
					//if (evnt)	Event.stop(evnt);
					_private._cancelButton(evnt);
				}
			return true;
		},

		// User clicked the Cancel button
		_cancelButton: function(evnt)
		{
			//if (evnt)	Event.stop(evnt);
			$(document).fire("Calendar:Cancel",{selectedDate:_private._SelectedDate, extendedProperty:_private._options.extendedProperty});
			_public.hide();
		},

		_closeButton: function(evnt)
		{
			//if (evnt)	Event.stop(evnt);
			_private._isDateSelectEvent = true;
			$(document).fire("Calendar:Close",{selectedDate:_private._SelectedDate, extendedProperty:_private._options.extendedProperty});
			_public.hide();
		},

		_clearButton: function(evnt)
		{
			//if (evnt)	Event.stop(evnt);
			_private._isDateSelectEvent = true;
			$(document).fire("Calendar:Clear",{selectedDate:_private._SelectedDate, extendedProperty:_private._options.extendedProperty});
			_public.hide();
		},

		_navPrevMonth: function(evnt)
		{
			if (evnt)	Event.stop(evnt);
			_private._isDateSelectEvent = false;
			if ( !_private || typeof _private._CalendarID == "undefined" || !_private._CalendarID ) return(false);
			var mnth = _private._Date.getMonth() - 1;
			var yr = _private._Date.getFullYear();
			var dy = _private._Date.getDate();
			if ( mnth < 0 )	 {
				mnth = 11;
				yr--;
				}
			var chkdte = new Date(yr,mnth,1);
			if ( !_private._ValidYears[chkdte.getFullYear()+''] ) return(false);
			if ( dy > _public.getDaysInMonth( chkdte ) )	dy = _public.getDaysInMonth(chkdte)
			_private._Date = new Date(yr,mnth,dy);
			_public.show();
			return(false);
		},

		_navNextMonth: function(evnt)
		{
			if (evnt)	Event.stop(evnt);
			_private._isDateSelectEvent = false;
			if ( !_private || typeof _private._CalendarID == "undefined" || !_private._CalendarID ) return(false);
			var mnth = _private._Date.getMonth() + 1;
			var yr = _private._Date.getFullYear();
			var dy = _private._Date.getDate();
			if ( mnth > 11 )	 {
				mnth = 0;
				yr++;
				}
			var chkdte = new Date(yr,mnth,1);
			if ( !_private._ValidYears[chkdte.getFullYear()+''] ) return(false);
			if ( dy > _public.getDaysInMonth( chkdte ) )	dy = _public.getDaysInMonth(chkdte)
			_private._Date = new Date(yr,mnth,dy);
			_public.show();
			return(false);
		},

		_navPrevYear: function(evnt)
		{
			//if (evnt)	Event.stop(evnt);
			_private._isDateSelectEvent = false;
			if ( !_private || typeof _private._CalendarID == "undefined" || !_private._CalendarID ) return(false);
			var mnth = _private._Date.getMonth();
			var yr = _private._Date.getFullYear() - 1;
			var dy = _private._Date.getDate();
			var chkdte = new Date(yr,mnth,1);
			if ( dy > _public.getDaysInMonth( chkdte ) )	dy = _public.getDaysInMonth(chkdte)
			_private._Date = new Date(yr,mnth,dy);
			_public.show();
			return(false);
		},

		_navNextYear: function(evnt)
		{
			//if (evnt)	Event.stop(evnt);
			_private._isDateSelectEvent = false;
			if ( !_private || typeof _private._CalendarID == "undefined" || !_private._CalendarID ) return(false);
			var mnth = _private._Date.getMonth();
			var yr = _private._Date.getFullYear() + 1;
			var dy = _private._Date.getDate();
			var chkdte = new Date(yr,mnth,1);
			if ( dy > _public.getDaysInMonth( chkdte ) )	dy = _public.getDaysInMonth(chkdte)
			_private._Date = new Date(yr,mnth,dy);
			_public.show();
			return(false);
		},

		_navYear: function(evnt)
		{// TODO:  CHECK if NEW DATE IS SELECTABLE
			//if (evnt)	Event.stop(evnt);
			_private._isDateSelectEvent = false;
			if ( !_private || typeof _private._CalendarID == "undefined" || !_private._CalendarID ) return(false);
			//var yr = _private._Date.getFullYear() + 1;
			var elm = $('_ClndrYr');
			if ( !elm || (elm && elm.selectedIndex < 0) ) return false;
			var yr = elm.options[elm.selectedIndex].value;
			var mnth = _private._Date.getMonth();
			var dy = _private._Date.getDate();
			var chkdte = new Date(yr,mnth,1);
			if ( dy > _public.getDaysInMonth( chkdte ) )	dy = _public.getDaysInMonth(chkdte)
			_private._Date = new Date(yr,mnth,dy);
			_public.show();
			return(false);
		},

		// Initiates the drag function of the overlay
		_dragOBJ : function (evnt)
		{// Context is the calling page's DOM
			//_private._CalendarElement = $(_private._CalendarID);
			var dragElement = _private._CalendarElement;
			//if ( !dragElement.hasClassName('Calendar_draggable') )	return true;
			if (evnt) evnt.stop();	// stop the event from bubbling any further
			var dragMode = true;

			function drag(evnt)
			{
				if( dragMode )
				{
					var pos = {x:evnt.screenX, y:evnt.screenY};
					//dragElement.innerHTML = "(" + pos.x + ", " + pos.y + ")"
					var dX = pos.x - anchor.x;
					var dY = pos.y - anchor.y;
					dragElement.style.left = (oX + dX) + 'px';
					dragElement.style.top = (oY + dY) + 'px';
					if (evnt) evnt.stop();	// stop the event from bubbling any further
				}
			}
			function stopdrag()
			{
				_private._options.left = dragElement.style.left;
				_private._options.top = dragElement.style.top;
				dragMode = false;
				dragElement = null;
				if (evnt) evnt.stop();	// stop the event from bubbling any further
				Event.stopObserving( document.body, 'mousemove', drag );
				Event.stopObserving( document.body, 'mouseup', stopdrag );
				_private._CalendarElement = $(_private._CalendarID);
			}

			var oX=parseInt(dragElement.style.left);
			var oY=parseInt(dragElement.style.top);
			//_private._options.left = oX;
			//_private._options.top = oY;
			var anchor = {x:evnt.screenX, y:evnt.screenY};
			// Capture mouse movements in parent(current) DOM space
			Event.observe(document.body, 'mousemove', drag );
			Event.observe(document.body, 'mouseup', stopdrag );
		},

		// Retrieves the maximum ZIndex value across the whole page
		_getMaxZIndex: function()
		{
			var allElems = window.top.document.getElementsByTagName ? window.top.document.getElementsByTagName("*") : window.top.document.all;
			var maxZIndex = 0;
			for (var i = 0; i < allElems.length; i++)
			{
				var zIndex = allElems[i].zIndex ? allElems[i].zIndex : allElems[i].style.zIndex;
				maxZIndex = (parseInt(zIndex) > maxZIndex) ? parseInt(zIndex) : maxZIndex;
			}
			return maxZIndex;
		},


		// Determine the page dimensions
		_getPageSize: function(parent)
		{
			parent = parent || document.body;
			var windowWidth, windowHeight, pageHeight, pageWidth;
			if ( parent  != document.body )
			{
				windowWidth = parent.getWidth();
				windowHeight = parent.getHeight();
				pageWidth = parent.scrollWidth;
				pageHeight = parent.scrollHeight;
			}
			else
			{
				var xScroll, yScroll;
				if (window.innerHeight && window.scrollMaxY)
				{
				xScroll = document.body.scrollWidth;
				yScroll = window.innerHeight + window.scrollMaxY;
				}
				else if (document.body.scrollHeight > document.body.offsetHeight)
				{ // all but Explorer Mac
					xScroll = document.body.scrollWidth;
					yScroll = document.body.scrollHeight;
				}
				else
				{ // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
					xScroll = document.body.offsetWidth;
					yScroll = document.body.offsetHeight;
				}

				if (self.innerHeight)
				{  // all except Explorer
					windowWidth = self.innerWidth;
					windowHeight = self.innerHeight;
				}
				else if (document.documentElement && document.documentElement.clientHeight)
				{ // Explorer 6 Strict Mode
					windowWidth = document.documentElement.clientWidth;
					windowHeight = document.documentElement.clientHeight;
				}
				else if (document.body)
				{ // other Explorers
					windowWidth = document.body.clientWidth;
					windowHeight = document.body.clientHeight;
				}

				// for small pages with total height less then height of the viewport
				if( yScroll < windowHeight )
				{
					pageHeight = windowHeight;
				}
				else
				{
					pageHeight = yScroll;
				}

				// for small pages with total width less then width of the viewport
				if(xScroll < windowWidth)
				{
					pageWidth = windowWidth;
				}
				else
				{
					pageWidth = xScroll;
				}
			}

			var scrOfX = 0, scrOfY = 0;
			if (typeof( window.pageYOffset ) == 'number')
			{
				//Netscape compliant
				scrOfY = window.pageYOffset;
				scrOfX = window.pageXOffset;
			}
			else if (document.body && (document.body.scrollLeft || document.body.scrollTop))
			{
				//DOM compliant
				scrOfY = document.body.scrollTop;
				scrOfX = document.body.scrollLeft;
			}
			else if (document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop))
			{
				//IE6 standards compliant mode
				scrOfY = document.documentElement.scrollTop;
				scrOfX = document.documentElement.scrollLeft;
			}
			return( {pageWidth: pageWidth ,pageHeight: pageHeight , windowWidth: windowWidth, windowHeight: windowHeight, scrollLeft: scrOfX, scrollTop: scrOfY} );
		},

		_getPosition: function(source)
		{
			// find page position of source
			source = $(source);
			var p = source.viewportOffset();
			//	Return the position
			return({left:(p[0] + options.offsetLeft) + 'px',
						top:(p[1] + options.offsetTop) + 'px',
						width:source.offsetWidth + 'px',
						height:source.offsetHeight + 'px'});
		},

		// Hides a list of SELECT elements (use for IE browser only)
		_hideDropDowns: function()
		{
			if ( !Prototype.Browser.IE )	return;
			var	elements = document.getElementsByTagName("select"),
					e, element, description, dLength, offsets, panelOffsets=[0,0];
			if ( (pnl = $("panel")) )	panelOffsets=pnl.cumulativeOffset();

			for (e = elements.length-1; e>=0; e--)
			{
				element = $(elements[e]);
				if (element.style.display != 'none')
				{
					description = (element.selectedIndex >= 0) ? element.options[element.selectedIndex].text : '';
					dLength = Math.ceil(1.1*description.length);
					dLength = (dLength <= 0) ? 1: dLength;
					var newInputelement = document.createElement('input');
					newInputelement = Object.extend(newInputelement,
						{id:"xcal_"+element.id, readOnly:true, className:'ReadOnly', type:'text', value:description, size:dLength});

					offsets = element.cumulativeOffset();
					newInputelement.setStyle({
						width:parseInt(element.style.width)+'px', height:parseInt(element.style.height)+'px',
						position:'absolute', left:offsets.left-panelOffsets.left+'px', top:offsets.top-panelOffsets.top+'px'});
					element.parentNode.insertBefore(newInputelement,element);
					element.style.display = 'none';
				}
			}
		},

		// Unhides all the elements that were hidden  (use for IE browser only)
		_unhideDropDowns: function()
		{
			if ( !Prototype.Browser.IE )	return;
			var element, tmpElement, e, elements = document.getElementsByTagName("select");
			for (e = elements.length-1; e>=0; e--)
			{
				element = elements[e];
				tmpElement = $("xcal_"+element.id);
				if (tmpElement)
				{// Existence of element prefixed by xcal_ indicates the calendar hid the select element
					element.parentNode.removeChild(tmpElement);
					element.style.display = '';
				}
			}
		},

		//	Enable the inclusion of dependent library components
		_loadJSFile: function(libraryName, scriptLoadedCallback)
		{
			var headID = document.getElementsByTagName("head")[0];
			var newScript = document.createElement('script');
			newScript.type = 'text/javascript';
			newScript.src = libraryName;
			if (typeof(scriptLoadedCallback) == "function")
			{
				newScript.onload = scriptLoadedCallback;
			}
			headID.appendChild(newScript);
			//inserting via DOM fails in Safari 2.0, so brute force approach
			//document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
		},

		//	Enable the inclusion of dependent library components
		_loadStyleFile: function(StyleSheetName)
		{
			var headID = document.getElementsByTagName("head")[0];
			var cssNode = document.createElement('link');
			cssNode.type = 'text/css';
			cssNode.rel = 'stylesheet';
			cssNode.href = StyleSheetName;
			cssNode.media = 'screen';
			headID.appendChild(cssNode);
		}

		// Deep copy/clone of an object
		/*	 no longer used in Calendar
		_clone : function(obj)
		{
			var seenObjects = [];
			var mappingArray = [];
			var	f = function(simpleObject) {
			  var indexOf = seenObjects.indexOf(simpleObject);
			  if (indexOf == -1) {
				 switch (type(simpleObject)) {
					case 'object':
					   seenObjects.push(simpleObject);
					   var newObject = {};
					   mappingArray.push(newObject);
					   for (var p in simpleObject)
						  newObject[p] = f(simpleObject[p]);
					   newObject.constructor = simpleObject.constructor;
					return newObject;

					case 'array':
					   seenObjects.push(simpleObject);
					   var newArray = [];
					   mappingArray.push(newArray);
					   for(var i=0,len=simpleObject.length; i<len; i++)
						  newArray.push(f(simpleObject[i]));
					return newArray;

					default:
					return simpleObject;
				 }
			  } else {
				 return mappingArray[indexOf];
			  }
			};
			return f(obj);
		}
		*/
	};

	/* -- Constructor -------------------------------------------------------- */

	//_public.initialize(arguments[0]);		// DOM isn't guaranteed to be constructed
	return(_public);
}();


// When the DOM is constructed, make sure the formMgr initialization is executed.
Event.observe(document, "dom:loaded", Calendar.initialize);

