/* ************************************************************************************* *\
 * The MIT License
 * Copyright (c) 2007 Fabio Zendhi Nagao - http://zend.lojcomm.com.br
 * Modified 2008 Ryan Mitchell - http://www.rtnetworks.net/
 * 
 * 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 fValidator = new Class({
						   
	options: {
		msgContainerTag: "span",
		msgClass: "fValidator-msg",

		styleNeutral: {"background-color": "#fff" },
		styleInvalid: {"background-color": "#fcc"},
		styleValid: {"background-color": "#fff" },

		required: {type: "required", re: /[^.*]/, msg: "This field is required."},
		alpha: {type: "alpha", re: /^[a-z ._-]+$/i, msg: "Please enter alphabetic characters only."},
		alphanum: {type: "alphanum", re: /^[a-z0-9 ._-]+$/i, msg: "Please enter 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)."},
		email: {type: "email", re: /^[a-z0-9._%-]+@[a-z0-9.-]+\.[a-z]{2,4}$/i, msg: "Please enter a valid email."},
		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: "This does not match."},

		onValid: Class.empty,
		onInvalid: Class.empty
	},

	initialize: function(form, options) {
		this.form = $(form);
		this.setOptions(options);

		this.fields = this.form.getElements("*[class^=fValidate]");
		this.validations = [];
		this.confirms = [];

		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) == '=') { 
							// hold an array of confirms, allowing multiple confirms on one form
							this.confirms[element.id] = aFilters[i].substr(1);
							this.register(element, this.options.confirm);
							// when field we are confirming against changes, validate this field
							$(aFilters[i].substr(1)).addEvent("blur",function() {
								this._validate($(element));												  
							}.bind(this));
						}
					}
				}
			}.bind(this));
		}.bind(this));

		this.form.addEvents({
			"submit": this._onSubmit.bind(this),
			"reset": this._onReset.bind(this)
		});
	},

	register: function(field, o) {
		
		// extend to mootools element
		field = $(field);
		
		// if array element isnt defined, make it 
		// bind validation to form element blurring
		if(!$defined(this.validations[field.id])) {
			
			this.validations[field.id] = Array(o);
			
			field.addEvent("blur", function() {
				this._validate(field);
			}.bind(this));
			
		}
		else {
		
			// validation on field already exists so add
			// extra validation
			this.validations[field.id].push(o);
		
		}
		
	},

	_isChildType: function(el) {
		var elType = el.type.toLowerCase();
		if((elType == "radio") || (elType == "checkbox")) return true;
		return false;
	},

	_validate: function(field,isReset) {
		
		// are we resetting?
		isReset = isReset || false;
		
		// errors
		var errors = Array();
		
		if(!isReset) {
			
			// loop over the options for this field
			this.validations[field.id].each(function(o) { 
				
				// if its a child type, validate it as such
				if(this._isChildType(field)) {
					
					if(!this._validateChild(field,o))
						errors.push(o);
					
				} else {
					
					// only check if field isnt empty, its a confirm field, or its a required
					// logic: - if its not confirm or required we should only check if its not empty
					if( (field.getValue() != '') || (o.type == 'confirm') || (o.type == 'required') ) {
												  
						switch(o.type) {
							case "confirm":
								if($(this.confirms[field.id]).getValue() != field.getValue())
									errors.push(o);
								break;
							default:
								if(!o.re.test(field.getValue()))
									errors.push(o);
						}
					
					}
				
				}
			
			}.bind(this));
			
		}
			
		// do we have errors?
		if(errors.length > 0) {
			
			// show field is invalid
			field.effects({duration: 500, transition: Fx.Transitions.linear}).start(this.options.styleInvalid);
		
			// fire events
			errors.each(function(o) { 
				this.fireEvent("onInvalid", [field, o], 50);					 
			});
									
		}
		else {
			
			// show field is valid
			field.effects({duration: 500, transition: Fx.Transitions.linear}).start(this.options.styleValid);
			
			// fire events
			errors.each(function(o) { 
				this.fireEvent("onValid", [field, o], 50);					 
			});
			
		}		
		
		// set errors to field for submit/reset
		field.errors = errors;
		
		// output errors
		// even if we don't have any - this will clear existing ones
		this._showErrors(field,errors);

				
	},
	
	_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;
		return isValid;
	},
	
	_showErrors: function(field,errors) {
		
		// do we already have messages?
		if(!$(field.getProperty("id") + "_msg")) {
					
			// set up our message container
			var msgContainer = new Element(this.options.msgContainerTag, {"id": field.getProperty("id") + "_msg", "class": this.options.msgClass});
			
		}
		else {
		
			// set up already existing message container
			var msgContainer = $(field.getProperty("id") + "_msg");
			msgContainer.empty();
		
		}
		
		// only show errors if we have any
		if(errors.length > 0) {
		
			// set the html inside the message container
			var html = errors[0].msg;
			
			// to show all messages, uncomment out the following:
			// for(i=1;i<errors.length;i++) { html += "<br />"+errors[i].msg; }
			
			msgContainer.setHTML(html)
				.setStyle("opacity", 0)
				.injectAfter(field)
				.effect("opacity", {
					duration: 500,
					transition: Fx.Transitions.Quart.easeInOut
				}).start(0, 1);
				
		}
		
	},
	
	_onSubmit: function(event) {
		event = new Event(event);
		var isValid = true;
		
		for(i in this.validations) {
			if($defined(this.validations[i][0])) { // check its an array, for some reason it throws up functions as well!
				field = $(i);
				this._validate(field);
				if(field.errors.length > 0) isValid = false;
			}
		}

		if(!isValid) { event.stop(); this.fireEvent("onInvalid"); }
		else { this.fireEvent("onValid"); }
		return isValid;
	},

	_onReset: function() {		
		for(i in this.validations) {
			if($defined(this.validations[i][0])) {// check its an array, for some reason it throws up functions as well!
				field = $(i);
				field.errors = Array();
				this._validate(field,true);
			}
		}
	}
});
fValidator.implement(new Events); // Implements addEvent(type, fn), fireEvent(type, [args], delay) and removeEvent(type, fn)
fValidator.implement(new Options);// Implements setOptions(defaults, options)
