/* -------------------------------------------------------------------------- */
/*                                 gsCalendar                                 */
/*                         Version 2.2.3 (2005-05-08)                         */
/* -------------------------------------------------------------------------- */
/*                                                                            */
/*                      Author: Gerhard Schlager, Austria                     */
/*                       http://www.gerhard-schlager.at/                      */
/*                                                                            */
/* -------------------------------------------------------------------------- */
/*                                                                            */
/* Some calculations are based on the formulas from Thomas Hofmann.           */
/* http://www.th-o.de/kalender.htm                                            */
/*                                                                            */
/* -------------------------------------------------------------------------- */
/*                                                                            */
/*                  Copyright (C) 2002-2005 Gerhard Schlager                  */
/*                                                                            */
/* This library is free software; you can redistribute it and/or modify it    */
/* under the terms of the GNU Lesser General Public License as published by   */
/* the Free Software Foundation; either version 2.1 of the License, or (at    */
/* your option) any later version.                                            */
/*                                                                            */
/* This library is distributed in the hope that it will be useful, but        */
/* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY */
/* or FITNESS FOR A PARTICULAR PURPOSE.                                       */
/*                                                                            */
/* For more details on the GNU Lesser General Public License,                 */
/* see http://www.gnu.org/licenses/lgpl.html                                  */
/*                                                                            */
/* -------------------------------------------------------------------------- */


/* ************************************************************************** */
/* ****************************** Configuration ***************************** */
/* ************************************************************************** */

var gscDefaultLanguage = 'de';

var gscDefaultDateFormat = 'yyyy-mm-dd';

var gscDefaultButtonText = '. . .';

var gscDefaultAutoHide				= true;
var gscDefaultDisplayFooter			= true;
var gscDefaultExtendedNavigation	= false;
var gscDefaultMonthNavigation		= false;
var gscDefaultPopupInfo				= false;

var gscMinYear = 1900;	// >= 100
var gscMaxYear = 2200;	// <= 9999

// space between the input element for the date and the calendar
// the unit for this value is pixel (px) - default: 3
var gscCalendarTop =  3; //-114;
var gscCalendarLeft = 30;  //-20;

// only used for debugging
var gscDebugMode = false;
var gscDebugOutput = '';
var gscDebugOutputTo = 'debugoutput';


/* ************************************************************************** */
/* ************************ Language specific strings *********************** */
/* ************************************************************************** */

var gscMonthNames = {
	'de'	: new Array('Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
				'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'),

	'en'	: new Array('January', 'February', 'March', 'April', 'May', 'June',
				'July', 'August', 'September', 'October', 'November', 'December')
};

var gscShortMonthNames = {
	'de'	: new Array('Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul',
				'Aug', 'Sep', 'Okt', 'Nov', 'Dez'),

	'en'	: new Array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
				'Aug', 'Sep', 'Oct', 'Nov', 'Dec')
};

// short day names (max. 2 characters)
var gscDayNames = {
	'de'	: new Array('Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'),

	'en'	: new Array('Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su')
};

var gscLanguageStrings = {
	'de'	:	{
					'today'			: 'Heute',
					'selectedDate'	: 'Gewähltes Datum',
					'previousMonth'	: 'Voriges Monat',
					'nextMonth'		: 'Nächstes Monat',
					'previousYear'	: 'Voriges Jahr',
					'nextYear'		: 'Nächstes Jahr'
				},

	'en'	:	{
					'today'			: 'Today',
					'selectedDate'	: 'Selected date',
					'previousMonth'	: 'Previous month',
					'nextMonth'		: 'Next month',
					'previousYear'	: 'Previous year',
					'nextYear'		: 'Next year'
				}
}


/* ************************************************************************** */
/* ************************** The gsCalendar itself ************************* */
/* ************************************************************************** */

// initializing the current date
var gscCurrentDate = new Date();

// can the browser display the calendar without errors?
var gscError = false;

// initialize
gscInit();

