//---------------------------------- Constants ----------------------------------

// Custom attributes
var OlvAttr_Control = "olv:Control";
var OlvAttr_OnValueChanged = "olv:onValueChanged";
var OlvAttr_OnDateClicked = "olv:onDateClicked";
var OlvAttr_Value = "olv:Value";
var OlvAttr_MonthMask = "olv:MonthMask";
var OlvAttr_DayMask = "olv:DayMask";
var OlvAttr_RestrictedDayMask = "olv:RestrictedDayMask";
var OlvAttr_FirstDOW = "olv:FirstDOW";
var OlvAttr_DisplayOtherMonthDays = "olv:DisplayOtherMonths";
var OlvAttr_DayFormat = "olv:DayFormat";
var OlvAttr_MonthFormat = "olv:MonthFormat";
var OlvAttr_YearFormat = "olv:YearFormat";
var OlvAttr_DayTooltipFormat = "olv:TooltipFormat";

// Predefiend CSS classes
var OlvCssClass_DayTable = "DayTable";
var OlvCssClass_DayTableWorkingDay = "WorkingDay";
var OlvCssClass_DayTableWeekendDay = "WeekendDay";
var OlvCssClass_DayTableCell = "DayCell";
var OlvCssClass_DayTableOtherMonthCell = "OtherMonthDayCell";
var OlvCssClass_DayTableCellSelected = "DayCellSelected";
var OlvCssClass_DayTableCellEnabled = "DayCellEnabled";
var OlvCssClass_DayTableCellRestricted = "DayCellRestricted";
var OlvCssClass_DayTableCellDisabled = "DayCellDisabled";

// Control types
var OlvControl_Calendar = "calendar";
var OlvControl_Date = "date";
var OlvControl_Year = "year";
var OlvControl_Month = "month";
var OlvControl_Day = "day";
var OlvControl_DayTable = "daytable";

// Calendar control properties
var OlvCalendarProp_DateSelected = "m_dateSelected";
var OlvCalendarProp_FirstDayOfWeek = "m_nFirstDayOfWeek";
var OlvCalendarProp_DisplayOtherMonthDays = "m_bDisplayOtherMonthDays";
var OlvCalendarProp_DayTextFormat = "m_sDayFormat";
var OlvCalendarProp_MonthTextFormat = "m_sMonthFormat";
var OlvCalendarProp_YearTextFormat = "m_sYearFormat";
var OlvCalendarProp_DayTooltipFormat = "m_sDayTooltipFormat";
var OlvCalendarProp_ActiveMonthMask = "m_nActiveMonthMask";
var OlvCalendarProp_ActiveDayMask = "m_nActiveDayMask";
var OlvCalendarProp_RestrictedDayMask = "m_nRestrictedDayMask";
var OlvCalendarProp_DatePartValue = "m_nDatePartValue";
var OlvCalendarProp_DayCount = "m_nDayCount";

//------------------------------- Initialization -------------------------------
function OlvControl_InitPage()
{
	var oBody = DHTML_getElementByTagName("body", document, 0);

	// Find nested controls and attach custom behavior
	DHTML_Walk(oBody, OlvControl_HtmlParse, null, null);
}

function OlvControl_HtmlParse(oHtmlElement)
{
	var sControlType = DHTML_getAttr(oHtmlElement, OlvAttr_Control);
	if (sControlType)
	{
		sControlType = sControlType.toLowerCase();
		switch (sControlType)
		{
			case OlvControl_Calendar:
				OlvCalendarControl_Apply(oHtmlElement);
				break;

			case OlvControl_Date:
				break;

			case OlvControl_Year:
				OlvYearControl_Apply(oHtmlElement);
				break;

			case OlvControl_Month:
				OlvMonthControl_Apply(oHtmlElement);
				break;

			case OlvControl_Day:
				OlvDayControl_Apply(oHtmlElement);
				break;

			case OlvControl_DayTable:
				OlvDayTableControl_Apply(oHtmlElement);
				break;
		}
	}
	return false;	// Continue scanning
}

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                                OlvControl                                 //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
function OlvControl_Apply(oHtmlElem)
{
	oHtmlElem.findParentControl = OlvControl_findParentControl;

	oHtmlElem.getControlProp = OlvControl_getControlProp;
	oHtmlElem.setControlProp = OlvControl_setControlProp;
	oHtmlElem.deleteControlProp = OlvControl_deleteControlProp;
	oHtmlElem.onControlPropChanged = null;
}

function OlvControl_findParentControl()
{
	var oParent = this;
	while (oParent = DHTML_getParent(oParent))
	{
		if (OlvControl_GetControlType(oParent))
			return oParent;
	}
	return null;
}

function OlvControl_getControlProp(sPropName, vDefaultValue)
{
	if (sPropName in this)
		return this[sPropName];
	var oParentControl = this.findParentControl();
	if (oParentControl)
		return oParentControl.getControlProp(sPropName, vDefaultValue);
	return vDefaultValue;
}

function OlvControl_setControlProp(sPropName, vValue, bDeleteIfNull)
{
	if (typeof(vValue) == "undefined")
		vValue = null;

	var vPrevValue = this.getControlProp(sPropName, null);
	if (bDeleteIfNull && !vValue && typeof(vValue) == "object")
		delete this[sPropName];
	else
		this[sPropName] = vValue;

	if (vPrevValue != vValue)
	{
		var bDoNotNotifySubControls = false;
		if (this.onControlPropChanged)
			bDoNotNotifySubControls = this.onControlPropChanged(sPropName, vValue, vPrevValue);

		if (!bDoNotNotifySubControls)
		{
			var arrArgs = new Array(4);
			arrArgs[0] = this;	// DOM node
			arrArgs[1] = sPropName;
			arrArgs[2] = vValue;
			arrArgs[3] = vPrevValue;
			DHTML_Walk(this, OlvControl_NotifyControlPropChanged, arrArgs, this);
		}
	}
}

function OlvControl_deleteControlProp(sPropName)
{
	this.setControlProp(sPropName, null, true);
}

