
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.arValidationGroup = new Array(); //assigns the validator to a group
	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.debugToConsole = false;
	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
	this.validationGroup = null; //the validation group to validate
	this.skipValidation = false; //set to true in order to skip validation
}

Validator.prototype.addField = validatorAddField;
Validator.prototype.removeField = validatorRemoveField;
Validator.prototype.addCompare = validatorAddCompare;
Validator.prototype.validate = validatorValidate;
Validator.prototype.validateLength = validatorLength;
Validator.prototype.compare = validatorCompare;
Validator.prototype.clear = validatorClear;

function validatorAddField(fieldName, type, validationGroup, selectCheck, params, highlight, message) {
	this.arElements[this.arElements.length] = fieldName;
	this.arValidationType[this.arValidationType.length] = type;
	this.arValidationGroup[this.arValidationGroup.length] = validationGroup;
	//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, validationGroup, highlight1, highlight2) {
	this.arElements[this.arElements.length] = fieldName1;
	this.arElements[this.arElements.length] = fieldName2;
	this.arValidationType[this.arValidationType.length] = 'compare1';
	this.arValidationType[this.arValidationType.length] = 'compare2';
	this.arValidationGroup[this.arValidationGroup.length] = validationGroup;
	this.arValidationGroup[this.arValidationGroup.length] = validationGroup;
	this.arHighlight[this.arHighlight.length] = highlight1;
	this.arHighlight[this.arHighlight.length] = highlight2;
}

function validatorRemoveField(fieldName) {
    if (window.gecko && typeof(window["console"]) != 'undefined' && this.debug)
        console.log('\n\nremove field ' + fieldName);
        
    for (x=this.arElements.length-1; x>=0; x--) {
        if (this.arElements[x] == fieldName) {
            
            if (this.arValidationType[x] == 'compare1')
                iterate = 2;
            else
                iterate = 1;

            if (window.gecko && typeof(window["console"]) != 'undefined' && this.debug)
                console.log('found field ' + fieldName + ', iterate: ' + iterate);
            
            for (y=0; y<iterate; y++) {
                if (y == 0 && this.arValidationType[x] == 'compare2') { } //skip as we want to delete compares from the first element, not the second
                else { 
                    if (window.gecko && typeof(window["console"]) != 'undefined' && this.debug) {
                        console.log('removing field ' + fieldName + ' iteration ' + y);
                        console.log('remove:\narElements.length: ' + this.arElements.length + '\n' + this.arElements[x] + '\n' + this.arValidationType[x] + '\n' + this.arValidationGroup[x] + '\n' + this.arParams[x]);
                    }
                    this.arElements.splice(x, 1);
	                this.arValidationType.splice(x, 1);
	                this.arValidationGroup.splice(x,1);
	                this.arSelectCheck.splice(x,1);
	                this.arParams.splice(x,1);
	                this.arMessages.splice(x,1);
	                this.arHighlight.splice(x,1);
	            } 
	        }
        }
        else
        {
             if (window.gecko && typeof(window["console"]) != 'undefined' && this.debug)
                console.log('non-matching field ' + this.arElements[x]);
        }
    } 
}

