// ===============================================================
// Core form validator
// ===============================================================
function FormValidator(formobj) {
	if(!_FV_IsDef(formobj)) {
		if(this.debug) {alert("Unable to attach form validator to a form!  The form object doesn't exist!");}
		return false;
	}
	this.debug = false;
	this.formobj = formobj;
	this.formobj.validator = this; // form will need ref into me, to call validation plugins.
	this.rules = new Array(); // the set of rule plugins we need.
	this.manual(false); // auto attach by default.
	return this;
}
FormValidator.prototype.validate = _FV_Validate;
FormValidator.prototype.addrule = _FV_AddRule;
FormValidator.prototype.manual = _FV_SetManual;

function _FV_Validate() {
	// loops through validation rules and tests 'em.  
	for(var rule in this.rules) {
		if(this.debug) {alert("Looking at a " + typeof this.rules[rule]);}
		if (!this.rules[rule].run(this.formobj)) {
			return false;
		}
	}
	return true;
}

function _FV_AddRule(rule) {
	this.rules[this.rules.length] = rule;
}

function _FV_SetManual(bManual) {
	if(bManual) {
		this.formobj.onsubmit = function() {return true;};
	} else {
		this.formobj.onsubmit = _FV_OnSubmit; // hook submit by default
	}
}

function _FV_OnSubmit() {
	return this.validator.validate();
}

function _FV_IsDef(thing) {
  return (typeof thing != 'undefined');
}



// ===============================================================
// Text box checker - Checks that the value entered in a textbox is at least (length) characters long.
// ===============================================================
function RequireText(box, len, failmessage) {
	this.box = box;
	this.len = len;
	this.message = failmessage;
	this.debug = false;
	this.silent = false;
	return this;
}

RequireText.prototype.run = function(formobj) {
	if(this.debug) {alert("running a require text.");}
	if (_FV_IsDef(formobj.elements[this.box])) {
		// we have a box
		if (formobj.elements[this.box].value.length < this.len) {
			if (!this.silent) { // don't holler if we're in silent mode.
				alert(this.message);
				formobj.elements[this.box].focus();
			}
			return false;
		} else {
			return true;
		}
	} else {
		if(this.debug) {alert("Warning: Textbox Validator encountered an unknown field called " + this.box);}
		return false;
	}
}

// ===============================================================
// Text box regex checker - Checks that the value entered in a textbox matches a regex.
// ===============================================================
function RequireRegex(box, regex, failmessage) {
	this.box = box;
	this.regex = regex;
	this.message = failmessage;
	this.debug = false;
	this.silent = false;
	return this;
}

RequireRegex.prototype.run = function(formobj) {
	if(this.debug) {alert("running a require regex on box " + this.box);}
	if (_FV_IsDef(formobj.elements[this.box])) {
		// we have a box
		if (!this.regex.exec(formobj.elements[this.box].value)) {
			if (!this.silent) {// don't holler if we're in silent mode.
				alert(this.message);
				formobj.elements[this.box].focus();
			}
			return false;
		} else {
			return true;
		}
	} else {
		if(this.debug) {alert("Warning: Regex Validator encountered an unknown field called " + this.box);}
		return false;
	}
}

// ===============================================================
// Radio box checker - Checks that one of a group of radio buttons is checked.
// ===============================================================
function RequireRadio(name, failmessage) {
	this.radio = name;
	this.message = failmessage;
	this.debug = false;
	this.silent = false;
	return this;
}

RequireRadio.prototype.run = function(formobj) {
	if(this.debug) {alert("running a require radio.");}
	if (_FV_IsDef(formobj.elements[this.radio])) {
		// we have a radio button
		var radiogroup = formobj.elements[this.radio];
		var hits = 0;
		for( var i = 0; i< radiogroup.length; i++) { 
			// loop through the radio elements and see if any are checked.
			if(radiogroup[i].checked) { 
				hits++; //increment hit counter
			}
		}
		if (hits < 1) {
			if (!this.silent) {// don't holler if we're in silent mode.
				alert(this.message);
				radiogroup[0].focus();
			}
			return false;
		} else {
			return true;
		}
	} else {
		if(this.debug) {alert("Warning: Radio Validator encountered an unknown field called " + this.radio);}
		return false;
	}
}

// ===============================================================
// Select box checker - Checks that an option of index (firstindex + 1) is selected within a select box.
// ===============================================================
function RequireSelect(select, failmessage, firstindex) {
	this.box = select;
	if(_FV_IsDef(firstindex)) {
		this.firstindex = firstindex;
	} else {
		this.firstindex = 0;
	}
	this.message = failmessage;
	this.debug = false;
	this.silent = false;
	return this;
}

RequireSelect.prototype.run = function(formobj) {
	if(this.debug) {alert("running a require select.");}
	if (_FV_IsDef(formobj.elements[this.box])) {
		// we have a real box
		if(formobj.elements[this.box].selectedIndex < this.firstindex) {
			if (!this.silent) {// don't holler if we're in silent mode.
				alert(this.message);
				formobj.elements[this.box].focus();
			}
			return false;
		} else {
			return true;
		}
	} else {
		if(this.debug) {alert("Warning: Select Validator encountered an unknown field called " + this.box);}
		return false;
	}
}

