//------------------------------------------------------------------------

// 01022003
var AllNumericRE = /^\s*\d{1,8}\s*$/;

// 01/02/2003
var FormattedRE = /^\s*(\d+)[\-\/ .,]+(\d+)([\-\/ .,]+(\d+))?\s*$/;

// jan 2 2003
var AlphaMDY_RE = /^\s*([A-Za-z]{3,})\W*(\d+)(\W+(\d+))?\s*$/;

// 2 jan 2003
var AlphaDMY_RE = /^\s*(\d+)\W*([A-Za-z]{3,})(\W*(\d+))?\s*$/;

// XXX You may note that we only use captures 1, 2 and 4; not 3.
// XXX Don't use the (?:...) non-capturing parentheses,
// XXX which aren't supported until JavaScript 1.5 (IE 5.5, JScript 5.5).
// XXX We have clients (even IE 6 clients) still running JScript 5.1.

var DisplayFormat = 'YMD';	// 'MDY'

//------------------------------------------------------------------------

function val_date(f, vmin, vmax, wmin, wmax)
{
	if (f.value.length < 1)
		return true;

	var vd = new ValDate(f.value, vmin, vmax, wmin, wmax);

	if (!vd.validate())
	{
		f.select();

		if (vd.dialog() == false)
			return false;

	}

	f.value = vd.formatted;
	return true;
}

//------------------------------------------------------------------------

function ValDate(raw, vmin, vmax, wmin, wmax)
{
	// Today
	var today = new Date();
	this.tmon = today.getMonth() + 1;
	this.tday = today.getDate();
	this.tyear = today.getYear();
	if (this.tyear < 1900)
		this.tyear += 1900;	// netscape

	this.vmin = vmin;
	this.vmax = vmax;
	this.wmin = wmin;
	this.wmax = wmax;

	this.vmin_ms = cv_valid_date_to_ms(vmin, today);
	this.vmax_ms = cv_valid_date_to_ms(vmax, today);
	this.wmin_ms = cv_warning_date_to_ms(wmin, today);
	this.wmax_ms = cv_warning_date_to_ms(wmax, today);

	this.raw = raw;
	this.error = '';
	this.warning = '';
	this.formatted = '';

	this.validate = validate;
	this.all_numeric_date = all_numeric_date;
	this.format = format;
	this.minmsg = minmsg;
	this.maxmsg = maxmsg;
	this.dialog = vd_dialog;
	this.within_valid_range = within_valid_range;
	this.within_warning_range = within_warning_range;
}

//------------------------------------------------------------------------

function cv_valid_date_to_ms(s, today)
{
	if (typeof(s) == 'undefined' || s == 'unbounded')
		return 0;

	var d = FormattedRE.test(s)
		? new Date(s)
		: new Date(today.getYear(), today.getMonth(),
			today.getDate() + parseInt(s, 10));

	return d.valueOf();
}

//------------------------------------------------------------------------

function cv_warning_date_to_ms(s, today)
{
	if (typeof(s) == 'undefined' || s == 'unbounded')
		return 0;

	var d = get_n_business_days_from_today(parseInt(s, 10), today);
	
	return d.valueOf();
}

//------------------------------------------------------------------------

ValDate.prototype.toString = function()
{
	var s =
		'today: mon [' + this.tmon +
		'] day [' + this.tday +
		'] year [' + this.tyear + ']\n';

	s += 'vmin [' + this.vmin + '] vmin_ms [' + this.vmin_ms + ']';
	if (this.vmin_ms) s += '=[' + Date(this.vmin_ms) + ']'; s += '\n';

	s += 'vmax [' + this.vmax + '] vmax_ms [' + this.vmax_ms + ']';
	if (this.vmax_ms) s += '=[' + Date(this.vmax_ms) + ']'; s += '\n';

	s += 'wmin [' + this.wmin + '] wmin_ms [' + this.wmin_ms + ']';
	if (this.wmin_ms) s += '=[' + Date(this.wmin_ms) + ']'; s += '\n';

	s += 'wmax [' + this.wmax + '] wmax_ms [' + this.wmax_ms + ']';
	if (this.wmax_ms) s += '=[' + Date(this.wmax_ms) + ']'; s += '\n';

	s +=	'raw [' + this.raw + ']\n' +
		'error [' + this.error + ']\n' +
		'warning [' + this.warning + ']\n' +
		'formatted [' + this.formatted + ']\n';

	return s;
};

//------------------------------------------------------------------------

