
function Validator(bSetClass, alertClass, nonAlertClass, form, msgElement, errorPreamble, errorPrefix, errorPostfix, errorPostamble, bLineBreaks) {
	this.arElements = new Array(); //array containing all the form fields to check
	this.arMessages = new Array(); //array containing all the messages to display for each field (only when displayIndividualErrors=true)
	this.arValidationType = new Array(); //what kind of validation to perform on the form fields
	this.arSelectCheck = new Array(); //what value to check for in select inputs 
	this.arCompare = new Array(); //array to store fields whose values you want to compare
	this.arParams = new Array(); //array to store any extra parameters
	this.arHighlight = new Array(); //array to store label elements to change the class of when a field does not pass validation
	this.arAlerted = new Array(); //internal use only
	this.errorPreamble = errorPreamble; //start of error message (only when displayGeneralError=true)
    this.errorPrefix = errorPrefix; //text to prefix every error with (only when displayGeneralError=true)
    this.errorPostfix = errorPostfix; //text to append to every error (only when displayGeneralError=true)
    this.errorPostamble = errorPostamble; //end of error message (only when displayGeneralError=true)
    this.msgElement = msgElement; //element to put the error msg into (only when displayGeneralError=true)
	this.messageDisplayType = 'block'; //on individual errors, the display style to set on the individual msg elements
	this.alertClass = alertClass; //the class to set the input field (when setClass=true), or to highlight the label (highlightLabel=true)
	this.nonAlertClass = nonAlertClass; //the class when there is no error
	this.setClass = false; //whether or not to change the class of individual form fields that fail validation
	this.form = form;	//the form to validate
	this.highlightLabel = false; //whether or not to change the class of an associated label element
	this.alertMoreThanOnce = false; //whether or not to output more than 1 error per field (eg, when notnull and number together on 1 field)
	this.debug = false; //debug mode
	this.CustomValidate = null; //a custom validation function - pass the function name to execute (no params allowed)
	this.ContinueValidationAfterCustom = true; //whether or not to process the normal validation routines if a custom validator returns true
	this.bLineBreaks = bLineBreaks; //whether or not to add line breaks to each error (only when displayGeneralError=true)
	this.displayIndividualErrors = true; //whether or not to output individual errors)
	this.displayGeneralError = false; //whether or not to output a single general error
}

Validator.prototype.addField = validatorAddField;
Validator.prototype.addCompare = validatorAddCompare;
Validator.prototype.validate = validatorValidate;
Validator.prototype.validateLength = validatorLength;
Validator.prototype.compare = validatorCompare;
Validator.prototype.clear = validatorClear;

function validatorAddField(fieldName, type, selectCheck, params, highlight, message) {
	this.arElements[this.arElements.length] = fieldName;
	this.arValidationType[this.arValidationType.length] = type;
	//this.arSelectCheck[this.arSelectCheck.length] = selectCheck;
	this.arSelectCheck[this.arElements.length - 1] = selectCheck;
	this.arParams[this.arParams.length] = params;
	this.arMessages[this.arMessages.length] = params;
	this.arHighlight[this.arHighlight.length] = highlight;
}

function validatorAddCompare(fieldName1, fieldName2, highlight1, highlight2) {
	this.arElements[this.arElements.length] = fieldName1;
	this.arElements[this.arElements.length] = fieldName2;
	this.arValidationType[this.arValidationType.length] = 'compare';
	this.arValidationType[this.arValidationType.length] = 'compare';
	this.arHighlight[this.arHighlight.length] = highlight1;
	this.arHighlight[this.arHighlight.length] = highlight2;
}