function OlvControl_GetControlType(oHtmlElem)
{
	var sControlType = DHTML_getAttr(oHtmlElem, OlvAttr_Control);
	if (sControlType)
		return sControlType.toLowerCase();
	return null;
}

function OlvControl_NotifyControlPropChanged(oDomNode, sPropName, vNewValue, vPrevValue)
{
	var sControlType = OlvControl_GetControlType(oDomNode);
	var bStopScanning = false;
	if (sControlType)
	{
		if (sPropName in oDomNode)
			return true;	// Control stored its own value of property
		if (oDomNode.onControlPropChanged)
			bStopScanning = oDomNode.onControlPropChanged(sPropName, vNewValue, vPrevValue);
	} // {if} Control element
	if (!bStopScanning)
		bStopScanning = false;
	return bStopScanning;
}

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                         Common Calendar functions                         //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
function OlvCalendarControl_ApplyProp(oHtmlElem)
{
	// Customization:
	OlvControl_Apply(oHtmlElem);

	oHtmlElem.getValue = OlvControl_getValue;
	oHtmlElem.setValue = OlvControl_setValue;
	oHtmlElem.getFirstDayOfWeek = OlvControl_getFirstDayOfWeek;
	oHtmlElem.setFirstDayOfWeek = OlvControl_setFirstDayOfWeek;
	oHtmlElem.getDisplayOtherMonthDays = OlvControl_getDisplayOtherMonthDays;
	oHtmlElem.setDisplayOtherMonthDays = OlvControl_setDisplayOtherMonthDays;

	OlvDayControl_ApplyProp(oHtmlElem);
	OlvMonthControl_ApplyProp(oHtmlElem);
	OlvYearControl_ApplyProp(oHtmlElem);
	// Set day of week names
	// Set abbreviated day of week names
}

//-------------------------------- Customization --------------------------------
function OlvControl_getValue()
{
	return this.getControlProp(OlvCalendarProp_DateSelected, new Date());
}

function OlvControl_setValue(dateValue)
{
	this.setControlProp(OlvCalendarProp_DateSelected, dateValue);
}

function OlvControl_getFirstDayOfWeek()
{
	return this.getControlProp(OlvCalendarProp_FirstDayOfWeek, Locale_Default.FirstDayOfWeek);
}

function OlvControl_setFirstDayOfWeek(nFirstDayOfWeek)
{
	this.setControlProp(OlvCalendarProp_FirstDayOfWeek, nFirstDayOfWeek);
}

function OlvControl_getDisplayOtherMonthDays()
{
	return this.getControlProp(OlvCalendarProp_DisplayOtherMonthDays, false);
}

function OlvControl_setDisplayOtherMonthDays(bDisplay)
{
	this.setControlProp(OlvCalendarProp_DisplayOtherMonthDays, (bDisplay ? bDisplay : false));
}

//---------------------------- Day-related functions ----------------------------
function OlvDayControl_ApplyProp(oHtmlElem)
{
	oHtmlElem.getDayTextFormat = OlvControl_getDayTextFormat;
	oHtmlElem.setDayTextFormat = OlvControl_setDayTextFormat;
	oHtmlElem.getDayTooltipFormat = OlvControl_getDayTooltipFormat;
	oHtmlElem.setDayTooltipFormat = OlvControl_setDayTooltipFormat;
	oHtmlElem.getActiveDayMask = OlvControl_getActiveDayMask;
	oHtmlElem.setActiveDayMask = OlvControl_setActiveDayMask;
	oHtmlElem.getRestrictedDayMask = OlvControl_getRestrictedDayMask;
	oHtmlElem.setRestrictedDayMask = OlvControl_setRestrictedDayMask;
	oHtmlElem.getDayCount = OlvControl_getDayCount;
	oHtmlElem.setDayCount = OlvControl_setDayCount;
}

function OlvControl_getActiveDayMask()
{
	return this.getControlProp(OlvCalendarProp_ActiveDayMask, 0x7FFFFFFF);
}

function OlvControl_setActiveDayMask(nMask)
{
	this.setControlProp(OlvCalendarProp_ActiveDayMask, nMask);
}

function OlvControl_getRestrictedDayMask()
{ 
    // by default all days allowed
	return this.getControlProp(OlvCalendarProp_RestrictedDayMask, 0);
}

function OlvControl_setRestrictedDayMask(nMask)
{
	this.setControlProp(OlvCalendarProp_RestrictedDayMask, nMask);
}

function OlvControl_getDayTextFormat()
{
	return this.getControlProp(OlvCalendarProp_DayTextFormat, false);
}

function OlvControl_setDayTextFormat(sFormat)
{
	this.setControlProp(OlvCalendarProp_DayTextFormat, sFormat);
}

function OlvControl_getDayTooltipFormat()
{
	return this.getControlProp(OlvCalendarProp_DayTooltipFormat, false);
}

function OlvControl_setDayTooltipFormat(sFormat)
{
	this.setControlProp(OlvCalendarProp_DayTooltipFormat, sFormat);
}

function OlvControl_getDayCount()
{
	var dateSelected = this.getControlProp(OlvCalendarProp_DateSelected, null);
	if (dateSelected)
		return dateSelected.getDaysInMonth();
	return this.getControlProp(OlvCalendarProp_DayCount, 31);
}

function OlvControl_setDayCount(nDayCount)
{
	this.setControlProp(OlvCalendarProp_DayCount, nDayCount);
}

//-------------------------- Month-related functions --------------------------
function OlvMonthControl_ApplyProp(oHtmlElem)
{
	oHtmlElem.getActiveMonthMask = OlvControl_getActiveMonthMask;
	oHtmlElem.setActiveMonthMask = OlvControl_setActiveMonthMask;
	oHtmlElem.getMonthTextFormat = OlvControl_getMonthTextFormat;
	oHtmlElem.setMonthTextFormat = OlvControl_setMonthTextFormat;
	// Set month names
	// Set abbreviated month names
}

function OlvControl_getActiveMonthMask()
{
	return this.getControlProp(OlvCalendarProp_ActiveMonthMask, 0xFFF);
}