// ===============================================================
// If (rule) (pass/fail) Then (rule) must (pass/fail).  if not, holler (message)
// ===============================================================
function DependentRules(rule1, passfail, rule2, passfail2, failmessage) {

	rule1.silent = true;
	rule2.silent = true;

	this.rule1 = rule1;
	this.rule1pass = passfail;
	this.rule2 = rule2;
	this.rule2pass = passfail2;

	this.silent = false;
	this.message = failmessage;
	this.debug = false;
	return this;
}

DependentRules.prototype.run = function(formobj) {
	if(this.debug) {alert("running a dependent rule set.");}
	var rule1output = this.rule1.run(formobj);
	if (rule1output == this.rule1pass) {
		// rule1 had the outcome we're looking for.  Test rule2.
		var rule2output = this.rule2.run(formobj);
		if (rule2output == this.rule2pass) {
			// everything's OK;
			return true;
		} else {
			if (!this.silent) {// don't holler if we're in silent mode.
				alert(this.message);
			}
			return false;
		}
	} else {
		return true;
	}
}



// ===============================================================
// Test value of (any type) equal to (value), return only (silent by default)
// ===============================================================
function ValueTest(formelement, testvalue, failmessage) {

	this.formelement = formelement;
	this.testvalue = testvalue;

	this.silent = true; // default silent
	if(_FV_IsDef(failmessage)) {
		this.message = failmessage;
	} else {
		this.message = "";
	}
	this.debug = false;
	return this;
}

ValueTest.prototype.run = function(formobj) {
	if(this.debug) {alert("running a value check.");}
	if (_FV_IsDef(formobj.elements[this.formelement])) {
		var el = formobj.elements[this.formelement];
		if (_FV_IsDef(el.selectedIndex)) {
			// it's a select box
			// loop through elements and see if one is selected that has this value.
			for (var opt = 0; opt < el.length; opt++) {
				if (el.options[opt].selected) {
					if(el.options[opt].value == this.testvalue) {
						return true;
					}
				}
			}
			return false; // didn't find a selected option with our value.
		} else if (_FV_IsDef(el[0])) {
			// it's a radio group
			for( var i = 0; i< el.length; i++) { // loop through the radio elements 
				if(el[i].checked) { // see if any are checked.
					if (el[i].value == this.testvalue) {
						return true;
					}
				}
			}
			return false; // if we got here, we didn't hit a checked item with our value.
		} else {
			// it's just a textbox
			// see if the value is what we're asking for.
			if(el.value == this.testvalue) {
				return true;
			} else {
				return false;
			}
		}
	} else {
		if(this.debug) {alert("Warning: Value check encountered an unknown field called " + this.formelement);}
		return false;
	}
}


// ===============================================================
// Check that one of a group of checkboxes is checked.
// ===============================================================
function RequireCheckboxGroup(aryboxes, minimum, failmessage) {
	this.boxes = aryboxes;
	this.minimum = minimum; // require at least this many group members to be checked.
	this.message = failmessage;
	this.debug = false;
	this.silent = false;
	return this;
}

RequireCheckboxGroup.prototype.run = function(formobj) {
	if(this.debug) {alert("running a require checkbox group.");}
	var foundCheckedCount = 0;
	
	for(var i in this.boxes) {
		if (_FV_IsDef(formobj.elements[this.boxes[i]])) {
			// we have a checkbox
			if (formobj.elements[this.boxes[i]].checked) {
				foundCheckedCount++;
			}
		} else {
			if(this.debug) {alert("Warning: Checkbox Group Validator encountered an unknown checkbox called " + this.boxes[i]);}
		}
	}
	
	if(foundCheckedCount < this.minimum) {
		if (!this.silent) { // don't holler if we're in silent mode.
			alert(this.message);
			formobj.elements[this.boxes[0]].focus();
		}
		return false;
	} else {
		return true;
	}
}


// ===============================================================
// Check that one of a group of checkboxes *all of which have the same name* is checked.
// ===============================================================
function RequireCheckboxesWithSingleName(boxname, minimum, failmessage) {
	this.boxname = boxname;
	this.minimum = minimum; // require at least this many group members to be checked.
	this.message = failmessage;
	this.debug = false;
	this.silent = false;
	return this;
}

RequireCheckboxesWithSingleName.prototype.run = function(formobj) {
	if(this.debug) {alert("running a require checkbox group.");}
	var foundCheckedCount = 0;
	var firstCheckbox = 0;
	for(var i = 0; i < formobj.elements.length; i++) {
		
		if(formobj.elements[i].name == this.boxname) {
			if(this.debug) {alert("found a box with the right name!");}
			// found a field with the requisite name
			if (formobj.elements[i].checked) {
				foundCheckedCount++;
			}
			if(firstCheckbox == 0) { // note that we've encountered a box with the correct name
				firstCheckbox = formobj.elements[i];
			}
		}
	}
	
	if(foundCheckedCount < this.minimum) {
		if (!this.silent) { // don't holler if we're in silent mode.
			alert(this.message);
			if(firstCheckbox != 0) {
				firstCheckbox.focus();
			}
		}
		return false;
	} else {
		return true;
	}
}