// <script>

	// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//  Form behavior: Validation
	// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	
	Form.Behavior.Validation = Class.create();	
	Form.Behavior.Validation.prototype = {
	
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		initialize : function(form, rules, callback) {
			this.form = $(form);
			this.rules = $H(rules);
			this.callback = callback;
			this.clearErrors();
			
			if (typeof rules == "string") this.getRulesFromXMLFile(rules, this.form.id);
			
			Event.observe(form, "submit", this.validateOnSubmit.bindAsEventListener(this));
			
			// *** removing this because it sometimes fires before fields have been shown/hidden
			//new Form.EventObserver(form, this.validateOnChange.bind(this));
		},
		
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		clearErrors : function() {
			// empty the error message array
			this.errors = [];
			
			// remove "error" class from all fields and fieldsets
			$A(this.form.getElementsByTagName("*")).each( function(el) {
				Element.removeClassName(el, "error");
			});
		},
		
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		getRulesFromXMLFile : function(fileName, formId) {
			new Ajax.Request(fileName, {
				method : "get",
				onSuccess : function(response, json) {
					this.buildRulesFromXMLDoc(response.responseXML, formId);
				}.bind(this)
			});		
		},
		
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		buildRulesFromXMLDoc : function(xmldoc, formId) {
			this.rules = $H();
			var formNodes = $A(xmldoc.getElementsByTagName("form"));
			var formNode = formNodes.find( function(node) { return (node.getAttribute("id") == formId) } );
			
			this.rules.enabled = true;
			this.rules.groups = new Array();
			
			var validationNode = formNode.getElementsByTagName("validationGroups")[0];
			$A(validationNode.getElementsByTagName("group")).each( function(groupNode) {

				var group = this.rules.groups[this.rules.groups.push(new Object()) - 1];
				group.enabled = (groupNode.getAttribute("enabled") != "false");
				group.name = groupNode.getAttribute("name");
				group.rules = new Array();

				$A(groupNode.getElementsByTagName("rule")).each( function(ruleNode) {

					var rule = group.rules[group.rules.push(new Object()) - 1];
					rule.test = ruleNode.getAttribute("test");				
					rule.message = ruleNode.getAttribute("message");

					rule.minimum = ruleNode.getAttribute("minimum");			
					rule.elements = new Array();	

					$A(ruleNode.getElementsByTagName("object")).each( function(objectNode) {

						rule.elements.push(objectNode.getAttribute("id"));
						
					});
				}.bind(this));
			}.bind(this));
		},
		
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		validateOnSubmit : function(e) {
			if (!this.validateForm()) Event.stop(e);
		},
		
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		validateOnChange : function() {
			// only validate on change if the form has already failed validation
			if (this.errors.length > 0) this.validateForm();
		},
		
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		validateForm : function() {
			this.clearErrors();
			if (this.rules.enabled) {
				this.rules.groups.each( function(group) {
					if (group.enabled) {
						group.rules.each( function(rule) {
						
							if (rule.minimum == null) {
								// validate the individual element rules
								rule.elements.each( function(element) {
									if (!this.validateField(element, rule.test)) this.addError(rule.message, [element]);
								}.bind(this));
							} else {
								// validate the group rule
								if (!this.validateGroupRule(rule)) this.addError(rule.message, rule.elements);
							}
							
						}.bind(this));
					}
				}.bind(this));
			}
			
			this.callback(this.errors);
			return (this.errors.length == 0)
		},
		
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		addError : function(message, elements) {
		  
		  message = message.replace('\\n','<br/>');
		  
			// add message to errors array
			this.errors.push({message:message,elements:elements});
			
			// style the elements with "error" class
			elements.each( function(element) {
				if (element = $(element)) {
					Element.addClassName(element, "error");
					
					// since we can't style checkboxes, we'll style the parent fieldset
					if (element.type == "checkbox") {
						var fieldset = Element.getParentByTagName(element, "fieldset");
						if (fieldset) Element.addClassName(fieldset, "error");
					}
				}
			});
		},
		
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		validateGroupRule : function(rule) {
      return isNaN(rule.minimum) ? this.validateCollectiveGroupRule(rule) : this.validateIndividualGroupRule(rule);
		},

		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
				
		validateIndividualGroupRule : function(rule){
			var numPassed = 0;
			rule.elements.each( function(elementId) {
				if (this.validateField(elementId, rule.test)) numPassed++;
			}.bind(this));
			
			var optional = (rule.test.indexOf(":optionalGroup") > -1)
			
			return ((optional && numPassed == 0) || numPassed >= rule.minimum);		  
		},

		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

		
		validateCollectiveGroupRule : function(rule){
		  values = rule.elements.collect( function(el){ return $F(el) })
		  return (Form.Behavior.Validators[rule.test](values))
		},
		
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		validateField : function(element, test) {
			element = $(element);
			value = $F(element) || "";
			var allow_hidden_field_validation = false;
			var hidden_field_pattern = /_hidden/gi
			
			if (test.search(hidden_field_pattern) >= 0) {
			  allow_hidden_field_validation = true;
			  test = test.replace(hidden_field_pattern,'')
			}
			
			if (!allow_hidden_field_validation && (element.disabled || element.getAttribute("readonly"))) return true;

			// test could contain additional parameters
			var testParams = test.split(/[=,:|]/);
			test = testParams.shift(); // extract the actual test, and we're left with any parameters

			return (Form.Behavior.Validators[test](value, testParams))
		},
		
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		enable : function(groupName) {
			this.toggle(groupName, true);
		},
		
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		disable : function(groupName) {
			this.toggle(groupName, false);		
		},
		
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		toggle : function(groupName, enable) {
			var group = this.rules.groups.find(function(group) { return group.name == groupName; });
			group ? group.enabled = enable : rules.enabled = enable;
		},
		
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		enableAll : function() {
			this.toggleAll(true);
		},
		
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		disableAll : function() {
			this.toggleAll(false);		
		},
		
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		toggleAll : function(enable) {
			this.rules.groups.each(function(group) {
				group.enabled = enable;
			});
		}
		
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
	} // end Form.Behavior.Validation



	// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//  Form behavior: Validators
	// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	
	Form.Behavior.Validators = {
		
		required : function(value) {
			return (value.replace(/^\s+|\s+$/g,'') != "") 
		},
			
		ghin_valid : function(value) {
		  return ($F('validGHIN') == '0');	  
		},

		properName : function(value) {
			return (value.match(/^[a-zA-Z\d\-'\.\,\s]*$/)) 
		},
		
		maxLength : function(value, params) {
			return (value== "" || value.length <= Number(params[0]));
		},
		
		minLength : function(value, params) {
			return (value== "" || value.length >= Number(params[0]));
		},
		
		alphaNumeric : function(value) {
			return (value.match(/^[a-zA-Z0-9]*$/));
		},
		
		numeric : function(value) {
			return (value.match(/^[0-9]*$/));
		},
		
		alphabetic : function(value) {
			return (value.match(/^[a-zA-Z ]*$/));
		},
		
		golfHandicapLessThanOrEqualTo : function(value, params){
		  return (value == "" || Number(value.sub(/\+/,'-')) <= Number(params[0]));
		},
		
		lessThan : function(value, params) {
			return (value == "" || Number(value) < Number(params[0]));
		},
		
		lessThanOrEqualTo : function(value, params) {
			return (value == "" || Number(value) <= Number(params[0]));
		},
		
		greaterThan : function(value, params) {
			return (value == "" || Number(value) > Number(params[0]));
		},
		
		greaterThanOrEqualTo : function(value, params) {
			return (value == "" || Number(value) >= Number(params[0]));
		},

		dateGreaterThanOrEqualTo : function(value, params) {
			return (value == "" || ( (new Date(value)) >= (new Date(params[0]))) );
		},	

		dateLessThanOrEqualTo : function(value, params) {
			return (value == "" || ( (new Date(value)) <= (new Date(params[0]))) );
		},
				
		email : function(value) {
			return (value == "" || value.match(/^([a-zA-Z0-9_.+-])+@(([a-zA-Z0-9-])+.)+([a-zA-Z0-9]{2,6})+$/));
		},
		
		zipCode : function(value) {
			return (value == "" || value.match(/^\d{5}(-\d{4})?$/));
		},
		
		state : function(value) {
			return (value == "" || value.match(/^(AL|AK|AR|AZ|CA|CO|CT|DC|DE|FL|GA|HI|ID|IL|IN|IA|KS|KY|LA|ME|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|OH|OK|OR|PA|RI|SC|SD|TN|TX|UT|VA|VT|WA|WV|WI|WY)$/i));
		},
		
		stateProvince : function(value) {
			return (value == "" || value.match(/^(AL|AK|AR|AZ|CA|CO|CT|DC|DE|FL|GA|HI|ID|IL|IN|IA|KS|KY|LA|ME|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|OH|OK|OR|PA|RI|SC|SD|TN|TX|UT|VA|VT|WA|WV|WI|WY|AB|BC|MB|NB|NL|NS|NT|NU|ON|PE|QC|SK|YT)$/i));
		},
		
		date : function(value) {
			var dateParts = value.split("/");
			if (dateParts.length != 3) return false;
			var date = new Date(dateParts[2], dateParts[0]-1, dateParts[1]);
			return (date.getFullYear() == Number(dateParts[2]) && date.getMonth() == Number(dateParts[0]-1) && date.getDate() == Number(dateParts[1]));
		},
		
		regexp : function(value, params) {
			var regexp = new RegExp(params[0], (params.length > 1 ? params[2] : null));
			return (value == "" || value.match(regexp));
		},
		
		equals : function(value, params) {
			return (value == params[0]);
		},
		
		noSpaces : function(value, params){
			return (value == "" || !(value.match(/\s/)));			
		},
		
		doesNotEqual : function(value, params) {
			return (value != params[0]);
		},
		
		maxSelected : function(value, params) {
			return (value.split(",").length <= Number(params[0]));
		},
		
		minSelected : function(value, params) {
			return (value.split(",").length <= Number(params[0]));
		},
		
		unique : function(values){
		  return (values.without("").uniq().size() == values.without("").size())
		},
		
		notUnique : function(values){
		  return !(values.without("").uniq().size() != values.without("").size())
		}
		
	}; // end Form.Behavior.Validators
			