function validatorClear() {
	for (var x=0; x<this.arElements.length; x++) {
		var elem = null;
		var label = null;
		
		switch (this.arValidationType[x]) {
			case "notnull":
				elem = document.getElementById(this.arElements[x]+'_notnull');
				break;
				
			case "email":
				elem = document.getElementById(this.arElements[x]+'_email');
				break;
				
			case "compare":
				elem = document.getElementById(this.arElements[x]+'_compare');
				break;

			case "postcode":
				elem = document.getElementById(this.arElements[x]+'_postcode');
				break;

			case "select":
				elem = document.getElementById(this.arElements[x]+'_select');
				break;

			case "checkbox":
				elem = document.getElementById(this.arElements[x]+'_checkbox');
				break;
			
			case "radio":
				elem = document.getElementById(this.arElements[x]+'_radio');
				break;
					
			case "minlength":
				elem = document.getElementById(this.arElements[x]+'_minlength');
				break;
				
			case "number":
				elem = document.getElementById(this.arElements[x]+'_number');
				break;
		}
		
		label = document.getElementById(this.arHighlight[x]);
		
		if (elem != null)
			elem.style.display = 'none';
			
		if (this.highlightLabel && label != null)
		    label.className = '';
	}
	
	this.arElements = new Array();
	this.arValidationType = new Array();
	this.arSelectCheck = new Array();
	this.arCompare = new Array();
	this.arParams = new Array();
	this.arHighlight = new Array();
}