function OlvControl_setActiveMonthMask(nMask)
{
	this.setControlProp(OlvCalendarProp_ActiveMonthMask, nMask);
}

function OlvControl_getMonthTextFormat()
{
	return this.getControlProp(OlvCalendarProp_MonthTextFormat, "%B");
}

function OlvControl_setMonthTextFormat(sFormat)
{
	this.setControlProp(OlvCalendarProp_MonthTextFormat, sFormat);
}

//--------------------------- Year control functions ---------------------------
function OlvYearControl_ApplyProp(oHtmlElem)
{
	oHtmlElem.getYearTextFormat = OlvControl_getYearTextFormat;
	oHtmlElem.setYearTextFormat = OlvControl_setYearTextFormat;
	// Set Year names
	// Set abbreviated Year names
}
function OlvControl_getYearTextFormat()
{
	return this.getControlProp(OlvCalendarProp_YearTextFormat, "%Y");
}

function OlvControl_setYearTextFormat(sFormat)
{
	this.setControlProp(OlvCalendarProp_YearTextFormat, sFormat);
}

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                             Date part Control                             //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
function OlvDatePartControl_Apply(oHtmlElem)
{
	OlvControl_Apply(oHtmlElem);

	// Operations on control value:
	oHtmlElem.getValue = OlvDatePartControl_getValue;
	oHtmlElem.setValue = OlvDatePartControl_setValue;
	oHtmlElem.getControlValue = OlvDatePartControl_getControlValue;
	oHtmlElem.setControlValue = OlvDatePartControl_setControlValue;
	oHtmlElem.getValueFromDate = null;
	oHtmlElem.parseValue = OlvDatePartControl_parseValue;
	oHtmlElem.formatValue = OlvDatePartControl_formatValue;

	// Operations on combo box:
	oHtmlElem.isComboBox = OlvDatePartControl_isComboBox;
	DHTML_attachEvent(oHtmlElem, "change", OlvDatePartControl_onChange);
	if (oHtmlElem.isComboBox())
		OlvDatePartControl_Combo_Apply(oHtmlElem);
	else
		DHTML_attachEvent(oHtmlElem, "keypress", OlvDatePartControl_onKeyPress);
}

//------------------------ Operations on control value ------------------------
function OlvDatePartControl_getValue()
{
	var nValue = -1;

	var dateValue = this.getControlProp(OlvCalendarProp_DateSelected, null);
	if (this.getValueFromDate && dateValue)
		nValue = this.getValueFromDate(dateValue);
	else
		nValue = this.getControlProp(OlvCalendarProp_DatePartValue, -1);
	if (nValue < 0)
	{
		nValue = getControlValue();
		if (isNan(nValue))
			nValue = -1;
	}
	return nValue;
}

function OlvDatePartControl_getControlValue()
{
	var sValue = DHTML_getValue(this);
	if (!sValue || sValue == "")
		return -1;
	return this.parseValue(sValue);
}

function OlvDatePartControl_setControlValue(nValue)
{
	var sFormattedValue = this.formatValue(nValue);
	DHTML_setValue(this, nValue, sFormattedValue);
	this.setControlProp(OlvCalendarProp_DatePartValue, nValue);
}

function OlvDatePartControl_setValue(nValue, bDoUpdateHtml)
{
	if (!bDoUpdateHtml)
		this.setControlValue(nValue);

	// Notify parent control
	var oParentControl = this.findParentControl();
	if (oParentControl && oParentControl.onDatePartValueChanged)
		oParentControl.onDatePartValueChanged(this, nValue);
}

function OlvDatePartControl_parseValue(sValue)
{
	return parseInt(sValue, 10);
}

function OlvDatePartControl_formatValue(nValue)
{
	return String(nValue);
}

//---------------------------- HTML event handlers ----------------------------
function OlvDatePartControl_onChange(oEvent)
{
	var oHtmlElem = DHTML_getEventTarget(oEvent);
	if (!oHtmlElem || !oHtmlElem.getControlValue)
		return false;

	// Parse value entered by user
	var nValue = oHtmlElem.getControlValue();
	oHtmlElem.setValue(nValue, true);
}

function OlvDatePartControl_onKeyPress(oEvent)
{
	if (oEvent && oEvent.keyCode == 13)
		OlvDatePartControl_onChange.apply(this, arguments);
}

//-------------------------- Operations on combo box --------------------------
function OlvDatePartControl_Combo_Apply(oHtmlElem)
{
	// Operations on combo box:
	oHtmlElem.fillComboBox = OlvDatePartControl_fillComboBox;
}

function OlvDatePartControl_isComboBox(oHtmlElem)
{
	if (!oHtmlElem)
		oHtmlElem = this;
	if (!oHtmlElem)
		return false;		
	return (oHtmlElem.tagName.toLowerCase() == "select");
}

function OlvDatePartControl_fillComboBox(nBitmask, nItemCount, nStartFrom)
{
	if (!this.isComboBox())
		return;

	var nControlValue = this.getValue();
	this.options.length = 0;

	var dwMask = 1;
	var bMatched = false;

	if (!nStartFrom)
		nStartFrom = 0;
	var nTo = nStartFrom + nItemCount;
	for (var iItem = nStartFrom; iItem < nTo; ++iItem)
	{
		if ((nBitmask & dwMask) == dwMask)
		{
			var sValue = this.formatValue(iItem);
			this.options[this.options.length] = new Option(sValue, iItem);

			if (iItem == nControlValue)
			{
				bMatched = true;
				this.selectedIndex = this.options.length-1;
			}
		} // {if} Add active month value to the combo box
		dwMask = (dwMask << 1);
	}
} // OlvDatePartControl_fillComboBox()

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                            OlvCalendarControl                             //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
function OlvCalendarControl_Apply(oHtmlElem)
{
	OlvCalendarControl_ApplyProp(oHtmlElem);
	oHtmlElem.onControlPropChanged = OlvCalendarControl_onControlPropChanged;
	oHtmlElem.onValueChanged = null;
	oHtmlElem.onDateClicked = null;

	// Controlling currently selected date
	oHtmlElem.setToday = OlvCalendarControl_setToday;
	oHtmlElem.prevDay = OlvCalendarControl_prevDay;
	oHtmlElem.nextDay = OlvCalendarControl_nextDay;
	oHtmlElem.prevMonth = OlvCalendarControl_prevMonth;
	oHtmlElem.nextMonth = OlvCalendarControl_nextMonth;
	oHtmlElem.prevYear = OlvCalendarControl_prevYear;
	oHtmlElem.nextYear = OlvCalendarControl_nextYear;

	// Enable / Disable dates
	oHtmlElem.isDateEnabled = OlvCalendarControl_isDateEnabled;
	oHtmlElem.isDateRestricted = OlvCalendarControl_isDateRestricted;

	// Internal operations:
	oHtmlElem.constructControl = OlvCalendarControl_constructControl;
	oHtmlElem.onDatePartValueChanged = OlvCalendarControl_onDatePartValueChanged;

	// Initialize control:
	oHtmlElem.constructControl();
}

