/*
 * Return the number of days in a given month. Keeping in mind leap years.
 */
function daysInMonth(month, year)
{
	if (month == 2 && (year % 4 == 0 && !(year % 100 == 0 && year % 400 != 0))) {
		return 29;
	}

	switch(month.toString()) {
		case '1':
		case '01': return 31;
		case '2':
		case '02': return 28;
		case '3':
		case '03': return 31;
		case '4':
		case '04': return 30;
		case '5':
		case '05': return 31;
		case '6':
		case '06': return 30;
		case '7':
		case '07': return 31;
		case '8':
		case '08': return 31;
		case '9':
		case '09': return 30;
		case '10': return 31;
		case '11': return 30;
		case '12': return 31;
	}
}

/*
 * Check if a given date is valid. Alert error message if it isn't.
 */
function isValidDate(day, month, year, text)
{
	var argv = isValidDate.arguments;
	var argc = isValidDate.arguments.length;
	if (argc < 4) {
		text = argv[1];
		var fields = splitDateFields(argv[0], argv[1]);
		if (!fields) {
			return false;
		}
		day = fields[0];
		month = fields[1];
		year = fields[2];
	}

	if (day == '' && month == '' && year == '') {
		alert("Please enter a date for " + text);	
		return false;
	}

	if (isNaN(day) || isNaN(month) || isNaN(year)) {
		alert("Please use only numbers in the date fields for "+text);
		return false;
	}

	if ((day < 1) || (day > daysInMonth(month, year))) {
		alert("Please specify a valid day for " + text);
		return false;
	}

	if ((month < 1) || (month > 12)) {
		alert("Please specify a month between 1 and 12 for " + text);
		return false;
	}

	if (year < 1000) {
		alert("Please use four digits for the year field for " + text);
	}

	return true;
}

/*
 * Check a date is in the future. Good for checking expiry times.
 */
function isFutureDate(day, month, year, text)
{
	var argv = isFutureDate.arguments;
	var argc = isFutureDate.arguments.length;
	if (argc < 4) {
		text = argv[1];
		var fields = splitDateFields(argv[0], argv[1]);
		if (!fields) {
			return false;
		}
		day = fields[0];
		month = fields[1];
		year = fields[2];
	}

	if (!isValidDate(day, month, year, text)) {
		return false;
	}

	var date = new Date(year, month - 1, day);
	var now = new Date();
	if (date < now) {
		alert("Please enter a date that is in the future for " + text);
		return false;
	}

	return true;
}

/*
 * Check a date is in the past. Good for checking date of birth.
 */
function isPastDate(day, month, year, text)
{
	var argv = isPastDate.arguments;
	var argc = isPastDate.arguments.length;
	if (argc < 4) {
		text = argv[1];
		var fields = splitDateFields(argv[0], argv[1]);
		if (!fields) {
			return false;
		}
		day = fields[0];
		month = fields[1];
		year = fields[2];
	}

	if (!isValidDate(day, month, year, text)) {
		return false;
	}

	var date = new Date(year, month - 1, day);
	var now = new Date();
	if (date > now) {
		alert("Please enter a date that is in the past for " + text);
		return false;
	}

	return true;
}

/*
 * Split up a date field from one string into 3 separate fields.
 */
function splitDateFields(val, text)
{
	var msg = "Please enter " + text + " in a valid format. E.g. DD/MM/YYYY";
	var reg = /^(\d\d?)[\-\/](\d\d?)[\-\/](\d\d\d\d)$/;

	if (!reg.test(val)) {
		alert(msg);
		return false;
	}

	var matches = reg.exec(val);
	var day = matches[1];
	var month = matches[2];
	var year = matches[3];

	if (day && month && year) {
		return new Array(day, month, year);
	}

	alert(msg);
	return false;
}

/*
 * Validate and format date string.
 * Requires "date_formatting".
 * If valid, returns [$day, $month, $year] else returns false.
 */
function date_format(e) {
	var str = e.value;
	str = date_formatting(str);
	if (str != false) {
		e.value = str[0];
		return [str[1], str[2], str[3]];
	}
}

/*
 * Validate and format date substring.
 * Used instead of "date_format" when the string also contains a time.
 * Requires "date_formatting".
 * If valid, returns [$day, $month, $year] else returns false.
 */
