//  FormValidator 1.3.2 - copyright (C) 2008 - Matt Newberry, info@mattnewberry.net
//  ALL RIGHTS RESERVED, except as site-licensed, under contract
//
//	Enforces required fields; also default values for non-checked Checkbox and Radio fields;
//	1.1 added Min, Max and Range checks for numeric values.
//	1.2 default value checking (value supplied on server) for any field
//	1.3 type checking added for numbers and ints
//	1.3.1 Match and TypeCheck debugged a bit
//	1.3.2 email regex changed
//
//	TODO: handle multi-value fields more gracefully.
	
	// constructor
	
function FormValidator(f) {		// pass the html form
	this.MATCH_EMAIL = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
	this.MATCH_PHONE = /^\d{3}-\d{3}-\d{4}$/;
	this.MATCH_DATE = /^\d{1,2}\W\d{1,2}\W\d{1,4}$/;
	this.MATCH_CREDIT = /(^\d{15,16}$)|(^\d{4}\W\d{4}\W\d{4}\W\d{4}$)|(^\d{4}\W\d{6}\W\d{5}$)/;
	this.frm = f;
	this.missing = new Array();
	this.missing_count = 0;
	this.failed = new Array();
	this.failed_count = 0;
	this.bugs = new Array();
	this.bug_count = 0;
}

	// field validation methods

FormValidator.prototype.Require = function(field) {
	/*if (field == "OrgType") {
		for (i = 0; i < this.frm[field].length; ++i)
			if (this.frm[field][i].selected)
				alert(this.frm[field][i].type + ": " + field + "=" + this.frm[field][i].value);
	}*/
	var fld = this.frm[field];
	if (fld == null) {
		this.bugs[this.bug_count++] = "unknown Require field: " + field;
		this.missing[this.missing_count++] = field;
		return false;			// FAILED!
	}
	else {
		var val = this.get_value(fld);
		if (val == null)
			return true;		// not checked always passes!
		if (val == "") {
			if (fld.title == undefined)
				this.missing[this.missing_count++] = (fld[0].title ? fld[0].title : this.titleize(field));
			else
				this.missing[this.missing_count++] = (fld.title ? fld.title : this.titleize(field));
			return false;		// FAILED!
		}
		else
			return true;		// PASSED!
	}
}

FormValidator.prototype.Requires = function(field) {	// alias for PHP compatibility
	return this.Require(field);
}

FormValidator.prototype.MatchEmail = function(field) {
	var fld = this.frm[field];
	var val = this.get_value(fld);
	if (val == null || val == '')
		return true;
	var pos1 = val.indexOf('@');
	var pos2 = val.indexOf('.', pos1);
	if (pos2 <= pos1) {
		this.failed[this.failed_count++] = "The " + (fld.title ? fld.title : this.titleize(fld.name)) + " format is incorrect";
		return false;
	}
	return true;
}

FormValidator.prototype.Match = function(field, matchspec, required) {	// NOT FULLY TESTED!!!
	if (required && ! this.Require(field))
		return;					// no value test if Require() fails
	var fld = this.frm[field];
	var val = this.get_value(fld);
	if (val == "" && ! required)
		return;
	var fieldtitle = (fld.title ? fld.title : this.titleize(fld.name));
	var matchupper = this.trim(matchspec.toUpperCase());
	switch (matchupper) {
		case "EMAIL":
			if (val.match(this.MATCH_EMAIL) == null)
				this.failed[this.failed_count++] = "The " + fieldtitle + " format is incorrect";
			return;
		case "DATE":
			if (val.search(this.MATCH_DATE) != 0)
				this.failed[this.failed_count++] = "The " + fieldtitle + " date format is incorrect; please use \'mm/dd/yyyy\'";
			return;
		case "PHONE":
			if (val.search(this.MATCH_PHONE) != 0)
				this.failed[this.failed_count++] = "The " + fieldtitle + " format is incorrect; please use \'###-###-####\'";
			return;
		case "CREDIT":
			if (val.search(this.MATCH_CREDIT) != 0)
				this.failed[this.failed_count++] = "The " + fieldtitle + " credit card format is incorrect";
			return;
		default:
			if (val.search(new RegExp(matchspec)) != 0)
				this.failed[this.failed_count++] = "The " + fieldtitle + " format is incorrect";
			return;
	}
}
	