function OlvCalendarControl_constructControl()
{
	var dateSelected = new Date();
	var nFirstDayOfWeek = Locale_Default.FirstDayOfWeek;
	var bDisplayOtherMonthDays = false;
	var sDayTooltipFormat = "%x";
	var sDayTextFormat = "%#d";
	var sMonthTextFormat = "%B";
	var sYearTextFormat = "%Y";
	var nMonthMask = 0xFFF;
	var nActiveDayMask = 0x7FFFFFFF;
	var nRestrictedDayMask = 0;

	var sAttrValue = DHTML_getAttr(this, OlvAttr_Value);
	if (sAttrValue)
	{
		var nParsedDateVal = Date.parse(sAttrValue);
		if (isNaN(nParsedDateVal))
			dateSelected = null;
		else
			dateSelected = new Date(nParsedDateVal);
	}
	sAttrValue = DHTML_getAttr(this, OlvAttr_FirstDOW);
	if (sAttrValue)
		nFirstDayOfWeek = parseInt(sAttrValue, 10);
	sAttrValue = DHTML_getAttr(this, OlvAttr_DisplayOtherMonthDays);
	if (sAttrValue)
		bDisplayOtherMonthDays = (sAttrValue == "true");
	sAttrValue = DHTML_getAttr(this, OlvAttr_DayTooltipFormat);
	if (sAttrValue)
		sDayTooltipFormat = sAttrValue;
	sAttrValue = DHTML_getAttr(this, OlvAttr_DayFormat);
	if (sAttrValue)
		sDayTextFormat = sAttrValue;
	sAttrValue = DHTML_getAttr(this, OlvAttr_MonthFormat);
	if (sAttrValue)
		sMonthTextFormat = sAttrValue;
	sAttrValue = DHTML_getAttr(this, OlvAttr_YearFormat);
	if (sAttrValue)
		sYearTextFormat = sAttrValue;
	sAttrValue = DHTML_getAttr(this, OlvAttr_MonthMask);
	if (sAttrValue)
		nMonthMask = parseInt(sAttrValue, 10);

	sAttrValue = DHTML_getAttr(this, OlvAttr_DayMask);
	if (sAttrValue)
		nActiveDayMask = parseInt(sAttrValue, 10);

	sAttrValue = DHTML_getAttr(this, OlvAttr_RestrictedDayMask);
	if (sAttrValue)
		nRestrictedDayMask = parseInt(sAttrValue, 10);

	sAttrValue = DHTML_getAttr(this, OlvAttr_OnValueChanged);
	if (sAttrValue)
		this.onValueChanged = sAttrValue;

	sAttrValue = DHTML_getAttr(this, OlvAttr_OnDateClicked);
	if (sAttrValue)
		this.onDateClicked = sAttrValue;

	this.setFirstDayOfWeek(nFirstDayOfWeek);
	this.setDisplayOtherMonthDays(bDisplayOtherMonthDays);
	this.setDayTextFormat(sDayTextFormat);
	this.setDayTooltipFormat(sDayTooltipFormat);
	this.setMonthTextFormat(sMonthTextFormat);
	this.setYearTextFormat(sYearTextFormat);
	this.setActiveMonthMask(nMonthMask);
	this.setActiveDayMask(nActiveDayMask);
	this.setRestrictedDayMask(nRestrictedDayMask);

	if (dateSelected && !isNaN(dateSelected))
		this.setValue(dateSelected);
}

function OlvCalendarControl_onControlPropChanged(sPropName, vNewValue, vPrevValue)
{
	if (sPropName == OlvCalendarProp_DateSelected && this.onValueChanged)
	{
		var retVal = eval(this.onValueChanged);
		if (typeof(retVal) == "function")
			retVal.call(this, vNewValue, vPrevValue);
	}

	return false;	// Continue
}

function OlvCalendarControl_onDatePartValueChanged(oHtmlElem, nValue)
{
	var dateValue = this.getValue();
	var dateNew = null;

	var sControlType = OlvControl_GetControlType(oHtmlElem);
	switch (sControlType)
	{
		case OlvControl_Year:
			if (dateValue.getFullYear() == nValue)
				return;	// Nothing to do
			dateNew = Date_createDate(nValue, dateValue.getMonth(), dateValue.getDate());
			break;

		case OlvControl_Month:
			if (dateValue.getMonth() == nValue)
				return;	// Nothing to do
			dateNew = Date_createDate(dateValue.getFullYear(), nValue, dateValue.getDate());
			break;

		case OlvControl_Day:
			if (dateValue.getDate() == nValue)
				return;	// Nothing to do
			dateNew = Date_createDate(dateValue.getFullYear(), dateValue.getMonth(), nValue);
			break;
	}

	if (dateNew)
		this.setValue(dateNew);
}

//-------------------- Operations on currently selected date --------------------
function OlvCalendarControl_setToday()
{
	var dateToday = new Date();
	this.setValue(dateToday);
}

