/*
 * Inline Form Validation Engine 2.2.4, jQuery plugin
 *
 * Copyright(c) 2010, Cedric Dugas
 * http://www.position-absolute.com
 *
 * 2.0 Rewrite by Olivier Refalo
 * http://www.crionics.com
 *
 * Form validation engine allowing custom regex rules to be added.
 * Licensed under the MIT License
 */
(function ($) {

    var methods = {

        /**
        * Kind of the constructor, called before any action
        * @param {Map} user options
        */
        init: function (options) {
            var form = this;
            if (!form.data('jqv') || form.data('jqv') == null) {
                methods._saveOptions(form, options);

                // bind all formError elements to close on click
                $(".formError").live("click", function () {
                    $(this).fadeOut(150, function () {

                        // remove prompt once invisible
                        $(this).parent('.formErrorOuter').remove();
                        $(this).remove();
                    });
                });
            }
            return this;
        },
        /**
        * Attachs jQuery.validationEngine to form.submit and field.blur events
        * Takes an optional params: a list of options
        * ie. jQuery("#formID1").validationEngine('attach', {promptPosition : "centerRight"});
        */
        attach: function (userOptions) {

            var form = this;
            var options;

            if (userOptions)
                options = methods._saveOptions(form, userOptions);
            else
                options = form.data('jqv');

            var validateAttribute = (form.find("[data-validation-engine*=validate]")) ? "data-validation-engine" : "class";

            if (!options.binded) {
                if (options.bindMethod == "bind") {

                    // bind fields
                    form.find("[class*=validate]").not("[type=checkbox]").not("[type=radio]").not(".datepicker").bind(options.validationEventTrigger, methods._onFieldEvent);
                    form.find("[class*=validate][type=checkbox],[class*=validate][type=radio]").bind("click", methods._onFieldEvent);
                    form.find("[class*=validate][class*=datepicker]").bind(options.validationEventTrigger, { "delay": 300 }, methods._onFieldEvent);

                    // bind form.submit
                    form.bind("submit", methods._onSubmitEvent);
                } else if (options.bindMethod == "live") {
                    // bind fields with LIVE (for persistant state)
                    form.find("[class*=validate]").not("[type=checkbox]").not(".datepicker").live(options.validationEventTrigger, methods._onFieldEvent);
                    form.find("[class*=validate][type=checkbox]").live("click", methods._onFieldEvent);
                    form.find("[class*=validate][class*=datepicker]").live(options.validationEventTrigger, { "delay": 300 }, methods._onFieldEvent);

                    // bind form.submit
                    form.live("submit", methods._onSubmitEvent);
                }

                options.binded = true;

                if (options.autoPositionUpdate) {
                    $(window).bind("resize", {
                        "noAnimation": true,
                        "formElem": form
                    }, methods.updatePromptsPosition);
                }

            }
            return this;
        },
        /**
        * Unregisters any bindings that may point to jQuery.validaitonEngine
        */
        detach: function () {
            var form = this;
            var options = form.data('jqv');
            if (options.binded) {

                // unbind fields
                form.find("[class*=validate]").not("[type=checkbox]").unbind(options.validationEventTrigger, methods._onFieldEvent);
                form.find("[class*=validate][type=checkbox],[class*=validate][type=radio]").unbind("click", methods._onFieldEvent);

                // unbind form.submit
                form.unbind("submit", methods.onAjaxFormComplete);

                // unbind live fields (kill)
                form.find("[class*=validate]").not("[type=checkbox]").die(options.validationEventTrigger, methods._onFieldEvent);
                form.find("[class*=validate][type=checkbox]").die("click", methods._onFieldEvent);

                // unbind form.submit
                form.die("submit", methods.onAjaxFormComplete);

                form.removeData('jqv');

                if (options.autoPositionUpdate) {
                    $(window).unbind("resize", methods.updatePromptsPosition)
                }
            }
            return this;
        },
        /**
        * Validates the form fields, shows prompts accordingly.
        * Note: There is no ajax form validation with this method, only field ajax validation are evaluated
        *
        * @return true if the form validates, false if it fails
        */
        validate: function () {
            return methods._validateFields(this);
        },
        /**
        * Validates one field, shows prompt accordingly.
        * Note: There is no ajax form validation with this method, only field ajax validation are evaluated
        *
        * @return true if the form validates, false if it fails
        */
        validateField: function (el) {
            var options = $(this).data('jqv');
            var r = methods._validateField($(el), options);

            if (options.onSuccess && options.InvalidFields.length == 0)
                options.onSuccess();
            else if (options.onFailure && options.InvalidFields.length > 0)
                options.onFailure();

            return r;
        },
        /**
        * Validates the form fields, shows prompts accordingly.
        * Note: this methods performs fields and form ajax validations(if setup)
        *
        * @return true if the form validates, false if it fails, undefined if ajax is used for form validation
        */
        validateform: function () {
            return methods._onSubmitEvent.call(this);
        },
        /**
        *  Redraw prompts position, useful when you change the DOM state when validating
        */
        updatePromptsPosition: function (event) {

            if (event && this == window)
                var form = event.data.formElem, noAnimation = event.data.noAnimation;
            else
                var form = $(this.closest('form'));

            var options = form.data('jqv');
            // No option, take default one
            form.find('[class*=validate]').not(':hidden').not(":disabled").each(function () {
                var field = $(this);
                var prompt = methods._getPrompt(field);
                var promptText = $(prompt).find(".formErrorContent").html();

                if (prompt)
                    methods._updatePrompt(field, $(prompt), promptText, undefined, false, options, noAnimation);
            })
            return this;
        },
        /**
        * Displays a prompt on a element.
        * Note that the element needs an id!
        *
        * @param {String} promptText html text to display type
        * @param {String} type the type of bubble: 'pass' (green), 'load' (black) anything else (red)
        * @param {String} possible values topLeft, topRight, bottomLeft, centerRight, bottomRight
        */
        showPrompt: function (promptText, type, promptPosition, showArrow) {

            var form = this.closest('form');
            var options = form.data('jqv');
            // No option, take default one
            if (!options) options = methods._saveOptions(this, options);
            if (promptPosition)
                options.promptPosition = promptPosition;
            options.showArrow = showArrow == true;

            methods._showPrompt(this, promptText, type, false, options);
            return this;
        },
        /**
        * Closes all error prompts on the page
        */
        hidePrompt: function () {
            var promptClass = "." + methods._getClassName($(this).attr("id")) + "formError";
            $(promptClass).fadeTo("fast", 0.3, function () {
                $(this).parent('.formErrorOuter').remove();
                $(this).remove();
            });
            return this;
        },
        /**
        * Closes form error prompts, CAN be invidual
        */
        hide: function () {
            var closingtag;
            if ($(this).is("form")) {
                closingtag = "parentForm" + methods._getClassName($(this).attr("id"));
            } else {
                closingtag = methods._getClassName($(this).attr("id")) + "formError";
            }
            $('.' + closingtag).fadeTo("fast", 0.3, function () {
                $(this).parent('.formErrorOuter').remove();
                $(this).remove();
            });
            return this;
        },
        /**
        * Closes all error prompts on the page
        */
        hideAll: function () {
            $('.formError').fadeTo("fast", 0.3, function () {
                $(this).parent('.formErrorOuter').remove();
                $(this).remove();
            });
            return this;
        },
        /**
        * Typically called when user exists a field using tab or a mouse click, triggers a field
        * validation
        */
        _onFieldEvent: function (event) {
            var field = $(this);
            var form = field.closest('form');
            var options = form.data('jqv');
            // validate the current field
            window.setTimeout(function () {
                methods._validateField(field, options);
                if (options.InvalidFields.length == 0 && options.onSuccess) {
                    options.onSuccess();
                } else if (options.InvalidFields.length > 0 && options.onFailure) {
                    options.onFailure();
                }
            }, (event.data) ? event.data.delay : 0);

        },
        /**
        * Called when the form is submited, shows prompts accordingly
        *
        * @param {jqObject}
        *            form
        * @return false if form submission needs to be cancelled
        */
        _onSubmitEvent: function () {
            var form = $(this);
            var options = form.data('jqv');
            // $("#ui-datepicker-div").;

            // validate each field (- skip field ajax validation, no necessary since we will perform an ajax form validation)
            var r = methods._validateFields(form, true);

            if (r && options.ajaxFormValidation) {
                methods._validateFormWithAjax(form, options);
                return false;
            }

            if (options.onValidationComplete) {
                options.onValidationComplete(form, r);
                return false;
            }
            return r;
        },

        /**
        * Return true if the ajax field validations passed so far
        * @param {Object} options
        * @return true, is all ajax validation passed so far (remember ajax is async)
        */
        _checkAjaxStatus: function (options) {
            var status = true;
            $.each(options.ajaxValidCache, function (key, value) {
                if (!value) {
                    status = false;
                    // break the each
                    return false;
                }
            });
            return status;
        },
        /**
        * Validates form fields, shows prompts accordingly
        *
        * @param {jqObject}
        *            form
        * @param {skipAjaxFieldValidation}
        *            boolean - when set to true, ajax field validation is skipped, typically used when the submit button is clicked
        *
        * @return true if form is valid, false if not, undefined if ajax form validation is done
        */
        _validateFields: function (form, skipAjaxValidation) {
            var options = form.data('jqv');

            // this variable is set to true if an error is found
            var errorFound = false;

            // Trigger hook, start validation
            form.trigger("jqv.form.validating");
            // first, evaluate status of non ajax fields
            var first_err = null;
            form.find('[class*=validate]').not(':hidden').not(":disabled").each(function () {
                var field = $(this);
                errorFound |= methods._validateField(field, options, skipAjaxValidation);
                //field.focus();//commented to prevent popup calender on validation EPI Survey Modification
               
                if (options.doNotShowAllErrosOnSubmit)
                    return false;
                if (errorFound && first_err == null) first_err = field;
            });
            // second, check to see if all ajax calls completed ok
            // errorFound |= !methods._checkAjaxStatus(options);

            // thrird, check status and scroll the container accordingly
            form.trigger("jqv.form.result", [errorFound]);

            if (errorFound) {
                if (options.scroll) {
                    var destination = first_err.offset().top;
                    var fixleft = first_err.offset().left;

                    //prompt positioning adjustment support. Usage: positionType:Xshift,Yshift (for ex.: bottomLeft:+20 or bottomLeft:-20,+10)
                    var positionType = options.promptPosition;
                    if (typeof (positionType) == 'string') {
                        if (positionType.indexOf(":") != -1) {
                            positionType = positionType.substring(0, positionType.indexOf(":"));
                        }
                    }

                    if (positionType != "bottomRight" &&
				    positionType != "bottomLeft") {
                        var prompt_err = methods._getPrompt(first_err);
                        destination = prompt_err.offset().top;
                    }

                    // get the position of the first error, there should be at least one, no need to check this
                    //var destination = form.find(".formError:not('.greenPopup'):first").offset().top;
                    if (options.isOverflown) {
                        var overflowDIV = $(options.overflownDIV);
                        if (!overflowDIV.length) return false;
                        var scrollContainerScroll = overflowDIV.scrollTop();
                        var scrollContainerPos = -parseInt(overflowDIV.offset().top);

                        destination += scrollContainerScroll + scrollContainerPos - 5;
                        var scrollContainer = $(options.overflownDIV + ":not(:animated)");

                        scrollContainer.animate({ scrollTop: destination }, 1100);
                    } else {
                        $("html:not(:animated),body:not(:animated)").animate({
                            scrollTop: destination,
                            scrollLeft: fixleft
                        }, 1100, function () {
                            if (options.focusFirstField) first_err.focus();
                        });
                    }

                } else if (options.focusFirstField)
                    first_err.focus();
                return false;
            }
            return true;
        },
        /**
        * This method is called to perform an ajax form validation.
        * During this process all the (field, value) pairs are sent to the server which returns a list of invalid fields or true
        *
        * @param {jqObject} form
        * @param {Map} options
        */
        _validateFormWithAjax: function (form, options) {

            var data = form.serialize();
            var url = (options.ajaxFormValidationURL) ? options.ajaxFormValidationURL : form.attr("action");
            $.ajax({
                type: "GET",
                url: url,
                cache: false,
                dataType: "json",
                data: data,
                form: form,
                methods: methods,
                options: options,
                beforeSend: function () {
                    return options.onBeforeAjaxFormValidation(form, options);
                },
                error: function (data, transport) {
                    methods._ajaxError(data, transport);
                },
                success: function (json) {

                    if (json !== true) {

                        // getting to this case doesn't necessary means that the form is invalid
                        // the server may return green or closing prompt actions
                        // this flag helps figuring it out
                        var errorInForm = false;
                        for (var i = 0; i < json.length; i++) {
                            var value = json[i];

                            var errorFieldId = value[0];
                            var errorField = $($("#" + errorFieldId)[0]);

                            // make sure we found the element
                            if (errorField.length == 1) {

                                // promptText or selector
                                var msg = value[2];
                                // if the field is valid
                                if (value[1] == true) {

                                    if (msg == "" || !msg) {
                                        // if for some reason, status==true and error="", just close the prompt
                                        methods._closePrompt(errorField);
                                    } else {
                                        // the field is valid, but we are displaying a green prompt
                                        if (options.allrules[msg]) {
                                            var txt = options.allrules[msg].alertTextOk;
                                            if (txt)
                                                msg = txt;
                                        }
                                        methods._showPrompt(errorField, msg, "pass", false, options, true);
                                    }

                                } else {
                                    // the field is invalid, show the red error prompt
                                    errorInForm |= true;
                                    if (options.allrules[msg]) {
                                        var txt = options.allrules[msg].alertText;
                                        if (txt)
                                            msg = txt;
                                    }
                                    methods._showPrompt(errorField, msg, "", false, options, true);
                                }
                            }
                        }
                        options.onAjaxFormComplete(!errorInForm, form, json, options);
                    } else
                        options.onAjaxFormComplete(true, form, "", options);
                }
            });

        },
        /**
        * Validates field, shows prompts accordingly
        *
        * @param {jqObject}
        *            field
        * @param {Array[String]}
        *            field's validation rules
        * @param {Map}
        *            user options
        * @return true if field is valid
        */
        _validateField: function (field, options, skipAjaxValidation) {
            if (!field.attr("id"))
                $.error("jQueryValidate: an ID attribute is required for this field: " + field.attr("name") + " class:" +
                field.attr("class"));

            var rulesParsing = field.attr('class');
            var getRules = /validate\[(.*)\]/.exec(rulesParsing);

            if (!getRules)
                return false;
            var str = getRules[1];
            var rules = str.split(/\[|,|\]/);

            // true if we ran the ajax validation, tells the logic to stop messing with prompts
            var isAjaxValidator = false;
            var fieldName = field.attr("name");
            var promptText = "";
            var required = false;
            options.isError = false;
            options.showArrow = true;

            var form = $(field.closest("form"));

            for (var i = 0; i < rules.length; i++) {
                // Fix for adding spaces in the rules
                rules[i] = rules[i].replace(" ", "")
                var errorMsg = undefined;
                switch (rules[i]) {

                    case "required":
                        required = true;
                        errorMsg = methods._required(field, rules, i, options);
                        break;
                    case "custom":
                        errorMsg = methods._customRegex(field, rules, i, options);
                        break;
                    case "customDate":

                        errorMsg = methods._customRegexDate(field, rules, i, options);
                        break;
                    case "groupRequired":
                        // Check is its the first of group, if not, reload validation with first field
                        // AND continue normal validation on present field
                        var classGroup = "[class*=" + rules[i + 1] + "]";
                        var firstOfGroup = form.find(classGroup).eq(0);
                        if (firstOfGroup[0] != field[0]) {
                            methods._validateField(firstOfGroup, options, skipAjaxValidation)
                            options.showArrow = true;
                            continue;
                        };
                        errorMsg = methods._groupRequired(field, rules, i, options);
                        if (errorMsg) required = true;
                        options.showArrow = false;
                        break;
                    case "ajax":
                        // ajax has its own prompts handling technique
                        if (!skipAjaxValidation) {
                            methods._ajax(field, rules, i, options);
                            isAjaxValidator = true;
                        }
                        break;
                    case "minSize":
                        errorMsg = methods._minSize(field, rules, i, options);
                        break;
                    case "maxSize":
                        errorMsg = methods._maxSize(field, rules, i, options);
                        break;
                    case "min":
                        errorMsg = methods._min(field, rules, i, options);
                        break;
                    case "max":
                        errorMsg = methods._max(field, rules, i, options);
                        break;
                    case "past":
                        errorMsg = methods._past(field, rules, i, options);
                        break;
                    case "future":
                        errorMsg = methods._future(field, rules, i, options);
                        break;
                    case "datePickerRange":


                        var firstOfGroup = rules[i + 1]; //lower
                        var secondOfGroup = rules[i + 2]; //upper

                        //if one entry out of the pair has value then proceed to run through validation
                        if (firstOfGroup || secondOfGroup) {
                            errorMsg = methods._datepickerRange(firstOfGroup, secondOfGroup, rules, i, options, field);
                        }

                        break;
                    case "dateRange":

                        var classGroup = "[class*=" + rules[i + 1] + "]";
                        var firstOfGroup = form.find(classGroup).eq(0);
                        var secondOfGroup = form.find(classGroup).eq(1);
                        //if one entry out of the pair has value then proceed to run through validation
                        if (firstOfGroup[0].value || secondOfGroup[0].value) {
                            errorMsg = methods._datepickerRange(firstOfGroup, secondOfGroup, rules, i, options);
                        }
                        if (errorMsg) required = true;
                        options.showArrow = false;
                        break;

                    case "dateTimeRange":
                        var classGroup = "[class*=" + rules[i + 1] + "]";
                        var firstOfGroup = form.find(classGroup).eq(0);
                        var secondOfGroup = form.find(classGroup).eq(1);

                        //if one entry out of the pair has value then proceed to run through validation
                        if (firstOfGroup[0].value || secondOfGroup[0].value) {
                            errorMsg = methods._dateTimeRange(firstOfGroup, secondOfGroup, rules, i, options);
                        }
                        if (errorMsg) required = true;
                        options.showArrow = false;
                        break;
                    case "maxCheckbox":
                        errorMsg = methods._maxCheckbox(form, field, rules, i, options);
                        field = $(form.find("input[name='" + fieldName + "']"));
                        break;
                    case "minCheckbox":
                        errorMsg = methods._minCheckbox(form, field, rules, i, options);
                        field = $(form.find("input[name='" + fieldName + "']"));
                        break;
                    case "equals":
                        errorMsg = methods._equals(field, rules, i, options);
                        break;
                    case "funcCall":
                        errorMsg = methods._funcCall(field, rules, i, options);
                        break;
                    case "creditCard":
                        errorMsg = methods._creditCard(field, rules, i, options);
                        break;

                    default:
                        //$.error("jQueryValidator rule not found"+rules[i]);
                }
                if (errorMsg !== undefined) {
                    promptText += errorMsg + "<br/>";
                    options.isError = true;
                }
            }
            // If the rules required is not added, an empty field is not validated
            if (!required && field.val() == "") options.isError = false;

            // Hack for radio/checkbox group button, the validation go into the
            // first radio/checkbox of the group
            var fieldType = field.prop("type");

            if ((fieldType == "radio" || fieldType == "checkbox") && form.find("input[name='" + fieldName + "']").size() > 1) {
                field = $(form.find("input[name='" + fieldName + "'][type!=hidden]:first"));
                options.showArrow = false;
            }

            if (fieldType == "text" && form.find("input[name='" + fieldName + "']").size() > 1) {
                field = $(form.find("input[name='" + fieldName + "'][type!=hidden]:first"));
                options.showArrow = false;
            }

            if (options.isError) {
                methods._showPrompt(field, promptText, "", false, options);
            } else {
                if (!isAjaxValidator) methods._closePrompt(field);
            }

            if (!isAjaxValidator) {
                field.trigger("jqv.field.result", [field, options.isError, promptText]);
            }

            /* Record error */
            var errindex = $.inArray(field[0], options.InvalidFields);
            if (errindex == -1) {
                if (options.isError)
                    options.InvalidFields.push(field[0]);
            } else if (!options.isError) {
                options.InvalidFields.splice(errindex, 1);
            }

            return options.isError;
        },
        /**
        * Required validation
        *
        * @param {jqObject} field
        * @param {Array[String]} rules
        * @param {int} i rules index
        * @param {Map}
        *            user options
        * @return an error string if validation failed
        */
        _required: function (field, rules, i, options) {
            switch (field.prop("type")) {
                case "text":
                case "password":
                case "textarea":
                case "file":
                default:
                    if (!field.val())
                        return options.allrules[rules[i]].alertText;
                    break;
                case "radio":
                case "checkbox":
                    var form = field.closest("form");
                    var name = field.attr("name");
                    if (form.find("input[name='" + name + "']:checked").size() == 0) {
                        if (form.find("input[name='" + name + "']").size() == 1)
                            return options.allrules[rules[i]].alertTextCheckboxe;
                        else
                            return options.allrules[rules[i]].alertTextCheckboxMultiple;
                    }
                    break;
                // required for <select>        
                case "select-one":
                    // added by paul@kinetek.net for select boxes, Thank you
                    if (!field.val())
                        return options.allrules[rules[i]].alertText;
                    break;
                case "select-multiple":
                    // added by paul@kinetek.net for select boxes, Thank you
                    if (!field.find("option:selected").val())
                        return options.allrules[rules[i]].alertText;
                    break;
            }
        },
        /**
        * Validate that 1 from the group field is required
        *
        * @param {jqObject} field
        * @param {Array[String]} rules
        * @param {int} i rules index
        * @param {Map}
        *            user options
        * @return an error string if validation failed
        */
        _groupRequired: function (field, rules, i, options) {
            var classGroup = "[class*=" + rules[i + 1] + "]";
            var isValid = false;
            // Check all fields from the group
            field.closest("form").find(classGroup).each(function () {
                if (!methods._required($(this), rules, i, options)) {
                    isValid = true;
                    return false;
                }
            })

            if (!isValid) return options.allrules[rules[i]].alertText;
        },
        /**
        * Validate Regex rules
        *
        * @param {jqObject} field
        * @param {Array[String]} rules
        * @param {int} i rules index
        * @param {Map}
        *            user options
        * @return an error string if validation failed
        */
        _customRegex: function (field, rules, i, options) {
            var customRule = rules[i + 1];
            var rule = options.allrules[customRule];
            if (!rule) {
                alert("jqv:custom rule not found " + customRule);
                return;
            }

            var ex = rule.regex;
            if (!ex) {
                alert("jqv:custom regex not found " + customRule);
                return;
            }
            var pattern = new RegExp(ex);

            if (!pattern.test(field.val()))
                return options.allrules[customRule].alertText;
        },
        _customRegexDate: function (field, rules, i, options) {

            if (field.val() != null && field.val() != "") {
                var customRule = rules[i + 1];
                var rule = options.allrules[customRule];
                if (!rule) {
                    alert("jqv:custom rule not found " + customRule);
                    return;
                }

                var ex = rule.regex;
                if (!ex) {
                    alert("jqv:custom regex not found " + customRule);
                    return;
                }
                var pattern = new RegExp(ex);

                if (!pattern.test(field.val()))
                    return options.allrules[customRule].alertText;
            }
        },
        /**
        * Validate custom function outside of the engine scope
        *
        * @param {jqObject} field
        * @param {Array[String]} rules
        * @param {int} i rules index
        * @param {Map}
        *            user options
        * @return an error string if validation failed
        */
        _funcCall: function (field, rules, i, options) {
            var functionName = rules[i + 1];
            var fn = window[functionName] || options.customFunctions[functionName];
            if (typeof (fn) == 'function')
                return fn(field, rules, i, options);

        },
        /**
        * Field match
        *
        * @param {jqObject} field
        * @param {Array[String]} rules
        * @param {int} i rules index
        * @param {Map}
        *            user options
        * @return an error string if validation failed
        */
        _equals: function (field, rules, i, options) {
            var equalsField = rules[i + 1];

            if (field.val() != $("#" + equalsField).val())
                return options.allrules.equals.alertText;
        },
        /**
        * Check the maximum size (in characters)
        *
        * @param {jqObject} field
        * @param {Array[String]} rules
        * @param {int} i rules index
        * @param {Map}
        *            user options
        * @return an error string if validation failed
        */
        _maxSize: function (field, rules, i, options) {
            var max = rules[i + 1];
            var len = field.val().length;

            if (len > max) {
                var rule = options.allrules.maxSize;
                return rule.alertText + max + rule.alertText2;
            }
        },
        /**
        * Check the minimum size (in characters)
        *
        * @param {jqObject} field
        * @param {Array[String]} rules
        * @param {int} i rules index
        * @param {Map}
        *            user options
        * @return an error string if validation failed
        */
        _minSize: function (field, rules, i, options) {
            var min = rules[i + 1];
            var len = field.val().length;

            if (len < min) {
                var rule = options.allrules.minSize;
                return rule.alertText + min + rule.alertText2;
            }
        },
        /**
        * Check number minimum value
        *
        * @param {jqObject} field
        * @param {Array[String]} rules
        * @param {int} i rules index
        * @param {Map}
        *            user options
        * @return an error string if validation failed
        */
        _min: function (field, rules, i, options) {
            var min = parseFloat(rules[i + 1]);
            var len = parseFloat(field.val());

            if (len < min) {
                var rule = options.allrules.min;
                if (rule.alertText2) return rule.alertText + min + rule.alertText2;
                return rule.alertText + min;
            }
        },
        /**
        * Check number maximum value
        *
        * @param {jqObject} field
        * @param {Array[String]} rules
        * @param {int} i rules index
        * @param {Map}
        *            user options
        * @return an error string if validation failed
        */
        _max: function (field, rules, i, options) {
            var max = parseFloat(rules[i + 1]);
            var len = parseFloat(field.val());

            if (len > max) {
                var rule = options.allrules.max;
                if (rule.alertText2) return rule.alertText + max + rule.alertText2;
                //orefalo: to review, also do the translations
                return rule.alertText + max;
            }
        },
        /*Convert date to the right format  */
        _FormatDate: function (field) {
            var NewDateFormat = "";
            var MM = "";
            var DD = "";
            var YYYY = "";
            var splitChar = '';
            if (field.val() != '' && field.val() != null) {

                if (field.val().indexOf("-") != -1) {
                    splitChar = '-';
                }
                else {

                    splitChar = '/';
                }
                var dateList = field.val().split(splitChar);

                MM = dateList[0];
                DD = dateList[1];
                YYYY = dateList[2];

                NewDateFormat = NewDateFormat.concat(YYYY);
                NewDateFormat = NewDateFormat.concat('/');
                NewDateFormat = NewDateFormat.concat(MM);
                NewDateFormat = NewDateFormat.concat('/');
                NewDateFormat = NewDateFormat.concat(DD);
            }
            else {
                NewDateFormat.concat("");

            }
            return NewDateFormat;
        },
        /**
        * Checks date is in the past
        *
        * @param {jqObject} field
        * @param {Array[String]} rules
        * @param {int} i rules index
        * @param {Map}
        *            user options
        * @return an error string if validation failed
        */
        _past: function (field, rules, i, options) {

            var p = rules[i + 1];
            var pdate = (p.toLowerCase() == "now") ? new Date() : methods._parseDate(p);


            //var vdate = methods._parseDate(field.val());
            var vdate = methods._parseDate(methods._FormatDate(field));

            if (vdate > pdate) {
                var rule = options.allrules.past;
                if (rule.alertText2) return rule.alertText + methods._dateToString(pdate) + rule.alertText2;
                return rule.alertText + methods._dateToString(pdate);
            }
        },
        /**
        * Checks date is in the future
        *
        * @param {jqObject} field
        * @param {Array[String]} rules
        * @param {int} i rules index
        * @param {Map}
        *            user options
        * @return an error string if validation failed
        */
        _future: function (field, rules, i, options) {

            var p = rules[i + 1];
            var pdate = (p.toLowerCase() == "now") ? new Date() : methods._parseDate(p);
            // var vdate = methods._parseDate(field.val());
            var vdate = methods._parseDate(methods._FormatDate(field));
            if (vdate < pdate) {
                var rule = options.allrules.future;
                if (rule.alertText2) return rule.alertText + methods._dateToString(pdate) + rule.alertText2;
                return rule.alertText + methods._dateToString(pdate);
            }
        },
        /**
        * Checks if valid date
        *
        * @param {string} date string
        * @return a bool based on determination of valid date
        */
        _isDate: function (value) {
            var dateRegEx = new RegExp(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$|^(?:(?:(?:0?[13578]|1[02])(\/|-)31)|(?:(?:0?[1,3-9]|1[0-2])(\/|-)(?:29|30)))(\/|-)(?:[1-9]\d\d\d|\d[1-9]\d\d|\d\d[1-9]\d|\d\d\d[1-9])$|^(?:(?:0?[1-9]|1[0-2])(\/|-)(?:0?[1-9]|1\d|2[0-8]))(\/|-)(?:[1-9]\d\d\d|\d[1-9]\d\d|\d\d[1-9]\d|\d\d\d[1-9])$|^(0?2(\/|-)29)(\/|-)(?:(?:0[48]00|[13579][26]00|[2468][048]00)|(?:\d\d)?(?:0[48]|[2468][048]|[13579][26]))$/);
            if (dateRegEx.test(value)) {
                return true;
            }
            return false;
        },
        /**
        * Checks if valid date time
        *
        * @param {string} date string
        * @return a bool based on determination of valid date time
        */
        _isDateTime: function (value) {
            var dateTimeRegEx = new RegExp(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])\s+(1[012]|0?[1-9]){1}:(0?[1-5]|[0-6][0-9]){1}:(0?[0-6]|[0-6][0-9]){1}\s+(am|pm|AM|PM){1}$|^(?:(?:(?:0?[13578]|1[02])(\/|-)31)|(?:(?:0?[1,3-9]|1[0-2])(\/|-)(?:29|30)))(\/|-)(?:[1-9]\d\d\d|\d[1-9]\d\d|\d\d[1-9]\d|\d\d\d[1-9])$|^((1[012]|0?[1-9]){1}\/(0?[1-9]|[12][0-9]|3[01]){1}\/\d{2,4}\s+(1[012]|0?[1-9]){1}:(0?[1-5]|[0-6][0-9]){1}:(0?[0-6]|[0-6][0-9]){1}\s+(am|pm|AM|PM){1})$/);
            if (dateTimeRegEx.test(value)) {
                return true;
            }
            return false;
        },
        //Checks if the start date is before the end date
        //returns true if end is later than start
        _dateCompare: function (start, end) {
            return (new Date(start.toString()) <= new Date(end.toString()));
        },
        /**
        * Checks date range
        *
        * @param {jqObject} first field name
        * @param {jqObject} second field name
        * @return an error string if validation failed
        */
        _datepickerRange: function (first, second, rules, i, options, field) {
            //are not both populated

            var Lower = methods._parseDate(first);
            var Upper = methods._parseDate(second);
            //are both dates but range is off
            if (field.val() != null && field.val() != "") {
                if (!methods._dateCompare(first, field.val())) {
                    return options.allrules[rules[i]].alertText + " " + methods._dateToString(Lower) + " and " + methods._dateToString(Upper);
                }
                ///are both dates but range is off
                if (!methods._dateCompare(field.val(), second)) {
                    return options.allrules[rules[i]].alertText + " " + methods._dateToString(Lower) + " and " + methods._dateToString(Upper);
                }
            }
        },
        _dateRange: function (first, second, rules, i, options) {
            //are not both populated
            if ((!first[0].value && second[0].value) || (first[0].value && !second[0].value)) {
                return options.allrules[rules[i]].alertText + options.allrules[rules[i]].alertText2;
            }

            //are not both dates
            if (!methods._isDate(first[0].value) || !methods._isDate(second[0].value)) {
                return options.allrules[rules[i]].alertText + options.allrules[rules[i]].alertText2;
            }

            //are both dates but range is off
            if (!methods._dateCompare(first[0].value, second[0].value)) {
                return options.allrules[rules[i]].alertText + options.allrules[rules[i]].alertText2;
            }
        },


        /**
        * Checks date time range
        *
        * @param {jqObject} first field name
        * @param {jqObject} second field name
        * @return an error string if validation failed
        */
        _dateTimeRange: function (first, second, rules, i, options) {
            //are not both populated
            if ((!first[0].value && second[0].value) || (first[0].value && !second[0].value)) {
                return options.allrules[rules[i]].alertText + options.allrules[rules[i]].alertText2;
            }
            //are not both dates
            if (!methods._isDateTime(first[0].value) || !methods._isDateTime(second[0].value)) {
                return options.allrules[rules[i]].alertText + options.allrules[rules[i]].alertText2;
            }
            //are both dates but range is off
            if (!methods._dateCompare(first[0].value, second[0].value)) {
                return options.allrules[rules[i]].alertText + options.allrules[rules[i]].alertText2;
            }
        },
        /**
        * Max number of checkbox selected
        *
        * @param {jqObject} field
        * @param {Array[String]} rules
        * @param {int} i rules index
        * @param {Map}
        *            user options
        * @return an error string if validation failed
        */
        _maxCheckbox: function (form, field, rules, i, options) {

            var nbCheck = rules[i + 1];
            var groupname = field.attr("name");
            var groupSize = form.find("input[name='" + groupname + "']:checked").size();
            if (groupSize > nbCheck) {
                options.showArrow = false;
                if (options.allrules.maxCheckbox.alertText2) return options.allrules.maxCheckbox.alertText + " " + nbCheck + " " + options.allrules.maxCheckbox.alertText2;
                return options.allrules.maxCheckbox.alertText;
            }
        },
        /**
        * Min number of checkbox selected
        *
        * @param {jqObject} field
        * @param {Array[String]} rules
        * @param {int} i rules index
        * @param {Map}
        *            user options
        * @return an error string if validation failed
        */
        _minCheckbox: function (form, field, rules, i, options) {

            var nbCheck = rules[i + 1];
            var groupname = field.attr("name");
            var groupSize = form.find("input[name='" + groupname + "']:checked").size();
            if (groupSize < nbCheck) {
                options.showArrow = false;
                return options.allrules.minCheckbox.alertText + " " + nbCheck + " " + options.allrules.minCheckbox.alertText2;
            }
        },
        /**
        * Checks that it is a valid credit card number according to the
        * Luhn checksum algorithm.
        *
        * @param {jqObject} field
        * @param {Array[String]} rules
        * @param {int} i rules index
        * @param {Map}
        *            user options
        * @return an error string if validation failed
        */
        _creditCard: function (field, rules, i, options) {
            //spaces and dashes may be valid characters, but must be stripped to calculate the checksum.
            var valid = false, cardNumber = field.val().replace(/ +/g, '').replace(/-+/g, '');

            var numDigits = cardNumber.length;
            if (numDigits >= 14 && numDigits <= 16 && parseInt(cardNumber) > 0) {

                var sum = 0, i = numDigits - 1, pos = 1, digit, luhn = new String();
                do {
                    digit = parseInt(cardNumber.charAt(i));
                    luhn += (pos++ % 2 == 0) ? digit * 2 : digit;
                } while (--i >= 0)

                for (i = 0; i < luhn.length; i++) {
                    sum += parseInt(luhn.charAt(i));
                }
                valid = sum % 10 == 0;
            }
            if (!valid) return options.allrules.creditCard.alertText;
        },
        /**
        * Ajax field validation
        *
        * @param {jqObject} field
        * @param {Array[String]} rules
        * @param {int} i rules index
        * @param {Map}
        *            user options
        * @return nothing! the ajax validator handles the prompts itself
        */
        _ajax: function (field, rules, i, options) {

            var errorSelector = rules[i + 1];
            var rule = options.allrules[errorSelector];
            var extraData = rule.extraData;
            var extraDataDynamic = rule.extraDataDynamic;

            if (!extraData)
                extraData = "";

            if (extraDataDynamic) {
                var tmpData = [];
                var domIds = String(extraDataDynamic).split(",");
                for (var i = 0; i < domIds.length; i++) {
                    var id = domIds[i];
                    if ($(id).length) {
                        var inputValue = field.closest("form").find(id).val();
                        var keyValue = id.replace('#', '') + '=' + escape(inputValue);
                        tmpData.push(keyValue);
                    }
                }
                extraDataDynamic = tmpData.join("&");
            } else {
                extraDataDynamic = "";
            }

            if (!options.isError) {
                $.ajax({
                    type: "GET",
                    url: rule.url,
                    cache: false,
                    dataType: "json",
                    data: "fieldId=" + field.attr("id") + "&fieldValue=" + field.val() + "&extraData=" + extraData + "&" + extraDataDynamic,
                    field: field,
                    rule: rule,
                    methods: methods,
                    options: options,
                    beforeSend: function () {
                        // build the loading prompt
                        var loadingText = rule.alertTextLoad;
                        if (loadingText)
                            methods._showPrompt(field, loadingText, "load", true, options);
                    },
                    error: function (data, transport) {
                        methods._ajaxError(data, transport);
                    },
                    success: function (json) {

                        // asynchronously called on success, data is the json answer from the server
                        var errorFieldId = json[0];
                        var errorField = $($("#" + errorFieldId)[0]);
                        // make sure we found the element
                        if (errorField.length == 1) {
                            var status = json[1];
                            // read the optional msg from the server
                            var msg = json[2];
                            if (!status) {
                                // Houston we got a problem - display an red prompt
                                options.ajaxValidCache[errorFieldId] = false;
                                options.isError = true;

                                // resolve the msg prompt
                                if (msg) {
                                    if (options.allrules[msg]) {
                                        var txt = options.allrules[msg].alertText;
                                        if (txt)
                                            msg = txt;
                                    }
                                }
                                else
                                    msg = rule.alertText;

                                methods._showPrompt(errorField, msg, "", true, options);
                            } else {
                                if (options.ajaxValidCache[errorFieldId] !== undefined)
                                    options.ajaxValidCache[errorFieldId] = true;

                                // resolves the msg prompt
                                if (msg) {
                                    if (options.allrules[msg]) {
                                        var txt = options.allrules[msg].alertTextOk;
                                        if (txt)
                                            msg = txt;
                                    }
                                }
                                else
                                    msg = rule.alertTextOk;

                                // see if we should display a green prompt
                                if (msg)
                                    methods._showPrompt(errorField, msg, "pass", true, options);
                                else
                                    methods._closePrompt(errorField);
                            }
                        }
                        errorField.trigger("jqv.field.result", [errorField, !options.isError, msg]);
                    }
                });
            }
        },
        /**
        * Common method to handle ajax errors
        *
        * @param {Object} data
        * @param {Object} transport
        */
        _ajaxError: function (data, transport) {
            if (data.status == 0 && transport == null)
                alert("The page is not served from a server! ajax call failed");
            else if (typeof console != "undefined")
                console.log("Ajax error: " + data.status + " " + transport);
        },
        /**
        * date -> string
        *
        * @param {Object} date
        */
        _dateToString: function (date) {

            // return date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
            return (date.getMonth() + 1) + "/" + date.getDate() + "/" + date.getFullYear();
        },
        /**
        * Parses an ISO date
        * @param {String} d
        */
        _parseDate: function (d) {

            var dateParts = d.split("-");
            if (dateParts == d)
                dateParts = d.split("/");
            return new Date(dateParts[0], (dateParts[1] - 1), dateParts[2]);
        },
        /**
        * Builds or updates a prompt with the given information
        *
        * @param {jqObject} field
        * @param {String} promptText html text to display type
        * @param {String} type the type of bubble: 'pass' (green), 'load' (black) anything else (red)
        * @param {boolean} ajaxed - use to mark fields than being validated with ajax
        * @param {Map} options user options
        */
        _showPrompt: function (field, promptText, type, ajaxed, options, ajaxform) {
            var prompt = methods._getPrompt(field);
            // The ajax submit errors are not see has an error in the form,
            // When the form errors are returned, the engine see 2 bubbles, but those are ebing closed by the engine at the same time
            // Because no error was found befor submitting
            if (ajaxform) prompt = false;
            if (prompt)
                methods._updatePrompt(field, prompt, promptText, type, ajaxed, options);
            else
                methods._buildPrompt(field, promptText, type, ajaxed, options);
        },
        /**
        * Builds and shades a prompt for the given field.
        *
        * @param {jqObject} field
        * @param {String} promptText html text to display type
        * @param {String} type the type of bubble: 'pass' (green), 'load' (black) anything else (red)
        * @param {boolean} ajaxed - use to mark fields than being validated with ajax
        * @param {Map} options user options
        */
        _buildPrompt: function (field, promptText, type, ajaxed, options) {

            // create the prompt
            var prompt = $('<div>');
            prompt.addClass(methods._getClassName(field.attr("id")) + "formError");
            // add a class name to identify the parent form of the prompt
            if (field.is(":input")) prompt.addClass("parentForm" + methods._getClassName(field.parents('form').attr("id")));
            prompt.addClass("formError");

            switch (type) {
                case "pass":
                    prompt.addClass("greenPopup");
                    break;
                case "load":
                    prompt.addClass("blackPopup");
                    break;
                default:
                    /* it has error  */
                    options.InvalidCount++;
            }
            if (ajaxed)
                prompt.addClass("ajaxed");

            // create the prompt content
            var promptContent = $('<div>').addClass("formErrorContent").html(promptText).appendTo(prompt);
            // create the css arrow pointing at the field
            // note that there is no triangle on max-checkbox and radio
            if (options.showArrow) {
                var arrow = $('<div>').addClass("formErrorArrow");

                //prompt positioning adjustment support. Usage: positionType:Xshift,Yshift (for ex.: bottomLeft:+20 or bottomLeft:-20,+10)
                var positionType = field.data("promptPosition") || options.promptPosition;
                if (typeof (positionType) == 'string') {
                    if (positionType.indexOf(":") != -1) {
                        positionType = positionType.substring(0, positionType.indexOf(":"));
                    };
                };


                switch (positionType) {
                    case "bottomLeft":
                    case "bottomRight":
                        prompt.find(".formErrorContent").before(arrow);
                        arrow.addClass("formErrorArrowBottom").html('<div class="line1"><!-- --></div><div class="line2"><!-- --></div><div class="line3"><!-- --></div><div class="line4"><!-- --></div><div class="line5"><!-- --></div><div class="line6"><!-- --></div><div class="line7"><!-- --></div><div class="line8"><!-- --></div><div class="line9"><!-- --></div><div class="line10"><!-- --></div>');
                        break;
                    case "topLeft":
                    case "topRight":
                        arrow.html('<div class="line10"><!-- --></div><div class="line9"><!-- --></div><div class="line8"><!-- --></div><div class="line7"><!-- --></div><div class="line6"><!-- --></div><div class="line5"><!-- --></div><div class="line4"><!-- --></div><div class="line3"><!-- --></div><div class="line2"><!-- --></div><div class="line1"><!-- --></div>');
                        prompt.append(arrow);
                        break;
                }
            }

            if (options.relative) {
                // empty relative span does not disturb page layout
                // prompt positioned absolute to relative span
                // vertical-align:top so position calculations are the same as isOverflown
                var outer = $('<span>').css('position', 'relative').css('vertical-align', 'top').addClass('formErrorOuter').append(prompt.css('position', 'absolute'));
                field.before(outer);
            } else if (options.isOverflown) {
                //Cedric: Needed if a container is in position:relative
                // insert prompt in the form or in the overflown container?
                field.before(prompt);
            } else {
                $("body").append(prompt);
            }

            var pos = methods._calculatePosition(field, prompt, options);
            prompt.css({
                "top": pos.callerTopPosition,
                "left": pos.callerleftPosition,
                "marginTop": pos.marginTopSize,
                "opacity": 0
            }).data("callerField", field);

            return prompt.animate({
                "opacity": 0.87
            });

        },
        /**
        * Updates the prompt text field - the field for which the prompt
        * @param {jqObject} field
        * @param {String} promptText html text to display type
        * @param {String} type the type of bubble: 'pass' (green), 'load' (black) anything else (red)
        * @param {boolean} ajaxed - use to mark fields than being validated with ajax
        * @param {Map} options user options
        */
        _updatePrompt: function (field, prompt, promptText, type, ajaxed, options, noAnimation) {

            if (prompt) {
                if (typeof type !== "undefined") {
                    if (type == "pass")
                        prompt.addClass("greenPopup");
                    else
                        prompt.removeClass("greenPopup");

                    if (type == "load")
                        prompt.addClass("blackPopup");
                    else
                        prompt.removeClass("blackPopup");
                }
                if (ajaxed)
                    prompt.addClass("ajaxed");
                else
                    prompt.removeClass("ajaxed");

                prompt.find(".formErrorContent").html(promptText);

                var pos = methods._calculatePosition(field, prompt, options);
                css = { "top": pos.callerTopPosition,
                    "left": pos.callerleftPosition,
                    "marginTop": pos.marginTopSize
                };

                if (noAnimation)
                    prompt.css(css);
                else
                    prompt.animate(css)
            }
        },
        /**
        * Closes the prompt associated with the given field
        *
        * @param {jqObject}
        *            field
        */
        _closePrompt: function (field) {

            var prompt = methods._getPrompt(field);
            if (prompt)
                prompt.fadeTo("fast", 0, function () {
                    prompt.parent('.formErrorOuter').remove();
                    prompt.remove();
                });
        },
        closePrompt: function (field) {
            return methods._closePrompt(field);
        },
        /**
        * Returns the error prompt matching the field if any
        *
        * @param {jqObject}
        *            field
        * @return undefined or the error prompt (jqObject)
        */
        _getPrompt: function (field) {
            var className = methods._getClassName(field.attr("id")) + "formError";
            var match = $("." + methods._escapeExpression(className))[0];
            if (match)
                return $(match);
        },
        /**
        * Returns the escapade classname
        *
        * @param {selector}
        *            className
        */
        _escapeExpression: function (selector) {
            return selector.replace(/([#;&,\.\+\*\~':"\!\^$\[\]\(\)=>\|])/g, "\\$1");
        },
        /**
        * returns true if we are in a RTLed document
        *
        * @param {jqObject} field
        */
        isRTL: function (field) {
            var $document = $(document);
            var $body = $('body');
            var rtl =
				(field && field.hasClass('rtl')) ||
				(field && (field.attr('dir') || '').toLowerCase() === 'rtl') ||
				$document.hasClass('rtl') ||
				($document.attr('dir') || '').toLowerCase() === 'rtl' ||
				$body.hasClass('rtl') ||
				($body.attr('dir') || '').toLowerCase() === 'rtl';
            return Boolean(rtl);
        },
        /**
        * Calculates prompt position
        *
        * @param {jqObject}
        *            field
        * @param {jqObject}
        *            the prompt
        * @param {Map}
        *            options
        * @return positions
        */
        _calculatePosition: function (field, promptElmt, options) {

            var promptTopPosition, promptleftPosition, marginTopSize;
            var fieldWidth = field.width();
            var promptHeight = promptElmt.height();

            var overflow = options.isOverflown || options.relative;
            if (overflow) {
                // is the form contained in an overflown container?
                promptTopPosition = promptleftPosition = 0;
                // compensation for the arrow
                marginTopSize = -promptHeight;
            } else {
                var offset = field.offset();
                promptTopPosition = offset.top;
                promptleftPosition = offset.left;
                marginTopSize = 0;
            }

            //prompt positioning adjustment support 
            //now you can adjust prompt position
            //usage: positionType:Xshift,Yshift
            //for example: 
            //   bottomLeft:+20 means bottomLeft position shifted by 20 pixels right horizontally
            //   topRight:20, -15 means topRight position shifted by 20 pixels to right and 15 pixels to top
            //You can use +pixels, - pixels. If no sign is provided than + is default.
            var positionType = field.data("promptPosition") || options.promptPosition;
            var shift1 = "";
            var shift2 = "";
            var shiftX = 0;
            var shiftY = 0;
            if (typeof (positionType) == 'string') {
                //do we have any position adjustments ?
                if (positionType.indexOf(":") != -1) {
                    shift1 = positionType.substring(positionType.indexOf(":") + 1);
                    positionType = positionType.substring(0, positionType.indexOf(":"));

                    //if any advanced positioning will be needed (percents or something else) - parser should be added here
                    //for now we use simple parseInt()

                    //do we have second parameter?
                    if (shift1.indexOf(",") != -1) {
                        shift2 = shift1.substring(shift1.indexOf(",") + 1);
                        shift1 = shift1.substring(0, shift1.indexOf(","));
                        shiftY = parseInt(shift2);
                        if (isNaN(shiftY)) { shiftY = 0; };
                    };

                    shiftX = parseInt(shift1);
                    if (isNaN(shift1)) { shift1 = 0; };

                };
            };

            if (!methods.isRTL(field)) {
                switch (positionType) {
                    default:
                    case "topRight":
                        if (overflow)
                        // Is the form contained in an overflown container?
                            promptleftPosition += fieldWidth - 30;
                        else {
                            promptleftPosition += fieldWidth - 30;
                            promptTopPosition += -promptHeight - 2;
                        }
                        break;
                    case "topLeft":
                        promptTopPosition += -promptHeight - 10;
                        break;
                    case "centerRight":
                        if (overflow) {
                            promptTopPosition = field.outerHeight();
                            promptleftPosition = field.outerWidth(1) + 5;
                        } else {
                            promptleftPosition += field.outerWidth() + 5;
                        }
                        break;
                    case "centerLeft":
                        promptleftPosition -= promptElmt.width() + 2;
                        break;
                    case "bottomLeft":
                        promptTopPosition = promptTopPosition + field.height() + 15;
                        break;
                    case "bottomRight":
                        promptleftPosition += fieldWidth - 30;
                        promptTopPosition += field.height() + 5;
                }
            }
            else {
                switch (positionType) {
                    default:
                    case "topLeft":
                        if (overflow)
                        // Is the form contained in an overflown container?
                            promptleftPosition -= promptElmt.width() - 30;
                        else {
                            promptleftPosition -= promptElmt.width() - 30;
                            promptTopPosition += -promptHeight - 2;
                        }
                        break;
                    case "topRight":
                        if (overflow)
                        // Is the form contained in an overflown container?
                            promptleftPosition += fieldWidth - promptElmt.width();
                        else {
                            promptleftPosition += fieldWidth - promptElmt.width();
                            promptTopPosition += -promptHeight - 2;
                        }
                        break;
                    case "centerRight":
                        if (overflow) {
                            promptTopPosition = field.outerHeight();
                            promptleftPosition = field.outerWidth(1) + 5;
                        } else {
                            promptleftPosition += field.outerWidth() + 5;
                        }
                        break;
                    case "centerLeft":
                        promptleftPosition -= promptElmt.width() + 2;
                        break;
                    case "bottomLeft":
                        promptleftPosition += -promptElmt.width() + 30;
                        promptTopPosition = promptTopPosition + field.height() + 15;
                        break;
                    case "bottomRight":
                        promptleftPosition += fieldWidth - promptElmt.width();
                        promptTopPosition += field.height() + 15;
                }
            }

            //apply adjusments if any
            promptleftPosition += shiftX;
            promptTopPosition += shiftY;

            return {
                "callerTopPosition": promptTopPosition + "px",
                "callerleftPosition": promptleftPosition + "px",
                "marginTopSize": marginTopSize + "px"
            };
        },
        /**
        * Saves the user options and variables in the form.data
        *
        * @param {jqObject}
        *            form - the form where the user option should be saved
        * @param {Map}
        *            options - the user options
        * @return the user options (extended from the defaults)
        */
        _saveOptions: function (form, options) {

            // is there a language localisation ?
            if ($.validationEngineLanguage)
                var allRules = $.validationEngineLanguage.allRules;
            else
                $.error("jQuery.validationEngine rules are not loaded, plz add localization files to the page");
            // --- Internals DO NOT TOUCH or OVERLOAD ---
            // validation rules and i18
            $.validationEngine.defaults.allrules = allRules;

            var userOptions = $.extend(true, {}, $.validationEngine.defaults, options);
            // Needed to be retro compatible
            if (userOptions.isOverflown) userOptions.relative = true;
            if (userOptions.relative) userOptions.isOverflown = true;

            form.data('jqv', userOptions);
            return userOptions;
        },

        /**
        * Removes forbidden characters from class name
        * @param {String} className
        */
        _getClassName: function (className) {
            if (className) {
                return className.replace(/:/g, "_").replace(/\./g, "_");
            }
        }
    };

    /**
    * Plugin entry point.
    * You may pass an action as a parameter or a list of options.
    * if none, the init and attach methods are being called.
    * Remember: if you pass options, the attached method is NOT called automatically
    *
    * @param {String}
    *            method (optional) action
    */
    $.fn.validationEngine = function (method) {

        var form = $(this);
        if (!form[0]) return false;  // stop here if the form does not exist

        if (typeof (method) == 'string' && method.charAt(0) != '_' && methods[method]) {

            // make sure init is called once
            if (method != "showPrompt" && method != "hidePrompt" && method != "hide" && method != "hideAll")
                methods.init.apply(form);

            return methods[method].apply(form, Array.prototype.slice.call(arguments, 1));
        } else if (typeof method == 'object' || !method) {

            // default constructor with or without arguments
            methods.init.apply(form, arguments);
            return methods.attach.apply(form);
        } else {
            $.error('Method ' + method + ' does not exist in jQuery.validationEngine');
        }
    };



    // LEAK GLOBAL OPTIONS
    $.validationEngine = { defaults: {

        // Name of the event triggering field validation
        validationEventTrigger: "blur",
        // Automatically scroll viewport to the first error
        scroll: true,
        // Focus on the first input
        focusFirstField: true,
        // Opening box position, possible locations are: topLeft,
        // topRight, bottomLeft, centerRight, bottomRight
        promptPosition: "topRight",
        bindMethod: "bind",
        // internal, automatically set to true when it parse a _ajax rule
        inlineAjax: false,
        // if set to true, the form data is sent asynchronously via ajax to the form.action url (get)
        ajaxFormValidation: false,
        // Ajax form validation callback method: boolean onComplete(form, status, errors, options)
        // retuns false if the form.submit event needs to be canceled.
        ajaxFormValidationURL: false,
        // The url to send the submit ajax validation (default to action)
        onAjaxFormComplete: $.noop,
        // called right before the ajax call, may return false to cancel
        onBeforeAjaxFormValidation: $.noop,
        // Stops form from submitting and execute function assiciated with it
        onValidationComplete: false,

        // better relative positioning
        relative: false,
        // Used when the form is displayed within a scrolling DIV
        isOverflown: false,
        overflownDIV: "",

        // Used when you have a form fields too close and the errors messages are on top of other disturbing viewing messages
        doNotShowAllErrosOnSubmit: false,

        // true when form and fields are binded
        binded: false,
        // set to true, when the prompt arrow needs to be displayed
        showArrow: true,
        // did one of the validation fail ? kept global to stop further ajax validations
        isError: false,
        // Caches field validation status, typically only bad status are created.
        // the array is used during ajax form validation to detect issues early and prevent an expensive submit
        ajaxValidCache: {},
        // Auto update prompt position after window resize
        autoPositionUpdate: false,

        InvalidFields: [],
        onSuccess: false,
        onFailure: false
    }
    };
    $(function () { $.validationEngine.defaults.promptPosition = methods.isRTL() ? 'topLeft' : "topRight" });
})(jQuery);