function validatorClear() {
	for (var x=0; x<this.arElements.length; x++) {
		var elem = null;
		var label = null;
		
		switch (this.arValidationType[x]) {
			case "notnull":
				elem = $(this.arElements[x]+'_notnull');
				break;
				
			case "email":
				elem = $(this.arElements[x]+'_email');
				break;
				
			case "compare":
				elem = $(this.arElements[x]+'_compare');
				break;

			case "postcode":
				elem = $(this.arElements[x]+'_postcode');
				break;

			case "select":
				elem = $(this.arElements[x]+'_select');
				break;

			case "checkbox":
				elem = $(this.arElements[x]+'_checkbox');
				break;
			
			case "radio":
				elem = $(this.arElements[x]+'_radio');
				break;
					
			case "minlength":
				elem = $(this.arElements[x]+'_minlength');
				break;
				
			case "number":
				elem = $(this.arElements[x]+'_number');
				break;
		}
		
		label = $(this.arHighlight[x]);
		
		if (elem != null && elem)
			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 (this.skipValidation)
	    return true;
	
	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;
	
	if (window.gecko && typeof(window["console"]) != 'undefined' && this.debug)
	    this.debugToConsole = true;
	
	if (this.debugToConsole)
	    if (this.validationGroup != '')
            console.log('starting iteration of arElements for validationGroup: '+this.validationGroup);
        else
            console.log('starting iteration of arElements');
                
	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.arElements[x]);
	    if (input == null)
	        alert('VALIDATION ERROR: ' + this.arElements[x] + ' does not exist');
		
	    if (this.debugToConsole)
            console.log('processing field '+this.arElements[x]+', validationType '+this.arValidationType[x]+', validationGroup '+this.arValidationGroup[x]);
                
        switch (this.arValidationType[x]) {
	        case "notnull":
		        //get the message element
		        elem = $(this.arElements[x]+'_notnull');
		        if (elem == null) {
			        bGotMessageElement = false;
			        elem = this.arElements[x]+'_notnull';
		        }
					
		        if (trim($(this.arElements[x]).value) == '') {
			        bError = true;
			        
			        if (this.debugToConsole)
                        console.log('field is null');
                        
			        if (this.arMessages[x] != null)
				        sErrors += ((this.bLineBreaks) ? '<br/>' : '') + this.errorPrefix + this.arMessages[x] + this.errorPostfix;
		        } else
		            if (this.debugToConsole)
                        console.log('field is not null: val: '+$(this.arElements[x]).value); 
		        break;
				
	        case "compare1":
	        case "compare2":
		        elem = $(this.arElements[x]+'_compare');
		        if (elem == null) {
			        bGotMessageElement = false;
			        elem = this.arElements[x]+'_compare';
		        }
			
		        if (!this.compare($(this.arElements[x]), $(this.arElements[x+1])))
			        bError = true;
		        else {
		            //need to set the class on the comparator
			        if (this.setClass) {
				        if ($(this.arElements[x+1]))
					        $(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 = $(this.arElements[x]+'_email');
		        if (elem == null) {
			        bGotMessageElement = false;
			        elem = this.arElements[x]+'_email';
		        }
							
		        if (trim($(this.arElements[x]).value) != '') {
			        result = $(this.arElements[x]).value.match(/^[^@]+@[^@]+\.[^@]+$/);
			        if (result!=$(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 = $(this.arElements[x]+'_number');
		        if (elem == null) {
			        bGotMessageElement = false;
			        elem = this.arElements[x]+'_number';
		        }
							
		        if (trim($(this.arElements[x]).value) != '') {
			        result = $(this.arElements[x]).value.match(/^[0-9,\.]+$/);
			        if (result!=$(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 = $(this.arElements[x]+'_int');
		        if (elem == null) {
			        bGotMessageElement = false;
			        elem = this.arElements[x]+'_int';
		        }
							
		        if (trim($(this.arElements[x]).value) != '') {
			        result = $(this.arElements[x]).value.match(/^[0-9]+$/);
			        if (result!=$(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 = $(this.arElements[x]+'_select');
		        if (elem == null) {
			        bGotMessageElement = false;
			        elem = this.arElements[x]+'_select';
		        }
							
		        //get the drop down
		        var dd = $(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 = $(this.arElements[x]+'_minlength');
		        if (elem == null) {
			        bGotMessageElement = false;
			        elem = this.arElements[x]+'_select';
		        }
				
		        var sValue = trim($(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 = $(this.arElements[x]+'_postcode');
		        if (elem == null) {
			        bGotMessageElement = false;
			        elem = this.arElements[x]+'_postcode';
		        }
							
		        if (trim($(this.arElements[x]).value) != '') {
			        result = $(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.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 = $(this.arElements[x]+'_checkbox');
		        if (elem == null) {
			        bGotMessageElement = false;
			        elem = this.arElements[x]+'_checkbox';
		        }
							
		        //get the checkbox
		        var chk = $(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 = $(this.arElements[x]+'_radio'); 
	            if (elem == null) {
			        bGotMessageElement = false;
			        elem = this.arElements[x]+'_radio';
		        } 

		        //get the radio group
		        var grp = $(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;
	        } //end switch statement
		
		    var bAlert = true;
		    
		    if ((this.arValidationGroup[x] == this.validationGroup) || this.validationGroup == null) {    		
		        if (this.debugToConsole)
                    console.log('field is part of current validation group');
                        
		        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 = $(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.debugToConsole)
			                console.log('setting '+elem.id+' to display.');
		            } 
		            if (this.setClass)
			            elem.className = this.alertClass;
		            if (!bFocussed && bFocus && !$(this.arElements[x]).disabled) {
			            $(this.arElements[x]).focus();
			            bFocussed = true;
		            }
        		    
                    if (label != null)
                        label.className = this.alertClass;
        		        
		        } else if (!bError) {
                    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 = '';
	            }
	        } else {
	            //this element isn't part of the validationGroup being validated
	            if (this.debugToConsole)
                    console.log('field is NOT part of current validation group');
                     
	            if (bGotMessageElement)
		            elem.style.display = 'none';
	        }
    	    	
	        if (this.displayIndividualErrors) {
		        if (!bGotMessageElement && this.debug)
			        alert('VALIDATION DEBUG: Could not find ' + elem);
	        }
	    } //end for loop
    	
	var generalMsgElement = $(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) {
	    //alert('no error');
		return true;	
	} else {
	    //alert('error');
		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;
}