function OlvCalendarControl_prevDay()
{
	var dateValue = new Date(this.getValue());
	this.setValue(dateValue.prevDay());
}

function OlvCalendarControl_nextDay()
{
	var dateValue = new Date(this.getValue());
	this.setValue(dateValue.nextDay());
}

function OlvCalendarControl_prevMonth()
{
	var dateValue = new Date(this.getValue());
	this.setValue(dateValue.prevMonth());
}

function OlvCalendarControl_nextMonth()
{
	var dateValue = new Date(this.getValue());
	this.setValue(dateValue.nextMonth());
}

function OlvCalendarControl_prevYear()
{
	var dateValue = new Date(this.getValue());
	this.setValue(dateValue.prevYear());
}

function OlvCalendarControl_nextYear()
{
	var dateValue = new Date(this.getValue());
	this.setValue(dateValue.nextYear());
}

//--------------------------- Enable / Disable dates ---------------------------
function OlvCalendarControl_isDateEnabled(oDateTest)
{
	if (!oDateTest)
		return false;

	// Default test: Check masks
	var nMonthMask = this.getActiveMonthMask();
	var nMonth = oDateTest.getMonth();
	var nMonthTest = (1 << nMonth);
	if ((nMonthMask & nMonthTest) == 0)
		return false;	// Month is disabled

	var nDayMask = this.getActiveDayMask();
	var nDay = oDateTest.getDate() - 1;
	var nDayTest = (1 << nDay);
	if ((nDayTest & nDayMask) == 0)
		return false;	// Month is disabled

	return true;
}

function OlvCalendarControl_isDateRestricted(oDateTest)
{
	if (!oDateTest)
		return false;

	// Default test: Check masks
	var nMonthMask = this.getActiveMonthMask();
	var nMonth = oDateTest.getMonth();
	var nMonthTest = (1 << nMonth);
	if ((nMonthMask & nMonthTest) == 0)
		return false;	// Month is disabled

	var nDayMask = this.getRestrictedDayMask();
	var nDay = oDateTest.getDate() - 1;
	var nDayTest = (1 << nDay);
	if ((nDayTest & nDayMask) == 0)
		return false;	// Month is disabled

	return true;
}

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                              OlvYearControl                               //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
function OlvYearControl_Apply(oHtmlElem)
{
	OlvDatePartControl_Apply(oHtmlElem);
	OlvYearControl_ApplyProp(oHtmlElem);

	// Formatting value
	oHtmlElem.getValueFromDate = OlvYearControl_getValueFromDate;
	oHtmlElem.formatValue = OlvYearControl_formatValue;

	// Implementation
	oHtmlElem.constructControl = OlvYearControl_constructControl;
	oHtmlElem.onControlPropChanged = OlvYearControl_onControlPropChanged;
	oHtmlElem.constructControl();
}

function OlvYearControl_constructControl()
{
	var nValue = this.getValue();
	if (nValue > 0)
		this.setControlValue(nValue);
}

function OlvYearControl_getValueFromDate(oDateValue)
{
	if (!oDateValue)
		return -1;
	return oDateValue.getFullYear();
}

function OlvYearControl_formatValue(nValue)
{
	var dateValue = new Date(nValue, 1, 1);

	var oLocale = null;	// TODO: Retrieve locale
	var sFormat = this.getYearTextFormat();
	var sFormattedValue = Date_Format(sFormat, dateValue, oLocale);
	return sFormattedValue;
}

function OlvYearControl_onControlPropChanged(sPropName, vNewValue, vPrevValue)
{
	switch (sPropName)
	{
		case OlvCalendarProp_DateSelected:
			this.setControlValue(vNewValue.getFullYear());
			break;

		case OlvCalendarProp_YearTextFormat:
			var nValue = this.getValue();
			if (nValue >= 0)
				this.setControlValue(nValue);
			break;
	}
}

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                              OlvMonthControl                              //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
function OlvMonthControl_Apply(oHtmlElem)
{
	OlvDatePartControl_Apply(oHtmlElem);
	OlvMonthControl_ApplyProp(oHtmlElem);

	// Formatting value
	oHtmlElem.getValueFromDate = OlvMonthControl_getValueFromDate;
	oHtmlElem.formatValue = OlvMonthControl_formatValue;

	// Implementation
	oHtmlElem.constructControl = OlvMonthControl_constructControl;
	oHtmlElem.onControlPropChanged = OlvMonthControl_onControlPropChanged;
	oHtmlElem.constructControl();
}

function OlvMonthControl_constructControl()
{
	if (this.isComboBox())
	{
		var nMonthMask = this.getActiveMonthMask();
		this.fillComboBox(nMonthMask, 12);
	}

	// Set initial value
	var nValue = this.getValue();
	if (nValue >= 0)
		this.setControlValue(nValue);
}

function OlvMonthControl_getValueFromDate(oDateValue)
{
	if (!oDateValue)
		return -1;
	return oDateValue.getMonth();
}

function OlvMonthControl_formatValue(nValue)
{
	var dateValue = new Date(2006, nValue, 1);

	var oLocale = null;	// TODO: Retrieve locale
	var sFormat = this.getMonthTextFormat();
	var sFormattedValue = Date_Format(sFormat, dateValue, oLocale);
	return sFormattedValue;
}

function OlvMonthControl_onControlPropChanged(sPropName, vNewValue, vPrevValue)
{
	switch (sPropName)
	{
		case OlvCalendarProp_DateSelected:
			this.setControlValue(vNewValue.getMonth());
			break;

		case OlvCalendarProp_MonthTextFormat:
			if (this.isComboBox())
			{
				var nMonthMask = this.getActiveMonthMask();
				this.fillComboBox(nMonthMask, 12);
			}
			else
			{
				var nValue = this.getValue();
				if (nValue >= 0)
					this.setControlValue(nValue);
			}
			break;

		case OlvCalendarProp_ActiveMonthMask:
			if (this.isComboBox())
			{
				var nMonthMask = this.getActiveMonthMask();
				this.fillComboBox(nMonthMask, 12);
			}
			break;
	}
}

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                               OlvDayControl                               //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
function OlvDayControl_Apply(oHtmlElem)
{
	OlvDatePartControl_Apply(oHtmlElem);
	OlvDayControl_ApplyProp(oHtmlElem);

	// Formatting value
	oHtmlElem.getValueFromDate = OlvDayControl_getValueFromDate;
	oHtmlElem.formatValue = OlvDayControl_formatValue;

	// Implementation
	oHtmlElem.constructControl = OlvDayControl_constructControl;
	oHtmlElem.onControlPropChanged = OlvDayControl_onControlPropChanged;
	oHtmlElem.updateControl = OlvDayControl_updateControl;

	oHtmlElem.constructControl();
}