function gsCalendar(name, inputId)
{
	this.CalendarName	= name;
	this.InputElementId	= inputId;

	this.SelectedDate	= new Date(gscCurrentDate);
	this.VisibleMonth	= gscCurrentDate.getMonth() + 1;
	this.VisibleYear	= gscCurrentDate.getFullYear();

	this.DateFormat		= new Array();

	this.Ids			= new Array();
	this.Ids['day']		= name + '_day';
	this.Ids['footer']	= name + '_footer';
	this.Ids['frame']	= name + '_frame';
	this.Ids['month']	= name + '_month';
	this.Ids['title']	= name + '_title';
	this.Ids['today']	= name + '_today';
	this.Ids['year']	= name + '_year';

	this.AllowedDates	= new Array();
	this.ElementsToHide	= new Array();
	this.ForbiddenDates	= new Array();
	this.HiddenElements	= new Array();

	this.ButtonText		= gscDefaultButtonText;
	this.Language		= '';
	this.StylePrefix	= '';

	this.SelectColor = {
		focus	: 'red',
		blur	: 'black'
	};

	this.AutoHide			= gscDefaultAutoHide;
	this.DisplayFooter		= gscDefaultDisplayFooter;
	this.ExtendedNavigation	= gscDefaultExtendedNavigation;
	this.MonthNavigation	= gscDefaultMonthNavigation;
	this.PopupInfo			= gscDefaultPopupInfo;
	this.Visible			= false;
	this.YearHasFocus		= false;

	// public methods
	this.addAllowedDates			= gscAddAllowedDates;
	this.addElementsToHide			= gscAddElementsToHide;
	this.addForbiddenDates			= gscAddForbiddenDates;
	this.disableFuture				= gscDisableFuture;
	this.disablePast				= gscDisablePast;
	this.enableAutoHide				= gscEnableAutoHide;
	this.enableExtendedNavigation	= gscEnableExtendedNavigation;
	this.enableFooter				= gscEnableFooter;
	this.enableMonthNavigation		= gscEnableMonthNavigation;
	this.enablePopupInfo			= gscEnablePopupInfo;
	this.filterKeys					= gscFilterKeys;
	this.insertCalendar				= gscInsertCalendar;
	this.setButtonText				= gscSetButtonText;
	this.setDate					= gscSetSelectedDate;
	this.setDateFormat				= gscSetDateFormat;
	this.setLanguage				= gscSetLanguage;
	this.setStylePrefix				= gscSetStylePrefix;
	this.toggleCalendar				= gscToggleCalendar;

	// private methods
	this.addDates					= gscAddDates;
	this.autoHideElements			= gscAutoHideElements;
	this.changeYear					= gscChangeYear;
	this.createParsePattern			= gscCreateParsePattern;
	this.displayToday				= gscDisplayToday;
	this.fillCalendar				= gscFillCalendar;
	this.fillCurrentMonth			= gscFillCurrentMonth;
	this.fillFollowingMonth			= gscFillFollowingMonth;
	this.fillFooter					= gscFillFooter;
	this.fillPreviousMonth			= gscFillPreviousMonth;
	this.getFormatedDate			= gscGetFormatedDate;
	this.getLanguageString			= gscGetLanguageString;
	this.getMonthName				= gscGetMonthName;
	this.getShortMonthName			= gscGetShortMonthName;
	this.getDayName					= gscGetDayName;
	this.hideElements				= gscHideElements;
	this.highlightDays				= gscHighlightDays;
	this.highlightYear				= gscHighlightYear;
	this.initInputElement			= gscInitInputElement;
	this.isDateAllowed				= gscIsDateAllowed;
	this.newAutoHideElements		= gscNewAutoHideElements;
	this.nextMonth					= gscNextMonth;
	this.nextYear					= gscNextYear;
	this.parseDate					= gscParseDate;
	this.previousMonth				= gscPreviousMonth;
	this.previousYear				= gscPreviousYear;
	this.selectDate					= gscSelectDate;

	// initialize the date format and check for errors in the format string
	this.setDateFormat(gscDefaultDateFormat);

	// initialize the language and check for errors
	this.setLanguage(gscDefaultLanguage);
}

function gscSetLanguage(language)
{
	var error = false;

	if (typeof(gscMonthNames[language]) == 'undefined' || gscMonthNames[language].length != 12)
	{
		error = true;
	}

	if (typeof(gscShortMonthNames[language]) == 'undefined' || gscShortMonthNames[language].length != 12)
	{
		error = true;
	}

	if (typeof(gscDayNames[language]) == 'undefined' || gscDayNames[language].length != 7)
	{
		error = true;
	}

	if (error)
	{
		alert("The language strings for '" + language + "' are not complete!");
		return;
	}

	this.Language = language;
}

function gscEnableFooter(enable)
{
	this.DisplayFooter = enable;
}

function gscEnablePopupInfo(enable)
{
	this.PopupInfo = enable;
}

function gscEnableMonthNavigation(enable)
{
	this.MonthNavigation = enable;
}

function gscEnableExtendedNavigation(enable)
{
	this.ExtendedNavigation = enable;
}

function gscEnableAutoHide(enable)
{
	this.AutoHide = enable;
}

function gscAddElementsToHide()
{
	for (var i = 0; i < arguments.length; ++i)
	{
		this.ElementsToHide[this.ElementsToHide.length] = arguments[i];
	}
}

function gscSetCurrentDate(year, month, day)
{
	year	= parseInt(year);
	month	= parseInt(month);
	day		= parseInt(day);

	if (! gscIsValidDate(year, month, day))
	{
		alert("Error in function gscSetCurrentDate()\nThe given date is not valid!");
		return;
	}

	gscCurrentDate = new Date(year, month - 1, day);
}

function gscSetDateFormat(format)
{
	var pattern = /^(d{1,2}|m{1,4}|y{4})(\s|-|\/|\.\s{0,1})(d{1,2}|m{1,4}|y{4})(\s|-|\/|\.)(d{1,2}|m{1,4}|y{4})$/;

	if (! pattern.test(format))
	{
		alert("Error in function gscSetDateFormat()\nThe given date format is not valid!");
		return;
	}

	var char1 = RegExp.$1.charAt(0);
	var char2 = RegExp.$3.charAt(0);
	var char3 = RegExp.$5.charAt(0);

	if (char1 == char2 || char1 == char3 || char2 == char3)
	{
		// The date format string must consist of all three parts (day, month, year)
		alert("Error in function gscSetDateFormat()\nThe given date format is not valid!");
		return;
	}

	this.DateFormat['format']		= new Array(RegExp.$1, RegExp.$3, RegExp.$5);
	this.DateFormat['delimiter']	= new Array(RegExp.$2, RegExp.$4, '');
}

