/*
 * Really easy field validation with Prototype
 * http://tetlaw.id.au/view/javascript/really-easy-field-validation
 * Andrew Tetlaw
 * Version 1.5.4.1 (2007-01-05)
 * 
 * Copyright (c) 2007 Andrew Tetlaw
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * 
 */
var Validator = Class.create();

Validator.prototype = {
	initialize : function(className, error, test, options) {
		if (typeof test == 'function') {
			this.options = $H(options);
			this._test = test;
		} else {
			this.options = $H(test);
			this._test = function() {
				return true
			};
		}
		this.error = error || 'Validation failed.';
		this.className = className;
	},
	test : function(v, elm) {
		return (this._test(v, elm) && this.options.all( function(p) {
			return Validator.methods[p.key] ? Validator.methods[p.key](v, elm,
					p.value) : true;
		}));
	}
}
Validator.methods = {
	pattern : function(v, elm, opt) {
		return Validation.get('IsEmpty').test(v) || opt.test(v)
	},
	minLength : function(v, elm, opt) {
		return v.length >= opt
	},
	maxLength : function(v, elm, opt) {
		return v.length <= opt
	},
	min : function(v, elm, opt) {
		return v >= parseFloat(opt)
	},
	max : function(v, elm, opt) {
		return v <= parseFloat(opt)
	},
	notOneOf : function(v, elm, opt) {
		return $A(opt).all( function(value) {
			return v != value;
		})
	},
	oneOf : function(v, elm, opt) {
		return $A(opt).any( function(value) {
			return v == value;
		})
	},
	is : function(v, elm, opt) {
		return v == opt
	},
	isNot : function(v, elm, opt) {
		return v != opt
	},
	equalToField : function(v, elm, opt) {
		return v == $F(opt)
	},
	notEqualToField : function(v, elm, opt) {
		return v != $F(opt)
	},
	include : function(v, elm, opt) {
		return $A(opt).all( function(value) {
			return Validation.get(value).test(v, elm);
		})
	}
}

var Validation = Class.create();

Validation.prototype = {
	initialize : function(form, options) {
		this.options = Object.extend( {
			onSubmit :true,
			stopOnFirst :false,
			immediate :false,
			focusOnError :true,
			useTitles :false,
			onFormValidate : function(result, form) {
			},
			onElementValidate : function(result, elm) {
			}
		}, options || {});
		this.form = $(form);
		if (this.options.onSubmit)
			Event.observe(this.form, 'submit', this.onSubmit.bind(this), false);
		if (this.options.immediate) {
			var useTitles = this.options.useTitles;
			var callback = this.options.onElementValidate;
			Form.getElements(this.form).each( function(input) { // Thanks Mike!
						Event.observe(input, 'blur', function(ev) {
							Validation.validate(Event.element(ev), {
								useTitle :useTitles,
								onElementValidate :callback
							});
						});
					});
		}
	},
	onSubmit : function(ev) {
		if (!this.validate())
			Event.stop(ev);
	},
	validate : function() {
		var result = false;
		var useTitles = this.options.useTitles;
		var callback = this.options.onElementValidate;
		if (this.options.stopOnFirst) {
			result = Form.getElements(this.form).all( function(elm) {
				return Validation.validate(elm, {
					useTitle :useTitles,
					onElementValidate :callback
				});
			});
		} else {
			result = Form.getElements(this.form).collect( function(elm) {
				return Validation.validate(elm, {
					useTitle :useTitles,
					onElementValidate :callback
				});
			}).all();
		}
		if (!result && this.options.focusOnError) {
			Form.getElements(this.form).findAll( function(elm) {
				return $(elm).hasClassName('validation-failed')
			}).first().focus()
		}
		this.options.onFormValidate(result, this.form);
		return result;
	},
	reset : function() {
		Form.getElements(this.form).each(Validation.reset);
	}
}