FormValidator.prototype.TypeCheck = function(field, typespec, required) {	// NOT FULLY TESTED!!!
	if (required && ! this.Require(field))
		return;					// no value test if Require() fails
	var fld = this.frm[field];
	var val = this.get_value(fld);
	if (val == "" && ! required)
		return;
	var fieldtitle = (fld.title ? fld.title : this.titleize(fld.name));
	typespec = this.trim(typespec.toUpperCase());
	switch (typespec) {
		case "NUMBER":
			var num = this.parse_number(val);
			if (isNaN(num))
				this.failed[this.failed_count++] = "The " + fieldtitle + " must be a number";
			return;
		case "INT":
			var num = this.parse_number(val);
			if (isNaN(num))
				this.failed[this.failed_count++] = "The " + fieldtitle + " must be a number";
			else if (parseFloat(val) - parseInt(val) > 0)
				this.failed[this.failed_count++] = "The " + fieldtitle + " must be a whole number";
			return;
		default:
			this.bugs[this.bug_count++] = "unknown typespec in TypeCheck: " + field;
			return;
	}
}

FormValidator.prototype.Min = function(field, minval, required) {
	if (required && ! this.Require(field))
		return;					// no value test if Require() fails
	var fld = this.frm[field];
	var val = this.get_value(fld);
	if (val == "" && ! required)
		return;
	var fieldtitle = (fld.title ? fld.title : this.titleize(fld.name));
	val = this.parse_number(val);
	if (isNaN(val))
		this.failed[this.failed_count++] = "The " + fieldtitle + " must be a numeric value";
	else if (val < minval) 
		this.failed[this.failed_count++] = "The " + fieldtitle + " must be at least " + minval;
}

FormValidator.prototype.Max = function(field, maxval, required) {
	if (required && ! this.Require(field))
		return;					// no value test if Require() fails
	var fld = this.frm[field];
	var val = this.get_value(fld);
	if (val == "" && ! required)
		return;
	var fieldtitle = (fld.title ? fld.title : this.titleize(fld.name));
	val = this.parse_number(val);
	if (isNaN(val))
		this.failed[this.failed_count++] = "The " + fieldtitle + " must be a numeric value";
	else if (val > maxval) 
		this.failed[this.failed_count++] = "The " + fieldtitle + " must be no greater than " + maxval;
}

FormValidator.prototype.Range = function(field, minval, maxval, required) {
	if (minval >= maxval)
		this.bugs[this.bug_count++] = "min/max flipped in Range field: " + field;
	if (required && ! this.Require(field))
		return;					// no value test if Require() fails
	var fld = this.frm[field];
	var val = this.get_value(fld);
	if (val == "" && ! required)
		return;
	var fieldtitle = (fld.title ? fld.title : this.titleize(fld.name));
	val = this.parse_number(val);
	if (isNaN(val))
		this.failed[this.failed_count++] = "The " + fieldtitle + " must be a numeric value";
	else if (val < minval || val > maxval) 
		this.failed[this.failed_count++] = "The " + fieldtitle + " must be between " + minval + " and " + maxval;
}
		
FormValidator.prototype.MinLength = function(field, minlen, required) {
	if (required && ! this.Require(field))
		return;					// no value test if Require() fails
	var fld = this.frm[field];
	var val = this.get_value(fld);
	if (val == "" && ! required)
		return;
	var fieldtitle = (fld.title ? fld.title : this.titleize(fld.name));
	if (minlen > val.length) {
		this.failed[this.failed_count++] = fieldtitle + " must be at least " + minlen + " characters";
	}
}

	// field value methods

