/** 
 * @fileOverview Form validation.
 * @author Oliver Bishop / Tom McCourt
 * @version 0.3.0
 * @changeLog Wow - a lot: fixed the "scroll to the error message", rejigged how the message are display (now absolutely) incase the containing element has position: relative (which will bugger up the layout). Also added a "quickValidation" config parameter to turn on validation on field blur events.
 */

/* This sets the global namespaces */
var UKISA = UKISA || {};
UKISA.widget = UKISA.widget || {};

/**
 * Create a form validation instance with localisation and fancy error messages.
 *
 * @constructor
 * @param config Custom configuration settings.
 * @param config.showAllErrors By default only the first error will show up. Set to true this will show all errors for a given field.
 */
UKISA.widget.FormValidation = function(formId, options) {
	var i, instance, locale, Event;

	// Default configuration.
	this.config = {
		showAllErrors: false,
		quickValidation: false, // If set to true this will validate the field on blur.
		debug: false,
		display: null,
		callback: null,
		scrollTo: true
	};

	// This hold all the form information.
	this.context = {
		form:  null, // The <form> element.
		log:   [],	 // Logging errors and messages.
		rules: {},	 // Holds all validation rules.
		errors: 0,	 // Total number of errors created.
		builder: null,  // Used for the Validation Builder.
		failure: false, // Used for single error messages.
		errored: [], // Collection of elements that have failed validation
		submit: null // The submit button
	};	

	this.validateFieldUpdate = function(e) {
		var event;
		e = e || window.event;
		event = (e.keyCode) ? e.keyCode : e.which;

		instance.log("KeyUp event: " + event);
		// Exclude tabs and shift tab
		if (event !== 9 && event !== 16) {
			instance.log("Validate from data change event: " + String.fromCharCode(event));
			instance.validate(el);
		}
	};

	// Get the form.
	el = document.getElementById(formId);
	if (!el) {
		this.log("Cannot find form: " + formId); 
		return; 
	};

	instance = this;
	Event = YAHOO.util.Event;
	this.context.form = el;

	Event.addListener(this.context.form, "submit", function(e) {
		var valid = instance.validate();

		if (valid) {
			instance.log("The form is valid");
			if (typeof instance.config.callback === "function") {
				instance.log("Callback: " + instance.config.callback);
				if (window.console) {
					console.info(instance.config.callback);
				}
				return instance.config.callback.call(instance, e);
			}
			return true;
		} else {
			instance.log("The form is NOT valid");
			Event.preventDefault(e);
			Event.stopEvent(e);
			return false;
		} 
	}); 


	this.log("Found form: " + el.getAttribute("id"));

	if (typeof UKISA.locale !== "undefined") {
		// Get the l10n values if specified in the config.
		locale = UKISA.locale.get("widget.FormValidation");

		this.log("Found locale settings.");

		// Look for locale specific error messages.
		if (locale && locale.messages) {
			this.config.messages = locale.messages;
		} else {
			this.config.messages = UKISA.widget.FormValidation.messages;
		}

		// Look for locale specific error messages.
		if (locale && locale.display) {
			this.config.display = locale.display;
		}
	} else {
		this.log("No locale settings.");
		this.config.messages = UKISA.widget.FormValidation.messages;
	}

	// Update the default configuration.
	for (var i in options) {
		if (typeof this.config[i] !== "undefined") {
			this.config[i] = options[i];
		}
	}

	if (this.config.debug) {
		window.onbeforeunload = function() {
			return false;
		};
	}

	// Make a dummy error box to preload the error messages CSS images.
	var canvas, errorHeader, errorContent, errorFooter, close, closeLink;

	canvas = document.createElement("div");

	errorHeader = canvas.cloneNode(false);
	errorContent = canvas.cloneNode(false);
	errorFooter = canvas.cloneNode(false);

	canvas.className = "validation-error";
	canvas.style.position = "absolute";
	canvas.style.left = "-999em";

	errorHeader.className = "validation-error-header";
	errorContent.className = "validation-error-content";
	errorFooter.className = "validation-error-footer";

	close = document.createElement("p");
	close.className = "close";
	
	closeLink = document.createElement("a");

	close.appendChild(closeLink);
	canvas.appendChild(errorHeader);
	errorContent.appendChild(close);	
	canvas.appendChild(errorContent);
	canvas.appendChild(errorFooter);
	document.body.appendChild(canvas);

	// Put it on the page to have the styles applied and initiate the image requrests, then hide it.
	canvas.style.display = "none";

	// Now get rid of it.
	document.body.removeChild(canvas);
};