Object
		.extend(
				Validation,
				{
					validate : function(elm, options) {
						options = Object.extend( {
							useTitle :false,
							onElementValidate : function(result, elm) {
							}
						}, options || {});
						elm = $(elm);
						var cn = elm.classNames();
						return result = cn.all( function(value) {
							var test = Validation.test(value, elm,
									options.useTitle);
							options.onElementValidate(test, elm);
							return test;
						});
					},
					test : function(name, elm, useTitle) {
						var v = Validation.get(name);
						var prop = '__advice' + name.camelize();
						try {
							if (Validation.isVisible(elm)
									&& !v.test($F(elm), elm)) {
								if (!elm[prop]) {
									var advice = Validation
											.getAdvice(name, elm);
									if (advice == null) {
										var errorMsg = useTitle ? ((elm && elm.title) ? elm.title
												: v.error)
												: v.error;
										advice = '<div class="validation-advice" id="advice-'
												+ name
												+ '-'
												+ Validation.getElmID(elm)
												+ '" style="display:none">'
												+ errorMsg + '</div>'
										switch (elm.type.toLowerCase()) {
										case 'checkbox':
										case 'radio':
											var p = elm.parentNode;
											if (p) {
												new Insertion.Bottom(p, advice);
											} else {
												new Insertion.After(elm, advice);
											}
											break;
										default:
											new Insertion.After(elm, advice);
										}
										advice = Validation
												.getAdvice(name, elm);
									}
									if (typeof Effect == 'undefined') {
										advice.style.display = 'block';
									} else {
										new Effect.Appear(advice, {
											duration :1
										});
									}
								}
								elm[prop] = true;
								elm.removeClassName('validation-passed');
								elm.addClassName('validation-failed');
								return false;
							} else {
								var advice = Validation.getAdvice(name, elm);
								if (advice != null)
									advice.hide();
								elm[prop] = '';
								elm.removeClassName('validation-failed');
								elm.addClassName('validation-passed');
								return true;
							}
						} catch (e) {
							throw (e)
						}
					},
					isVisible : function(elm) {
						while (elm.tagName != 'BODY') {
							if (!$(elm).visible())
								return false;
							elm = elm.parentNode;
						}
						return true;
					},
					getAdvice : function(name, elm) {
						return $('advice-' + name + '-'
								+ Validation.getElmID(elm))
								|| $('advice-' + Validation.getElmID(elm));
					},
					getElmID : function(elm) {
						return elm.id ? elm.id : elm.name;
					},
					reset : function(elm) {
						elm = $(elm);
						var cn = elm.classNames();
						cn.each( function(value) {
							var prop = '__advice' + value.camelize();
							if (elm[prop]) {
								var advice = Validation.getAdvice(value, elm);
								advice.hide();
								elm[prop] = '';
							}
							elm.removeClassName('validation-failed');
							elm.removeClassName('validation-passed');
						});
					},
					add : function(className, error, test, options) {
						var nv = {};
						nv[className] = new Validator(className, error, test,
								options);
						Object.extend(Validation.methods, nv);
					},
					addAllThese : function(validators) {
						var nv = {};
						$A(validators)
								.each(
										function(value) {
											nv[value[0]] = new Validator(
													value[0],
													value[1],
													value[2],
													(value.length > 3 ? value[3]
															: {}));
										});
						Object.extend(Validation.methods, nv);
					},
					get : function(name) {
						return Validation.methods[name] ? Validation.methods[name]
								: Validation.methods['_LikeNoIDIEverSaw_'];
					},
					methods : {
						'_LikeNoIDIEverSaw_' :new Validator(
								'_LikeNoIDIEverSaw_', '', {})
					}
				});

Validation.add('IsEmpty', '', function(v) {
	return ((v == null) || (v.length == 0)); // || /^\s+$/.test(v));
	});

