// ===============================================================
// Core form validator
// ===============================================================
function FormValidator(formobj, dontHookSubmit) {
	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.rules = new Array(); // the set of rule plugins we need.
	if(!dontHookSubmit) {
		this.formobj.validator = this; // form will need ref into me, to call validation plugins.
		this.manual(false); // auto attach by default.
	}

	// compensate for broken Norton form filler thing
  	if(!_FV_IsDef(document.validators)) {
	    document.validators = new Array();
    }
    document.validators[this.formobj] = this; 
  	         
    // done initializing
	return this;
}
FormValidator.prototype.validate = _FV_Validate;
FormValidator.prototype.run = _FV_Validate;
FormValidator.prototype.addrule = _FV_AddRule;
FormValidator.prototype.manual = _FV_SetManual;
FormValidator.prototype.clear = _FV_ClearRules;

function _FV_ClearRules() {
	this.rules = new Array();
}

function _FV_Validate() {
	// loops through validation rules and tests 'em.  
	for(var rule = 0; rule < this.rules.length; rule++) {
		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() {

	// 'this' is the FORM here, NOT the validator!
  	if(_FV_IsDef(this.validator)) {
  		return this.validator.validate();
  	} else {
  	// compensate for broken Norton form-filler. 
  		if(_FV_IsDef(document.validators)) {
  	        if(_FV_IsDef(document.validators[this])) {
  				return document.validators[this].validate();
  			}
  		}
  	}
  	alert("warning: form validation non-functional.  Please check your browser settings or email support.");
	return false;
}

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
		// are we working on a select box?
		var thevalue;
		if (_FV_IsDef(formobj.elements[this.box].selectedIndex)) 
		{
			// yep, select box.  get the value from the selected option.
			thevalue = formobj.elements[this.box].options[formobj.elements[this.box].selectedIndex].value;
		}
		else
		{
			// we have a text box. get the value directly.
			thevalue = formobj.elements[this.box].value;
		}
		
		if (!this.regex.exec(thevalue)) {
			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 at least one radio button
		if (_FV_IsDef(formobj.elements[this.radio].length))
		{
			// If the object is a group of radio buttons
			var radiogroup = formobj.elements[this.radio];
		}
		else
		{
			// If the object is a single radio button
			var radiogroup = new Array(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(ifrule, ifpassfail, thenrule, thenpassfail, thenfailmessage, thenfocusbox, elserule, elsepassfail, elsefailmessage, elsefocusbox) {

	ifrule.silent = true;
	thenrule.silent = true;

	this.rule1 = ifrule;
	this.rule1pass = ifpassfail;
	
	this.rule2 = thenrule;
	this.rule2pass = thenpassfail;
	this.message = thenfailmessage;
	this.focusbox = thenfocusbox;
	
	if(elserule) {
		elserule.silent = true;
		this.rule3 = elserule;
		this.rule3pass = elsepassfail;
		this.rule3message = elsefailmessage;
		this.rule3focus = elsefocusbox;
	}

	this.silent = false;
	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);
				if(_FV_IsDef(this.focusbox)) {
					if (_FV_IsDef(formobj.elements[this.focusbox])) {
						if (_FV_IsDef(formobj.elements[this.focusbox].focus)) {
							formobj.elements[this.focusbox].focus();
						} else if (_FV_IsDef(formobj.elements[this.focusbox][0])) {
							if (_FV_IsDef(formobj.elements[this.focusbox][0].focus)) {
								formobj.elements[this.focusbox][0].focus();
							}
						}
					}
				}
			}
			return false;
		}
	} else {
		// do we have an else rule?
		if(!this.rule3) {
			// nope
			return true;
		} else {
				
			// rule1 did not have the outcome we're looking for.  Test rule3 (the else rule).
			var rule3output = this.rule3.run(formobj);
			if (rule3output == this.rule3pass) {
				// everything's OK;
				return true;
			} else {
				if (!this.silent) {// don't holler if we're in silent mode.
					alert(this.rule3message);
					if(_FV_IsDef(this.rule3focusbox)) {
						if (_FV_IsDef(formobj.elements[this.rule3focusbox])) {
							if (_FV_IsDef(formobj.elements[this.rule3focusbox].focus)) {
								formobj.elements[this.rule3focusbox].focus();
							} else if (_FV_IsDef(formobj.elements[this.rule3focusbox][0])) {
								if (_FV_IsDef(formobj.elements[this.rule3focusbox][0].focus)) {
									formobj.elements[this.rule3focusbox][0].focus();
								}
							}
						}
					}
				}
				return false;
			}
		}
	}
}



// ===============================================================
// 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;
					}
				}
			}
			if (!this.silent) {
				alert(this.message);
			}
			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;
					}
				}
			}
			if (!this.silent) {
				alert(this.message);
			}
			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 {
				if (!this.silent) {
					alert(this.message);
				}
				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;
	}
}


// ===============================================================
// Check that the value of the element [fieldname] is (<,>,<=,>=) than [checkvalue].
// ===============================================================
function InequalityTest(fieldname,operator,checkvalue,errmsg, bPassBlankValue){
	this.fieldname = fieldname;
	if( ! (typeof this.fieldname == "string") ) { // handle if they passed an object instead of a name.
		this.field = fieldname;
		this.fieldname = this.field.name;
	}
	
	var validoperators = Array("<",">","<=",">=");
	var bvalidoperator = false;
	this.bpassblank = bPassBlankValue;
	this.silent = false;
	for (var o in validoperators){
		if ( validoperators[o] == operator) {
			bvalidoperator = true;
		}
	}
	if (!bvalidoperator) {
		alert("You attempted an InequalityTest with an invalid operator [" + operator + "]");
	} else {
		this.operator = operator;
	}
	this.checkvalue = checkvalue;
	this.errmsg = errmsg;
	return this;
}


