/* ************************************************************************************* *\
 * The MIT License
 * Copyright (c) 2007 Fabio Zendhi Nagao - http://zend.lojcomm.com.br
 * 
 * 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 ValidatorRE_Required = /[^.*]/;
var ValidatorRE_Email = /^[a-z0-9._%-]+@[a-z0-9.-]+\.[a-z]{2,4}$/i;

var ValidatorClass = new Class({

	options: {
		msgContainerTag: "span",
		msgClass: "Validator-msg",

		styleNeutral: {"background-color": "#ffffff"},
		styleInvalid: {"background-color": "#ffffff"},
		styleValid: {"background-color": "#ffffff"},

		alpha: {type: "alpha", re: /^[a-z ._-]+$/i, msg: "This field accepts alphabetic characters only."},
		alphanum: {type: "alphanum", re: /^[a-z0-9 ._-]+$/i, msg: "This field accepts alphanumeric characters only."},
		integer: {type: "integer", re: /^[-+]?\d+$/, msg: "Please enter a valid integer."},
		real: {type: "real", re: /^[-+]?\d*\.?\d+$/, msg: "Please enter a valid number."},
		date: {type: "date", re: /^((((0[13578])|([13578])|(1[02]))[\/](([1-9])|([0-2][0-9])|(3[01])))|(((0[469])|([469])|(11))[\/](([1-9])|([0-2][0-9])|(30)))|((2|02)[\/](([1-9])|([0-2][0-9]))))[\/]\d{4}$|^\d{4}$/, msg: "Please enter a valid date (mm/dd/yyyy)."},
		phone: {type: "phone", re: /^[\d\s ().-]+$/, msg: "Please enter a valid phone."},
		url: {type: "url", re: /^(http|https|ftp)\:\/\/[a-z0-9\-\.]+\.[a-z]{2,3}(:[a-z0-9]*)?\/?([a-z0-9\-\._\?\,\'\/\\\+&amp;%\$#\=~])*$/i, msg: "Please enter a valid url."},
		confirm: {type: "confirm", msg: "Confirm Password does not match original Password."},

		onValid: Class.empty,
		onInvalid: Class.empty
	},

	initialize: function(form, options) {
	
		// So you can turn off the checking procedure
		this.Check = true;
	
		this.form = $(form);
		this.setOptions(options);

		this.fields = this.form.getElements("*[class^=fValidate]");
		this.validations = [];
		
		this.GlobalMessage = options.GlobalMessage;

		this.fields.each(function(element) {
			if(!this._isChildType(element)) element.setStyles(this.options.styleNeutral);
			element.cbErr = 0;
			var classes = element.getProperty("class").split(' ');
			classes.each(function(klass) {
				if(klass.match(/^fValidate(\[.+\])$/)) {
					var aFilters = eval(klass.match(/^fValidate(\[.+\])$/)[1]);
					for(var i = 0; i < aFilters.length; i++) {
						if(this.options[aFilters[i]]) this.register(element, this.options[aFilters[i]]);
						if(aFilters[i].charAt(0) == '=') this.register(element, $extend(this.options.confirm, {idField: aFilters[i].substr(1)}));
					}
				}
			}.bind(this));
		}.bind(this));

		this.form.addEvents({
			"submit": this._onSubmit.bind(this),
			"reset": this._onReset.bind(this)
		});
		
	},

	register: function(field, options) {
		
		if(options.type=='Radio') {
			
			var RadioInputs = $$('input[name='+options.fieldname+']');
			field = RadioInputs[0];
			
			// Do an on blur for each radio input
			RadioInputs.each(function(input) {
				input.addEvent("blur", function() { this._validate(field, options); }.bind(this));
			}.bind(this));
						
			this.validations.push([field, options]);
			
		} else if(options.type=='Total') {
		
			options.TotalEnd ='';
			var Ttl;
		
			if ($(options.updateid)) {
				if($(options.updateid).innerHTML != '') {
					options.TotalEnd = $(options.updateid).innerHTML;
				}
			}
		
			options.fields.each(function(JsonField) {
				$(JsonField.fieldname).addEvent("keyup", function() {
					Ttl = this.WorkoutTotal(options.fields, $(options.updateid), options.TotalEnd);
				}.bind(this));
			}.bind(this));
			
			Ttl = this.WorkoutTotal(options.fields, $(options.updateid), options.TotalEnd);
			
			this.validations.push([field, options]);
		
		} else {
			
			this.validations.push([field, options]);
			if($(field)) { $(field).addEvent("blur", function() { this._validate($(field), options); }.bind(this)); }
			
		}
		
	},

	_isChildType: function(el) {
		if(el) {
			var elType = el.type.toLowerCase();
			//if((elType == "radio") || (elType == "checkbox") || (elType == "select")) return true;
		}
		return false;
	},

	_validate: function(field, options) {
	
		// Here we need to a simple check of the parent of this element
		var FieldParents = field.getParents('fieldset');
		var FieldParents2 = field.getParents('div');
		var ParentHidden = false;
		
		FieldParents.each(function(FieldParent) {
			if(FieldParent.getStyle('display')=='none') { ParentHidden = true; }
		});	
		FieldParents2.each(function(FieldParent) {
			if(FieldParent.getStyle('display')=='none') { ParentHidden = true; }
		});	
		
		if (ParentHidden == false) {
	
			switch(options.type) {
			
				case "terms":
							
					if (field.checked) {
						this._msgRemove(field, options);	
					} else {
						this._msgInject(field, options);			
					}			
				
					break;
					
				case "UKPostcode" :
				
					if ((field.value!='') && (field.value.indexOf(' ') > 0)) {
						this._msgRemove(field, options);	
					} else {
						this._msgInject(field, options);			
					}			
				
					break;
			
				case "confirm":
				
					if(field.value)  {
	
						if($(options.idField).value.toLowerCase() == field.value.toLowerCase()) {
							this._msgRemove(field, options); 
						} else {
							this._msgInject(field, options);
						}
	
					}
					
					break;
					
				case "AJAXMatch" : 
				
					var jsonRequest = new Request.JSON({url: options.url, onComplete: function(MatchResult){
						if(MatchResult.match){
							this._msgInject(field, options);					
						} else {
							this._msgRemove(field, options); 
						}
					}.bind(this)}).send(field.name+'='+field.value);
				
					break;
				
				case "Radio":
				
					var RadioInputs = $$('input[name='+options.fieldname+']');
					var CheckWrong = true;
					
					RadioInputs.each(function(input) {
						if (input.checked) {
							CheckWrong=false;
						}
					}.bind(this));
					
					field = RadioInputs[0];
					
					if (CheckWrong==true) {
						this._msgInject(field, options);
					} else {
						this._msgRemove(field, options);
					}
					
					break;
					
				case "Total":
					
					Ttl = this.WorkoutTotal(options.fields, $(options.updateid), options.TotalEnd);
					
					if (((Ttl>=options.totalvalue) && (options.minimum)) || ((Ttl==options.totalvalue) && (!options.minimum))) {
						this._msgRemove(field, options);
					} else {
						this._msgInject(field, options);				
					}
	
					break;
				
				case "StringDouble":
				
					if ((field.value!='') && ($(options.fieldone).value!='')) {
						this._msgRemove(field, options);
					} else {
						this._msgInject(field, options);
					}
					
					break;
					
				case "Luhn":
				
					var LuhnNumber = field.value;
					
					if (LuhnNumber!='') {
						if(luhn_check(LuhnNumber.toInt())) {
							this._msgRemove(field, options);
						} else {
							this._msgInject(field, options);						
						}
					} else {
						this._msgInject(field, options);
					}			
				
					break;
			
				default:
				
					field = $(field);
				
					if(options.re.test(field.value)) {
						this._msgRemove(field, options);
					} else {
						this._msgInject(field, options);
					}
					
					break;
					
			}
		
		} else {
		
			switch(options.type) {
			
				case "terms":
					this._msgRemove(field, options);	
					break;
				case "confirm":
					this._msgRemove(field, options); 
					break;
				case "AJAXMatch" : 
					this._msgRemove(field, options); 
					break;
				case "Radio":
					var RadioInputs = $$('input[name='+options.fieldname+']');
					var CheckWrong = true;
					
					RadioInputs.each(function(input) {
						if (input.checked) {
							CheckWrong=false;
						}
					}.bind(this));
					
					field = RadioInputs[0];
					this._msgRemove(field, options);
					break;
				case "Total":
					this._msgRemove(field, options);
					break;
				case "StringDouble":
					this._msgRemove(field, options);
					break;
				case "Luhn":
					this._msgRemove(field, options);
					break;
				default:
					field = $(field);
					this._msgRemove(field, options);
					break;
			}
	
		}
		
	},

	_validateChild: function(child, options) {
	
		var nlButtonGroup = this.form[child.getProperty("name")];
		var cbCheckeds = 0;
		var isValid = true;
 		
		for(var i = 0; i < nlButtonGroup.length; i++) {
			if(nlButtonGroup[i].checked) {
				cbCheckeds++;
				if(!options.re.test(nlButtonGroup[i].getValue())) {
					isValid = false;
					break;
				}
			}
		}
		
		if(cbCheckeds == 0 && options.type == "required") isValid = false;
		if(isValid) this._msgRemove(child, options);
		else this._msgInject(child, options);

	},

	_msgInject: function(owner, options) {
		
		var ownerelType = ownerelType = owner.type.toLowerCase();
		
		//alert(ownerelType);
		//alert($(owner.getProperty("id") + options.type +"_msg"));


		if(!$(owner.getProperty("id") + options.type +"_msg")) {
		
			var ValidateMessage = $('ValidateMessage');
		
			if ((ValidateMessage) && (this.GlobalMessage!='')) {
				if (ValidateMessage.getStyle('display') != 'block') {
					ValidateMessage.setStyle('display', 'block');
				}
				ValidateMessage.getFirst().innerHTML = this.GlobalMessage;
			}
			
			this.options.msgClass = 'RequiredMsg';
						
			if(options.type=='Radio') {
				var ownerParent = owner.getParent();
				var msgContainer = new Element(this.options.msgContainerTag, {"id": owner.getProperty("id") + options.type +"_msg", "class": this.options.msgClass})
					.set('html', options.msg)
					.setStyle("opacity", 0)
					.setStyle("color", "#9E0000")
					.setStyle("width", "200px")
					.setStyle("padding-bottom", "15px")
					.setStyle("float", "left")
					.setStyle("font-weight", "bold")
					.injectBefore(ownerParent)
					.fade(1);			
			} else if(ownerelType == 'checkbox') {
				var msgContainer = new Element(this.options.msgContainerTag, {"id": owner.getProperty("id") + options.type +"_msg", "class": this.options.msgClass})
					.set('html', options.msg)
					.setStyle("opacity", 0)
					.setStyle("color", "#9E0000")
					.setStyle("width", "200px")
					.setStyle("padding-bottom", "15px")
					.setStyle("float", "left")
					.setStyle("font-weight", "bold")
					.injectBefore(owner)
					.fade(1);
			} else if(options.type=='Total') {
				
				var msgContainer = new Element(this.options.msgContainerTag, {"id": owner.getProperty("id") + options.type +"_msg", "class": this.options.msgClass})
					.set('html', options.msg)
					.setStyle("opacity", 0)
					.setStyle("color", "#9E0000")
					.setStyle("width", "200px")
					.setStyle("padding-bottom", "15px")
					.setStyle("float", "left")
					.setStyle("font-weight", "bold")
					.injectBefore($(options.msginsertpoint))
					.fade(1);			
			
			} else {
			
				if(owner.getParent()) {
				
					if(owner.getParent().tagName.toLowerCase()=='td') {
					
						var msgContainer = new Element(this.options.msgContainerTag, {"id": owner.getProperty("id") + options.type +"_msg", "class": this.options.msgClass})
							.set('html', '<br />' + options.msg)
							.setStyle("opacity", 0)
							.setStyle("color", "#9E0000")
							.setStyle("width", '200px')
							.setStyle("text-align", "left")
							.setStyle("font-weight", "bold")
							.injectAfter(owner)
							.fade(1);
						
					} else {
					
						var msgContainer = new Element(this.options.msgContainerTag, {"id": owner.getProperty("id") + options.type +"_msg", "class": this.options.msgClass})
							.set('html', '&nbsp;&nbsp;' + options.msg)
							.setStyle("opacity", 0)
							.setStyle("color", "#9E0000")
							.setStyle("padding-top", "5px")
							.setStyle("width", '200px')
							.setStyle("float", "left")
							.setStyle("font-weight", "bold")
							.injectAfter(owner)
							.fade(1);	
							
					}
				
				} else {
					
					var msgContainer = new Element(this.options.msgContainerTag, {"id": owner.getProperty("id") + options.type +"_msg", "class": this.options.msgClass})
						.set('html', '&nbsp;&nbsp;' + options.msg)
						.setStyle("opacity", 0)
						.setStyle("color", "#9E0000")
						.setStyle("padding-top", "5px")
						.setStyle("width", '200px')
						.setStyle("float", "left")
						.setStyle("font-weight", "bold")
						.injectAfter(owner)
						.fade(1);	
					
				}
				
						
			}
						
			if(owner.cbErr) {
				owner.cbErr++;
			} else {
				owner.cbErr=1;
			}
			
			this._chkStatus(owner, options);
		}
	},

	_msgRemove: function(owner, options, isReset) {
		isReset = isReset || false;
		if($(owner.id + options.type +"_msg")) {
			
			var el = $(owner.getProperty("id") + options.type +"_msg");
			
			el.fade(0, function(){ el.dispose() });
			
			if(!isReset) {
				owner.cbErr--;
				this._chkStatus(owner, options);
			}
		}
	},

	_chkStatus: function(field, options) {
		if(field.cbErr == 0) {
			this.fireEvent("onValid", [field, options], 50);
		} else {
			this.fireEvent("onInvalid", [field, options], 50);
		}
	},

	_onSubmit: function(event) {
	
		if(this.Check) {
	
			event = new Event(event);
			var isValid = true;
			
			this.validations.each(function(array) {
				if (array[0] instanceof Array) {
					var ArrayIn = array[0];
	
					this._validate($(ArrayIn[ArrayIn.length-1]), array[1]);
					if($(ArrayIn[ArrayIn.length-1]).cbErr > 0) { isValid = false; }
	
				} else {
					if($(array[0])) {
						this._validate($(array[0]), array[1]);
						if($(array[0]).cbErr > 0) { isValid = false; }
					}
				}
				
			}.bind(this));
			
			if(!isValid) {
				if($('ValidateMessage')) {
					window.scrollTo(0, $('ValidateMessage').getCoordinates().top - 20);
				}
				event.stop();
			}
					
			return isValid;
		
		} else {
		
			return true;
		}
	},

	_onReset: function() {
		this.validations.each(function(array) {
			if(!this._isChildType(array[0])) array[0].setStyles(this.options.styleNeutral);
			array[0].cbErr = 0;
			this._msgRemove(array[0], array[1], true);
		}.bind(this));
	},
	
	// Does an auto check when coming back for a second go, so will automatically marks the fields
	// Have been wanting to do this for a long time
	Wrong : function(FieldAutoCheck, Type) {
		
		var Field = $(FieldAutoCheck);
		
		this.validations.each(function(array) {
			if ((FieldAutoCheck == array[0]) && (Type == array[1].type)) {
				this._msgInject($(array[0]), array[1]);
			}
		}.bind(this));
			
	},
	
	// Used to determine the total value inside several fields
	// used with the total validate option
	WorkoutTotal : function(FieldJson, UpdateDiv, TotalEnd) {
	
		var Total = 0;
		
		FieldJson.each(function(FieldJsonA) {
			var Field = $(FieldJsonA.fieldname);
			if(Field) {
				if (Field.value=='') { Field.value = 0; }
				Total = Total + Math.round(Field.value);
			}
		}.bind(this));
		
		if(UpdateDiv) {
			UpdateDiv.innerHTML = Total + TotalEnd;
		}
		
		return Total;
	
	}
});

ValidatorClass.implement(new Events); // Implements addEvent(type, fn), fireEvent(type, [args], delay) and removeEvent(type, fn)
ValidatorClass.implement(new Options);// Implements setOptions(defaults, options)

/* Luhn algorithm number checker - (c) 2005-2008 shaman - www.planzero.org *
* This code has been released into the public domain, however please      *
* give credit to the original author where possible.                      */
function luhn_check(number) {
	// Strip any non-digits (useful for credit card numbers with spaces and hyphens)
	//var number=number.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; }
}