function gscIsValidDate(year, month, day)
{
	var check = new Date(year, month - 1, day);

	var result = (year == check.getFullYear());
	var result = result && (month == check.getMonth() + 1);
	var result = result && (day == check.getDate());

	return result;
}

function gscSetSelectedDate(year, month, day)
{
	year	= parseInt(year, 10);
	month	= parseInt(month, 10);
	day		= parseInt(day, 10);

	if (! gscIsValidDate(year, month, day))
	{
		alert("Error in function gscSetSelectedDate()\nThe given date is not valid!");
		return;
	}

	this.SelectedDate	= new Date(year, month - 1, day);
	this.VisibleMonth	= month;
	this.VisibleYear	= year;
}

function gscSetButtonText(text)
{
	if (typeof(text) != 'undefined')
	{
		this.ButtonText = text;
	}
	else
	{
		this.ButtonText = '';
	}
}

function gscAddZero(value)
{
	return (value <= 9 ? '0' : '') + value;
}

function gscGetFormatedDate(date)
{
	var year	= date.getFullYear();
	var month	= date.getMonth() + 1;
	var day		= date.getDate();

	date = '';

	for (var i = 0; i < 3; ++i)
	{
		switch (this.DateFormat['format'][i])
		{
			case 'yyyy':
				date += year;
				break;
			case 'mmmm':
				date += this.getMonthName(month);
				break;
			case 'mmm':
				date += this.getShortMonthName(month);
				break;
			case 'mm':
				date += gscAddZero(month);
				break;
			case 'm':
				date += month;
				break;
			case 'dd':
				date += gscAddZero(day);
				break;
			case 'd':
				date += day;
				break;
		}

		date += this.DateFormat['delimiter'][i];
	}

	return date;
}

function gscCreateParsePattern()
{
	var pattern = '^';

	for (var i = 0; i < 3; ++i)
	{
		pattern += '('
		switch (this.DateFormat['format'][i])
		{
			case 'yyyy':
				var min = (gscMinYear.toString()).length;
				var max = (gscMaxYear.toString()).length;
				pattern += '\\d{' + (min == max ? max : min + ',' + max) + '}';
				break;
			case 'mmmm':
				for (var month = 1; month <= 12; ++month)
				{
					pattern += this.getMonthName(month);
					pattern += '|';
				}
				break;
			case 'mmm':
				for (var month = 1; month <= 12; ++month)
				{
					pattern += this.getShortMonthName(month);
					pattern += '|';
				}
				break;
			case 'mm':
			case 'm':
			case 'dd':
			case 'd':
				pattern += '\\d{1,2}';
				break;
		}
		pattern += ')';

		switch (this.DateFormat['delimiter'][i])
		{
			case ' ':
				pattern += '\\s';
				break;
			case '/':
				pattern += '\\/';
				break;
			case '.':
				pattern += '\\.';
				break;
			case '. ':
				pattern += '\\.\\s';
				break;
			default:
				pattern += this.DateFormat['delimiter'][i];
				break;
		}
	}

	pattern += '$';

	return pattern;
}

function gscParseDate(date)
{
	var pattern = this.createParsePattern();
	var regExp = new RegExp(pattern);

	if (! regExp.test(date))
	{
		return false;
	}

	var y, m, d;
	for (var i = 0; i < 3; ++i)
	{
		eval((this.DateFormat['format'][i]).charAt(0) + ' = RegExp.$' + (i + 1) + ';');
	}

	y = parseInt(y, 10);
	m = parseInt(m, 10);
	d = parseInt(d, 10);

	if (y < gscMinYear || y > gscMaxYear || ! gscIsValidDate(y, m, d))
	{
		return false;
	}

	return new Date(y, m - 1, d);
}