/**
 * Flag to determine if the form is valid.
 */
UKISA.widget.FormValidation.prototype.isValid = false;

UKISA.widget.FormValidation.prototype = {
	/**
	 * Disable a validation rule on an element.
	 */
	disable: function(el) {
		if (this.context.rules[el]) {
			this.context.rules[el]["active"] = false;
			this.log("Disabled element: " + el);
		} else {
			this.log("Cannot disable element: " + el);
		}
	},
	/**
	 * Reenable a validation field.
	 */
	enable: function(el) {
		if (this.context.rules[el]) {
			this.context.rules[el]["active"] = true;
			this.log("Enabled element: " + el);
		} else {
			this.log("Cannot enable element: " + el);
		}
	},
	/**
	 * Adds a validation rule to the collection.
	 */
	add: function(el) {
		var instance, builder;

		instance = this;

		builder = {
			is: function(rule, args) {
				if (instance.context.builder.el) {
					instance.context.builder.options[rule] = args;
					instance.context.builder.lastOption = rule;
				}

				instance.log("Validation builder: " + instance.context.builder.el + ", is: " + rule);

				return this;
			},
			andIs: function(rule, args) {
				this.is.apply(this, arguments);

				return this;
			},
			withMessage: function(msg) {
				if (typeof instance.context.builder.options.message === "undefined") {
					instance.context.builder.options.message = {}
				}
				instance.context.builder.options.message[instance.context.builder.lastOption] = msg;

				return this;
			}
		};

		if (!this.context.builder) {
			this.context.builder = {
				el: null,
				options: {},
				lastOption: ""
			};
		}

		// Add the new rule
		if (this.context.builder.el) {
			this.rule(this.context.builder.el, this.context.builder.options);
		}

		// Reset
		this.context.builder.el = el;
		this.context.builder.options = {};
		this.context.builder.lastOption = "";

		this.log("Validation builder: " + el);

		return builder;
	},
	/** 
     * Add a form input element to the validation collection.
	 *
	 * @returns {UKISA.widget.FormValidation} Returns instance of the FormValidation object for method chaining.
	 */
	rule: function(el, options) {
		var instance, Event, validate, node, nodeType, nodeCollection, i, event;

		// Expose object to closure
		instance = this;

		// Get YUI.
		Event = YAHOO.util.Event;

		// If there is no form then don't add any rules.
		if (!this.context.form) {
			return;
		}

		if (this.context.form[el]) {
			this.log("Found form input: " + el);
			
			this.context.rules[el] = options;

			// Add the enable/disable flag.

			this.context.rules[el]["active"] = true;

			if (typeof this.context.form[el].nodeName !== "undefined") {
				// If the element is NOT a radio/checkbox
				node = this.context.form[el];
			} else {
				// If the element IS a radio/checkbox
				node = this.context.form[el][0];
			}

			if (typeof options.events !== "undefined") {
				
				for (event in options.events) {
					if (options.events.hasOwnProperty(event)) {
						if (event !== "submit") {
							Event.addListener(node, event, this.validateFieldUpdate); 
						}
					}
				}

			} else {

				nodeType = node.getAttribute("type");

				// Sort out the event handlers to control when to validate the form values.
				switch (node.nodeName.toLowerCase()) {
					case "select":
						node.onchange = this.validateFieldUpdate;
					break;
					case "input":
						if (nodeType === "checkbox" || nodeType === "radio") {
							nodeCollection = this.context.form[el];
				
							// A collection of radio/checkboxes a returned as an array and so we need to loop.
							if (typeof nodeCollection.length !== "undefined" && this.config.quickValidation) {
								for (i = 0, ix = nodeCollection.length; i < ix; i++) {
									Event.addListener(nodeCollection[i], "click", this.validateFieldUpdate); 
								}
							} else {
								// A sinlge checkbox (like a "I have read the T&Cs") return the HTMLObject.
								
								if (this.config.quickValidation) {
									Event.addListener(nodeCollection, "click", this.validateFieldUpdate);
								}
							}
						} else if (nodeType === "password") {
							// To prevent annoyances and for comfirming 2 passwords.
							if (this.config.quickValidation) {
								Event.addListener(node, "blur", this.validateFieldUpdate);
							}
						} else {
							// A normal textbox.
							//Event.addListener(node, "keyup", validate); Remove this for now until
							// I put in the events: {
							//		email: ["keyup", "blur"],
							//		required: "blur"
							// }

							// This is for the case when an option from autocomplete is selected as no other event is fired.
							
							if (this.config.quickValidation) {
								Event.addListener(node, "blur", this.validateFieldUpdate);
							}
						}
					break;
					case "textarea":
						//Event.addListener(node, "keyup", validate);

						// This is for the case when an option from autocomplete is selected as no other event is fired.
						if (this.config.quickValidation) {
							Event.addListener(node, "blur", validate);
						}
						break;
					default:
						this.log("Unkown element node type.");
					break;	
				}		
			}
		} else {
			this.log("Cannot find form input: " + el);
		}

		return this;
	},
	/**
	 * Validate the form.
	 *
	 * @returns {Boolean} If the form passes validation.
	 */
	validate: function(el) {
		var instance, validators, messages, validate;

		// If there is no form then don't add any rules.
		if (!this.context.form) {
			return true;
		}

		// Add the new rule for the Validation Builder.
		if (this.context.builder && this.context.builder.el) {
			this.rule(this.context.builder.el, this.context.builder.options);
		}

		// Expose to closure.
		instance = this;

		// Shorthand the validators.
		validators = UKISA.widget.FormValidation.validators;

		// Shorthand the validators.
		messages = this.config.messages;

		// Reset error count.
		this.context.errors = 0;

		// Store the elements that have errors
		this.context.errored = [];

		// Validate the form input.
		validate = function(el) {
			var rule, options, input, errors, i, args, message, errorMessage;

			// Get the validation rules for a form element
			options = instance.context.rules[el]

			if (typeof options["active"] !== "undefined" && options["active"] == false) {
				instance.log("This element has been disabled: " + el);

				// Remove the error message if there is one.
				errorMessage = document.getElementById("error-" + instance.context.form.id + "-" + el);
				YAHOO.util.Event.purgeElement(errorMessage, true);

				if (errorMessage) {
					errorMessage.parentNode.removeChild(errorMessage);
				}
				return;
			}

			input = instance.context.form[el];
			
			errors = [];

			i = 0;

			for (rule in options) {
				if (rule !== "messages" && rule !== "errors" && rule !== "events" && rule !== "display" && rule !== "active" && rule !== "onError" && rule !== "onSuccess") {
		
					// Failure is used to determine if the validation has failed to only show one element at a time.
					instance.context.failure = false;

					if (typeof validators[rule] !== "undefined") {

						args = options[rule];
					
						if (validators[rule].call(instance, input, input.value, args)) {
							instance.log("Validating: " + el + " with rule: " + rule + " and status: passed");
						
							if (options["onSuccess"]) {
								options["onSuccess"].call(instance, input);
							}

						} else {
							instance.log("Validating: " + el + " with rule: " + rule + " and status: failed");

							instance.context.errored.push(el);

							instance.context.failure = true;

							if (typeof options.messages !== "undefined" && typeof options.messages[rule] !== "undefined") {
								message = options.messages[rule];
								instance.log("Using custom message: " + message);
							} else {
								if (typeof messages[rule] !== "undefined") {
									message = messages[rule];
								} else {
									instance.log("Default message not found");
								}
							}	
							
							// Store the error messages.
							errors.push(message.replace(/\{[0-9]\}/g, 
								function(match) {
									// Add one as the first argument is the placeholder string
									var i = parseInt(match.charAt(1));
									
									// Check that the array has the index
									if (typeof args === "object") {
										// Fixed this to not evaluate int 0 as false.
										if (typeof args[i] !== "undefined") {
											return args[i];
										} else {
											return "";
										}
									} else {
										return args;
									}
								}
							));

							// Increment error count.
							i++;
						}

					} else {
						instance.log("Cannot find validator: " + rule + " for: " + el);
					}

					// Exceptions here to change the events after the form element has been used for the first time.

					// These are exceptions for specific validation rules for better usability.
					if (rule === "compare") {
						instance.log("Exception for 'compare': add onkeychange");
						input.onkeyup = input.onblur;
					}		
				}
			}

			// Add the error messages and number.
			instance.context.rules[el].errors = {
				length: i,
				messages: errors
			}
			
			// Add to global error count.
			instance.context.errors += i;

			// Draw the error messages for the form input.
			instance.render(el);
		};
		
		if (el) {
			// Validate a single element.
			validate(el);
			return false;
		} else {
			// Loop throught and validate the entire validation group.
			for (el in this.context.rules) {
				validate(el);	
				if (!this.config.showAllErrors && this.context.failure) {
					break;
				}
			}

			this.isValid = (this.context.errors === 0);
			
			if (!this.isValid && this.config.scrollTo) {
				//console.info(this.context.errored);

				var dummy = document.getElementById("form-validation-dummy");
				
				if (!dummy) {
					this.context.submit = YAHOO.util.Selector.query("input.submit", this.context.form, true);
					dummy = document.createElement("div");
					dummy.id = "form-validation-dummy";
					dummy.style.position = "absolute";
					dummy.style.left = "-999em";
					document.body.appendChild(dummy);
				}

				var pageX = YAHOO.util.Dom.getDocumentScrollTop();

				dummy.style.top = pageX;

				var submitClip = YAHOO.util.Dom.getRegion(this.context.submit);

				var region = YAHOO.util.Dom.getRegion(document.getElementById("error-" + instance.context.form.id + "-" + this.context.errored[0]));
	
				var scroller = new YAHOO.util.Anim("form-validation-dummy", { top: {from: pageX, to: region.top - 20} }, 1, YAHOO.util.Easing.easeOut); 

				scroller.onTween.subscribe(function(s, o) { 
					window.scrollTo(0, Math.ceil(this.getAttribute("top")));		
				}); 

				scroller.animate();
			}

			return this.isValid;
		}				
	},
	render: function(el) {
		var error, errorId, errors, input, parent, canvas, errorHeader, errorFooter, errorContent, canvasList, canvasItem, message, canvasMessage, inputClip, canvasClip;

		// Shorthand the errors
		errors = this.context.rules[el].errors;

		// Get the form element.
		input = this.context.form[el];

		// If nodeName is not defined then the element is a radio/checkbox
		if (typeof input.nodeName === "undefined") {
			input = input[0];
		}

		// Get the form element container.
		parent = input.parentNode;	
		
		// Set the error Id.
		errorId = "error-" + this.context.form.id + "-" + el;
		
		// Remove the old error.
		error = document.getElementById(errorId);
		if (error) {
			error.parentNode.removeChild(error);
		}

		canvas = document.createElement("div");

		errorHeader = canvas.cloneNode(false);
		errorContent = canvas.cloneNode(false);
		errorFooter = canvas.cloneNode(false);

		canvas.className = "validation-error";
		canvas.id = errorId;

		errorHeader.className = "validation-error-header";
		errorContent.className = "validation-error-content";
		errorFooter.className = "validation-error-footer";

		var close = document.createElement("p");
		close.className = "close";
		
		var closeLink = document.createElement("a");
		closeLink.innerHTML = "Close";
		closeLink.title = "Close";
		close.appendChild(closeLink);
		YAHOO.util.Event.addListener(closeLink, "mousedown", function(ev) {
			var error = document.getElementById(errorId);
			error.parentNode.removeChild(error);

			YAHOO.util.Event.preventDefault(ev);
			YAHOO.util.Event.stopEvent(ev);
		});


		canvasList = document.createElement("ul");
		canvasItem = document.createElement("li");

		if (errors.length) {
			for (message in errors.messages) {
				if (errors.messages.hasOwnProperty(message)) {
					this.log("Form element: " + el + " has the error message: " + errors.messages[message]);

					// Display error message.
					canvasMessage = canvasItem.cloneNode(true);
					canvasMessage.appendChild(document.createTextNode(errors.messages[message]));
					canvasList.appendChild(canvasMessage);
				}
			}

			errorContent.appendChild(canvasList);
			canvas.appendChild(errorHeader);
			canvas.appendChild(errorContent);
			canvas.appendChild(errorFooter);
			canvas.appendChild(close);	
			
			// Now do any custom display settings.
			var display = this.context.rules[el].display;

			// Test to see if it needs to be appended somewhere different.
			if (false && typeof display !== "undefined" && (display.insertBefore || display.insertAfter)) {
				var selector = YAHOO.util.Selector.query || document.getElementById;
				
				if (display.insertBefore) {
					parent = selector(display.insertBefore);
					if (parent) {
						YAHOO.util.Dom.insertBefore(canvas, parent);
					}
				} else if (display.insertBefore) {
					parent = selector(display.insertBefore);
					if (parent) {
						YAHOO.util.Dom.insertBefore(canvas, parent);
					}
				}

				this.log("Custom error message DOM insertion.");
			} else {
				parent.insertBefore(canvas, input);
			}

			parent.appendChild(canvas);
	
			// Get the dimensions of the error message.
			canvasClip = YAHOO.util.Dom.getRegion(canvas);

			// Get the position of the form element.
			var inputClip = YAHOO.util.Dom.getRegion(input);

			var canvasX = inputClip.left;
			var canvasY = inputClip.top - canvasClip.height;

			
			if (YAHOO.util.Dom.getStyle(parent, "position") === "relative") {
				this.log("Container has a relative position.");	
			
				canvasX = 0;
				canvasY = canvasClip.height * -1;
			}

			// There must be a more elegant way to do this but I'm tired.
			if (this.config.display || typeof display !== "undefined") {
				this.log("Custom display for: " + el);

				var xPosition = "left";
				var yPosition = "top";

				if (this.config.display && this.config.display.xPosition) { xPosition = this.config.display.xPosition; }
				if (this.config.display && this.config.display.yPosition) { yPosition = this.config.display.yPosition; }
				
				if (display && display.xPosition) { yPosition = display.xPosition; }
				if (display && display.yPosition) { yPosition = display.yPosition; }

				var xOffset = 0;
				var yOffset = 0;

				if (this.config.display && this.config.display.xOffset) { xOffset = this.config.display.xOffset; }
				if (this.config.display && this.config.display.yOffset) { yOffset = this.config.display.yOffset; }

				if (display && display.xOffset) { xOffset = display.xOffset; }
				if (display && display.yOffset) { yOffset = display.yOffset; }

				if (xPosition === "right") {
					canvasX	= inputClip.left + inputClip.width;
				}

				if (xOffset) {
					canvasX += xOffset;
				}

				if (yOffset) {
					canvasY += canvasClip.height + yOffset;
				}
			}

			// Set the final top position.
			canvas.style.left = canvasX + "px";
			canvas.style.top = canvasY + "px";

			// Now call an error callback.					
			if (this.context.rules[el]["onError"]) {
				this.context.rules[el]["onError"].call(this, this.context.form[el]);
			}
		}
	},
	/**
	 * Log errors and messages.
	 *
	 * @param {String} s Message.
	 */
	log: function(s) {
		if (s) {
			if (window.console) {
				console.log(s);
			} else {
				if (this.config.debug) {
					var p = document.createElement("p");
					p.innerHTML = s;
					document.body.appendChild(p);
				}
			}
			this.context.log.push(s);
		}
	}
};


