/** 
 * @fileOverview Form management/validation
 * @author Oliver Bishop / Tom McCourt
 * @version 0.0.1
 */

/**
 * Puts the cursor focus on the field that did not pass validation.
 *
 * @param {HTMLObject} field The field that did not validate.
 */
function highlightField(field) {
	field.focus();
	if (field.select) {
		field.select();
	}
}

/**
 * Raised when a field input fails validation. This calls highlightField and raises an alert.
 *
 * @see highlightField
 * @param {HTMLObject} field	The field that did not validate.
 * @param {String} msg			Error message to display.
 * @returns {Boolean}			Return FALSE to prevent form submission.
 */
function fieldError(field, msg) {
	if (field) {
		highlightField(field);
	}
	alert(msg);
	return false;
}

/**
 * Validation that checks for numbers.
 *
 * @returns {Boolean} Validation result.
 */
function hasNumbers(str) {
	var exp = new RegExp("[0-9]");
	return exp.test(str);
}

/**
 * Validation that checks at least one radio button being checked.
 *
 * @returns {Boolean} Validation result.
 */
function isRadiogroupChecked(radioGroup) {
	for (var i=0;i<radioGroup.length;i++) {
		if (radioGroup[i].checked) {
			return true;
		}
	}
	return false;
}

/**
 * Validation that checks that the value is a number (float).
 *
 * @returns {Boolean} Validation result.
 */
function isNumeric(str) {
	var num=parseFloat(str);
	return (!isNaN(num)||str=="");
}

/**
 * Validation that checks that the value is an integer without a decimal point.
 *
 * @returns {Boolean} Validation result.
 */
function isInteger(str) {
	var num=parseInt(str);
	return (str.indexOf(".")<0 && (!isNaN(num)||str==""));
}

/**
 * Validation that checks that the value is a phone number.
 *
 * @returns {Boolean} Validation result.
 */
function isPhoneNumber(str) {
	var exp = new RegExp("^[0-9 ]*$");
	return exp.test(str);
}

/**
 * Validation that checks that a number is within a range or values.
 *
 * @returns {Boolean} Validation result.
 */
function inRange(num, lower, upper) {
	if (isNaN(num)) {
		num=0;
	}
	return (num>=lower&&num<=upper);
}

/**
 * Validation that checks that the value only contains alpha characters.
 *
 * @returns {Boolean} Validation result.
 */
function isAlpha(str) {
	var exp = new RegExp("[^A-Za-z]");
	return !exp.test(str);
}

/**
 * Validation that checks  that the value only contains alphanumeric characters.
 *
 * @returns {Boolean} Validation result.
 */
function isAlphaAndNumeric(str) {
	var exp = new RegExp("[^A-Za-z0-9]");
	return !exp.test(str);
}

/**
 * Trims all whitespace from user input.
 *
 * @returns {String} The trimmed value.
 */
function trimSpaces(str) {
	return str.replace(/\s+/g, "");
}

/**
 * Trims whitespace from the start and end of a value.
 *
 * @returns {String} The trimmed value.
 */
function trimOutsideSpaces(str) {
	return str.replace(/^\s+|\s+$/g, "");
}

/**
 * Validation that that a value is in the correct sequence.
 *
 * @returns {Boolean} Validation result.
 */
function isValidSequence(str, validSequence) {
	// is this correct - need to test
	var exp = new RegExp("[^" + validSequence + "]");
	return !exp.test(str);
}

/**
 * Validation that checks the the value is a valid email.
 *
 * @returns {Boolean} Validation result.
 */
function isEmail(str) {
	str = trimOutsideSpaces(str.toLowerCase());
	var exp = new RegExp(/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[(2([0-4]\d|5[0-5])|1?\d{1,2})(\.(2([0-4]\d|5[0-5])|1?\d{1,2})){3} \])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/);
	return exp.test(str);
}

/**
 * Checks that a value has been selected from a drop down.
 *
 * @returns {Boolean} Validation result.
 */
function isValueSelected(field) {
	return (field.options[field.selectedIndex].value!="");
}

/**
 * Validates a postcode.
 *
 * @returns {Boolean} Validation result.
 */
function validatePostCode(field, combo, partial) {
	// drop out if not a UK postcode
	if(((combo) && combo.options[combo.options.selectedIndex].value!="United Kingdom")) {
		return true;
	}
	var str = trimSpaces(field.value.toUpperCase());
	
	// check if field filled
	if (str.length==0) {
		return fieldError(field, "Please enter a postcode.");
	}
	
	// check if its alphanumeric
	if (!isAlphaAndNumeric(str)) {
		return fieldError(field, "Please enter a valid UK postcode (containing both letters and numbers).");
	}
	
	// check postcode formatting
	var exp = (partial&&partial==true)?new RegExp("^[A-Z]{1,2}([0-9]{1,2}|[0-9][A-Z])($|[0-9][ABD-HJLNP-UW-Z]{2}$)"):new RegExp("^[A-Z]{1,2}([0-9]{1,2}|[0-9][A-Z])[0-9][ABD-HJLNP-UW-Z]{2}$");
	
	// need to disable this final validation because NS4 doesn't evalute regexp properly
	if (!exp.test(str)&&!document.layers) return fieldError(field, "Please enter a valid UK postcode.");
	else return true;
}

/**
 * Gets the number of days in a specified month.
 *
 * @returns {Integer} Number of dats in the year.
 */
function getDays(month, year) {
	var monthDays = new Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
	monthDays[1] = (year % 4 == 0 && year % 100 != 0 || year % 400 ==0)?monthDays[1]+1:monthDays[1];
	return monthDays[month-1];
}

/**
 * Validates a date of birth. 
 *
 * @returns {Boolean} Validation result.
 */
function validateDateOfBirth(day, month, year) {
	var dayStr = trimSpaces(day.value);
	var monthStr = trimSpaces(month.value);
	var yearStr = trimSpaces(year.value);
	var theDate = new Date();
	// check if fields filled
	if (dayStr.length==0||monthStr.length==0||yearStr.length==0) {
		return fieldError(day, "Please complete your full date of birth.");
	}
	// check if they are all individually valid
	if (!(isNumeric(yearStr)&&inRange(parseInt(yearStr), 1849, theDate.getFullYear()))) {
		return fieldError(year, "Please give a valid year.  It may not be before 1849 or after the present year.");
	}
	if (!(isNumeric(monthStr)&&inRange(parseInt(monthStr), 1, 12))) {
		return fieldError(month, "Please give a valid month (1 - 12).");
	}
	var daysInMonth = getDays(parseInt(monthStr), parseInt(yearStr));
	if (!(isNumeric(dayStr)&&inRange(parseInt(dayStr), 1, daysInMonth))) {
		return fieldError(day, "Please give a valid day.");
	}
	// check its not in the future
	var DOB = new Date(parseInt(yearStr), parseInt(monthStr) - 1, parseInt(dayStr));
	if (DOB>=theDate) {
		return fieldError(day, "Please give a date of birth that is not in the future.");
	}
	return true;
}
