﻿// Common initialization
var xVal = xVal || {};
xVal.Plugins = xVal.Plugins || {};
xVal.Messages = xVal.Messages || {};
xVal.AttachValidator = function(elementPrefix, rulesConfig, pluginName) {
    if (pluginName != null)
        this.Plugins[pluginName].AttachValidator(elementPrefix, rulesConfig);
    else
        for (var key in this.Plugins) {
        this.Plugins[key].AttachValidator(elementPrefix, rulesConfig);
        return;
    }
};

// xVal.jquery.validate.js
// An xVal plugin to enable support for jQuery Validate
// http://xval.codeplex.com/
// (c) 2009 Steven Sanderson
// License: Microsoft Public License (Ms-PL) (http://www.opensource.org/licenses/ms-pl.html)

(function($) {
    xVal.Plugins["jquery.validate"] = {
        AttachValidator: function(elementPrefix, rulesConfig) {
            this._ensureCustomFunctionsRegistered();
            for (var i = 0; i < rulesConfig.Fields.length; i++) {
                var fieldName = rulesConfig.Fields[i].FieldName;
                var fieldRules = rulesConfig.Fields[i].FieldRules;

                // Is there a matching DOM element?
                var elemId = this._makeAspNetMvcHtmlHelperID((elementPrefix ? elementPrefix + "." : "") + fieldName);
                var elem = document.getElementById(elemId);

                if (elem) {
                    for (var j = 0; j < fieldRules.length; j++) {
                        var ruleName = fieldRules[j].RuleName;
                        var ruleParams = fieldRules[j].RuleParameters;
                        var errorText = (typeof (fieldRules[j].Message) == 'undefined' ? null : fieldRules[j].Message);
                        this._attachRuleToDOMElement(ruleName, ruleParams, errorText, $(elem), elementPrefix);
                    }
                }
            }
        },

        _makeAspNetMvcHtmlHelperID: function(fullyQualifiedModelName) {
            // If you have changed HtmlHelper.IdAttributeDotReplacement from "_" to something else, then you must also change the following line to match
            return fullyQualifiedModelName.replace(".", "_");
        },

        _attachRuleToDOMElement: function(ruleName, ruleParams, errorText, element, elementPrefix) {
            var parentForm = element.parents("form");
            if (parentForm.length != 1)
                alert("Error: Element " + element.attr("id") + " is not in a form");
            this._ensureFormIsMarkedForValidation($(parentForm[0]));
            this._associateNearbyValidationMessageSpanWithElement(element);

            var options = {};

            switch (ruleName) {
                case "Required":
                    options.required = true;
                    options.messages = { required: errorText || xVal.Messages.Required };
                    break;

                case "Range":
                    if (ruleParams.Type == "string") {
                        options.xVal_stringRange = [ruleParams.Min, ruleParams.Max];
                        if (errorText != null) options.messages = { xVal_stringRange: $.format(errorText) };
                    }
                    else if (ruleParams.Type == "datetime") {
                        var minDate, maxDate;
                        if (typeof (ruleParams.MinYear) != 'undefined')
                            minDate = new Date(ruleParams.MinYear, ruleParams.MinMonth - 1, ruleParams.MinDay, ruleParams.MinHour, ruleParams.MinMinute, ruleParams.MinSecond);
                        else if (typeof (ruleParams.MaxYear) != 'undefined')
                            maxDate = new Date(ruleParams.MaxYear, ruleParams.MaxMonth - 1, ruleParams.MaxDay, ruleParams.MaxHour, ruleParams.MaxMinute, ruleParams.MaxSecond);
                        options.xVal_dateRange = [minDate, maxDate];
                        if (errorText != null) options.messages = { xVal_dateRange: $.format(errorText) };
                    }
                    else if (typeof (ruleParams.Min) == 'undefined') {
                        options.max = ruleParams.Max;
                        errorText = errorText || xVal.Messages.Range_Numeric_Max;
                        if (errorText != null) options.messages = { max: $.format(errorText) };
                    }
                    else if (typeof (ruleParams.Max) == 'undefined') {
                        options.min = ruleParams.Min;
                        errorText = errorText || xVal.Messages.Range_Numeric_Min;
                        if (errorText != null) options.messages = { min: $.format(errorText) };
                    }
                    else {
                        options.range = [ruleParams.Min, ruleParams.Max];
                        errorText = errorText || xVal.Messages.Range_Numeric_MinMax;
                        if (errorText != null) options.messages = { range: $.format(errorText) };
                    }

                    break;

                case "StringLength":
                    if (typeof (ruleParams.MinLength) == 'undefined') {
                        options.maxlength = ruleParams.MaxLength;
                        errorText = errorText || xVal.Messages.StringLength_Max;
                        if (errorText != null) options.messages = { maxlength: $.format(errorText) };
                    }
                    else if (typeof (ruleParams.MaxLength) == 'undefined') {
                        options.minlength = ruleParams.MinLength;
                        errorText = errorText || xVal.Messages.StringLength_Min;
                        if (errorText != null) options.messages = { minlength: $.format(errorText) };
                    }
                    else {
                        options.rangelength = [ruleParams.MinLength, ruleParams.MaxLength];
                        errorText = errorText || xVal.Messages.StringLength_MinMax;
                        if (errorText != null) options.messages = { rangelength: $.format(errorText) };
                    }
                    break;

                case "DataType":
                    switch (ruleParams.Type) {
                        case "EmailAddress":
                            options.email = true;
                            options.messages = { email: errorText || xVal.Messages.DataType_EmailAddress };
                            break;
                        case "Integer":
                            options.xVal_regex = ["^\\-?\\d+$", ""];
                            options.messages = { xVal_regex: errorText || xVal.Messages.DataType_Integer || "Please enter a whole number." };
                            break;
                        case "Decimal":
                            options.number = true;
                            options.messages = { number: errorText || xVal.Messages.DataType_Decimal };
                            break;
                        case "Date":
                            options.date = true;
                            options.messages = { date: errorText || xVal.Messages.DataType_Date };
                            break;
                        case "DateTime":
                            options.xVal_regex = ["^\\d{1,2}/\\d{1,2}/(\\d{2}|\\d{4})\\s+\\d{1,2}\\:\\d{2}(\\:\\d{2})?$", ""];
                            options.messages = { xVal_regex: errorText || xVal.Messages.DataType_DateTime || "Please enter a valid date and time." };
                            break;
                        case "Currency":
                            options.xVal_regex = ["^\\D?\\s?([0-9]{1,3},([0-9]{3},)*[0-9]{3}|[0-9]+)(.[0-9][0-9])?$", ""];
                            options.messages = { xVal_regex: errorText || xVal.Messages.DataType_Currency || "Please enter a currency value." };
                            break;
                        case "CreditCardLuhn":
                            options.xVal_creditCardLuhn = true;
                            if(errorText != null) options.messages = { xVal_creditCardLuhn: errorText };
                            break;
                    }
                    break;

                case "RegEx":
                    options.xVal_regex = [ruleParams.Pattern, ruleParams.Options];
                    if(errorText != null) options.messages = { xVal_regex: errorText };
                    break;

                case "Comparison":
                    var elemToCompareId = this._makeAspNetMvcHtmlHelperID((elementPrefix ? elementPrefix + "." : "") + ruleParams.PropertyToCompare);
                    var elemToCompare = document.getElementById(elemToCompareId);
                    if (elemToCompare != null) {
                        options.xVal_comparison = [ruleParams.PropertyToCompare, elemToCompare, ruleParams.ComparisonOperator];                        
                        if (errorText != null) options.messages = { xVal_comparison: errorText };
                    }
                    break;
            }

            element.rules("add", options);
        },

        _associateNearbyValidationMessageSpanWithElement: function(element) {
            // If there's a <span class='field-validation-error'> soon after, it's probably supposed to display the error message
            // jquery.validation goes looking for attributes called "htmlfor" and "generated", set as follows
            var nearbyMessages = element.nextAll("span.field-validation-error");
            if (nearbyMessages.length > 0) {
                $(nearbyMessages[0]).attr("generated", "true")
                                    .attr("htmlfor", element.attr("id"));
            }
        },

        _ensureFormIsMarkedForValidation: function(formElement) {
            if (!formElement.data("isMarkedForValidation")) {
                formElement.data("isMarkedForValidation", true);
                formElement.validate({
                    errorClass: "field-validation-error",
                    errorElement: "span",
                    highlight: function(element) { $(element).addClass("input-validation-error"); },
                    unhighlight: function(element) { $(element).removeClass("input-validation-error"); }
                });
            }
        },

        _ensureCustomFunctionsRegistered: function() {
            if (!jQuery.validator.xValFunctionsRegistered) {
                jQuery.validator.xValFunctionsRegistered = true;

                jQuery.validator.addMethod("xVal_stringRange", function(value, element, params) {
                    if (this.optional(element)) return true;
                    if (params[0] != null)
                        if (value < params[0]) return false;
                    if (params[1] != null)
                        if (value > params[1]) return false;
                    return true;
                }, function(params) {
                    if ((params[0] != null) && (params[1] != null))
                        return $.format(xVal.Messages.Range_String_MinMax || "Please enter a value alphabetically between '{0}' and '{1}'.", params[0], params[1]);
                    else if (params[0] != null)
                        return $.format(xVal.Messages.Range_String_Min || "Please enter a value not alphabetically before '{0}'.", params[0]);
                    else
                        return $.format(xVal.Messages.Range_String_Max || "Please enter a value not alphabetically after '{0}'.", params[1]);
                });

                jQuery.validator.addMethod("xVal_dateRange", function(value, element, params) {
                    if (this.optional(element)) return true;

                    var parsedValue = Date.parse(value);
                    if (isNaN(parsedValue))
                        return false;
                    else
                        parsedValue = new Date(parsedValue);
                    if (params[0] != null)
                        if (parsedValue < params[0]) return false;
                    if (params[1] != null)
                        if (parsedValue > params[1]) return false;
                    return true;
                }, function(params, elem) {
                    if (isNaN(Date.parse(elem.value)))
                        return xVal.Messages.DataType_Date || "Please enter a valid date in yyyy/mm/dd format.";
                    var formatDate = function(date) {
                        var result = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
                        if (date.getHours() + date.getMinutes() + date.getSeconds() != 0)
                            result += " " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();
                        return result.replace(/\b(\d)\b/g, '0$1');
                    };
                    if ((params[0] != null) && (params[1] != null))
                        return $.format(xVal.Messages.Range_DateTime_MinMax || "Please enter a date between {0} and {1}.", formatDate(params[0]), formatDate(params[1]));
                    else if (params[0] != null)
                        return $.format(xVal.Messages.Range_DateTime_Min || "Please enter a date no earlier than {0}.", formatDate(params[0]));
                    else
                        return $.format(xVal.Messages.Range_DateTime_Max || "Please enter a date no later than {0}.", formatDate(params[1]));
                });

                jQuery.validator.addMethod("xVal_regex", function(value, element, params) {
                    if (this.optional(element)) return true;
                    var pattern = params[0];
                    var options = params[1];
                    var regex = new RegExp(pattern, options);
                    return regex.test(value);
                }, function(params) {
                    return xVal.Messages.Regex || "This value is invalid."; // Pity we can't be more descriptive
                });

                jQuery.validator.addMethod("xVal_creditCardLuhn", function(value, element, params) {
                    if (this.optional(element)) return true;
                    value = value.replace(/\D/g, "");
                    if (value == "") return false;
                    var sum = 0;
                    for (var i = value.length - 2; i >= 0; i -= 2)
                        sum += Array(0, 2, 4, 6, 8, 1, 3, 5, 7, 9)[parseInt(value.charAt(i), 10)];
                    for (var i = value.length - 1; i >= 0; i -= 2)
                        sum += parseInt(value.charAt(i), 10);
                    return (sum % 10) == 0;
                }, function(params) {
                    return xVal.Messages.DataType_CreditCardLuhn || "Please enter a valid credit card number.";
                });

                jQuery.validator.addMethod("xVal_comparison", function(value, element, params) {
                    if (this.optional(element)) return true;
                    var elemToCompare = params[1];
                    var comparisonOperator = params[2];
                    switch (comparisonOperator) {
                        case "Equals": return value == elemToCompare.value;
                        case "DoesNotEqual": return value != elemToCompare.value;
                    }
                    return true; // Ignore unrecognized operator
                }, function(params) {
                    var propertyToCompareName = params[0];
                    var comparisonOperator = params[2];
                    switch (comparisonOperator) {
                        case "Equals": return $.format(xVal.Messages.Comparison_Equals || "This value must be the same as {0}.", propertyToCompareName);
                        case "DoesNotEqual": return $.format(xVal.Messages.Comparison_DoesNotEqual || "This value must be different from {0}.", propertyToCompareName);
                    }
                });
            }
        }
    };
})(jQuery);