(function(global, doc, $){
	
	var events = {};
	
	var notify = function(){};
	
	var validator = {
		listen: function(){},
		deafen: function(){}
	};
	
	var getRules = function(className, rules, prefix){
		var list = className.split(' ')
		, i = list.length
		, prefix = new RegExp('^'+prefix);
		while(i--){
			if(!prefix.test(list[i])){
				list.splice(i, 1);
				continue;
			}
			list[i] = list[i].replace(prefix, '');
		}
		return list;
	};
	
	var validateValue = function(value, list, config){
		var rules = config.rules
		, messages = config.messages
		, i = 0
		, len = list.length
		, rule;
		for(i;i<len;i++){
			rule = rules[list[i]];
			if(rule && !(typeof rule == 'function' ? rule(value) : rule.test(value))) {
				rule = list[i];
				return {
					valid:false, 
					rule:rule,
					message: messages[rule]
				};
			}
		}
		return {valid:true};
	};
	
	validator.config = {
		classPrefix: 'validate-',
		classNames: {
			invalid: 'invalid',
			valid: 'valid'
		},
		rules: {
			required: /[\w\d]+/,
			email: /^(?:[^@]+@[^@]+\.[\w]|\s?)$/,
			time: /^(?:(?:[0-1]\d|2[0-4]):[0-5]\d|\s?)$/,
			zipcode: /^(?:\d{4}\w{2}|\s?)$/,
			date: /^(?:\d{2}-\d{2}-\d{4}|\s?)/,
			number: /^(?:\-?[\d\.,]+|\s?)$/,
			price: /^(?:\d*(?:,\d*)?|\s?)$/
		},
		messages: {
			required: null, // uses field title attribute
			email: 'Is not a valid email',
			time: 'Is not a valid time',
			zipcode: 'Is not a valid zipcode',
			date: 'Is not a valid date',
			number: 'Is not a valid number',
			price: 'Is not a valid price'
		}
	};
	
	$.validator = validator;
	
	$.fn.validate = function(config){
		
		config = $.extend(true, {}, validator.config, config);
		
		var targetExp = '[class*='+config.classPrefix+']';
		
		$(targetExp).each(function(){
			$( this ).data('original-title' , this.title);
		});
		
		var setValid = function(field, result){
			field.trigger('valid',result);
			field.removeClass(config.classNames.invalid);
			field.addClass(config.classNames.valid);
			field.attr('title', field.data('original-title'));
		};
		
		var setInvalid = function(field, result){
			field.trigger('invalid',result);
			field.removeClass(config.classNames.valid);
			field.addClass(config.classNames.invalid);
			field.attr('title', result.message||field.data('original-title'));
		};
		
		var validate = function(e){
			var rules = getRules(this.className, config.rules, config.classPrefix)
			, field = $(this)
			, result = validateValue($.trim(field.val()), rules, config);
			result.valid ? setValid(field, result) : setInvalid(field, result);
		};

		this.delegate(targetExp, 'blur', validate);
		this.delegate(targetExp, 'keyup', function(e){
			
			if ( !/select|option/i.test( e.target.nodeName ) ) {
				
				validate.call(this, e);
				
			}
			
		});
		this.delegate(targetExp, 'keyup', function(e){
			
			if ( !/select|option/i.test( e.target.nodeName ) ) {
				
				$(this).trigger('focus');
				
			}
			
		});
		
		this.submit(function(e){
			var valid = false
			, self = $(this);
			self.find(targetExp).each(validate);
			if(self.find('.invalid').length) {
				e.stopImmediatePropagation();
				e.preventDefault();
			}
		});
		
		return this;
		
	};
	
}(this, document.documentElement, jQuery));
