/*
 * jVal - dynamic jquery form field validation framework
 *	version 0.1.5
 * Author: Jim Palmer
 * Released under MIT license.
 */
(function($) {
	// core jVal function - returns TRUE if all jVal validation passes, false if not
	$.fn.jVal = function (options) {
		$.fn.jVal.clean(this);
		// clear jVal warnings
		if ((options || '').toString().toUpperCase() == 'CLEAN')
			return this; // chainable
		$.fn.jVal.defaults = $.extend($.fn.jVal.defaults, options);
		$(this).stop().find('.jfVal,.jValCover').stop().remove();
		var passVal = true, dm = $.fn.jVal.defaults.message, ds = $.fn.jVal.defaults.style;
		$(this).find('.jVal,[jVal]:not(:disabled):visible').each(
			function () {
				var cmd = $(this).data('jVal');
				if ( typeof cmd !== 'object' ) eval( 'cmd = ' + ( $(this).data('jVal') || $(this).attr('jVal') ) + ';' );
				//$.fn.jVal.clean(this);
				if ( cmd instanceof Object && cmd.valid instanceof RegExp && !cmd.valid.test($(this).val()) ) {
					$.fn.jVal.showWarning(cmd.target || this, cmd.message || dm, cmd.autoHide || false, cmd.styleType || ds);
					passVal = false;
				} else if ( cmd instanceof Object && cmd.valid instanceof Function ) {
					var testFRet = cmd.valid( $(this).val(), this );
					if ( testFRet === false || testFRet.length > 0 ) {
						$.fn.jVal.showWarning(cmd.target || this, testFRet || cmd.message || dm, cmd.autoHide || false, cmd.styleType || ds);
						passVal = false;
					}
				} else if ( ( cmd instanceof RegExp && !cmd.test($(this).val()) ) || ( cmd instanceof Function && !cmd($(this).val()) ) ) {
					$.fn.jVal.showWarning(cmd.target || this, dm, cmd.autoHide || false, cmd.styleType || ds);
					passVal = false;
				}
			}
		);
		return passVal;
	};
	// showWarning utility function
	$.fn.jVal.showWarning = function (elements, message, autoHide, styleType) {
		var par = $(elements).eq(0).parent();
		clearTimeout( $(par).data('autoHide') ) && $(par).data('autoHide', null);
		$.fn.jVal.clean(par);
		$(elements).css({marginTop:'',position:'',borderColor:'red'});
		var dbw = $.fn.jVal.defaults.border,
			dp = $.fn.jVal.defaults.padding,
			fieldWidth = 0, fieldHeight = 0,
			absoluteLeft = $(elements).eq(0).position().left,
			absoluteTop = $(elements).eq(0).position().top;
		// normalize multi-element coordinates
		$(elements).each(function () {
			fieldWidth += $(this).outerWidth(true);
			fieldHeight = Math.max( $(elements).outerHeight(true), fieldHeight );
			absoluteLeft = Math.min( $(this).position().left, absoluteLeft );
			absoluteTop = Math.min( $(this).position().top, absoluteTop );
		});
		var fwidth = 0, tPos = absoluteTop - dp - dbw, lPos = absoluteLeft + dp, rPos = absoluteLeft + fieldWidth + dp,
			ph = (fieldHeight + (dp * 2)), fhp = fieldHeight + dp + dbw, fwp = fieldWidth + dp,
			clips = [ 
				'rect(0px, 5000px, ' + (dp + dbw) + 'px, 0px)', // top
				'rect(' + fhp + 'px, 5000px, 5000px, 0px)', // bottom
				'rect(' + dp + 'px, ' + dp + 'px, 5000px, 0px)', // left
				'rect(' + dp + 'px, 5000px, 5000px, ' + fwp + 'px)']; // right
		// fixed alerts, no wrapping
		if (!$.fn.jVal.defaults.wrap) {
			fieldWidth = ph = fph = 12;
			rPos = lPos;
		}
		$(elements).eq(0).before(
			'<div class="jValSpacer jValSpacer' + styleType + '" style="line-height:' + ph + 'px; height:' + ph + 'px; clip:' + clips[0] + '; ' +
				'left:' + (absoluteLeft - dp) + 'px; top:' + tPos + 'px; ' + 
				'width:' + (fieldWidth + (dp * 2)) + 'px;" />' +
			'<div class="jfVal' + ( styleType ? ' jfVal' + styleType : '' ) + '" style="left:' + lPos + 'px; ' +
				'top:' + tPos + 'px;">' +
				'<div class="icon' + ( styleType ? ' icon' + styleType : '' ) + '" style="height:' + ph + 'px;"><div class="iconbg" /></div>' +
				'<div class="content' + ( styleType ? ' content' + styleType : '' ) + '" style="height:' + ph + 'px; line-height:' + ph + 'px;">' +
					'<span class="message' + styleType + '">' + message + '</span>' +
				'</div>' +
			'</div>')
			.parent().find('.jfVal>*').each(function () { fwidth += $(this).outerWidth() }).end()
			.find('.jfVal').width(fwidth + 20);
		for (var si = 1; si < 4; si++) 
			$(par).find('.jValSpacer:first').before( 
				$(par).find('.jValSpacer:first').clone().css({clip:clips[si]}) );
		// autoHide = set spacer width + add autohide function to fx queue
		if ( autoHide )
			$(par).data( 'autoHide', setTimeout(function () { $(par).find('.jfVal').animate({left:lPos,opacity:0}, 200, function () { $.fn.jVal.clean(par); }); }, 2000) )
				.find('.jfVal').css({'left':rPos});
		else
			$(par).find('.jfVal').css({opacity:0}).animate({left:rPos,opacity:1}, 200);
	};
	// validate key stroke
	$.fn.jVal.valKey = function (keyRE, e, cF, cA) {
		var ek = (typeof(e.keyCode) != 'undefined'), ec = (typeof(e.charCode) != 'undefined'),
			k = e.keyCode, c = e.charCode, ks = k.toString();
		// return if invalid regex
		if ( !(keyRE instanceof RegExp) ) 
			return false;
		// test for ENTER key and cF being valid function otherwise return true
		if ( /^13$/.test(String(k || c)) ) {
			try { (this[cF]) ? this[cF](cA) : eval(cF); } catch(e) { return true; }
			return -1;
		}
		// otherwise test for valid keys supported by the regex allowing meta keys by default
		if	( e.ctrlKey || e.shiftKey || e.metaKey || (
				ek && k > 0 && keyRE.test(String.fromCharCode(k)) ) ||
				( ec && c > 0 && String.fromCharCode(c).search(keyRE) != (-1) ) ||
				( ec && c != k && ek && ks.search(/^(8|9|45|46|35|36|37|39)$/) != (-1) ) ||
				( ec && c == k && ek && ks.search(/^(8|9)$/) != (-1) ||
				( !ec && ek && ks.search(/^(8|9|45|46|35|36|37|39)$/) != (-1) ) ) 
			) {
			return 1;
		} else {
			return 0;
		}
	};
	// clear all displayed jVal warnings within scope
	$.fn.jVal.clean = function (target) {
		$(target)
			.find('.jfVal,.jValSpacer').stop().remove().end()
			.find('.jVal,[jVal]').css({position:'',borderColor:'',left:'0px',top:'0px'}).parent().find('.jValRelWrap').remove();
	};
	$.fn.jVal.init = function (options) {
		$.fn.jVal.defaults = $.extend($.fn.jVal.defaults, options);
		$('.jVal,[jVal]').filter(':not(:disabled)').unbind("blur").bind("blur", function (e) { 
			if ($.fn.jVal.defaults.blurCheck || $(this).hasClass('jValBlur'))
				$(this).parent().jVal();
		});
		var keyFunc = function (e) {
			eval( 'var cmd = ' + ( $(this).data('jValKey') || $(this).attr('jValKey') || $(this).data('jValKeyUp') || $(this).attr('jValKeyUp') ) + ';' );
			var keyTest, ds = $.fn.jVal.defaults.style, dkm = $.fn.jVal.defaults.keyMessage,
				autoHide = typeof(cmd.autoHide) != 'undefined' ? cmd.autoHide : true;
			if ( cmd instanceof Object && cmd.valid instanceof Function ) {
				keyTest = cmd.valid( e, this );
				if ( keyTest === false || keyTest.length > 0 ) {
					$.fn.jVal.clean(cmd.target || this);
					// only show warning and allow keypress event to return true
					$.fn.jVal.showWarning(cmd.target || this, ( keyTest || (cmd.message || dkm).replace('%c', String.fromCharCode(e.keyCode || e.charCode))), autoHide, cmd.styleType || ds);
				} else 
					$.fn.jVal.clean($(cmd.target || this).parent());
			} else {
				keyTest = $.fn.jVal.valKey( ( (cmd instanceof Object) ? cmd.valid : cmd ), e, (cmd instanceof Object) ? cmd.cFunc : null, (cmd instanceof Object) ? cmd.cArgs : null );
				if ( keyTest == 0 ) {
					$.fn.jVal.clean(cmd.target || this);
					$.fn.jVal.showWarning(cmd.target || this, (( cmd instanceof Object && cmd.message) || dkm).replace('%c', String.fromCharCode(e.keyCode || e.charCode)), autoHide, cmd.styleType || ds);
					return false;
				} else if ( keyTest == -1 ) 
					return false;
				else 
					$.fn.jVal.clean($(cmd.target || this).parent());
			}
			return true;
		};
		$('.jValKey,[jValKey]').filter(':not(:disabled)').unbind("keypress").bind("keypress", keyFunc);
		$('.jValKeyUp,[jValKeyUp]').filter(':not(:disabled)').unbind("keyup").bind("keyup", keyFunc);
		return this; // chainable
	};
	// jVal defaults
	$.fn.jVal.defaults = {
		blurCheck: false,
		message: 'Invalid entry',
		style: 'pod',
		keyMessage: '"%c" Invalid character',
		padding: 3,
		border: 1,
		wrap: true
	};
	// automatically init on dom load
	$($.fn.jVal.init);
})(jQuery);