function date_format_substr(e) {
	var str = e.value;
	var datetime = str.split(/\s+/);
	str = date_formatting(datetime[0]);
	if (str != false) {
		var string = '';
		for(i = 1; i < datetime.length; i++) {
			string = string + ' ' + datetime[i];
		}
		datetime[1] ? e.value = str[0] + string : e.value = str[0];
		return [str[1], str[2], str[3]];
	}
}

/*
 * Actual date formatting and validation code.
 * Requires sprintf.
 * Used by both "date_format" and "date_format_substr".
 */
function date_formatting(str)
{
	function _process_date(day, month, year) {
		if (
			isNaN(day) || isNaN(month) || isNaN(year)
			|| day < 1 || day > daysInMonth(month,year)
			|| month < 1 || month > 12
			|| year < 1000 || year > 2100
		) {
			return false;
		}
		return [ sprintf('%02d/%02d/%04d', day, month, year), day, month, year ];
	}

	function _fix_year(year) {
		return year > '30' ? ('19' + year) : ('20' + year);
	}

	// dd/mm/yyyy
	if (str.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/)) {
		return _process_date(RegExp.$1, RegExp.$2, RegExp.$3);
	}
	// dd-mm-yyyy
	else if (str.match(/^(\d{1,2})-(\d{1,2})-(\d{4})$/)) {
		return _process_date(RegExp.$1, RegExp.$2, RegExp.$3);
	}
	// dd/mm/yy
	else if (str.match(/^(\d{1,2})\/(\d{1,2})\/(\d{2})$/)) {
		return _process_date(RegExp.$1, RegExp.$2, _fix_year(RegExp.$3));
	}
	// dd-mm-yy
	else if (str.match(/^(\d{1,2})-(\d{1,2})-(\d{2})$/)) {
		return _process_date(RegExp.$1, RegExp.$2, _fix_year(RegExp.$3));
	}
	// ddmmyy
	else if (str.match(/^(\d\d)(\d\d)(\d\d)$/)) {
		return _process_date(RegExp.$1, RegExp.$2, _fix_year(RegExp.$3));
	}
	// ddmmyyyy
	else if (str.match(/^(\d\d)(\d\d)(\d{4})$/)) {
		return _process_date(RegExp.$1, RegExp.$2, RegExp.$3);
	}
	// yyyy-mm-dd
	else if (str.match(/^(\d{4})-(\d\d)-(\d\d)$/)) {
		return _process_date(RegExp.$3, RegExp.$2, RegExp.$1);
	}
	return false;
}

/*
 * Validate and format time string.
 * Requires "time_formatting".
 * If valid, returns [$hours, $minutes, $seconds] else returns false.
 */
function time_format(e) {
	var str = e.value;
	str = time_formatting(str);
	if (str != false) {
		e.value = str[0];
		return [str[1], str[2], str[3]];
	}
}

/*
 * Validate and format time substring.
 * Used instead of "time_format" when the string also contains a date.
 * Requires "time_formatting".
 * If valid, returns [$hours, $minutes, $seconds] else returns false.
 */
function time_format_substr(e) {
	var str = e.value;
	var datetime = str.split(/\s+/);
	if (!datetime[1]) return false;
	str = time_formatting(datetime[1]);
	if (str != false) {
		e.value = datetime[0] + ' ' + str[0];
		return [str[1], str[2], str[3]];
	}
}

/*
 * Actual time formatting and validation code.
 * Requires sprintf.
 * Used by both "time_format" and "time_format_substr".
 */
function time_formatting(str)
{	
	function _process_time(hours, minutes, seconds) {
		if (
			isNaN(hours) || isNaN(minutes) || isNaN(seconds)
			|| hours < 0 || hours > 23
			|| minutes < 0 || minutes > 59
			|| seconds < 0 || seconds > 59
		) {
			return false;
		}
		return [ sprintf('%02d:%02d:%02d', hours, minutes, seconds), hours, minutes, seconds ];
	}

	// hh:mm:ss
	if (str.match(/^(\d{1,2}):(\d{2}):?(\d{0,2})$/)) {
		return _process_time(RegExp.$1, RegExp.$2, RegExp.$3);
	}
	// hh-mm-ss
	else if (str.match(/^(\d{1,2})-(\d{2})-?(\d{0,2})$/)) {
		return _process_time(RegExp.$1, RegExp.$2, RegExp.$3);
	}
	// hhmmss
	else if (str.match(/^(\d\d)(\d\d)(\d{0,2})$/)) {
		return _process_time(RegExp.$1, RegExp.$2, RegExp.$3);
	}
	return false;
}
