
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 = true; //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;
	
    if (typeof(window["console"]) != 'undefined' && this.debug)
	    console.log('add new field:\n' + fieldName + 'of type: ' + type + '\narElements.length: ' + this.arElements.length + '\narValidationType.length: ' + this.arValidationType.length + '\narValidationGroup.length: ' + this.arValidationGroup.length + '\narSelectCheck.length: ' + this.arSelectCheck.length + '\narParams.length: ' + this.arParams.length);
}

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;
	this.arParams[this.arParams.length] = '';
	this.arParams[this.arParams.length] = '';
}

function validatorRemoveField(fieldName) {
    if (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 (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 (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 (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 = 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)
			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 (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.form.elements[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 = document.getElementById(this.arElements[x]+'_notnull');
			    if (elem == null) {
				    bGotMessageElement = false;
				    elem = this.arElements[x]+'_notnull';
			    }
					
			   if (this.form.elements[this.arElements[x]].disabled == false) { 
			    if (trim(this.form.elements[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.form.elements[this.arElements[x]].value); 
			    }
			    break;
				
	        case "compare1":
	        case "compare2":
			    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) != '') {
		            //regex from http://www.regular-expressions.info/email.html 
				    result = this.form.elements[this.arElements[x]].value.match(/^[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+(?:[A-Z]{2,4}|museum)$/i);
				    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.disabled == false) { 
			    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]+'_minlength';
			    }
		       
		        if (this.debugToConsole)
		            console.log('minlength for ' + this.arElements[x] + ': val = ' + this.form.elements[this.arElements[x]].value + ', minlength = ' + this.arParams[x]); 
				
			    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;
		    } //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 = 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.debugToConsole) {
			                console.log('setting '+elem.id+' to display: display value = ' + this.messageDisplayType);
			                console.log(elem.id + '.style.display = ' + elem.style.display); 
			            } 
		            } 
		            if (this.setClass)
			            elem.className = this.alertClass;
		            if (!bFocussed && bFocus && !this.form.elements[this.arElements[x]].disabled) {
			            this.form.elements[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 = 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 (this.debugToConsole)
	    console.log('VALIDATION: errors = ' + bAnyErrors);
                    
	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;
   if (temp != null) {
       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;
   }
}