Validation
		.addAllThese( [
				[ 'required', 'This is a required field.', function(v) {
					return !Validation.get('IsEmpty').test(v);
				} ],
				[
						'validate-number',
						'Please enter a valid number in this field.',
						function(v) {
							return Validation.get('IsEmpty').test(v)
									|| (!isNaN(v) && !/^\s+$/.test(v));
						} ],
				[
						'validate-digits',
						'Please use numbers only in this field. please avoid spaces or other characters such as dots or commas.',
						function(v) {
							return Validation.get('IsEmpty').test(v)
									|| !/[^\d]/.test(v);
						} ],
				[
						'validate-alpha',
						'Please use letters only (a-z) in this field.',
						function(v) {
							return Validation.get('IsEmpty').test(v)
									|| /^[a-zA-Z]+$/.test(v)
						} ],
				[
						'validate-alphanum',
						'Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed.',
						function(v) {
							return Validation.get('IsEmpty').test(v)
									|| !/\W/.test(v)
						} ],
				[ 'validate-date', 'Please enter a valid date.', function(v) {
					var test = new Date(v);
					return Validation.get('IsEmpty').test(v) || !isNaN(test);
				} ],
				[
						'validate-email',
						'Please enter a valid email address. For example fred@domain.com .',
						function(v) {
							return Validation.get('IsEmpty').test(v)
									|| /\w{1,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/
											.test(v)
						} ],
				[
						'validate-url',
						'Please enter a valid URL.',
						function(v) {
							return Validation.get('IsEmpty').test(v)
									|| /^(http|https|ftp):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i
											.test(v)
						} ],
				[
						'validate-date-au',
						'Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006.',
						function(v) {
							if (Validation.get('IsEmpty').test(v))
								return true;
							var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
							if (!regex.test(v))
								return false;
							var d = new Date(v.replace(regex, '$2/$1/$3'));
							return (parseInt(RegExp.$2, 10) == (1 + d
									.getMonth()))
									&& (parseInt(RegExp.$1, 10) == d.getDate())
									&& (parseInt(RegExp.$3, 10) == d
											.getFullYear());
						} ],
				[ 'validate-currency-dollar',
						'Please enter a valid $ amount. For example $100.00 .',
						function(v) {
							// [$]1[##][,###]+[.##]
						// [$]1###+[.##]
						// [$]0.##
						// [$].##
						return Validation.get('IsEmpty').test(v)
								|| /^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/
										.test(v)
					} ],
				[
						'validate-selection',
						'Please make a selection',
						function(v, elm) {
							return elm.options ? elm.selectedIndex > 0
									: !Validation.get('IsEmpty').test(v);
						} ],
				[ 'validate-one-required',
						'Please select one of the above options.',
						function(v, elm) {
							var p = elm.parentNode;
							var options = p.getElementsByTagName('INPUT');
							return $A(options).any( function(elm) {
								return $F(elm);
							});
						} ],
				[ 'validate-postcode', 'Please enter a valid post code',
						function(toCheck) {

							// Permitted letters depend upon their position in
							// the postcode.
						var alpha1 = "[abcdefghijklmnoprstuwyz]"; // Character
																	// 1
						var alpha2 = "[abcdefghklmnopqrstuvwxy]"; // Character
																	// 2
						var alpha3 = "[abcdefghjkstuw]"; // Character 3
						var alpha4 = "[abehmnprvwxy]"; // Character 4
						var alpha5 = "[abdefghjlnpqrstuwxyz]"; // Character 5

						// Array holds the regular expressions for the valid
						// postcodes
						var pcexp = new Array();

						// Expression for postcodes: AN NAA, ANN NAA, AAN NAA,
						// and AANN NAA
						pcexp.push(new RegExp("^(" + alpha1 + "{1}" + alpha2
								+ "?[0-9]{1,2})(\\s*)([0-9]{1}" + alpha5
								+ "{2})$", "i"));

						// Expression for postcodes: ANA NAA
						pcexp.push(new RegExp("^(" + alpha1 + "{1}[0-9]{1}"
								+ alpha3 + "{1})(\\s*)([0-9]{1}" + alpha5
								+ "{2})$", "i"));

						// Expression for postcodes: AANA NAA
						pcexp.push(new RegExp("^(" + alpha1 + "{1}" + alpha2
								+ "?[0-9]{1}" + alpha4 + "{1})(\\s*)([0-9]{1}"
								+ alpha5 + "{2})$", "i"));

						// Exception for the special postcode GIR 0AA
						pcexp.push(/^(GIR)(\s*)(0AA)$/i);

						// Standard BFPO numbers
						pcexp.push(/^(bfpo)(\s*)([0-9]{1,4})$/i);

						// c/o BFPO numbers
						pcexp.push(/^(bfpo)(\s*)(c\/o\s*[0-9]{1,3})$/i);

						// Overseas Territories
						pcexp.push(/^([A-Z]{4})(\s*)(1ZZ)$/i);

						// Load up the string to check
						var postCode = toCheck;

						// Assume we're not going to find a valid postcode
						var valid = false;

						// Check the string against the types of post codes
						for ( var i = 0; i < pcexp.length; i++) {
							if (pcexp[i].test(postCode)) {

								// The post code is valid - split the post code
								// into component parts
								pcexp[i].exec(postCode);

								// Copy it back into the original string,
								// converting it to uppercase and
								// inserting a space between the inward and
								// outward codes
								postCode = RegExp.$1.toUpperCase() + " "
										+ RegExp.$3.toUpperCase();

								// If it is a BFPO c/o type postcode, tidy up
								// the "c/o" part
								postCode = postCode.replace(/C\/O\s*/, "c/o ");

								// Load new postcode back into the form element
								valid = true;

								// Remember that we have found that the code is
								// valid and break from loop
								break;
							}
						}

						// Return with either the reformatted valid postcode or
						// the original invalid
						// postcode
						if (valid) {
							return postCode;
						} else
							return false;
					} ] ]);