function OlvDayControl_constructControl()
{
	this.updateControl(true, true);
}

function OlvDayControl_updateControl(bFillComboBox, bSetValue)
{
	if (this.isComboBox())
	{
		var nDayMask = this.getActiveDayMask();
		var nDaysCount = this.getDayCount();

		this.fillComboBox(nDayMask, nDaysCount, 1);
	}
	else
	{
		var nValue = this.getValue();
		if (nValue >= 0)
			this.setControlValue(nValue);
	}
}

function OlvDayControl_getValueFromDate(oDateValue)
{
	if (!oDateValue)
		return -1;
	return oDateValue.getDate();
}

function OlvDayControl_formatValue(nValue)
{
	var dateValue = new Date(2006, 0, nValue);

	var oLocale = null;	// TODO: Retrieve locale
	var sFormat = this.getDayTextFormat();
	var sFormattedValue = Date_Format(sFormat, dateValue, oLocale);
	return sFormattedValue;
}

function OlvDayControl_onControlPropChanged(sPropName, vNewValue, vPrevValue)
{
	switch (sPropName)
	{
		case OlvCalendarProp_DateSelected:
			if (!vPrevValue || 
				(vPrevValue.getMonth() != vNewValue.getMonth()) ||
				(vNewValue.getMonth() == 1 && 
					(vPrevValue.getFullYear() != vNewValue.getFullYear())))
				this.updateControl();
			else
				this.setControlValue(vNewValue.getDate());
			break;

		case OlvCalendarProp_DayTextFormat:
		case OlvCalendarProp_ActiveDayMask:
		case OlvCalendarProp_RestrictedDayMask:
			this.updateControl();
			break;
	}
}

///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//                            OlvDayTableControl                             //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
function OlvDayTableControl_Apply(oHtmlElem)
{
	OlvCalendarControl_ApplyProp(oHtmlElem);

	// Data members
	oHtmlElem.m_nYearDisplayed = null;
	oHtmlElem.m_nMonthDisplayed = null;
	oHtmlElem.m_nDayActive = null;

	oHtmlElem.m_sDayTableAttr = " cellspacing='1' cellpadding='0'";
	oHtmlElem.m_sCssDayTable = OlvCssClass_DayTable;
	oHtmlElem.m_sCssDayTableWorkingDay = OlvCssClass_DayTableWorkingDay;
	oHtmlElem.m_sCssDayTableWeekendDay = OlvCssClass_DayTableWeekendDay;
	oHtmlElem.m_sCssDayTableHeading = null;
	oHtmlElem.m_sCssDayCell = null;

	// Construction
	oHtmlElem.constructControl = OlvDayTableControl_constructControl;
	oHtmlElem.onControlPropChanged = OlvDayTableControl_onControlPropChanged;
	oHtmlElem.isDateEnabled = OlvDayTableControl_isDateEnabled;
	oHtmlElem.isDateRestricted = OlvDayTableControl_isDateRestricted;

	// Implementation:
	oHtmlElem.getDayOfWeekTitle = OlvDayTableControl_getDayOfWeekTitle;
	oHtmlElem.generateDayTable = OlvDayTableControl_generateDayTable;
	oHtmlElem.getTableElement = OlvDayTableControl_getTableElement;
	oHtmlElem.updateControl = OlvDayTableControl_updateControl;
	oHtmlElem.updateDayHeadings = OlvDayTableControl_updateDayHeadings;
	oHtmlElem.updateSelectedDay = OlvDayTableControl_updateSelectedDay;
	oHtmlElem.getDateCell = OlvDayTableControl_getDateCell;
	oHtmlElem.selectDateCell = OlvDayTableControl_selectDateCell;
	oHtmlElem.generateDayCells = OlvDayTableControl_generateDayCells;

	oHtmlElem.constructControl();
}

//-------------------------------- Construction --------------------------------
function OlvDayTableControl_constructControl()
{
	DHTML_attachEvent(this, "click", OlvDayTableControl_onClick);
	this.generateDayTable();
	this.updateControl(true, true);
}

function OlvDayTableControl_onControlPropChanged(sPropName, vNewValue, vPrevValue)
{
	switch (sPropName)
	{
		case OlvCalendarProp_DateSelected:
			this.updateSelectedDay(vNewValue);
			break;

		case OlvCalendarProp_FirstDayOfWeek:
		case OlvCalendarProp_ActiveDayMask:
		case OlvCalendarProp_RestrictedDayMask:
			this.updateControl(true, true);
			break;

		case OlvCalendarProp_DisplayOtherMonthDays:
		case OlvCalendarProp_DayTextFormat:
		case OlvCalendarProp_DayTooltipFormat:
			this.updateControl(false, true);
			break;
	}
}

function OlvDayTableControl_isDateEnabled(oDateTest)
{
	if (!oDateTest)
		return false;

	var oParentControl = this.findParentControl();
	if (!oParentControl)
		return true;
	return oParentControl.isDateEnabled(oDateTest);
}

function OlvDayTableControl_isDateRestricted(oDateTest)
{
	if (!oDateTest)
		return false;

	var oParentControl = this.findParentControl();
	if (!oParentControl)
		return true;
	return oParentControl.isDateRestricted(oDateTest);
}

