/**
* validation.js - basic form input validation functions for JavaScript
*
* @version 1.00.10 2008-10-20
* @package validation
* @author Ross McKay <rmckay@webaware.com.au>
* @link http://www.webaware.com.au/
* @copyright copyright © 2007-2008 WebAware Pty Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*
* Full license: {@link http://www.webaware.com.au/free/license.htm}
*/

/**
* constructor, save handle to form and set error message string to empty string
*
* <p>NB:</p>
* <ul>
* <li>a string of only spaces / tabs is an empty string</li>
* <li>an empty email address is NOT an invalid email address!</li>
* <li>these validation functions only work if Javascript is enabled!</li>
* <li>must explicitly set the value on select options or won't work on Internet Explorer!</li>
* </ul>
*
* @constructor
* @param {HTMLForm} frm handle to HTML input form to be validated
*/
function ValidateForm(frm) {
	/**
	* handle to HTML input form to be validated
	* @type HTMLform
	*/
	this.frm = frm;
	/**
	* incrementally built string of error messages, separated by newline characters
	* @type String
	*/
	this.errmsg = "";
}

// ----- instance public methods -----

/**
* test whether any errors have been detected (through previous calls to methods below)
*
* @return {boolean}
*/
ValidateForm.prototype.hasErrors = function() {
	return (this.errmsg.length == 0) ? false : true;
}

/**
* show a pop-up dialogue window detailing errors
*/
ValidateForm.prototype.showErrors = function() {
	if (this.errmsg.length > 0)
		alert(this.errmsg);
}

/**
* return field name with underscored replaced by spaces,
* and PHP array names 'ArrayVariable[member]' => 'member'
*
* @return {String} the nicer tidied-up version of the field name
* @param {String} fieldName field name to be tidied up for human presentation
*/
ValidateForm.prototype.fieldNameNicer = function(fieldName) {
	var nicer = fieldName.replace(/_/g, " ");
	nicer = nicer.replace(/\w+\[(.*)\]/, "$1");
	return nicer;
}

/**
* test for empty text field or textarea, ignores leading and trailing spaces
*
* @return {boolean} true if effectively empty, else false
* @param {String} fieldName name of text field or textarea to check for viable input
*/
ValidateForm.prototype.isEmpty = function(fieldName) {
	var fld = this.frm.elements[fieldName];

	if (fld && !/\S/.test(fld.value)) {
		this.errmsg += "# " + this.fieldNameNicer(fieldName) + ": field is empty.\n";
		return true;
	}
	else
		return false;
}

/**
* test for text fields that should match, but don't
*
* @return {boolean} true if fields don't match, else false
* @param {String} fieldName1 name of first field
* @param {String} fieldName2 name of second field
*/
ValidateForm.prototype.isNotMatching = function(fieldName1, fieldName2) {
	var fld1 = this.frm.elements[fieldName1];
	var fld2 = this.frm.elements[fieldName2];

	if (fld1 && fld2 && fld1.value != fld2.value) {
		this.errmsg += "# " + this.fieldNameNicer(fieldName2) + ": does not match " + this.fieldNameNicer(fieldName1) + ".\n";
		return true;
	}
	else
		return false;
}

/**
* test for invalid email address
*
* NB: only accepts current standard-form Internet email addresses! xxx{@}yyy.com[.au] etc.
*
* @return {boolean} true if not a valid email address, or if empty and require was true
* @param {String} fieldName name of email address field to validate
* @param {boolean} require true if this field is mandatory (must have an email address)
*/
ValidateForm.prototype.isInvalidEmail = function(fieldName, require) {
	var fld = this.frm.elements[fieldName];

	if (fld) {
		// if not required, don't see empty field as error
		if (!/\S/.test(fld.value)) {
			if (require) {
				this.errmsg += "# " + this.fieldNameNicer(fieldName) + ": field is empty.\n";
				return true;
			}
			else
				return false;
		}

		if (!/^\w+([-.]\w+)*@\w+([-.]\w+)*(\.[a-z]{2,3})$/i.test(fld.value)) {
			this.errmsg += "# " + this.fieldNameNicer(fieldName) + ": not a valid email address.\n";
			return true;
		}
	}
	else
		return false;
}

/**
* test for invalid integer, ignores leading and trailing spaces
*
* @return {boolean} true if not a valid integer or too many digits, or is empty and require was true
* @param {String} fieldName name of field to validate
* @param {boolean} require true if this field is mandatory (must have some content)
*/
ValidateForm.prototype.isInvalidInteger = function(fieldName, require, digits) {
	var fld = this.frm.elements[fieldName];
	if (digits == undefined)
		digits = 3;

	if (fld) {
		// if not required, don't see empty field as error
		if (!/\S/.test(fld.value)) {
			if (require) {
				this.errmsg += "# " + this.fieldNameNicer(fieldName) + ": field is empty.\n";
				return true;
			}
			else
				return false;
		}

		var reg = '^\\s*[1-9]\\d{0,' + (digits - 1) + '}\\s*$';
		reg = new RegExp(reg);
		if (!reg.test(fld.value)) {
			this.errmsg += "# " + this.fieldNameNicer(fieldName) + ": number is invalid.\n";
			return true;
		}
	}
	else
		return false;
}

