/*
 * jQuery Validator Plugin v1.0(beta)
 *
 * Copyright (c) 2009 Rafael Cesar
 * Licensed under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 *
 * Date: 2009-01-16 00:26:00 -0300 (Fri, 16 Jan 2009)
 */
(function($) {
	$.fn.validator = function(options) {
		
		options = options || {};//The ID's instance stay in this obj
		
		//Generating ID's instance
		var data = options.data = $.data(this[0]);
		
		
		$.validator[data] = new $.validator.init(this[0],options);
		return this;
	};
	$.extend($.expr[':'],{
		//jQuery extension
		validable:function(elm) {
			elm = elm.jquery?elm[0]:elm;
			if(!elm || !elm.type)
				return false;
			var tagName = elm.tagName.toLowerCase(),
				type = elm.type;
			
			if(tagName == 'input' || tagName == 'select' || tagName == 'textarea') {
				if(type != 'submit' && type != 'button' && type != 'hidden' && type != 'image' && type != 'reset') {
					return true;
				}
			}
			return false;
		}
	});
	$.extend({
		validator: {
			/*
			options = {
				handler:function(msg) {this == field},
				onkeyup:true,
				nocache:true,
				onFail:function(array) {},
				alert:true,
				onSuccess:function(element) {}
			}
			*/
			init:function(form,options) {
				
				form = $(form);
				
				//Making options public
				this.options = options;
				
				$.validator.cacheValidatorsName.call(this);
				
				var v = $.validator,
					fields = v.cacheRelationship.call(this,$(':validable',form[0].tagName.toLowerCase()=='form'?form[0].elements:form));//parse and cache fields
				
				//Making fields public
				this.options.fields = fields;
					
				//'on demand' validation
				if(options.handler) {
					var validate, listener;
					
					
					//Function binded in event
					listener = function(evt) {
						//Recovering instance
						var instance = evt.data.data;
						
						$.validator[instance].options.handler.apply($(this),validate.call(instance,this));
						
					};
					
					
					//'on demand' validation only on 'blur' and 'keyup' events
					//one at once
					
					fields.bind(options.onkeyup?'keyup':'blur',this.options,listener);
					
					
					
					//keyup validations goes in just one validator (performance)
					if(options.onkeyup)
						validate = function(f) {
							var _ret = v.validate.call(this,f,$.data(f,'names')[0]);
							return _ret?[_ret]:[];
						};
					
					//Blur validation
					else
						validate = function(f) {
							var instance = this;
							return $.map($.data(f,'names'),function(name) {
								return v.validate.call(instance,f,name);
							});
						};
					
					
					
				}
				
				//Submit function
				//You can call it when you want
				//INFO: When calling the submit function, YOU MUST SEND THE INSTANCE ID
				this.options.submit = function(evt) {
					var v = $.validator,
						data = isNaN(parseInt(evt,10))?evt.data.data:evt,
						obj = v[data],//Recovering instance
						invalids,
						fields = obj.options.fields;
					
					//re-cache of fields
					if(obj.options.nocache === true) {
						v.cacheValidatorsName.call(obj);
						fields = obj.options.fields = v.cacheRelationship.call(obj,$(':validable',form));
					}
					
					//It's here the magic happens
					//It'll generate an array of (possible) arrays
					invalids = v.validateFields.call(obj,fields);
					
					if(invalids.length) {//When something is invalid
						
						//You can manipulate the return of submit function
						var _ret;
						
						//Your function to make the alert to user more friendly
						if(obj.options.onFail)
							_ret = obj.options.onFail(invalids);
						
						//simple alert
						else if(obj.options.alert === true)
							alert($.map(invalids,function(a) {return a.join?a.join('\n'):a;}).join('\n'));
						
						return _ret || false;
					}
					
					//Function for valids fields
					//You still can manipulate the return of submit funcion
					return options.onSuccess?
						options.onSuccess(form)
						:true;
				};
				//If you send a tag form
				//The submit function will go to the onsubmit event
				//Else you can call it when you want, for personalizations
				if(form[0].tagName.toLowerCase() == 'form')
					form.bind('submit',this.options,this.options.submit);
			},
			//Catching the name of the validators
			cacheValidatorsName:function() {
				var ret = [],
					v = $.validator;
				$.each(this.options.validators || v.validators,function(a) {ret[ret.length] = a;});
				if(this.options.validators)
					this.nameOfValidators = ret;
				else
					v.nameOfValidators = ret;
				return ret;
			},
			
			//Make all the values of the array uniques
			uniquier:function(array) {
				if(array.length <= 2) {
					if(array.length == 2 && array[0] == array[1]) {
						return [array[0]];
					}
					return array;
				}
				var unique = [],
					exist,i=0,j,len_i=array.length,len_j;
				for(;i<len_i;i++) {
					exist = false;
					len_j = unique.length;
					for(j=0;j<len_j;j++) {
						if(array[i] == unique[j]) {
							exist = true;
							break;
						}
					}
					if(!exist)
						unique[unique.length] = array[i];
				}
				return unique;
			},
			
			injectRelationship:function(field, validator, instance) {
				var data = $.data(field,'validator') || [],
					v = $.validator,
					validators = instance.validators || v.validators,
					flds = validators[validator]['fields'].split('|'),
					type = field.type;
				for(var i=0,len=flds.length;i<len;i++) {
					if(type == flds[i]) {
						data.push(validator);
						$.data(field,'validator',v.uniquier(data));
						return true;
					}
				}
				return false;
			},
			
			//Return the fields that match with the validators
			//(used for cache too)
			cacheRelationship:function(fields) {
				
				var v = $.validator,
				
					validators = this.options.validators || v.validators,
					
					_ret = function() {
						
						var f = this,
							
							type = f.type,
							
							//Iterate over the names of the list
							fNames = $.map(this.nameOfValidators || v.nameOfValidators,function(name) {
								//If the className match the validator name and
								//the field match with the fields attribute of the validator obj...
								if($.className.has(f,name)) {
									var flds = validators[name]['fields'].split('|');
									if(flds[0] == '*')
										return name;
									var i = 0,
										len = flds.length;
									while(i<len) {
										if(type == flds[i++])
											return name;
									}
								}
								return null;
								
							}),
							names = $.data(f,'validator') || [];
						
						names = names.concat(fNames.length>1?v.uniquier(fNames):fNames);
						//Caching the name of validators in jQuery cache
						$.data(f,'validator',names);
						
						//If we can validate, the field is returned
						return fNames.length?this:null;
					};
					
				
				//When a jQuery-object is passed
				//we call it again with obj-per-obj
				if(fields.jquery) {
					if(fields.length > 1)
						return fields.map(_ret);
					else
						return [_ret(fields[0])];
				}
				//When you pass the field
				//It execute the function
				else
					return [_ret(fields)];
			},
			
			clearRelationship:function(elm) {
				if($.data(elm,'validator'))
					$.data(elm,'validator',[]);
				else if($.validator[$.data(elm)]) {
					var fields = $.validator[$.data(elm)].options.fields;
					for(var i=0,len=fields.length;i<len;i++)
						$.data(fields[i],'validator',[]);
				}
			},
			
			//Fields validation
			validateFields:function(fields) {
				var validate = $.validator.validate,
					instance = this;
				
				return fields.map(function() {
					
					var field = $(this),
						
						//Make the REAL validation
						valid = $.map($.data(this,'names'),function(name) {
							return validate.call(instance,field,name);
						});
					
					//Give the access to the field of the messages
					valid['field'] = field;
					
					//Sending the messages
					return valid.length?valid:null;
				});
			},
			
			//The Engine of validation
			validate:function(field,vName) {
				
				//Catching the field
				field = field['jquery']?field[0]:field;
				
				//The obj of validation
				var validator = (this.options.validators || $.validator.validators)[vName];
				
				//Invalid proccess
				//Replace of '%' to the field title attribute
				if(!validator.test.call(validator,field))
					return validator.msg.indexOf('%')!=-1?validator.msg.replace('%','\'' + field.title + '\''):validator.msg;
					
				//Valid proccess
				else
					return null;
			},
			//Validators....
			//Extensibles...
			validators: {
	
				//Example
				
				//The name of the obj must match with the className
				'jLetter':{
					
					//Validator function
					//The arguments[0] can't be a jquery-object
					//True for valid and False for invalid
					test:function(f) {
						return (/^[A-Za-záàãâäéèêëíìîïóòõôöúùûüçÁÀÃÂÄÉÈÊËÍÌÎÏÓÒÕÔÖÚÙÛÜÇ]+/i.test(f.value));
					},
					
					//Default message
					//The '%' will be replaced for the title attribute of the field
					//Or you can put a message without the '%'
					msg:'The field % must contain only letters.',
					
					//These are the kind of fields that this validator-object can validate
					fields:'text|textarea|password'//The string must be compatible with is function of jQuery
				},
				
				'jEmpty':{
					test:function(f) {
						return !(/^\s*$/i.test(f.value));
					},
					msg:'The field % must be filled.',
					fields:'text|textarea|password|file'
				},
				'jNumber':{
					test:function(f) {
						return (/^[0-9]+/i.test(f.value));
					},
					msg:'The field % must contain only numbers.',
					fields:'text|textarea|password'
				},
				'jAlpha':{
					test:function(f) {
						return (/^[0-9A-Za-záàãâäéèêëíìîïóòõôöúùûüçÁÀÃÂÄÉÈÊËÍÌÎÏÓÒÕÔÖÚÙÛÜÇ]+/i.test(f.value));
					},
					msg:'The field % must contain only letters and numbers.',
					fields:'text|textarea|password'
				},
				'jEmail':{
					test:function(f) {
						return (/^[a-zA-Z0-9_.+-]{2,}@([a-zA-Z0-9-]{2,}.)+[a-zA-Z0-9]{2,4}$/i.test(f.value));
					},
					msg:'The field % must contain a valid email.',
					fields:'text|textarea'
				},
				'jEq':{
					test:function(f) {//Id == Id2
						var f2 = $('#' + f.id.substr(0,f.id.length-1))[0];
						if(!this._msg)
							this._msg = this.msg;
						if(f.value != f2.value) {
							this.msg = this._msg + f2.title;
							return false;
						}
						else
							return true;
					},
					msg:'The field % must be equal to the field ',
					fields:'text|password'
				},
				'jReq':{
					test:function(f) {
						switch(f.type) {
							case 'text':
							case 'textarea':
							case 'file':
							case 'password':
								return $.validator.validators['jEmpty'].test(f);
								break;
							case 'checkbox':
								return f.checked;
								break;
							case 'select-one':
								return !(f.options.selectedIndex < $.validator['select']?1:0);
								break;
							case 'select-multiple':
								return !(f.options.selectedIndex < 0);
								break;
							case 'radio':
								return $('input[name="' + f.name + '"]:checked').length;
								break;
						}
					},
					msg:'The field % is required',
					fields:'*'
				}
			}
		}
	});
})(jQuery);