function validatorValidate(bNoCustom) {
	if (bNoCustom == null)
		bNoCustom = false;
		
	if (this.CustomValidate != null && !bNoCustom) {
		var bCustom = eval(this.CustomValidate + '();');
		if (!bCustom && this.ContinueValidationAfterCustom)
		    return false;
		else
		    return bCustom;
    }
	
	this.arAlerted = new Array();
	var bError = false;
	var bAnyErrors = false;
	var bDefaultError = true;
	var bFocussed = false;
	var bFocus = true;
	var bGotMessageElement = false;
	
	var sErrors = '';
    if (this.bLineBreaks == null)
        this.bLineBreaks = true;
	
	for (var x=0; x<this.arElements.length; x++) {
		//check the validation element exists
		var elem = null;
		var bGotMessageElement = true;
		bError = false;
		
		var input = this.form.elements[this.arElements[x]];
		if (input == null)
		    alert('VALIDATION ERROR: ' + this.arElements[x] + ' does not exist');
		
		switch (this.arValidationType[x]) {
			case "notnull":
				//get the message element
				elem = document.getElementById(this.arElements[x]+'_notnull');
				if (elem == null) {
					bGotMessageElement = false;
					elem = this.arElements[x]+'_notnull';
				}
					
				if (trim(this.form.elements[this.arElements[x]].value) == '') {
					bError = true;
					if (this.arMessages[x] != null)
    					sErrors += ((this.bLineBreaks) ? '<br/>' : '') + this.errorPrefix + this.arMessages[x] + this.errorPostfix;
				}
				break;
				
			case "compare":
				elem = document.getElementById(this.arElements[x]+'_compare');
				if (elem == null) {
					bGotMessageElement = false;
					elem = this.arElements[x]+'_compare';
				}
			
				if (!this.compare(this.form.elements[this.arElements[x]], this.form.elements[this.arElements[x+1]]))
					bError = true;
				else {
				    //need to set the class on the comparator
					if (this.setClass) {
						if (document.getElementById(this.arElements[x+1]))
							document.getElementById(this.arElements[x+1]).className = this.nonAlertClass;
					}
				}
				
				//need to increment x as the comparator is stored sequentially after the current position
				x++;
				break;
				
			case "email":
				elem = document.getElementById(this.arElements[x]+'_email');
				if (elem == null) {
					bGotMessageElement = false;
					elem = this.arElements[x]+'_email';
				}
							
				if (trim(this.form.elements[this.arElements[x]].value) != '') {
					result = this.form.elements[this.arElements[x]].value.match(/^[^@]+@[^@]+\.[^@]+$/);
					if (result!=this.form.elements[this.arElements[x]].value) {
						bError = true;
						if (this.arMessages[x] != null)
						    sErrors += ((this.bLineBreaks) ? '<br/>' : '') + this.errorPrefix + this.arMessages[x] + this.errorPostfix;
					}
				}
				break;
				
			case "number":
				elem = document.getElementById(this.arElements[x]+'_number');
				if (elem == null) {
					bGotMessageElement = false;
					elem = this.arElements[x]+'_number';
				}
							
				if (trim(this.form.elements[this.arElements[x]].value) != '') {
					result = this.form.elements[this.arElements[x]].value.match(/^[0-9,\.]+$/);
					if (result!=this.form.elements[this.arElements[x]].value) {
						bError = true;
						if (this.arMessages[x] != null)
						    sErrors += ((this.bLineBreaks) ? '<br/>' : '') + this.errorPrefix + this.arMessages[x] + this.errorPostfix;
					}
				}
				break;

			case "int":
				elem = document.getElementById(this.arElements[x]+'_int');
				if (elem == null) {
					bGotMessageElement = false;
					elem = this.arElements[x]+'_int';
				}
							
				if (trim(this.form.elements[this.arElements[x]].value) != '') {
					result = this.form.elements[this.arElements[x]].value.match(/^[0-9]+$/);
					if (result!=this.form.elements[this.arElements[x]].value) {
						bError = true;
						if (this.arMessages[x] != null)
						    sErrors += ((this.bLineBreaks) ? '<br/>' : '') + this.errorPrefix + this.arMessages[x] + this.errorPostfix;
					}
				}
				break;
								
			case "select":
				elem = document.getElementById(this.arElements[x]+'_select');
				if (elem == null) {
					bGotMessageElement = false;
					elem = this.arElements[x]+'_select';
				}
							
				//get the drop down
				var dd = this.form.elements[this.arElements[x]];
				if (dd[dd.selectedIndex].value == this.arSelectCheck[x]) {
					bError = true;
					if (this.arMessages[x] != null)
					    sErrors += ((this.bLineBreaks) ? '<br/>' : '') + this.errorPrefix + this.arMessages[x] + this.errorPostfix;
				}
				break;
				
			case "minlength":
				elem = document.getElementById(this.arElements[x]+'_minlength');
				if (elem == null) {
					bGotMessageElement = false;
					elem = this.arElements[x]+'_select';
				}
				
				var sValue = trim(this.form.elements[this.arElements[x]].value);
				if (sValue.length < this.arParams[x]) {
					bError = true;
					if (this.arMessages[x] != null)
					    sErrors += ((this.bLineBreaks) ? '<br/>' : '') + this.errorPrefix + this.arMessages[x] + this.errorPostfix;
				}
				break;
				
			case "postcode":
				elem = document.getElementById(this.arElements[x]+'_postcode');
				if (elem == null) {
					bGotMessageElement = false;
					elem = this.arElements[x]+'_postcode';
				}
							
				if (trim(this.form.elements[this.arElements[x]].value) != '') {
					result = this.form.elements[this.arElements[x]].value.match(/[A-Z|a-z]{1,2}[0-9R][0-9A-Z|0-9a-z]?[\s]?[0-9][A-Z|a-z]{2}/);
					if (result!=this.form.elements[this.arElements[x]].value) {
						bError = true;
						if (this.arMessages[x] != null)
						    sErrors += ((this.bLineBreaks) ? '<br/>' : '') + this.errorPrefix + this.arMessages[x] + this.errorPostfix;
					}
				}
				break;
				
			case "checkbox":
			    bFocus = false;
				elem = document.getElementById(this.arElements[x]+'_checkbox');
				if (elem == null) {
					bGotMessageElement = false;
					elem = this.arElements[x]+'_checkbox';
				}
							
				//get the checkbox
				var chk = this.form.elements[this.arElements[x]];
				if (!chk.checked) {
					bError = true;
					if (this.arMessages[x] != null)
					    sErrors += ((this.bLineBreaks) ? '<br/>' : '') + this.errorPrefix + this.arMessages[x] + this.errorPostfix;
				}
				break;
				
			case "radio":
			    bFocus = false;
			    elem = document.getElementById(this.arElements[x]+'_radio'); 
			    if (elem == null) {
					bGotMessageElement = false;
					elem = this.arElements[x]+'_radio';
				} 

				//get the radio group
				var grp = this.form.elements[this.arElements[x]];
				var selected = false;
				for (var i=0; i<grp.length; i++) {
				    if (grp[i].checked) {
				        selected = true;
				        break;
				    }
				}
				
				if (!selected) {
				    bError = true;
				    if (this.arMessages[x] != null)
				        sErrors += ((this.bLineBreaks) ? '<br/>' : '') + this.errorPrefix + this.arMessages[x] + this.errorPostfix;
				}
			    break;
		}
		
		var bAlert = true;
		
		if (!this.alertMoreThanOnce) {
		    //see if we have already alerted this element - may not want to do it more than once!
		    var bAlerted = false;
		    for (var i=0; i<this.arAlerted.length; i++) {
		        if (this.arAlerted[i] == this.arElements[x]) {
		            bAlerted = true;
		            bAlert = false;
		            break;
		        }
		    }
		    
		    if (!bAlerted && bError)
		        this.arAlerted[this.arAlerted.length] = this.arElements[x];
		}
		
		//do the label highlighting
        var label = null;
        if (this.highlightLabel)
           label = document.getElementById(this.arHighlight[x]);		
		
        if (this.highlightLabel && this.arHighlight[x] != null && label == null && this.debug)
            alert('VALIDATION DEBUG: Could not find label: ' + this.arHighlight[x] + ' for: ' + this.arElements[x]);

        //overall 'was there an error' property
		if (bError)
		    bAnyErrors = true;
		
		if (bError && bDefaultError && bAlert) {
            if (bGotMessageElement)
			    elem.style.display = this.messageDisplayType;
		    if (this.setClass)
			    elem.className = this.alertClass;
		    if (!bFocussed && bFocus) {
			    this.form.elements[this.arElements[x]].focus();
			    bFocussed = true;
		    }
		    
            if (label != null)
                label.className = this.alertClass;
		        
		} else if (!bError && !bAlerted) {
            if (bGotMessageElement)
		        elem.style.display = 'none';				
	        if (this.setClass)
		        elem.className = this.nonAlertClass;		
            if (label != null)
                label.className = this.nonAlertClass;
                
            if (label != null)
                label.className = '';
	    }
	    	
	    if (this.displayIndividualErrors) {
		    if (!bGotMessageElement && this.debug)
			    alert('VALIDATION DEBUG: Could not find ' + elem);
	    }
	}
	
	var generalMsgElement = document.getElementById(this.msgElement);
	if (bAnyErrors && this.displayGeneralError && generalMsgElement != null) {
        if (this.errorPrefix != null && this.errorPrefix.length > 0)
            sErrors = sErrors.substr(this.errorPrefix.length);

        if (this.bLineBreaks)
            sErrors = sErrors.substr(5);

        if (this.errorPostfix != null && this.errorPostfix.length > 0)
            sErrors = sErrors.substr(0, sErrors.length - this.errorPostfix.length);
        
        if (this.errorPreamble != null)
            sErrors = this.errorPreamble + sErrors;
        
        if (this.errorPostamble != null)
            sErrors = sErrors + this.errorPostamble;
        
        generalMsgElement.innerHTML = sErrors;
        generalMsgElement.style.display = 'block';
    } else if (this.displayGeneralError && generalMsgElement != null)
        generalMsgElement.style.display = 'none';
	
	if (!bAnyErrors) {
		return true;	
	} else {
		return false;
	}
}

function validatorLength(fieldPassword, fieldConfirm, msg) {
	if (fieldPassword.value.length < 6) {
		fieldPassword.className = this.alertClass;
		fieldConfirm.className = this.alertClass;
		fieldPassword.focus();
		this.msgElement.innerHTML = msg;
		return false;
	} else {
		return true;
	}
}

function validatorCompare(field1, field2) {
	if (field1.value != field2.value)
		return false;
	else
		return true;
}

function trim(value) {
   var temp = value;
   var obj = /^(\s*)([\W\w]*)(\b\s*$)/;
   if (obj.test(temp)) { temp = temp.replace(obj, '$2'); }
   var obj = /  /g;
   while (temp.match(obj)) { temp = temp.replace(obj, " "); }
   if(temp==' ')
      return ''
   else
      return temp;
}