function gscInsertCalendar()
{
	// check for errors
	if (gscErrorCheck(this.InputElementId))
	{
		if (gscDebugMode)
		{
			alert("Error: This browser can't display the calendar!");
		}

		return;
	}

	var i, j, day;

	// writes a copyright notice
	gscWriteLn('<!-- gsCalendar - Version 2.2.3 (2005-05-08) -->');
	gscWriteLn('<!-- Copyright (C) 2002-2005 Gerhard Schlager -->');
	gscWriteLn('<!-- http://www.gerhard-schlager.at/ -->');

	// writes the button for displaying/hiding the calendar
	if (this.ButtonText == '')
	{
		gscWriteLn('<input class="' + this.StylePrefix + 'calendar-button-image" onclick="' + this.CalendarName + '.toggleCalendar();" type="button" value="&nbsp;" />');
	}
	else
	{
		gscWriteLn('<input class="' + this.StylePrefix + 'calendar-button-text" onclick="' + this.CalendarName + '.toggleCalendar();" type="button" value="' + this.ButtonText + '" />');
	}

	// writes the frame (div) --> Opera needs this!
	gscWriteLn('<div class="' + this.StylePrefix + 'calendar" id="' + this.Ids['frame'] + '">');

	// writes the beginning of the table
	gscWriteLn('<table class="' + this.StylePrefix + 'calendar-table">');

	// writes the headline
	if (! this.ExtendedNavigation)
	{
		gscWriteLn('<tr class="' + this.StylePrefix + 'calendar-header">');
		gscWriteLn('<th>');
		gscWriteLn('<a href="javascript:' + this.CalendarName + '.previousMonth()" title="' + this.getLanguageString('previousMonth') + '">&laquo;</a>');
		gscWriteLn('</th>');
		gscWriteLn('<th colspan="5" id="' + this.Ids['title'] + '">&nbsp;</th>');
		gscWriteLn('<th>');
		gscWriteLn('<a href="javascript:' + this.CalendarName + '.nextMonth()" title="' + this.getLanguageString('nextMonth') + '">&raquo;</a>');
		gscWriteLn('</th>');
		gscWriteLn('</tr>');
	}
	else
	{
		// write alternative headline
		gscWriteLn('<tr class="' + this.StylePrefix + 'calendar-header">');
		gscWriteLn('<th colspan="2">');
		gscWriteLn('<a href="javascript:' + this.CalendarName + '.previousMonth()" title="' + this.getLanguageString('previousMonth') + '">&laquo;</a>');
		gscWriteLn('</th>');
		gscWriteLn('<th colspan="3">');
		gscWriteLn('<span id="' + this.Ids['month'] + '">&nbsp;</span>');
		gscWriteLn('</th>');
		gscWriteLn('<th colspan="2">');
		gscWriteLn('<a href="javascript:' + this.CalendarName + '.nextMonth()" title="' + this.getLanguageString('nextMonth') + '">&raquo;</a>');
		gscWriteLn('</th>');
		gscWriteLn('</tr>');
		gscWriteLn('<tr class="' + this.StylePrefix + 'calendar-header">');
		gscWriteLn('<th colspan="7">');
		gscWriteLn('<a href="javascript:' + this.CalendarName + '.previousYear()" title="' + this.getLanguageString('previousYear') + '">&laquo;</a>');
		gscWriteLn('<input id="' + this.Ids['year'] + '" type="text" class="' + this.StylePrefix + 'calendar-year" value="&nbsp;" maxlength="4" onmouseover="' + this.CalendarName + '.highlightYear(true);" onfocus="' + this.CalendarName + '.highlightYear(true, true);" onmouseout="' + this.CalendarName + '.highlightYear(false);" onblur="' + this.CalendarName + '.highlightYear(false, false); ' + this.CalendarName + '.changeYear(this.value);" onkeypress="return ' + this.CalendarName + '.filterKeys(this.value, event);" />');
		gscWriteLn('<a href="javascript:' + this.CalendarName + '.nextYear()" title="' + this.getLanguageString('nextYear') + '">&raquo;</a>');
		gscWriteLn('</th>');
		gscWriteLn('</tr>');
	}

	// writes a row of weekdays
	gscWriteLn('<tr class="' + this.StylePrefix + 'calendar-day-name">');
	for (i = 0; i < 7; ++i)
	{
		gscWriteLn('<th>' + this.getDayName(i) + '</th>');
	}
	gscWriteLn('</tr>');

	day = 0;
	// writes the 6 rows of days
	for (i = 1; i <= 6; ++i)
	{
		gscWriteLn('<tr class="' + this.StylePrefix + 'calendar-day">');

		// writes every day into the table
		for (j = 1; j <= 7; ++j)
		{
			++day;

			gscWriteLn('<td>');
			gscWriteLn('<a href="#" class="' + this.StylePrefix + 'calendar-day" id="' + this.Ids['day'] + day + '">' + day + '</a>');
			gscWriteLn('</td>');
		}

		gscWriteLn('</tr>');
	}

	if (this.DisplayFooter)
	{
		// bottom line
		gscWriteLn('<tr class="' + this.StylePrefix + 'calendar-footer">');
		gscWriteLn('<td colspan="7" id="' + this.Ids['today'] + '">');
		gscWriteLn('<a id="' + this.Ids['footer'] + '" href="javascript:' + this.CalendarName + '.displayToday();">&nbsp;</a>');
		gscWriteLn('</td>');
		gscWriteLn('</tr>');
	}

	// end of the table
	gscWriteLn('</table>');

	// end of the frame
	gscWriteLn('</div>');

	// @debug mode
	if (gscDebugMode)
	{
		var debugOutputTo = gscGetElementById(gscDebugOutputTo);

		if (debugOutputTo != null)
		{
			debugOutputTo.value = gscDebugOutput;
		}
	}
}

// writes a line to the document and begins a new line
function gscWriteLn(outputString)
{
	// @debug mode
	if (gscDebugMode)
	{
		gscDebugOutput += outputString + "\n";
	}

	document.writeln(outputString);
}

function gscInitInputElement()
{
	gscGetElementById(this.InputElementId).value = this.getFormatedDate(this.SelectedDate);
}

