(function(){
var L = YAHOO.lang, Dom = YAHOO.util.Dom, Evt = YAHOO.util.Event, U = ZC.Util, _GT = U.GetText;

var oDTWidget = ZC.Core.Widget.Create('DateTime');
oDTWidget.prototype._FindElements = function()
{
	this._aDatePartEls = {};
	this._elInput = [];
	var sID = this._WidgetNameToID();

	var el = Dom.get(sID);
	if (el)
		this._elInput.push(el);

	for (var sPart in this.aDef.TimePartSet)
	{
		if (L.hasOwnProperty(this.aDef.TimePartSet, sPart) && this.aDef.TimePartSet[sPart])
		{
			el = Dom.get(sID + '_' + sPart.toLowerCase());
			if (el)
			{
				this._aDatePartEls[sPart] = el
				this._elInput.push(this._aDatePartEls[sPart]);
			}
		}
	}

	// check for a placeholder element for our calendar button
	this._elButtonHolder = Dom.get(sID + '-calbutton');

	if (this._elInput.length == 0)
	{
		this.aDef.SuppressCalendar = true;
		this._elInput = undefined;
	}

	this._FindContainers();

}
oDTWidget.prototype.SetupYUICalendar = function()
{
	var sCaption = this.GetCaption();
	if (!sCaption && this.oParent)
		sCaption = this.oParent.GetCaption();
	if (!sCaption)
		sCaption = 'Calendar';

	var elCalendarContainer = document.createElement('div');
	this.oYUICalendar = new YAHOO.widget.Calendar(elCalendarContainer, { iframe: false, navigator: true });

	this.oCalDialog = ZC.JSManager.CreateDialog(sCaption, elCalendarContainer, document.body,
	{
		width: "16em",
		context: [this.elCalButton, "tl", "bl"],
		buttons: [ {text: _GT("Close"), handler: { fn: function() { this.HideCalendar() }, scope: this, correctScope: true }}]
	});

	// tell the dialog to update when the calender changes
	var fnRenderEventHandler = function()
	{
		this.oCalDialog.fireEvent("changeContent");
	}
	this.oYUICalendar.renderEvent.subscribe(fnRenderEventHandler, this, true);
	this.oYUICalendar.selectEvent.subscribe(this.SelectionChangeHandler, this, true);

	this._CheckMinWidgets();
	this._CheckMaxWidgets();
}

oDTWidget.prototype.CustomSetupStart = function()
{
	var aTimePartSet = this.aDef.TimePartSet,
		fnCalButtonClickHandler,
		aHelp;

	if (this.aDef.SuppressCalendar || this.IsReadOnly()
		|| !aTimePartSet.Year || !aTimePartSet.Month || !aTimePartSet.Day)
		return true;

	this.elCalButton = document.createElement('input');
	this.elCalButton.type = 'button';
	this.elCalButton.className = 'opencalendar';
	this.elCalButton.value = _GT('Calendar');
	this.elCalButton.title = _GT('Open a calendar for selecting a date');

	if (this._elButtonHolder)
	{
		this._elButtonHolder.parentNode.replaceChild(this.elCalButton, this._elButtonHolder);
	}
	else
	{
		aHelp = Dom.getElementsByClassName('newhelp', 'img', this.elContainer);
		if (aHelp.length)
			Dom.insertBefore(this.elCalButton, aHelp[0]);
		else if (this._aDatePartEls.Year)
			this._aDatePartEls.Year.parentNode.appendChild(this.elCalButton);
	}

	fnCalButtonClickHandler = function(oEvent)
   	{
		Evt.stopEvent(oEvent);
		if (!L.isUndefined(this.oCalDialog) && this.oCalDialog.cfg.getProperty('visible'))
			this.HideCalendar();
		else
			this.ShowCalendar();
	}

	Evt.on(this.elCalButton, "click", fnCalButtonClickHandler, this, true);

	Evt.on(this._elInput, "change", this._SelectsToCalendar, this, true);

	return true;
}

oDTWidget.prototype.Destruct = function()
{
	oDTWidget.superclass.Destruct.apply(this, arguments);

	this._aDatePartEls = {};
	this._elButtonHolder = null;
	this.oYUICalendar = null;
	this.oCalDialog = null;
	this.elCalButton = null;
	this._aMinWidgets = [];
	this._aMaxWidgets = [];
}

// updates the calendar with the value in the select boxes
oDTWidget.prototype._SelectsToCalendar = function()
{
	if (L.isUndefined(this.oYUICalendar))
		return;

	this.oYUICalendar.select(this.GetValue());
	this.oYUICalendar.cfg.setProperty('pagedate', this.GetValue());
	this.oYUICalendar.render();
}

oDTWidget.prototype.ShowCalendar = function()
{
	if (L.isUndefined(this.oYUICalendar))
		this.SetupYUICalendar();

	this._SelectsToCalendar();
	this.oCalDialog.show();
	if (YAHOO.env.ua.opera && document.documentElement) {
		// Opera needs to force a repaint
		document.documentElement.className += "";
	}
}
oDTWidget.prototype.HideCalendar = function()
{
	this.oCalDialog.hide();
	this._FireEventHandlers('blur');
}

oDTWidget.prototype.CustomSetupEnd = function()
{
	// get min/max year from select box, use as default min/max date
	var iStartYear = Infinity, iStopYear = -Infinity;

	if (!L.isUndefined(this._aDatePartEls.Year))
	{
		U.ForEach(this._aDatePartEls.Year.options, function(elOption) { 
			var sValue = elOption.value, iValue;
			if (sValue != 'Null')
			{
				iValue = new Number(sValue);
				iStartYear = Math.min(iStartYear, iValue);
				iStopYear = Math.max(iStopYear, iValue);
			}
		});
		this.SetAttrib('StartYear', iStartYear);
		this.SetAttrib('StopYear', iStopYear);
	}

	this._aMinWidgets = [];
	this._aMaxWidgets = [];

	/*
	 * Special handling for the CompareTo validator.
	 * We can set min/max selection ranges on the calendar widgets based on the constraints of that validator.
	 */
	if (this.HasValidator('CompareTo'))
	{
		// Do this bit from ManagerInit - once all widgets are ready
		ZC.JSManager.GetEvent('ManagerInit').subscribe(function()
		{
			if (this.AttribIsset('GreaterThanField'))
			{
				var aWidgets = this.GetAttrib('GreaterThanField');
				if (L.isString(aWidgets))
					aWidgets = [aWidgets];
				aWidgets = U.Map(aWidgets, ZC.JSManager.GetWidget, ZC.JSManager);

				this.AddLowerBoundWidget(aWidgets, false);
				U.ForEach(aWidgets, function(oWidget) { 
					if (!L.isUndefined(oWidget) && oWidget.AddUpperBoundWidget) oWidget.AddUpperBoundWidget(this, true); 
				}, this);
			}
			if (this.AttribIsset('GreaterThanOrEqualField'))
			{
				var aWidgets = this.GetAttrib('GreaterThanOrEqualField');
				if (L.isString(aWidgets))
					aWidgets = [aWidgets];
				aWidgets = U.Map(aWidgets, ZC.JSManager.GetWidget, ZC.JSManager);

				this.AddLowerBoundWidget(aWidgets, true);
				U.ForEach(aWidgets, function(oWidget) { 
					if (!L.isUndefined(oWidget) && oWidget.AddUpperBoundWidget) oWidget.AddUpperBoundWidget(this, true); 
				}, this);
			}
			if (this.AttribIsset('LessThanField'))
			{
				var aWidgets = this.GetAttrib('LessThanField');
				if (L.isString(aWidgets))
					aWidgets = [aWidgets];
				aWidgets = U.Map(aWidgets, ZC.JSManager.GetWidget, ZC.JSManager);

				this.AddUpperBoundWidget(aWidgets, false);
				U.ForEach(aWidgets, function(oWidget) { 
					if (!L.isUndefined(oWidget) && oWidget.AddLowerBoundWidget) oWidget.AddLowerBoundWidget(this, true); 
				}, this);
			}
			if (this.AttribIsset('LessThanOrEqualField'))
			{
				var aWidgets = this.GetAttrib('LessThanOrEqualField');
				if (L.isString(aWidgets))
					aWidgets = [aWidgets];
				aWidgets = U.Map(aWidgets, ZC.JSManager.GetWidget, ZC.JSManager);

				this.AddUpperBoundWidget(aWidgets, true);
				U.ForEach(aWidgets, function(oWidget) { 
					if (!L.isUndefined(oWidget) && oWidget.AddLowerBoundWidget) oWidget.AddLowerBoundWidget(this, true); 
				}, this);
			}
		}, this, true);
	}

	return true;
}
/**
 * Checks all _aMinWidgets for the highest value, then sets the lower bound on the calendar.
 * @access private
 */
oDTWidget.prototype._CheckMinWidgets = function(event, oWidget)
{
	if (!this.oYUICalendar)
		return;

	var oMinDate;
   	if (this.AttribIsset('StartYear'))
	{
 		oMinDate = new Date(this.GetAttrib('StartYear'),0,1);
	}
	U.ForEach(this._aMinWidgets, function(aMinWidget)
	{
		var oValue = aMinWidget.oWidget.GetValue();
		if (!oValue)
			return;
		if (!aMinWidget.bInclusive)
			oValue.setUTCDate(oValue.getUTCDate() + 1);
		if (L.isUndefined(oMinDate) || oValue > oMinDate)
			oMinDate = oValue;
	});
	if (oMinDate)
	{
		this.oYUICalendar.cfg.setProperty('mindate', oMinDate);
		this.oYUICalendar.render();
	}
}
/**
 * Checks all _aMaxWidgets for the lowest value, then sets the upper bound on the calendar.
 * @access private
 */
oDTWidget.prototype._CheckMaxWidgets = function(event, oWidget)
{
	if (!this.oYUICalendar)
		return;

	var oMaxDate;
	if (this.AttribIsset('StopYear'))
	{	
		oMaxDate = new Date(this.GetAttrib('StopYear'),11,31);
	}
	U.ForEach(this._aMaxWidgets, function(aMaxWidget)
	{
		var oValue = aMaxWidget.oWidget.GetValue();
		if (!oValue)
			return;
		if (!aMaxWidget.bInclusive)
			oValue.setUTCDate(oValue.getUTCDate() - 1);
		if (L.isUndefined(oMaxDate) || oValue < oMaxDate)
			oMaxDate = oValue;
	});
	if (oMaxDate)
	{
		this.oYUICalendar.cfg.setProperty('maxdate', oMaxDate);
		this.oYUICalendar.render();
	}
}

/**
 * Add widget(s) to the list of "lower bound" widgets. The minimum selectable
 * date on the calendar will be set to the highest value contained in the list
 * of widgets plus one day.
 *
 * @param {Object / Array} aWidgets Widget or array of widgets
 * @param {boolean} bInclusive If true, then the widget value will be included in the lower bound
 */
oDTWidget.prototype.AddLowerBoundWidget = function(aWidgets, bInclusive)
{
	if (!L.isArray(aWidgets))
		aWidgets = [aWidgets];

	U.ForEach(aWidgets, function(oWidget)
	{
		if (L.isUndefined(oWidget))
			return;

		this._aMinWidgets.push({oWidget: oWidget, bInclusive: bInclusive});
		oWidget.AddEvent(this._CheckMinWidgets, 'change', this);
	}, this);

	this._CheckMinWidgets.call(this);
}

/**
 * Add widget(s) to the list of "upper bound" widgets. The maximum selectable
 * date on the calendar will be set to the lowest value contained in the list
 * of widgets minus one day.
 *
 * @param {Object / Array} aWidgets Widget or array of widgets
 * @param {boolean} bInclusive If true, then the widget value will be included in the lower bound
 */
oDTWidget.prototype.AddUpperBoundWidget = function(aWidgets, bInclusive)
{
	if (!L.isArray(aWidgets))
		aWidgets = [aWidgets];

	U.ForEach(aWidgets, function(oWidget)
	{
		if (L.isUndefined(oWidget))
			return;

		this._aMaxWidgets.push({oWidget: oWidget, bInclusive: bInclusive});
		oWidget.AddEvent(this._CheckMaxWidgets, 'change', this);
	}, this);

	this._CheckMaxWidgets.call(this);
}

oDTWidget.prototype.SelectionChangeHandler = function(sEventName, aSelectedDates)
{
	if (this.bChangingSelection)
		return;

	var aSelectedDateParts = aSelectedDates[0][0];
	this._aDatePartEls.Year.value = aSelectedDateParts[0];
	this._aDatePartEls.Month.value = aSelectedDateParts[1];
	this._aDatePartEls.Day.value = aSelectedDateParts[2];

	this.bChangingSelection = true;
	this._FireEventHandlers('change');
	this.bChangingSelection = false;
}

oDTWidget.prototype.Enable = function(bEnable, sEnableClass, sDisableClass)
{
	oDTWidget.superclass.Enable.call(this, bEnable, sEnableClass, sDisableClass);
	if (!this.aDef.SuppressCalendar)
	{
		this.elCalButton.disabled = !bEnable;
		Dom[bEnable ? 'removeClass' : 'addClass'](this.elCalButton, 'disabled');
	}
}

// Pretty basic at the moment, YUI does have some date formatting code though:
// http://yuiblog.com/blog/2009/02/11/date-formatting-pt1-2/
oDTWidget.prototype.GetTextValue = function(aTimePartsOverride)
{
	aTimePartsOverride = aTimePartsOverride || this.aDef.TimePartSet;
	var bDate = (aTimePartsOverride.Year && aTimePartsOverride.Month && aTimePartsOverride.Day);
	var bTime = (aTimePartsOverride.Hour && aTimePartsOverride.Minute);

	if (bDate && !bTime)
	{
		return this.GetValue().toLocaleDateString();
	}
	else if (bTime && !bDate)
	{
		return this.GetValue().toLocaleDateString();
	}
	else
	{
		return this.GetValue().toLocaleString();
	}
}


/**
 * Returns the value of this DateTime widget as a Date object. Any fields not
 * present in the date widget will be set to the current date/time.
 *
 * @return {Date} value of the widget
 */
oDTWidget.prototype.GetValue = function()
{
	var oDate = new Date(0); // start off with zero for everything, so that Date fields have zeroed time & vice-versa

	for (var sPart in this.aDef.TimePartSet)
	{
		if (L.hasOwnProperty(this.aDef.TimePartSet, sPart) && this.aDef.TimePartSet[sPart])
		{
			if (U.InArray(sPart, ['Year', 'Month', 'Day', 'Hour', 'Minute', 'Second']) && L.isUndefined(this._aDatePartEls[sPart]))
				return undefined;

			switch(sPart)
			{
				case 'Year':
					if (this._aDatePartEls.Year.value == 'Null')
						return undefined;
					oDate.setFullYear(this._aDatePartEls.Year.value);
					break;
				case 'Month':
					if (this._aDatePartEls.Month.value == 'Null')
						return undefined;
					oDate.setMonth(this._aDatePartEls.Month.value - 1);
					break
				case 'Day':
					if (this._aDatePartEls.Day.value == 'Null')
						return undefined;
					oDate.setDate(this._aDatePartEls.Day.value);
					break;
				case 'Hour':
					if (this._aDatePartEls.Hour.value == 'Null')
						return undefined;

					if (this.aDef.TimePartSet.AmPm)
						oDate.setHours(this._aDatePartEls.Hour.value + (12 * this._aDatePartEls.AmPm.value));
					else
						oDate.setHours(this._aDatePartEls.Hour.value);
					break;
				case 'Minute':
					if (this._aDatePartEls.Minute.value == 'Null')
						return undefined;
					oDate.setMinutes(this._aDatePartEls.Minute.value);
					break;
				case 'Second':
					if (this._aDatePartEls.Second.value == 'Null')
						return undefined;

					oDate.setSeconds(this._aDatePartEls.Second.value);
					break;
			}
		}
	}

	return oDate;
}

/**
 * Sets the value of this DateTime widget from a Date object
 *
 * @param {Date/String/Integer} mDate value to set - can be a Date object, a date string or an integer representing the number of seconds since 01/01/1970
 * @param {Object} aTimePartsOverride override aTimeParts - if provided, then only the time parts listed in this object are set (e.g. { "Year":true, "Month":true, "Day":true } only sets the date part)
 */
oDTWidget.prototype.SetValue = function(mDate, aTimePartsOverride, bFromEvent)
{
	aTimePartsOverride = aTimePartsOverride || this.aDef.TimePartSet;
	if (mDate == null)
		return this.Clear(aTimePartsOverride);
	if (L.isString(mDate))
		oDate = new Date(mDate);
	else if (L.isNumber(mDate))
		oDate = new Date(mDate * 1000); // JS uses milliseconds, but we use seconds everywhere, so it makes sense to accept seconds here.
	else
		oDate = mDate;

	for (var sPart in aTimePartsOverride)
	{
		if (L.hasOwnProperty(aTimePartsOverride, sPart) && aTimePartsOverride[sPart])
		{
			switch(sPart)
			{
				case 'Year':
					this._aDatePartEls.Year.value = oDate.getFullYear();
					break;
				case 'Month':
					this._aDatePartEls.Month.value = oDate.getMonth() + 1;
					break
				case 'Day':
					this._aDatePartEls.Day.value = oDate.getDate();
					break;
				case 'AmPm':
					this._aDatePartEls.AmPm.value = (oDate.getHours() >= 12) ? 1 : 0;
					break;
				case 'Hour':
					if (this.aDef.TimePartSet.AmPm)
						this._aDatePartEls.Hour.value = (oDate.getHours() % 12);
					else
						this._aDatePartEls.Hour.value = oDate.getHours();
					break;
				case 'Minute':
					this._aDatePartEls.Minute.value = oDate.getMinutes();
					break;
				case 'Second':
					this._aDatePartEls.Second.value = oDate.getSeconds();
					break;
			}
		}
	}

	if (!bFromEvent && this.oYUICalendar)
		this.oYUICalendar.select(oDate);

	this._FireEventHandlers('change');
}

/**
 * Clears this DateTime widget
 *
 * @param {Object} aTimePartsOverride override aTimeParts - if provided, then only the time parts listed in this object are set (e.g. { "Year":true, "Month":true, "Day":true } only sets the date part)
 */
oDTWidget.prototype.Clear = function(aTimePartsOverride)
{
	aTimePartsOverride = aTimePartsOverride || this.aDef.TimePartSet;
	for (var sPart in aTimePartsOverride)
	{
		if (L.hasOwnProperty(aTimePartsOverride, sPart) && aTimePartsOverride[sPart])
		{
			this._aDatePartEls[sPart].value = 'Null';
		}
	}
	this._FireEventHandlers('change');
}

oDTWidget.prototype._WireUpEvent = function(sEvent)
{
	if (sEvent == 'blur')
	{
		Evt.on(this._elInput, 'blur', function()
		{
			// suppresses the blur event if the user has just gone from one of the widget's selects to another
			var i, iMax, aArgs = ['blur'];
			for (i = 1, iMax = arguments.length; i < iMax; i++)
			{
				aArgs.push(arguments[i]);
			}

			L.later(0, this, function() 
			{
				if (!U.Some(this._elInput, function(el) { return (el == document.activeElement); }))
				{
					this._EventDispatcher.apply(this, aArgs);
				}
			});
		}, this, true);
	}
	else
	{
		oDTWidget.superclass._WireUpEvent.apply(this, arguments);
	}
}

ZC.Core.Widget.Create('Date', 'Core', oDTWidget);
ZC.Core.Widget.Create('Time', 'Core', oDTWidget);

})();