UKISA.widget.FormValidation.filters = {
	/**
	 * Remove trailing whitespace from a string.
	 */
	trim: function(s) {
		return s.replace(/^\s+|\s+$/g, "");
	},
	/**
	 * Remove all whitespace from a string.
	 */
	strip: function(s) {
		return s.replace(/\s+/g, "");
	}
};

UKISA.widget.FormValidation.validators = {	
	/**
	 * Data that is valid as long as it does not equal a value.
	 */
	not: function(el, v, arg) {
		var i, ix;

		for (i = 0, ix = arg.length; i < ix; i++) {
			if (v === arg[i]) {
				return false;
			}
		}

		return true;
	},
	/**
	 * Test for ensuring a text box has a value and radio/checkbox have had at least one option checked.
	 */
	required: function(el, v, arg) {
		if (typeof el.nodeName === "undefined") {
			return UKISA.widget.FormValidation.validators.checked(el, v, arg);
		} else {
			if (el.getAttribute("type") === "checkbox" || el.getAttribute("type") === "radio") {
				return UKISA.widget.FormValidation.validators.checked(el, v, arg);	
			} else {
				return (v.length > 0) ? true : false;
			}
		}
	},
	/**
	 * Ensure that a radio/checkbox has at least one option checked.
	 */
	checked: function(el, v, arg) {
		if (typeof el.length !== "undefined") {
			for (var i = 0, ix = el.length; i < ix; i++) {
				if (el[i].checked) { return true; }
			}
			return false;
		} else {
			return (el.checked);
		}
	},
	/**
	 * Determines if a value lies between a range of characters.
	 */
	range: function(el, v, arg) {
		return (arg[1]) ? (arg[0] === 0) ? (v.length > 0 && v.length <= arg[1]) : (v.length >= arg[0] && v.length <= arg[1]) : (v.length >= arg[0]);
	},	
	/**
	 * Determines if an email address is in a legal format.
	 */
	email: function(el, v, arg) {
		v = v.toLowerCase();
		var exp = new RegExp(/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[(2([0-4]\d|5[0-5])|1?\d{1,2})(\.(2([0-4]\d|5[0-5])|1?\d{1,2})){3} \])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/);
		return exp.test(v);
	},
	
	luhn10: function(el, v, arg) {	

		/* Luhn algorithm number checker - (c) 2005-2009 - planzero.org            *
		 * This code has been released into the public domain, however please      *
		 * give credit to the original author where possible.                      */
 
		  // Strip any non-digits (useful for credit card numbers with spaces and hyphens)
		  var number=v.replace(/\D/g, '');
		 
		  // Set the string length and parity
		  var number_length=number.length;
		  var parity=number_length % 2;
		 
		  // Loop through each digit and do the maths
		  var total=0;
		  for (i=0; i < number_length; i++) {
			var digit=number.charAt(i);
			// Multiply alternate digits by two
			if (i % 2 == parity) {
			  digit=digit * 2;
			  // If the sum is two digits, add them together (in effect)
			  if (digit > 9) {
				digit=digit - 9;
			  }
			}
			// Total up the digits
			total = total + parseInt(digit);
		  }
		 
		  // If the total mod 10 equals 0, the number is valid
		  if (total % 10 == 0) {
			return true;
		  } else {
			return false;
		  }
 
	},

	nectar: function(el, v, arg) {	

		/* Luhn algorithm number checker - (c) 2005-2009 - planzero.org            *
		 * This code has been released into the public domain, however please      *
		 * give credit to the original author where possible.                      */
 
		// check if field filled
		if (v.length == 0) {
			return true;
		}
 
		  // Strip any non-digits (useful for credit card numbers with spaces and hyphens)
		  var number="98263000"+v.replace(/\D/g, '');
		 
		  // Set the string length and parity
		  var number_length=number.length;
		  var parity=number_length % 2;
		 
		  // Loop through each digit and do the maths
		  var total=0;
		  for (i=0; i < number_length; i++) {
			var digit=number.charAt(i);
			// Multiply alternate digits by two
			if (i % 2 == parity) {
			  digit=digit * 2;
			  // If the sum is two digits, add them together (in effect)
			  if (digit > 9) {
				digit=digit - 9;
			  }
			}
			// Total up the digits
			total = total + parseInt(digit);
		  }
		 
		  // If the total mod 10 equals 0, the number is valid
		  if (total % 10 == 0) {
			return true;
		  } else {
			return false;
		  }
 
	},

	postcode: function(el, v, arg) {	
		var exp, postcode;

		// check if field filled
		if (v.length == 0) {
			return false;
		}
		
		postcode = UKISA.widget.FormValidation.filters.strip(v.toUpperCase());
		
		if (!UKISA.widget.FormValidation.validators.alphaNumeric(postcode)) {
			return false;
		}
		
		// check postcode formatting
		exp = new RegExp("^[A-Z]{1,2}([0-9]{1,2}|[0-9][A-Z])[0-9][ABD-HJLNP-UW-Z]{2}$");
		
		// need to disable this final validation because NS4 doesn't evalute regexp properly
		if (!exp.test(postcode)) {
			return false;
		}

		return true;
	},
	/**
	 * Check to ensure that the value contains only numbers and letters.
	 */
	alphaNumeric: function(el, v, arg) {
		var exp = new RegExp("[^A-Za-z0-9]");
		return !exp.test(v);
	},
	/**
	 * Check to see that the value is a number (with possible decimal point).
	 */
	 numeric: function(el, v, arg) {
		var num = parseFloat(v);
		return (!isNaN(num) || v === "");
	},
	/**
	 * Check to see if the value is a valid telephone number.
	 */ 
	phoneNumber: function(el, v, arg) {
		var exp = new RegExp("^[0-9 ]*$");
		return exp.test(v);
	},
	compare: function(el, v, arg) {
		var compare;

		compare = this.context.form[arg];

		return (compare && compare.value === v);
	},
	min: function(el, v, arg) {
		return (v.length >= arg);
	},
	max: function(el, v, arg) {
		return (v.length < arg);
	},
	/** 
	 * Validate a given number of words.
	 */
	maxWords: function(el, v, arg) {
		var max, cleanString, words;

		max = arg;
		cleanString = "";

		// Trim leading and trailing spaces.
		cleanString = v.replace(/^\s+|\s+$/g, "");

		// Remove excessive spaces.
		cleanString = cleanString.replace(/\s+/g, " ");

		// Split up the words.
		words = cleanString.split(" ");

		return (words.length <= max);
	},
	/**
	 * Checks that a number is an integer.
	 */
	integer: function(el, v, args) {
		var num = parseInt(v);
		return (v.indexOf(".") < 0 && (!isNaN(num) || v === ""));
	},
	/**
	 * Tests that a number lies between a range of values. 
	 */
	between: function(el, v, args) {
		var num = (isNaN(v)) ? 0 : v;
		return (num >= args[0] && num <= args[1]);
	}
};

/**
 * Default validation messages.
 */
UKISA.widget.FormValidation.messages = {
	required: "This is a required field",
	range: "Please enter between {0} and  {1} characters",
	email: "This email address does not appear to be correct",
	postcode: "Please enter a valid UK postcode",
	luhn10: "Please check you have entered your card number correctly",
	nectar: "Please check you have entered your Nectar card number correctly",	
	compare: "Please ensure that this value is correct",
	min: "Please enter more than {0} characters",
	max: "Please enter less than {0} characters",
	maxWords: "You may only enter up to {0} words",
	integer: "Only whole numbers are accepted e.g. 14",
	between: "Please enter a number between {0} and  {1}",
	not: "Please enter the required information"
};