// displays or hides a calendar
function gscToggleCalendar()
{
	var calendarObject = gscGetElementById(this.Ids['frame']);

	if (this.Visible)		// if the calendar is currently visible...
	{
		// hide the calendar
		calendarObject.style.display	= 'none';

		// display hidden elements again
		this.hideElements(false);

		// set the state of the calendar to "invisible"
		this.Visible = false;
	}
	else	// if the calendar is currently *not* visible...
	{
		this.SelectedDate = this.parseDate(gscGetElementById(this.InputElementId).value);
		// if it was not possible to parse the date in the <input> element
		// then assign the current date
		if (this.SelectedDate == false)
		{
			this.SelectedDate = new Date(gscCurrentDate);
		}

		this.VisibleMonth = this.SelectedDate.getMonth() + 1;
		this.VisibleYear = this.SelectedDate.getFullYear();

		// fill the calendar with data
		this.fillCalendar();
		// determine if the browser permits the usage of units (px) and/or
		// if it *needs* them (like Netscape/Mozilla)
		var unit = (typeof(calendarObject.style.left) == 'number') ? 0 : 'px';

		// determine the coordinates of the input element for the date and
		// display the calendar right under this input element
		var coord = gscGetPosition(this.InputElementId, true);
		coord.y += gscCalendarTop;
		coord.x += gscCalendarLeft;
		calendarObject.style.left	= coord.x + unit;
		calendarObject.style.top	= coord.y + unit;

		// if there are elements to hide (like a selectbox) then hide them
		this.hideElements(true);

		// display the calendar
		calendarObject.style.display = 'block';

		// hide automatically elements (selectbox) which are behind the calendar
		if (this.AutoHide)
		{
			this.autoHideElements(coord, calendarObject.offsetHeight, calendarObject.offsetWidth);
		}

		// set the state of the calendar to "visible"
		this.Visible = true;
	}
}

// returns the position of a given element
function gscGetPosition(elementId, countHeight, element)
{
	// die Koordinaten eines Objektes ermitteln und zurückgeben
	var coords = {x: 0, y: 0};

	if (elementId)
	{
		element = gscGetElementById(elementId);
	}

	if (countHeight)
	{
		coords.y = element.offsetHeight;
	}

	while (element)
	{
		coords.x += element.offsetLeft;
		coords.y += element.offsetTop;
		element = element.offsetParent;
	}

	return coords;
}

function gscSetInnerText(elementObject, text)
{
	if (elementObject.appendChild && elementObject.ownerDocument)
	{
		while (elementObject.hasChildNodes())
		{
			elementObject.removeChild(elementObject.lastChild);
		}
		elementObject.appendChild(elementObject.ownerDocument.createTextNode(text));
	}
	else if (typeof(elementObject.innerText) != 'undefined')
	{
		elementObject.innerText = text;
	}
}

function gscGetElementById(elementId)
{
	if (document.getElementById)
	{
		return document.getElementById(elementId);
	}
	else if (document.all)
	{
		return document.all[elementId];
	}
	else
	{
		return null;
	}
}

// returns the name of a month
function gscGetMonthName(month)
{
	return gscMonthNames[this.Language][month - 1];
}

function gscGetShortMonthName(month)
{
	return gscShortMonthNames[this.Language][month - 1];
}

function gscGetDayName(weekday)
{
	return gscDayNames[this.Language][weekday];
}

function gscGetLanguageString(stringName)
{
	return gscLanguageStrings[this.Language][stringName];
}

function gscGetWeekday(year, month, day)
{
	var weekday = (new Date(year, month - 1, day)).getDay();

	// Sunday is 0 and should be 6
	// Monday is 1 and should be 0
	// ...
	return (weekday == 0 ? 6 : weekday - 1);
}

// returns true if the given year is a leap year
function gscIsLeapYear(year)
{
	if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
	{
		return true;
	}
	else
	{
		return false;
	}
}

// returns the number of days in the given month and year
function gscGetNumberOfDaysInMonth(year, month)
{
	// if the month is February...
	if (month == 2)
	{
		return (gscIsLeapYear(year)) ? 29 : 28;
	}

	// remaining months
	return 32 - (Math.abs(month * 2 - 15) % 4 + 1) / 2;
}

function gscFillCurrentMonth(firstDay, numberDays)
{
	var inactiveClassName	= this.StylePrefix + 'calendar-day ' + this.StylePrefix + 'calendar-day-inactive';
	var activeClassName		= this.StylePrefix + 'calendar-day ' + this.StylePrefix + 'calendar-day-active';

	for (var day = 1; day <= numberDays; ++day)
	{
		outputObject = gscGetElementById(this.Ids['day'] + (firstDay + day));

		// output the day (two-digit)
		gscSetInnerText(outputObject, gscAddZero(day));

		if (this.isDateAllowed(day))
		{
			if (this.PopupInfo)
			{
				outputObject.title = this.getFormatedDate(new Date(this.VisibleYear, this.VisibleMonth - 1, day));
			}

			// assign the CSS class "calendar-day-active" for clickable days
			outputObject.className = activeClassName;

			// set the link to the JavaScript function setDate()
			outputObject.href = 'javascript:' + this.CalendarName + '.selectDate(' + day + ');';
		}
		else
		{
			// remove the title of the previously displayed month
			outputObject.title = '';

			// assign the CSS class "calendar-day-inactive"
			outputObject.className = inactiveClassName;

			// point the link into the void ;-)
			outputObject.href = 'javascript:void(0);';
		}
	}
}

function gscFillFollowingMonth(firstDay, numberDays)
{
	var inactiveClassName = this.StylePrefix + 'calendar-day ' + this.StylePrefix + 'calendar-day-inactive';

	var day = 1;
	for (var i = firstDay + numberDays + 1; i <= 42; ++i)
	{
		outputObject = gscGetElementById(this.Ids['day'] + i);

		// output the day (two-digit)
		gscSetInnerText(outputObject, gscAddZero(day));

		// remove the title of the previously displayed month
		outputObject.title = '';

		// assign the CSS class "calendar-day-inactive" for nonclickable days
		outputObject.className = inactiveClassName;

		if (this.MonthNavigation)
		{
			// set the link to the function nextMonth() and enable the
			// navigation within the calendar
			outputObject.href = 'javascript:' + this.CalendarName + '.nextMonth();';
		}
		else
		{
			// point this link into the void
			outputObject.href = 'javascript:void(0);';

			// remove the border after clicking the link
			outputObject.onclick = new Function('gscBlur(this)');
		}

		++day;
	}
}

