/***************************************************************
 * jQuery.nextVal 1.7.2    7/14/2010
 * Author: John Norton - jukebox42@gmail.com
 * This version works best with jquery 1.4+
 ***************************************************************/
if(typeof jQuery != 'function'){ alert('You need to include jQuery 1.4.x first.'); } 
(function($) {
	var nextVal = function($f, opts) {
		var self = this;
		var opt = {
			useSummary:          false,
			useInline:           true,
			useStyles:           true,
			styleParent:         false,
			summaryStyle:        'validation-summary',
			messageStyle:        'validation-message',
			watermarkStyle:	     'validation-watermark',
			passedStyle:         'validation-passed',
			failedStyle:         'validation-failed',
			ignoreFieldClass:    'validation-ignore',
			useTitles:           false,
			useErrorText:        true,
			summaryId:           'vsummary-',
			messageId:           'vmessage-',
			validationTag:       'ul',
			submitButton:        '', // jQuery Selector
			itemCallback:        function(r){return r;},
			formCallback:        function(r){return r;},
			onCall:              function() {return true;},
			onBlur:              false,
			watermark:           true,
			attach:              'top',
			customRules:         Array() // Array(Array(NAME, COMPARE FUNCTION, DEFAULT MESSAGE), ... )
		};
		$.extend(opt, opts ? opts : {});		
		var init = function(){
			var r = Math.floor(Math.random()*200);
			$f.attr('nextVal', r);
			if(opt.submitButton != '')
			    $(opt.submitButton).click(function(){return doValidation();});
			else{
			    $p = (!$f.is('form')) ? $f.parents('form:first') : $f;
			    $p.bind('submit', function(){return doValidation();});
			}
			if(opt.watermark){
				$('input[watermark], textarea[watermark]', $f).each(function() {
					var $t = $(this);
					attachWatermarks($t);
				});
			}
			if(opt.onBlur){
				$.each($('input[validate]:checkbox, input[validate]:radio', $f), function() {
					var $c = $(this);
					var n = $c.attr('name');
					var v = $c.attr('validate');
					var t = $c.attr('title');
					$('input[name="'+escN(n)+'"]', $f).attr({validate: v, title: t});
				});
				$('input[validate], textarea[validate], select[validate]', $f).bind('blur', function(){return validateItem($(this));});
			}
			$f.data('nextVal', self);
			$f.data('nextVal.validate', function() {doValidation();});
			$f.data('nextVal.validateField', function($o) {validateItem($o);});
		};
		var attachWatermarks = function($t){
			if($t.val() == '' || $t.val() == $t.attr('watermark')){
				if(opt.styleParent)
					$t.val($t.attr('watermark')).parent().addClass(opt.watermarkStyle);
				else
					$t.val($t.attr('watermark')).addClass(opt.watermarkStyle);
			}
			$t.bind('focus', function() {
					var $o = $(this);
					if($o.val() == $o.attr('watermark')){
						if(opt.styleParent)
							$o.val('').parent().removeClass(opt.watermarkStyle);
						else
							$o.val('').removeClass(opt.watermarkStyle);
					}
				}).bind('blur', function() {
					var $o = $(this);
					if($o.val() == ''){
						if(opt.styleParent)
							$o.val($o.attr('watermark')).parent().addClass(opt.watermarkStyle);
						else
							$o.val($o.attr('watermark')).addClass(opt.watermarkStyle);
					}
				});
		};
		var doValidation = function(){
			if(!opt.onCall)
				return false;
			var r = true;
			var n = $f.attr('nextVal');
			$('#'+opt.summaryId+n).remove();
			$.each($('input[validate], textarea[validate], select[validate]', $f), function(){
				var vo = validateItem($(this));
				if(!vo)
					r = false;
			});
			r = opt.formCallback(r) || r;
			return r;
		};
		var validateItem = function($o){
			var f = $f.attr('nextVal');
			if($o.hasClass(opt.ignoreFieldClass))
				return true;
			var vo = true;
			if(opt.styleParent)
				$o.parent().removeClass(opt.passedStyle+' '+opt.failedStyle);
			else
				$o.removeClass(opt.passedStyle+' '+opt.failedStyle);
			if(opt.watermark && $o.attr('watermark') && $o.val() == $o.attr('watermark')){
				if(opt.styleParent)
					$o.val('').parent().removeClass(opt.watermarkStyle);
				else
					$o.val('').removeClass(opt.watermarkStyle);
			}
			$('#'+opt.messageId+f+'-'+escN($o.attr('name'))).remove();
			var validators = [['empty', function($o){return $o.val()=='';}, 'Please enter some text in this field.'],
							  ['checked', function($o){return !$o.is(':checked');}, 'It is required that you check this check box.'],
							  ['alpha', function($o){return !$o.val().match(/^[a-zA-Z]+$/);}, 'Please use letter only. For example Sketchy.'],
							  ['decimal', function($o){return !$o.val().match(/^[0-9]+$/);}, 'Please use decimal numbers only. For example 42.'],
							  ['number', function($o){return !$o.val().match(/^[-]?([1-9]{1}[0-9]{0,}(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|\.[0-9]{1,2})$/);}, 'Please use numbers only. For example -4.2.'],
							  ['email', function($o){return !$o.val().match(/^\w+([&\-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/);}, 'Please enter a valid email address. For example john@somedomain.com.'],
							  ['phone', function($o){return !$o.val().match(/^[01]?[- .]?\(?(?!\d[1]{2})[2-9]\d{2}\)?[- .]?(?!\d[1]{2})\d{3}[- .]?\d{4}$/);}, 'Please enter a valid US/Canada phone number. For example (603) 555-5555.'],
							  ['postal', function($o){return !$o.val().match(/^\d{5}-\d{4}|\d{5}|[A-Z]\d[A-Z] \d[A-Z]\d$/);}, 'Please enter a valid US/Canada postal code. For example 03102.'],
							  ['date', function($o){return !$o.val().match(/^(((((((0?[13578])|(1[02]))[\.\-/]?((0?[1-9])|([12]\d)|(3[01])))|(((0?[469])|(11))[\.\-/]?((0?[1-9])|([12]\d)|(30)))|((0?2)[\.\-/]?((0?[1-9])|(1\d)|(2[0-8]))))[\.\-/]?(((19)|(20))?([\d][\d]))))|((0?2)[\.\-/]?(29)[\.\-/]?(((19)|(20))?(([02468][048])|([13579][26])))))$/);}, 'Please enter a valid date. For example 7/3/1987.']];
			validators = validators.concat(opt.customRules);
			var l = validators.length;
			if($o.is(':checkbox') && $o.attr('validate') != 'checked' ){
				var a = $o.attr('validate');
				var n = a.match(/.*min\[([\d]*)\].*/) ? a.match(/.*min\[([\d]*)\].*/)[1] : 0;
				var kim = a.match(/.*max\[([\d]*)\].*/) ? a.match(/.*max\[([\d]*)\].*/)[1] : 99999;
				var t = $('input[name="'+escN($o.attr('name'))+'"]:checked', $f).size();
				vo = (t >= n && t <= kim) ? vo : false;
				if(!vo)
					doReport($o, 'You have not met the selection requirements.');
			}else if($o.attr('validate').match(/regex\[([^\s]*)\]/)){
				var x = $o.val().match($o.attr('validate').match(/regex\[([^\s]*)\]/)[1]);
				if(x==null){
					doReport($o, 'Please match the information specified.');
					vo = false;
				}
			}else{
				for(i=0;i<l;i++){
					if(hasValidator($o, validators[i][0])&&validators[i][1]($o)){
						doReport($o, validators[i][2]);
						vo = false;
						break;
					}
				}
			}
			if(opt.useStyles){
				if(opt.styleParent)
					$o.parent().addClass((vo ? opt.passedStyle : opt.failedStyle));
				else
					$o.addClass((vo ? opt.passedStyle : opt.failedStyle));
			}
			vo = opt.itemCallback(vo) || vo;
			if(opt.watermark && $o.attr('watermark') && $o.val() == ''){
				if(opt.styleParent)
					$o.val($o.attr('watermark')).parent().addClass(opt.watermarkStyle);
				else
					$o.val($o.attr('watermark')).addClass(opt.watermarkStyle);
			}
			return vo;
		};
		var doReport = function($o, m){
			if($o.is(':checkbox') || $o.is(':radio')){
				var $c = $('input[name="'+escN($o.attr('name'))+'"]', $f);
				switch(opt.attach){
					case 'top': case 'custom': $o = $c.first(); break;
					case 'bottom': $o = $c.last(); break;
					default: $o = $c.first(); break;
				}
			}
			if(opt.useInline )
				writeMessage($o, m);
			if(opt.useSummary)
				writeSummary($o, m);
		};
		var writeMessage = function($o, m){	
			var o = '<'+opt.validationTag+' class="'+opt.messageStyle+'" id="'+opt.messageId+$f.attr('nextVal')+'-'+$o.attr('name')+'">'+(opt.validationTag=='ul' ? '<li>' : '')+(opt.useErrorText==true&&$o.attr('errorText') ? $o.attr('errorText') : m)+(opt.validationTag=='ul' ? '</li>' : '')+'</'+opt.validationTag+'>';
			switch(opt.attach){
				case 'top': return $o.before(o); break;
				case 'bottom': return $o.after(o); break;
				case 'custom': return $o.attr('attach') ? $($o.attr('attach')).first().prepend(o) : $o.before(o); break;
				default: return $(opt.attach).first().prepend(o); break;
			}
		};
		var writeSummary = function($o, m){
			var f = opt.summaryId+$f.attr('nextVal');
			if($('#'+f).size() < 1) { //if summary dosnt exist
				s = '<'+opt.validationTag+' class="'+opt.summaryStyle+'" id="'+f+'"></'+opt.validationTag+'>';
				switch(opt.attach){
					case 'top': $f.prepend(s); break;
					case 'bottom': $f.append(s); break;
					case 'custom': $f.attr('attach') ? $($f.attr('attach')).first().prepend(s) : $f.prepend(s); break;
					default: $(opt.attach).first().prepend(s); break;
				}
			}
			return $('#'+f).append('<'+(opt.validationTag=='ul' ? 'li' : opt.validationTag)+' id="'+opt.messageId+$f.attr('nextVal')+'-'+$o.attr('name')+'">'+(opt.useTitles==true&&$o.attr('title') ? $o.attr('title') : m)+'</'+(opt.validationTag=='ul' ? 'li' : opt.validationTag)+'>');
		};
		var hasValidator = function($o, s){
			var s = ' '+s+' ';
			var v = ' '+$o.attr('validate')+' ';
			return (v.match(s) == null) ? false : v.match(s);
		};
		var escN = function(n){
			var a = new Array('#', ';', '&', ',', '.', '+', '*', '~', '\'', ':', '"', '!', '^', '$', '[', ']', '(', ')', '=', '>', '|', '/');
			var l = a.length;
			for(i=0;i<l;i++){
				n = n.replace(a[i], '\\'+a[i]);
			}
			return n;
		};
		init();
	};
	
	$.fn.nextVal = function(opt) {
		return this.each(function() {
			(new nextVal($(this), opt));
		});
	};
	$.fn.nextVal.version = "1.7.1";
	$.fn.nextVal.validate = function() {
		return this.each(function() {
			$(this).data('nextVal.validate')();
		});
	};
	$.fn.nextVal.validateField = function() {
		return this.each(function($o) {
			$(this).data('nextVal.validateField')($o);
		});
	};
})(jQuery);