//------------------------------- Implementation -------------------------------
function OlvDayTableControl_onClick(oEvent)
{
	var oSrcElement = DHTML_getEventTarget(oEvent);
	var oParentElem = DHTML_getParent(oSrcElement);
	if (!oParentElem || !oParentElem.m_dateValue)
		return false;

	// Check whether clicked date is enabled
	var bCanSelectDate = this.isDateEnabled(oParentElem.m_dateValue);
	if (bCanSelectDate)
	{
		var oParentControl = this.findParentControl();
		if (oParentControl)
		{
			var bCancelDefaultAction = false;
			if (oParentControl.onDateClicked)
			{
				var retVal = eval(oParentControl.onDateClicked);
				if (typeof(retVal) == "function")
					bCancelDefaultAction = retVal.call(this, oParentElem.m_dateValue);
			}
			if (!bCancelDefaultAction)
				oParentControl.setValue(oParentElem.m_dateValue);
		}
		else
			this.setValue(oParentElem.m_dateValue);
	} // {if} Select new date
}

function OlvDayTableControl_getDayOfWeekTitle(nDayOfWeek)
{
	var arrDayOfWeek = Locale_Default.AbbrWeekDayName;
//	return arrDayOfWeek[nDayOfWeek].slice(0, 1);
	return arrDayOfWeek[nDayOfWeek];
}

function OlvDayTableControl_generateDayTable()
{
	var sHeadingCell = "<th";
	if (this.m_sCssDayTableHeading)
		sHeadingCell += " class='" + this.m_sCssDayTableHeading + "'";
	sHeadingCell += ">&nbsp;</th>";

	var sDayCell = "<td";
	if (this.m_sCssDayCell)
		sDayCell += " class='" + this.m_sCssDayCell + "'";
	sDayCell += ">&nbsp;</td>";

	// Generate table columns definition
	var sTableColumns = "";
	if (this.m_sCssDayTableWeekendDay || this.m_sCssDayTableWorkingDay)
	{
		sTableColumns += "<colgroup span='5'";
		if (this.m_sCssDayTableWorkingDay)
			sTableColumns += " class='" + this.m_sCssDayTableWorkingDay + "'";
		sTableColumns += "></colgroup>";

		sTableColumns += "<colgroup span='2'";
		if (this.m_sCssDayTableWeekendDay)
			sTableColumns += " class='" + this.m_sCssDayTableWeekendDay + "'";
		sTableColumns += "></colgroup>";
	}
	
	// Generate headings and week row cells
	var sHeadingRowCells = "";
	var sWeekRowCells = "";
	for (var iDayOfWeek=0; iDayOfWeek<7; ++iDayOfWeek)
	{
		sHeadingRowCells += sHeadingCell;
		sWeekRowCells += sDayCell;
	}

	var sHtmlContent = "<table";
	if (this.m_sCssDayTable)
		 sHtmlContent += " class='" + this.m_sCssDayTable + "'";
	if (this.m_sDayTableAttr)
		 sHtmlContent += this.m_sDayTableAttr;
	sHtmlContent += ">";
	sHtmlContent += sTableColumns;
	sHtmlContent += "<thead><tr>" + sHeadingRowCells + "</tr></thead>";
	sHtmlContent += "<tbody>";
	for (var iWeek=0; iWeek<6; ++iWeek)
		sHtmlContent += "<tr>" + sWeekRowCells + "</tr>";
	sHtmlContent += "</tbody></table>";
	DHTML_pasteHtmlContent(this, sHtmlContent);
}

function OlvDayTableControl_getTableElement()
{
	var arrTables = DHTML_getElementsByTagName("table", this);
	if (!arrTables || arrTables.length == 0)
		return null;
	return arrTables[0];
}

function OlvDayTableControl_updateControl(bUpdateDayHeadings, bGenerateDayCells, bUpdateSelectedDay)
{
	if (bUpdateDayHeadings)
		this.updateDayHeadings();

	var dateCur = this.getValue();
	if (dateCur)
	{
		if (bGenerateDayCells)
			this.generateDayCells(dateCur.getFullYear(), dateCur.getMonth(), dateCur.getDate());
		else if (bUpdateSelectedDay)
			this.updateSelectedDay(dateCur);
	}
}

function OlvDayTableControl_updateDayHeadings()
{
	var oTableElem = this.getTableElement();
	var oHeadCells = DHTML_TableGetRowCells(oTableElem, 0);
	if (!oHeadCells)
		return;

	// Update headings
	var nFirstDayOfWeek = this.getFirstDayOfWeek();
	for (var iCell=0; iCell<7; ++iCell)
	{
		var oHeadCell = oHeadCells.item(iCell);
		var nDayOfWeek = (nFirstDayOfWeek + iCell) % 7;
		var sDayTitle = this.getDayOfWeekTitle(nDayOfWeek);

		// TODO: Update CSS class for weekends
		DHTML_pasteHtmlContent(oHeadCell, sDayTitle);
	}
}

function OlvDayTableControl_updateSelectedDay(dateNewValue)
{
	var bGenerateDayCells = true;
	if (!dateNewValue)
		dateNewValue = this.getValue();

	if (dateNewValue)
	{
		if ((this.m_nYearDisplayed == dateNewValue.getFullYear()) &&
			(this.m_nMonthDisplayed == dateNewValue.getMonth()) )
		{
			// Change selection
			if (this.m_nDayActive == dateNewValue.getDate())
				return;	// Nothing to do

			bGenerateDayCells = false;
		} // {if} Only selected day has been changed
	}
	else if (this.m_nYearDisplayed != null && this.m_nMonthDisplayed != null)
		bGenerateDayCells = false;

	if (!bGenerateDayCells)
	{
		// Remove CSS class from previously selected cell
		if (this.m_nDayActive != null)
		{
			var datePrevSelect = new Date(this.m_nYearDisplayed, this.m_nMonthDisplayed, this.m_nDayActive);
			this.selectDateCell(datePrevSelect, false);
		}

		// Add CSS class to selected cell
		if (dateNewValue)
		{
			this.m_nDayActive = dateNewValue.getDate();
			this.selectDateCell(dateNewValue, true);
		}
		else
			this.m_nDayActive = null;
	}
	else
		this.generateDayCells(dateNewValue.getFullYear(), dateNewValue.getMonth(), dateNewValue.getDate());

}