function gscFillPreviousMonth(firstDay)
{
	var month	= this.VisibleMonth;
	var year	= this.VisibleYear;

	if (firstDay == 0)
		return;

	// determine the previous month and year
	if (month > 1)	// all months except January
	{
		--month;
	}
	else	// January
	{
		month = 12;
		--year;
	}

	// determine the number of days in this (= the previous) month
	var numberDays = gscGetNumberOfDaysInMonth(year, month);

	var inactiveClassName = this.StylePrefix + 'calendar-day ' + this.StylePrefix + 'calendar-day-inactive';

	day = 0;
	for (i = firstDay; i >= 1; --i)
	{
		outputObject = gscGetElementById(this.Ids['day'] + i);

		// output the day
		gscSetInnerText(outputObject, numberDays - day);

		// remove the title of the previously displayed month
		outputObject.title = '';

		// assign the CSS class "calendar-day-inactive" for nonclickable days
		outputObject.className = inactiveClassName;

		if (this.MonthNavigation)
		{
			// set the link to the function previousMonth() and enable the
			// navigation within the calendar
			outputObject.href = 'javascript:' + this.CalendarName + '.previousMonth();';
		}
		else
		{
			// point this link into the void
			outputObject.href = 'javascript:void(0);';

			// remove the border after clicking the link
			outputObject.onclick = new Function('gscBlur(this)');
		}

		++day;
	}
}

function gscBlur(obj)
{
	if (obj.blur)
	{
		obj.blur();
	}
}

function gscHighlightDays(firstDay)
{
	var dayClassName		= this.StylePrefix + 'calendar-day';
	var inactiveClassName	= this.StylePrefix + 'calendar-day-inactive';
	var activeClassName		= this.StylePrefix + 'calendar-day-active';
	var searchPattern		= new RegExp(inactiveClassName);

	// if the month and year of the selected date are the same as the
	// currently displayed month and year than highlight this day
	if ((this.VisibleMonth == this.SelectedDate.getMonth() + 1) && (this.VisibleYear == this.SelectedDate.getFullYear()))
	{
		outputObject = gscGetElementById(this.Ids['day'] + (firstDay + this.SelectedDate.getDate()));

		outputObject.title = this.getLanguageString('selectedDate');

		// assign the CSS class "calendar-selected-day"
		outputObject.className = dayClassName + ' ' +	this.StylePrefix + 'calendar-selected-day' + ' ' +
			(outputObject.className.search(searchPattern) == -1 ? activeClassName : inactiveClassName);
	}

	// if the month and year of the current date are the same as the
	// currently displayed month and year than highlight this day
	if ((this.VisibleMonth == gscCurrentDate.getMonth() + 1) && (this.VisibleYear == gscCurrentDate.getFullYear()))
	{
		outputObject = gscGetElementById(this.Ids['day'] + (firstDay + gscCurrentDate.getDate()));

		outputObject.title = this.getLanguageString('today');

		// assign the CSS class "calendar-current-day"
		outputObject.className = dayClassName + ' ' +	this.StylePrefix + 'calendar-current-day' + ' ' +
			(outputObject.className.search(searchPattern) == -1 ? activeClassName : inactiveClassName);
	}
}

function gscFillFooter()
{
	var outputObject = gscGetElementById(this.Ids['footer']);
	var text = this.getLanguageString('today') + ': ';

	text += this.getFormatedDate(gscCurrentDate);

	gscSetInnerText(outputObject, text);
}

function gscFillCalendar()
{
	var numberDays, firstDay;

	if (! this.ExtendedNavigation)
	{
		// output the calendar's title (name of the month and year, e.g. April 2003)
		gscSetInnerText(gscGetElementById(this.Ids['title']), this.getMonthName(this.VisibleMonth) + ' ' + this.VisibleYear);
	}
	else
	{
		gscGetElementById(this.Ids['year']).value = this.VisibleYear;

		gscSetInnerText(gscGetElementById(this.Ids['month']), this.getMonthName(this.VisibleMonth));
	}

	// determine the first weekday for the currentently selected month
	firstDay = gscGetWeekday(this.VisibleYear, this.VisibleMonth, 1);

	// determine the number of days in the currently selected month
	numberDays = gscGetNumberOfDaysInMonth(this.VisibleYear, this.VisibleMonth);

	// output all days of the current month
	this.fillCurrentMonth(firstDay, numberDays);

	// output the visible days of the following month
	this.fillFollowingMonth(firstDay, numberDays);

	// output the visible days of the previous month
	this.fillPreviousMonth(firstDay);

	this.highlightDays(firstDay);

	if (this.DisplayFooter)
	{
		this.fillFooter();
	}
}

// changes the visible month and shows the previous or following month
function gscPreviousMonth()
{
	if (this.VisibleMonth == 1) // month = January
	{
		if (this.VisibleYear - 1 < gscMinYear)
		{
			return;
		}

		this.VisibleMonth = 12; // set the month to December
		--this.VisibleYear;   // decrement the year
	}
	else
	{
		--this.VisibleMonth;
	}

	// fill the calendar with the new dates and display it
	this.fillCalendar();
}