FormValidator.prototype.SetDefault = function(field, dvalue) {		// supply value, if missing
	var fld = this.frm[field];
	if (fld == null)
		this.bugs[this.bug_count++] = "unknown SetDefault field: " + field;
}

	// validation

FormValidator.prototype.Validate = function(debug, retval) {	// no debug or retval for production
	debug = (debug == undefined ? false : debug);
	retval = (retval == undefined ? false : retval);
	if (debug && this.bug_count > 0) {
	    msg  = "_______________________________________________________\n\n"
	    msg += "The Form Validator reports the following form configuration errors:\n";
	    msg += "_______________________________________________________\n\n"
	
		for (var i = 0; i < this.bugs.length; ++i)
			msg += this.bugs[i] + "\n";
		alert(msg);
	}
	
	var success = this.perform_validation();
	
	if (debug)
		return retval;
	else
		return success;
}

FormValidator.prototype.perform_validation = function() {
    if (this.missing_count + this.failed_count == 0) 
		return true;

	var err_count = this.failed_count + this.missing_count;
    msg  = "_______________________________________________________\n\n"
    msg += "The form was not submitted due to the following omission" + (err_count > 1 ? "s" : "") + "";
    msg += " or error" + (err_count > 1 ? "s" : "") + ".\n";
    msg += "Please make appropriate corrections and re-submit.\n";
    msg += "_______________________________________________________\n\n"

    if (this.missing_count > 0) {
        msg += "- The following required fields are empty:";
			for (var i = 0; i < this.missing.length; ++i)
				msg += "\n        " + this.missing[i];
        if (this.failed_count > 0) 
			msg += "\n";
    }
	for (var i = 0; i < this.failed.length; ++i)
		msg += "- " + this.failed[i] + ".\n";
    alert(msg);
    return false;
}

	// utility

FormValidator.prototype.get_value = function(field) {
	var typ = field.type;
	if (typ == undefined) {		// multi-radio or multi-checkbox
		typ = field[0].type;
		if (typ == undefined) {
			return "";
		}
		else {
			for (i = 0; i < field.length; ++i) {
				//alert(field[i].value + "=" + field[i].checked);
				if (field[i].checked) {
					return field[i].value;
				}
			}
			return "";
		}
		return "";
	}
	else if (typ = "text" || typ == "textarea")
		return this.trim(field.value);
	else if (typ == "select-one")
		return field.options[field.options.selectedIndex].value;
	//else if (typ == "checkbox")
	//	var val = (field.value = (field.value == "") ? "false" : field.value);
	else if (typ == "radio" || typ == "hidden" || typ == "checkbox" || typ == "password") {
		//alert(typ + "=" + field.value);
		return field.value;
	}
	else
		return null;
}

FormValidator.prototype.trim = function(s) {		// utility: trim leading & trailing whitespace
	var first, last;
	for (var i = 0; i < s.length; ++i) {
		var c = s.charAt(i);
		if (c != ' ' && c != '\n' && c != '\t') {
			first = i;
			break;
		}
	}
	for (var i = s.length-1; i >= 0; --i) {
		var c = s.charAt(i);
		if (c != ' ' && c != '\n' && c != '\t') {
			last = i + 1;
			break;
		}
	}
	return s.substring(first, last);
}

FormValidator.prototype.titleize = function(str) {	// utility: add spaces to mixed-case string
	rstr = "";
	lastnumeric = false;
	for (i = 0; i < str.length; ++i) {
		c = str.charAt(i);
		if (c >= 'A' && c <= 'Z' && i > 0) {
			rstr += " " + c;
			lastnumeric = false;
		}
		else if (c >= '0' && c <= '9') {
			if (lastnumeric)
				rstr += c;
			else
				rstr += " " + c;
			lastnumeric = true;
		}
		else {
			rstr += c;
			lastnumeric = false;
		}
	}
	return rstr;
}

FormValidator.prototype.parse_number = function(str) {	// utility: stricter than parseFloat()
	return str - 0;
}

/*
*/