function OlvDayTableControl_getDateCell(dateValue)
{
	if (this.m_nYearDisplayed == null || this.m_nMonthDisplayed == null)
		return null;

	var oTableElem = this.getTableElement();
	if (!oTableElem)
		return;

	// Compute number of days from other monthes to be displayed
	var dateMonthStart = new Date(this.m_nYearDisplayed, this.m_nMonthDisplayed, 1);
	var nFirstDayOfWeek = this.getFirstDayOfWeek();
	var nNumOfDaysInMonth = dateMonthStart.getDaysInMonth();
	var nMonthFirstDayOfWeek = dateMonthStart.getDay();

	var nFirstMonthCell = (	nMonthFirstDayOfWeek >= nFirstDayOfWeek ?
							nMonthFirstDayOfWeek - nFirstDayOfWeek :
							nMonthFirstDayOfWeek + 7 - nFirstDayOfWeek);

	var nDateCell = nFirstMonthCell + dateValue.getDate() - 1;
	var iCol = nDateCell % 7;
	var iRow = 1 + (nDateCell - iCol) / 7;

	return DHTML_TableGetCell(oTableElem, iRow, iCol);
}

function OlvDayTableControl_selectDateCell(dateSelected, bSelect)
{
	var oDayCell = this.getDateCell(dateSelected);
	if (!oDayCell)
		return;
	var arrDaySpan = DHTML_getElementByTagName("span", oDayCell, 0);
	if (!arrDaySpan)
		return;
	
	var sSelectedCssClass = OlvCssClass_DayTableCellSelected;
	if (bSelect)
		DHTML_addCssClassToElem(arrDaySpan, sSelectedCssClass);
	else
		DHTML_removeCssClassFromElem(arrDaySpan, sSelectedCssClass);
}

function OlvDayTableControl_generateDayCells(nYear, nMonth, nDay)
{
	var oTableElem = this.getTableElement();
	if (!oTableElem)
		return;
	if (!nDay)
		nDay = null;
	
	// Compute number of days from other months to be displayed
	// 2007-07-19 B.G. Enter noon time to avoid bug of addDate() vs. Daylight saving.
	var dateMonthStart = new Date(nYear, nMonth, 1, 12);
	var nFirstDayOfWeek = this.getFirstDayOfWeek();
	var nNumOfDaysInMonth = dateMonthStart.getDaysInMonth();
	var nMonthFirstDayOfWeek = dateMonthStart.getDay();

	var nFirstMonthCell = (	nMonthFirstDayOfWeek >= nFirstDayOfWeek ?
							nMonthFirstDayOfWeek - nFirstDayOfWeek :
							nMonthFirstDayOfWeek + 7 - nFirstDayOfWeek);
	var nLastMonthCell = nFirstMonthCell + nNumOfDaysInMonth;

	var dateCur = new Date(dateMonthStart);
	dateCur.addDate(0, 0, -nFirstMonthCell);

	var bDisplayOtherMonthDays = this.getDisplayOtherMonthDays();
	var sOtherMonthDayCssClass = OlvCssClass_DayTableOtherMonthCell;

	var sEnabledDayCssClass = OlvCssClass_DayTableCellEnabled;
	var sRestrictedDayCssClass = OlvCssClass_DayTableCellRestricted;
	var sDisabledDayCssClass = OlvCssClass_DayTableCellDisabled;

	var sSelectedDayCssClass = OlvCssClass_DayTableCellSelected;
	var sDayTextFormat = this.getDayTextFormat();
	var sDayTooltipFormat = this.getDayTooltipFormat();
	if (sDayTooltipFormat == "")
		sDayTooltipFormat = null;

	for (var iRow=1; iRow<7; ++iRow)
	{
		for (var iCol=0; iCol<7; ++iCol)
		{
			var oDayCell = DHTML_TableGetCell(oTableElem, iRow, iCol);
			var bOtherMonth = (dateCur.getMonth() != nMonth);
			var dateCellValue = null;
			var sCellText = "";
			var bDateEnabled = false;
			var bDateRestricted = false;
			var bDateSelected = (!bOtherMonth && (nDay == dateCur.getDate()));

			if (!bOtherMonth || bDisplayOtherMonthDays)
			{
				dateCellValue = new Date(dateCur);
				bDateEnabled = this.isDateEnabled(dateCellValue);
				bDateRestricted = this.isDateRestricted(dateCellValue);
				sCellText = dateCellValue.formatDateTimeString(sDayTextFormat);
			}
			else
				sCellText = "&nbsp;";
			
			oDayCell.m_dateValue = dateCellValue;

			// Update "this month" / "other month" CSS class
			if (bOtherMonth)
				DHTML_addCssClassToElem(oDayCell, sOtherMonthDayCssClass);
			else
				DHTML_removeCssClassFromElem(oDayCell, sOtherMonthDayCssClass);

			// Add / remove tooltips
			if (sDayTooltipFormat && dateCellValue)
				oDayCell.title = dateCellValue.formatDateTimeString(sDayTooltipFormat);
			else
				oDayCell.title = "";

			// Paste cell text
			var sCellHtml = "<span class='";
			sCellHtml += (bDateEnabled ? (bDateRestricted ? sRestrictedDayCssClass : sEnabledDayCssClass) : sDisabledDayCssClass);
			if (bDateSelected)
				sCellHtml += " " + sSelectedDayCssClass;
			sCellHtml += "'>";
			sCellHtml += sCellText;
			sCellHtml += "</span>";
			
			DHTML_pasteHtmlContent(oDayCell, sCellHtml);
			//dateCur.nextDay();  There is a bug in nextDay() - it adds 24 hours and ignores daylight saving shift
			//                      creating double date in October.
			dateCur.setDate(dateCur.getDate() + 1);
		}
	}
	
	this.m_nYearDisplayed = nYear;
	this.m_nMonthDisplayed = nMonth;
	this.m_nDayActive = nDay;
}