function gscNextMonth()
{
	if (this.VisibleMonth == 12)  // month = December
	{
		if (this.VisibleYear + 1 > gscMaxYear)
		{
			return;
		}

		this.VisibleMonth = 1;  // set the month to January
		++this.VisibleYear;   // increment the year
	}
	else
	{
		++this.VisibleMonth;
	}

	// fill the calendar with the new dates and display it
	this.fillCalendar();
}

function gscPreviousYear()
{
	this.changeYear(this.VisibleYear - 1);
}

function gscNextYear()
{
	this.changeYear(this.VisibleYear + 1);
}

// this function is triggert by a click on a day and writes the date into
// the input element for the date
function gscSelectDate(day)
{
	var dateObject = gscGetElementById(this.InputElementId);

	this.SelectedDate = new Date(this.VisibleYear, this.VisibleMonth - 1, day);

	// set the date
	dateObject.value = this.getFormatedDate(this.SelectedDate);

	// hide the calendar
	this.toggleCalendar();
}

function gscHighlightYear(highlight, hasFocus)
{
	if (this.YearHasFocus && typeof(hasFocus) == 'undefined')
	{
		return;
	}

	var color = this.SelectColor[(highlight ? 'focus' : 'blur')]
	gscGetElementById(this.Ids['year']).style.color = color;

	if (typeof(hasFocus) != 'undefined')
	{
		this.YearHasFocus = hasFocus;
	}
}

function gscChangeYear(year)
{
	year = parseInt(year);
	var date = new Date(year, 0, 1)

	if (date.getFullYear() == year && year != this.VisibleYear && year <= gscMaxYear)
	{
		this.VisibleYear = year;
		this.fillCalendar();

		return;
	}

	gscGetElementById(this.Ids['year']).value = this.VisibleYear;
}

function gscFilterKeys(year, e)
{
	var keycode;

	if (window.event)
	{
		keycode = window.event.keyCode;
	}
	else if (e)
	{
		keycode = e.which;
	}
	else
	{
		return true;
	}

	if (keycode == 13)
	{
		this.changeYear(year);
	}

	return ((keycode > 47) && (keycode < 58)) || (keycode == 8);
}

function gscNewAutoHideElements(tagName, coord, height, width)
{
	var elements = document.getElementsByTagName(tagName);

	for (i = 0; i < elements.length; ++i)
	{
		if (gscElementBehindCalendar(elements[i], coord, height, width))
		{
			this.HiddenElements[this.HiddenElements.length] = elements[i];
			elements[i].style.visibility = 'hidden';
		}
	}
}

function gscAutoHideElements(coord, height, width)
{
	if (document.getElementsByTagName)
	{
		// new browsers have this nice function, so let's use it...

		this.newAutoHideElements('select', coord, height, width);
		this.newAutoHideElements('select-multiple', coord, height, width);
		this.newAutoHideElements('applet', coord, height, width);

		return;
	}

	var type, i, j;

	if (document.forms)
	{
		// hides <select>-elements which are behind the calendar
		for (i = 0; i < document.forms.length; ++i)
		{
			for (j = 0; j < document.forms[i].elements.length; ++j)
			{
				type = document.forms[i].elements[j].type;
				if (type != 'select-one' && type != 'select-multiple')
				{
					continue;
				}

				if (gscElementBehindCalendar(document.forms[i].elements[j], coord, height, width))
				{
					this.HiddenElements[this.HiddenElements.length] = document.forms[i].elements[j];
					document.forms[i].elements[j].style.visibility = 'hidden';
				}
			}
		}
	}

	if (! document.applets)
	{
		// untested - but should hide applets which are behind the calendar
		for (i = 0; i < document.applets.length; ++i)
		{
			if (gscElementBehindCalendar(document.applets[i], coord, height, width))
			{
				this.HiddenElements[this.HiddenElements.length] = document.applets[i];
				document.applets[i].style.visibility = 'hidden';
			}
		}
	}
}

function gscElementBehindCalendar(element, coord, height, width)
{
	var elementCoord1	= gscGetPosition(null, false, element);
	var elementCoord2	= {x: elementCoord1.x + element.offsetWidth, y: elementCoord1.y + element.offsetHeight};

	if (! ((elementCoord1.x >= coord.x && elementCoord1.x <= coord.x + width) ||
		(elementCoord2.x >= coord.x && elementCoord2.x <= coord.x + width)))
	{
		return false;
	}

	return ((elementCoord1.y >= coord.y && elementCoord1.y <= coord.y + height) ||
		(elementCoord2.y >= coord.y && elementCoord2.y <= coord.y + height));
}

function gscHideElements(hide)
{
	var i;
	var visibility = hide ? 'hidden' : 'visible';

	for (i = 0; i < this.ElementsToHide.length; ++i)
	{
		gscGetElementById(this.ElementsToHide[i]).style.visibility = visibility;
	}

	if (! hide)
	{
		for (i = 0; i < this.HiddenElements.length; ++i)
		{
			this.HiddenElements[i].style.visibility = visibility;
		}

		this.HiddenElements = new Array();
	}
}