function validate()
{
	if (this.raw == 't')	// Today
	{
		return this.format('', '', '');
	}

	if (this.raw == 'm')	// 1st of current month
	{
		return this.format('', '1', '');
	}

	if (this.raw == 'h')	// Last of current month
	{
		return this.format('', '99', '');
	}

	if (AllNumericRE.test(this.raw))
	{
		return this.all_numeric_date();
	}

	var a = this.raw.match(FormattedRE);
	if (a) return this.format(a[1], a[2], a[4]);

	a = this.raw.match(AlphaMDY_RE);
	if (a)
	{
		var mon = name_to_month(a[1]);
		if (!mon)
		{
			this.error = name + ' is not a valid month name';
			return false;
		}
		return this.format(mon, a[2], a[4]);
	}

	a = this.raw.match(AlphaDMY_RE);
	if (a)
	{
		var mon = name_to_month(a[2]);
		if (!mon)
		{
			this.error = name + ' is not a valid month name';
			return false;
		}
		return this.format(mon, a[1], a[4]);
	}

	return false;
}

//------------------------------------------------------------------------

function all_numeric_date()
{
	var s = this.raw;
	var len = s.length;

	if (len <= 2)
	{
		// 0
		// d
		// or
		// 01
		// dd

		return this.format('', s, '');
	}
	else if (len == 3)
	{
		// 012
		// mdd

		return this.format(s.substr(0, 1), s.substr(1), '');
	}
	else if (len == 4)
	{
		// 0123
		// mmdd

		return this.format(s.substr(0, 2), s.substr(2), '');
	}
	else if (len == 5 || len == 7)
	{
		// 01234
		// mddyy
		// or
		// 0123456
		// mddyyyy

		return this.format(s.substr(0, 1), s.substr(1, 2), s.substr(3));
	}
//	else if (s == '00000000')
//	{
//		this.formatted = '00/00/0000';
//		return true;
//	}
//	else if (s == '11111111')
//	{
//		this.formatted = '11/11/1111';
//		return true;
//	}

	// len == 6 || len == 8

	// 012345
	// mmddyy
	// or
	// 01234567
	// mmddyyyy

	return this.format(s.substr(0, 2), s.substr(2, 2), s.substr(4));
}

//------------------------------------------------------------------------

function format(m, d, y)
{
	// Input values.

	var mon = parseInt(m, 10);
	var day = parseInt(d, 10);
	var year = parseInt(y, 10);

	// See if YYYYMMDD

	if (mon > 12 && day <= 12 && year <= 31)
	{
		// FormattedRE 1961-08-04 would be mon=1961 day=8 year=4
		syear = year;
		year = mon;
		mon = day;
		day = syear;
	}
	else if (mon == 19)
	{
		// AllNumericRE 19610804 would be mon=19 day=61 year=804
		syear = year;
		year = (mon * 100) + day;
		mon = (syear / 100) | 0;	// Clever cast to int
		day = syear % 100;
	}

	// Default missing values with today's values.

	if (isNaN(mon))		mon = this.tmon;
	if (isNaN(day))		day = this.tday;
	if (isNaN(year))	year = this.tyear;

	// Default century.

	     if (year <= 30) year += 2000;
	else if (year < 100) year += 1900;

	// Last day of the month.

	if (day == 99) day = ndays_in_month(mon, year);

	// Reformat the input, even if invalid, so the user can
	// see how we interpreted it.

	this.formatted = date_string(mon, day, year);

	// Is it a valid date?

	if (mon < 1 || mon > 12 || day < 1 ||
		day > ndays_in_month(mon, year) || year < 1900)
	{
		this.error = this.formatted + ' is not a valid date';
		return false;
	}

	// Convert to object.

	var date = new Date(year, mon - 1, day);
	var ms = date.valueOf();

	// Is it within the low end?

	if (this.vmin_ms && ms < this.vmin_ms)
	{
		this.error = this.minmsg();
		return false;
	}

	// Is it within high end?

	if (this.vmax_ms && ms > this.vmax_ms)
	{
		this.error = this.maxmsg();
		return false;
	}

	// Is it within the warning range?

	if (this.within_warning_range(ms))
	{
		this.warning = this.formatted + ' is within ' +
				this.wmax + ' business day';

		if (this.wmax > 1)
			this.warning += 's';

		this.warning += '.';
		return false;
	}

	return true;
}

//------------------------------------------------------------------------

function within_valid_range(ms)
{
	return	(!this.vmin_ms || ms >= this.vmin_ms)
	&&	(!this.vmax_ms || ms <= this.vmax_ms);
}

//------------------------------------------------------------------------

function within_warning_range(ms)
{
	return	((this.wmin_ms && ms >= this.wmin_ms) &&
			(!this.wmax_ms || ms <= this.wmax_ms))

	||	((this.wmax_ms && ms <= this.wmax_ms) && !this.wmin_ms)
}

//------------------------------------------------------------------------