/**
* test for invalid dollar value, allows leading $, separating commas, decimal point with two digits, leading and trailing spaces
*
* @return {boolean} true if not a valid dollar amount, or is empty and require was true
* @param {String} fieldName name of field to validate
* @param {boolean} require true if this field is mandatory (must have some content)
*/
ValidateForm.prototype.isInvalidDollars = function(fieldName, require) {
	var fld = this.frm.elements[fieldName];

	if (fld) {
		// if not required, don't see empty field as error
		if (!/\S/.test(fld.value)) {
			if (require) {
				this.errmsg += "# " + this.fieldNameNicer(fieldName) + ": field is empty.\n";
				return true;
			}
			else
				return false;
		}

		if (!/^\s*\$?((\d{1,3},)?(\d{3},)*\d{3}|\d+)(\.\d\d)?\s*$/.test(fld.value)) {
			this.errmsg += "# " + this.fieldNameNicer(fieldName) + ": dollar figure is invalid.\n";
			return true;
		}
	}
	else
		return false;
}

/**
* test for select list with not first option selected (e.g. the "--please choose--" option)
*
* @return {boolean} true if nothing is selected or first option selected
* @param {String} fieldName name of field to validate
*/
ValidateForm.prototype.isNotSelected = function(fieldName) {
	var fld = this.frm.elements[fieldName];

	if (fld && fld.selectedIndex < 1) {
		this.errmsg += "# " + this.fieldNameNicer(fieldName) + ": please select an option.\n";
		return true;
	}
	else
		return false;
}

/**
* test for select list without a value selected (i.e. not a separator)
*
* NB: Internet Explorer will always return an empty string for a list option's value if not explicitly set!
*
* @return {boolean} true if nothing is selected or selected option has no value
* @param {String} fieldName name of field to validate
*/
ValidateForm.prototype.isNotSelectedValue = function(fieldName) {
	var fld = this.frm.elements[fieldName];

	if (fld && fld.options && fld.options[fld.selectedIndex].value == "") {
		this.errmsg += "# " + this.fieldNameNicer(fieldName) + ": please select an option.\n";
		return true;
	}
	else
		return false;
}

/**
* test for invalid date from day, month, year pick boxes
*
* NB: Internet Explorer will always return an empty string for a list option's value if not explicitly set!
*
* @return {boolean} true if not all parts are selected, date selected is invalid, is empty if require is true, or isn't a future date if forceFutureDate is true
* @param {String} dateName name of date (complete, e.g. Party_Date) to be used for error message if invalid
* @param {String} dayName name of select element for selected day
* @param {String} monthName name of select element for selected month
* @param {String} yearName name of select element for selected year
* @param {boolean} forceFutureDate true to force the date to be sometime in the future
* @param {boolean} require
*/
ValidateForm.prototype.isInvalidDate = function(dateName, dayName, monthName, yearName, forceFutureDate, require) {
	var selectday = this.frm.elements[dayName];
	var selectmonth = this.frm.elements[monthName];
	var selectyear = this.frm.elements[yearName];

	if (selectday && selectmonth && selectyear) {
		// check for each no elements selected
		if (selectday.selectedIndex < 1 && selectmonth.selectedIndex < 1 && selectyear.selectedIndex < 1) {
			if (require) {
				this.errmsg += "# " + this.fieldNameNicer(dateName) + ": date is empty.\n";
				return true;
			}
			else
				return false;
		}

		// check for each element selected
		if (selectday.selectedIndex < 1 || selectmonth.selectedIndex < 1 || selectyear.selectedIndex < 1) {
			this.errmsg += "# " + this.fieldNameNicer(dateName) + ": please select all parts of the date.\n";
			return true;
		}

		// check for valid date
		var d = selectday.options[selectday.selectedIndex].value;
		var m = selectmonth.options[selectmonth.selectedIndex].value;
		var y = selectyear.options[selectyear.selectedIndex].value;
		var dt = new Date(y, m - 1, d);
		if (dt.getFullYear() != y || dt.getMonth() != m - 1 || dt.getDate() != d) {
			this.errmsg += "# " + this.fieldNameNicer(dateName) + ": not a valid date.\n";
			return true;
		}

		if (forceFutureDate) {
			// check for not earlier than "tomorrow", by testing against a "today" of 1 second to midnight
			var today = new Date();
			today.setHours(23);
			today.setMinutes(59);
			today.setSeconds(59);
			if (today > dt) {
				this.errmsg += "# " + this.fieldNameNicer(dateName) + ": can't be earlier than tomorrow.\n";
				return true;
			}
		}

		return false;
	}
	else
		return false;
}

/**
* test for at least one radio button or checkbox checked
*
* @return {boolean} true if no radio button or checkbox in the named set is checked
* @param {String} buttonSetName
*/
ValidateForm.prototype.isUnchecked = function(buttonSetName) {
	var buttons = this.frm.elements[buttonSetName];

	if (buttons) {
		if (buttons.length == undefined) {
			// just one checkbox / radio button
			if (buttons.checked)
				return false;
		}
		else {
			// multiple checkboxes / radio buttons
			for (b = buttons.length; --b >= 0; ) {
				if (buttons[b].checked)
					return false;
			}
		}
		this.errmsg += "# " + this.fieldNameNicer(buttonSetName) + ": please pick one of the options.\n";
		return true;
	}
	return null;
}