InequalityTest.prototype.run = function(formobj){
	// find the element	
	if(!this.field) {
		if (!_FV_IsDef(this.field)) {
			this.field = formobj.elements[this.fieldname];
		}
	}
	
	
	if (_FV_IsDef(this.field)) {
		
		// check if we are working on dates.
		if (!isNaN(Date.parse(this.field.value)))
		{
			// this is a date.  we should have different syntax.
			var testdate = Date.parse(this.field.value);
			var testvalue = Date.parse(this.checkvalue);
			var evalstring = "testdate " + this.operator + " testvalue";
			var bvalid = eval(evalstring);
			if (!bvalid) {
				if (!this.silent) {
					alert(this.errmsg);
				}
				this.field.focus();
				return false;
			} else {
				return true;
			}
		}
		if (isNaN(parseFloat(this.field.value))){
			if (this.bpassblank && this.field.value.length == 0) {
				return true;	
			} else {
				if (!this.silent) {
					alert(this.errmsg);
				}
				this.field.focus();
				return false;
			}
		} else {
			var evalstring = "parseFloat(this.field.value) " + this.operator + " this.checkvalue";
			var bvalid = eval(evalstring);
			if (!bvalid) {
				if (!this.silent) {
					alert(this.errmsg);
				}
				this.field.focus();
				return false;
			} else {
				return true;
			}
		}
	} else {
		alert("You are attempting to validate the field " + this.fieldname + " which does not exist.");
		return false;	
	}
	
	
}


//=========================
// AutoFalse- this rule always returns a false value.
//=========================
function AutoFalse (failmessage) {
	this.failmessage = failmessage;
	this.debug = false;
	
	return this;
}

AutoFalse.prototype.run = function (formobj){
	if(this.debug) {alert("running an AutoFalse.");}
	return false;
}

//=========================
// UniqueValuesTest- this rule will check that every value within an array of controls is unique.
//=========================
function UniqueValuesTest(aryNames,failmessage,focusbox) {
	this.names = aryNames;
	this.failmessage = failmessage;
	this.focusbox = focusbox;
	this.debug = false;
	
	return this;
}

UniqueValuesTest.prototype.run = function (formobj) {
	// get the values of the elements into an array.
	var aryValues = new Array();
	var theValue;
	var theName;
	for (var i = 0; i < this.names.length; i++) {
		// check that this element exists.
		theName = this.names[i];
		if (_FV_IsDef(formobj.elements[theName])) {	
			// got a valid element.
			// check if it is a select.
			if (_FV_IsDef(formobj.elements[theName].selectedIndex)){
				// yep, select box. get the selected value.
				theValue = formobj.elements[theName].options[formobj.elements[theName].selectedIndex].value;
			} 
			else {
				// nope, retrieve the value.
				theValue = formobj.elements[theName].value;
			}
			// put the value into our work array.
			aryValues[theValue] = true;
		}
		else {
			// tried to validate a nonexistant element.
			return false;	
		}
	}
	
	// if the length of the values array is different than the length of the names array,
	// there is a duplicate value amongst the bunch.
	var ValueCount = 0;
	for (var j in aryValues){
		ValueCount++;	
	}
	if (this.names.length == ValueCount) {
		return true;
	}
	else {
		alert(this.failmessage);
		formobj.elements[this.focusbox].focus();	
		return false;
	}	
	
}

//=========================
// RequireTextMany- this rule will check that at least mincount elements within an array of controls pass a RequireText of testlength.
//=========================
function RequireTextMany(aryNames, testlength, mincount, failmessage, focusbox) {
	this.names = aryNames;
	this.testlength = testlength;
	this.mincount = mincount;
	this.failmessage = failmessage;
	this.focusbox = focusbox;
	this.debug = false;
	
	return this;
}

RequireTextMany.prototype.run = function (formobj) {
	var iPassCount = 0;
	var sElement;
	var oRequireText;
	for (var i = 0; i < this.names.length; i++) {
		sElement = this.names[i];
		oRequireText = new RequireText(sElement, this.testlength,"");
		oRequireText.silent = true;
		if (oRequireText.run(formobj)){
			iPassCount++;
		}	
	}
	if (iPassCount >= this.mincount) {
		return true;	
	}
	else {
		alert(this.failmessage);
		formobj.elements[this.focusbox].focus();	
		return false;
	}
}

//=========================
// Field Value Compare - Comparing two textfields to each other.
//=========================
function FieldValueCompare (field1, field2, failmessage, focusbox) {
	this.field1 = field1;
	this.field2 = field2;
	this.failmessage = failmessage;
	this.focusbox = focusbox;
	this.silent = false;
	this.debug = false;
	
	return this;
}

FieldValueCompare.prototype.run = function (formobj){
	if(this.debug) {alert("running an FieldValueCompare.");}
	var passed = false;
	if (_FV_IsDef(formobj.elements[this.field1])) {
		if (_FV_IsDef(formobj.elements[this.field2])) {
			passed = (formobj.elements[this.field1].value == formobj.elements[this.field2].value);
		}
	}
	if (!passed) {
		alert(this.failmessage);
		formobj.elements[this.focusbox].focus();	
		return false;
	}	
	else {
		return true;
	}
			
}