function gscAddAllowedDates(fromDate, toDate)
{
	if (this.ForbiddenDates.length > 0)
	{
		alert("Error in function gscAddAllowedDates().\n" +
			"It's not possible to use AllowedDates and ForbiddenDates at the same time.\n" +
			"Please use either AllowedDates or ForbiddenDates to constrain the calendar.");
	}

	this.addDates(this.AllowedDates, fromDate, toDate);
}

function gscAddForbiddenDates(fromDate, toDate)
{
	if (this.AllowedDates.length > 0)
	{
		alert("Error in function gscAddAllowedDates().\n" +
			"It's not possible to use AllowedDates and ForbiddenDates at the same time.\n" +
			"Please use either AllowedDates or ForbiddenDates to constrain the calendar.");
	}

	this.addDates(this.ForbiddenDates, fromDate, toDate);
}

function gscAddDates(dateArray, fromDate, toDate)
{
	if (typeof(fromDate) == 'string')
	{
		fromDate = this.parseDate(fromDate);
	}

	if (! gscIsValidDate(fromDate.getFullYear(), fromDate.getMonth() + 1, fromDate.getDate()))
	{
		alert("Error in function gscAddDates().\nThe given date '" + fromDate + "' is not valid!");
		return;
	}

	if (typeof(toDate) != 'undefined')
	{
		if (typeof(toDate) == 'string')
		{
			toDate = this.parseDate(toDate);
		}

		if (! gscIsValidDate(toDate.getFullYear(), toDate.getMonth() + 1, toDate.getDate()))
		{
			alert("Error in function gscAddDates().\nThe given date '" + toDate + "' is not valid!");
			return;
		}
	}
	else
	{
		toDate = fromDate;
	}

	if (fromDate > toDate)
	{
		var tempDate	= fromDate;
		fromDate		= toDate;
		toDate			= tempDate;
	}

	dateArray[dateArray.length] = {
		'fromDate'	: fromDate,
		'toDate'	: toDate
	};
}

// check if a date is between the allowed range or not
function gscIsDateAllowed(day)
{
	var currentDate = new Date(this.VisibleYear, this.VisibleMonth - 1, day);

	for (var i = 0; i < this.ForbiddenDates.length; ++i)
	{
		if ((currentDate >= this.ForbiddenDates[i]['fromDate']) && (currentDate <= this.ForbiddenDates[i]['toDate']))
		{
			return false;
		}
	}

	for (var i = 0; i < this.AllowedDates.length; ++i)
	{
		if ((currentDate >= this.AllowedDates[i]['fromDate']) && (currentDate <= this.AllowedDates[i]['toDate']))
		{
			return true;
		}
	}

	return (this.AllowedDates.length > 0 ? false : true);
}

function gscDisplayToday()
{
	if ((this.VisibleMonth == gscCurrentDate.getMonth() + 1) && (this.VisibleYear == gscCurrentDate.getFullYear()))
	{
		return;
	}

	this.VisibleMonth	= gscCurrentDate.getMonth() + 1;
	this.VisibleYear	= gscCurrentDate.getFullYear();

	this.fillCalendar();
}

function gscDisableFuture(disableToday)
{
	var startDate = new Date(gscCurrentDate);

	if (typeof(disableToday) == 'undefined' || disableToday == false)
	{
		startDate.setDate(startDate.getDate() + 1);
	}

	this.addForbiddenDates(startDate, new Date(gscMaxYear, 12, 31));
}

function gscDisablePast(disableToday)
{
	var endDate = new Date(gscCurrentDate);

	if (typeof(disableToday) == 'undefined' || disableToday == false)
	{
		endDate.setDate(endDate.getDate() - 1);
	}

	this.addForbiddenDates(new Date(gscMinYear, 1, 1), endDate);
}

function gscInit()
{
	if (gscMinYear > gscMaxYear)
	{
		gscError = true;
		alert('Error: gscMinYear has to be smaller than gscMaxYear!');
	}

	if (gscMinYear < 100)
	{
		gscError = true;
		alert('Error: gscMinYear must be at least 100!');
	}

	if (gscMaxYear > 9999)
	{
		gscError = true;
		alert('Error: gscMaxYear must be smaller than 9999!');
	}
}

function gscErrorCheck(elementId)
{
	if (gscError)
	{
		return gscError;
	}

	var elementObject = gscGetElementById(elementId);

	// gscGetElementById()
	if (elementObject == null)
	{
		gscError = true;
		return true;
	}

	// setInnterText()
	if (typeof(elementObject.innerText) == 'undefined' && ! (elementObject.appendChild && elementObject.ownerDocument))
	{
		gscError = true;
		return true;
	}

	// Opera until version 7 can't change the value of the display-property
	// It also can't change the innerText...
	if (typeof(elementObject.style.display) == 'undefined')
	{
		gscError = true;
		return true;
	}

	// Internet Explorer 4 is not able to display the calendar but handles all error checks
	// Code parts are from http://www.mozilla.org/docs/web-developer/sniffer/browser_type.html
	var agt		= navigator.userAgent.toLowerCase();
	var is_ie	= ((agt.indexOf('msie') != -1) && (agt.indexOf('opera') == -1));
	var is_ie4	= (is_ie && (parseInt(navigator.appVersion) == 4) && (agt.indexOf('msie 4') != -1) );

	if (is_ie4)
	{
		gscError = true;
		return true;
	}

	return false;
}

function gscSetStylePrefix(prefix)
{
	this.StylePrefix = prefix;
}