function minmsg()
{
	if (typeof(this.vmin) == 'string' && this.vmin.length == 10)
		return 'This date cannot be before ' + this.vmin;

	if (this.vmin <= -2)
		return 'This date cannot be before ' + -this.vmin + ' days ago.';

	if (this.vmin == -1)
		return 'This cannot be before yesterday.';

	if (this.vmin == 0)
		return 'This date cannot be in the past.';

	if (this.vmin == 1)
		return 'This date must be after today.';

	if (this.vmin == 2)
		return 'This date must be after tomorrow.';

	return 'This date cannot be before ' + this.vmin + ' days from today.';
}

//------------------------------------------------------------------------

function maxmsg()
{
	if (typeof(this.vmax) == 'string' && this.vmax.length == 10)
		return 'This date cannot be after ' + this.vmax;

	if (this.vmax <= -3)
		return 'This date cannot be after ' + -this.vmax + ' days ago.';

	if (this.vmax == -2)
		return 'This date must be before yesterday.';

	if (this.vmax == -1)
		return 'This date must be in the past.';

	if (this.vmax == 0)
		return 'This date cannot be after today.';

	if (this.vmax == 1)
		return 'This date cannot be after tomorrow.';

	return 'This date cannot be after ' + this.vmax + ' days from today.';
}

//------------------------------------------------------------------------

function vd_dialog(w)
{
	var win = w ? w : window;

	if (this.warning)
	{
		if (win.confirm(this.warning))
			return true;
	}
	else
	{
		win.alert(this.error ? this.error : 'Invalid date');
	}

	return false;
}

//------------------------------------------------------------------------

var nd_in_m = new Array(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);

function ndays_in_month(mon, year)
{
	if (mon == 2 && is_leap(year))
		return 29;

	return nd_in_m[mon];
}

//------------------------------------------------------------------------

function is_leap(year)
{
	return ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0));
}

//------------------------------------------------------------------------

function date_string(mon, day, year)
{
	switch (DisplayFormat)
	{
	case 'MDY':
		var s = '';
		if (mon < 10) s += '0'; s += mon; s += '/';
		if (day < 10) s += '0'; s += day; s += '/';
		s += year;
		return s;

	case 'YMD':
		var s = year + '-';
		if (mon < 10) s += '0'; s += mon; s += '-';
		if (day < 10) s += '0'; s += day;
		return s;
	}

	alert('Invalid DisplayFormat in date.js');
}

//------------------------------------------------------------------------

var Month_Names = new Array(
	'JANUARY', 'FEBRUARY', 'MARCH', 'APRIL', 'MAY', 'JUNE',
	'JULY', 'AUGUST', 'SEPTEMBER', 'OCTOBER', 'NOVEMBER', 'DECEMBER');

//------------------------------------------------------------------------

function month_to_name(mon)
{
	return Month_Names[mon-1];
}

//------------------------------------------------------------------------

function name_to_month(name)
{
	var len = name.length;
	var s = name.toUpperCase();

	for (var i = 0 ; i < 12 ; i++)
		if (s == Month_Names[i].substr(0, len))
			return i + 1;

	return null;
}

//------------------------------------------------------------------------
// Stolen [and modified] from Yehuda Shiran, Ph.D.
// http://webreference.com/js/tips/010220.html

function get_n_business_days_from_today(n_business_days, today)
{
	var now = today ? today : new Date();
	var dow = now.getDay();
	var n_calendar_days = n_business_days;

	if (dow + n_business_days >= 6)
	{
		// deduct this-week days
		n_business_days -= 6 - dow;

		// count this coming weekend
		n_calendar_days += 2;

		// how many whole weeks?
		var n_weeks = Math.floor(n_business_days / 5);

		// two days per weekend per week
		n_calendar_days += n_weeks * 2;
	}

	// ms_per_day = (24 * 60 * 60 * 1000) = 86400000

	now.setTime(now.getTime() + (n_calendar_days * 86400000));

	// We're dealing with whole days, so remove the time component.
	// Besides, if we left it in, it would be wrong if we crossed
	// daylight savings.

	var date = new Date(now.getYear(), now.getMonth(), now.getDate());

	return date;
}

//------------------------------------------------------------------------

function calc_years(f, destid)
{
	var d = document.getElementById(destid);
	if (!d) return;

	var a = f.value.match(FormattedRE);
	var v;

	switch (DisplayFormat)
	{
	case 'MDY':
		v = (parseInt(a[4], 10) * 10000) + (parseInt(a[1], 10) * 100) + parseInt(a[2], 10);
		break;

	case 'YMD':
		v = (parseInt(a[1], 10) * 10000) + (parseInt(a[2], 10) * 100) + parseInt(a[4], 10);
		break;
	}

	var today = new Date();
	var tmon = today.getMonth() + 1;
	var tday = today.getDate();
	var tyear = today.getYear() + 1900;

	var t = (tyear * 10000) + (tmon * 100) + tday;

	d.innerHTML = (((t - v) / 10000) | 0) + '';	// Clever cast to int
}
