(function()
{

    // Basic Configuration
    var CONFIG = {
        VALIDATE_HTML_CLASS: 'validate',
        REQUIRED_FIELD_HTML_CLASS: 'required',
        VALIDATE_ERROR_CLASS: 'onError'
    };

    var CUSTOMS = {};
    var LAST_ERROR = '';

    // Update LAST_ERROR
    function __onError(errorMessage)
    {
        LAST_ERROR = errorMessage;
        return false;
    }

    // Trying a rule, returns TRUE on SUCCESS
    function __tryRule(aRule, testValue)
    {
        if (aRule.test)
            return aRule.test(testValue);

        return aRule(testValue);
    }

    // Actual Methodology Behind Check Field
    function __checkField(elField)
    {
        var curElement = elField;
        var classNames = curElement.attr('class').split(' ');

        // Try Generics

        for (var i = 0, l = classNames.length; i < l; ++i)
            if (classNames[i] in MAP_GENERICS)
            if (!Validate2.runGeneric(curElement, MAP_GENERICS[classNames[i]]))
            return false;

        // Try custom (validate = '')
        var uniqueRule = curElement.attr('validate');

        if (uniqueRule in CUSTOMS)
            return Validate2.runCustom(curElement, uniqueRule);

        // Fine, return true
        return true;
    }

    // Popup an Alert
    function __popupAlert(nextToElement, errorMessage)
    {
        var testValue = nextToElement.addClass('onError').val() + '';

        if (nextToElement.hasClass(CONFIG.REQUIRED_FIELD_HTML_CLASS) || testValue.length > 0)
        {
            /* RAY'S MODIFIED POSITIONING */
            var parentLeft = $(nextToElement).parent().offset().left;
            var parentTop = $(nextToElement).parent().offset().top;

            var left = (nextToElement.offset().left + nextToElement.width() - 1) - parentLeft;
            var top = (nextToElement.offset().top + nextToElement.height() - 30) - parentTop;

            if (nextToElement.data('errorNode'))
                nextToElement.data('errorNode').remove();

            /*  MATI'S ORIGINAL
            var errorNode = $('<span>').addClass('val2-errorNode')
            .text(errorMessage)
            .css({
            'left': left,
            'top': top
            })
            .prependTo($('#content')).click(function onClick() {
            $(this).remove();
            nextToElement.focus();
            });*/

            var errorNode = $('<span>').addClass('val2-errorNode')
            .text(errorMessage)
            .css({
                'margin-left': left,
                'margin-top': top,
                'float': 'left'
            })
            .prependTo($(nextToElement).parent()).click(function onClick()
            {
                $(this).remove();
                nextToElement.focus();
            });

            nextToElement.data('errorNode', errorNode);
            //errorNode.css('left', left - errorNode.width()).css('position', 'absolute').fadeIn(); // MATI'S ORIGINAL
            errorNode.css('margin-left', left - errorNode.width()).css('position', 'absolute').fadeIn();

            /*setTimeout(function() {
            errorNode.fadeOut();
            }, 4000);*/
        }
    }

    // Global Object
    var Validate2 = {
        addCustom: function addCustom(customRuleName, testBy, errorMessage)
        {
            CUSTOMS[customRuleName] = {
                'test': testBy,
                'error': errorMessage
            };
        },

        addGeneric: function addGeneric(genericRuleName, elClass, testBy, errorMessage)
        {
            GENERICS[genericRuleName] = {
                'class': elClass,
                'test': testBy,
                'error': errorMessage
            };

            MAP_GENERICS[elClass] = genericRuleName;
        },

        checkField: function checkElement(elField)
        {
            if (!__checkField(elField))
                __popupAlert(elField, LAST_ERROR);
        },

        runCustom: function runCustom(elField, customRuleName)
        {
            var objRule = CUSTOMS[customRuleName];

            if (objRule.test instanceof Array)
            {
                for (var i = 0, l = objRule.test.length; i < l; ++i)
                {
                    if (!__tryRule(objRule.test[i]), elField.val())
                        return __onError(objRule.error);
                }
            } else
            {
                if (!__tryRule(objRule.test, elField.val()))
                    return __onError(objRule.error);
            }

            return true;
        },

        runGeneric: function runGeneric(elField, genericRuleName)
        {

            var objRule = GENERICS[genericRuleName];

            if (objRule.test instanceof Array)
            {
                for (var i = 0, l = objRule.test.length; i < l; ++i)
                    if (!__tryRule(objRule.test[i], elField.val()))
                    return __onError(objRule.error);
            } else
            {
                if (!__tryRule(objRule.test, elField.val()))
                    return __onError(objRule.error);
            }

            return true;
        }
    };

    // Generic Validation Types

    /*
    Settings
    - Class to apply to
    - Test function or regular expression or array of said parameters to use
    - Error message to show
    */

    /*
    Example:

	'email' : {
    'class': 'val-email',
    'test' : /^(\w+[\-\.])*\w+@(\w+\.)+[A-Za-z]+$/,
    'error': 'Must be a valid email address in the format of name@domain.com'
    }
    */

    var GENERICS = {
        'email': {
            'class': 'val-email',
            'test': /^(\w+[\-\.])*\w+@(\w+\.)+[A-Za-z]+$/,
            'error': 'Field must be a valid email of type name@domain.com'
        },

        'integer': {
            'class': 'val-integer',
            'test': function testInteger(testValue)
            {
                return !isNaN(testValue) && parseInt(testValue) == testValue;
            },
            'error': 'Field must be a valid integer'
        },

        'posNum': {
            'class': 'val-positive',
            'test': function testPositive(testValue)
            {
                return testValue >= 0;
            },
            'error': 'Field must be a positive number'
        },

        'notEmpty': {
            'class': 'val-exists',
            'test': function testNotEmpty(testValue)
            {
                return testValue != '' && testValue != null;
            },
            'error': 'Field cannot be left blank'
        },

        'date': {
            'class': 'val-date',
            'test': function testDate(testValue)
            {
                return Date.parse(testValue) == testValue;
            },
            'error': 'Field must be a date in the form of mm/dd/yyyy'
        }
    };

    // Map Classes to Names
    var MAP_GENERICS = {};

    // Faster Searching, Cache Generic Mappings
    for (var k in GENERICS)
        MAP_GENERICS[GENERICS[k]['class']] = k;

    // Run Validation Rules
    $(function onDocumentReady()
    {
        $('.' + CONFIG.VALIDATE_HTML_CLASS).each(function forEach(n, el)
        {
            $(el).blur(function onBlur()
            {
                if ($(this).removeClass('onError').data('errorNode'))
                    $(this).data('errorNode').remove();

                Validate2.checkField($(this));
            });

            if (el.type == "submit")
            {
                //attach a click event to the submit buttons
                $(el).click(function onClick()
                {
                    $('.' + CONFIG.VALIDATE_HTML_CLASS).each(function forEach(n, el)
                    {
                        //force blur() event for all of the validated elements
                        //to make sure the input is ok
                        $(el).blur();
                    });

                    //if any validated elements still have errors, don't submit
                    if ($('.' + CONFIG.VALIDATE_ERROR_CLASS).length > 0)
                    {
                        alert("Please fix the indicated errors on the form and submit again.");
                        return false;
                    }
                    else
                    {
                        return true;
                    }
                });
            }
        });
    });

    // Export
    window.Validate2 = Validate2;

})();