/*
 * 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
        if ((options || '').toString().toUpperCase() == 'FAIL')
        {
            $(this).find('.jVal,[jVal]:not(:disabled):visible').each(
                function()
                {
                    var failcmd = $(this).data('jVal'), ds = $.fn.jVal.defaults.style;
                    if (typeof failcmd !== 'object') eval('failcmd = ' + ($(this).data('jVal') || $(this).attr('jVal')) + ';');
                    $.fn.jVal.showWarning(failcmd.target || this, '', false, ds);
                }
            );
            return;
        }

        $.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
			        {
			            $.fn.jVal.showCheckmark(cmd.target || this, 'Success!', cmd.styleType || ds);
			        }
			    } 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;
			    } else
			    {
			        $.fn.jVal.showCheckmark(cmd.target || this, 'Winner!', cmd.styleType || ds);
			    }
			}
		);
        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);
    };
    $.fn.jVal.showCheckmark = function(elements, message, styleType)
    {
        var par = $(elements).eq(0).parent();
        $.fn.jVal.clean(par);
        $(elements).css({ marginTop: '', position: '', borderColor: '#50FF58' });
        var dbw = $.fn.jVal.defaults.border,
			dp = 0; //$.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,
            rPos = absoluteLeft + fieldWidth + 3,
			ph = (fieldHeight + (dp * 2)),
			fhp = fieldHeight + dp + dbw,
			fwp = fieldWidth + dp,
			clips = [
				'rect(0px, 5000px, ' + dbw + 'px, 0px)', // top
				'rect(' + fhp + 'px, 5000px, 5000px, 0px)', // bottom
				'rect(0px, 0px, 5000px, 0px)', // left
				'rect(0px, 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="validicon' + (styleType ? ' validicon' + styleType : '') + '" style="height:' + ph + 'px;"><div class="validiconbg" /></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] }));

        $(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.showCheckmark(cmd.target || this, 'Delightful!', cmd.styleType || ds);
                }
            } 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.showCheckmark(cmd.target || this, 'Victory!', cmd.styleType || ds);

                }
            }
            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);
