var xVal = xVal || {
    Plugins: {},
    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;
        }
    }
};

var Page_Validators;
var Page_ValidationActive;

xVal.Plugins["AspNetNative"] = {
    AttachValidator: function(elementPrefix, rulesConfig) {
        Page_Validators = new Array();

        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 = (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);
                }
            }
        }

        Page_ValidationActive = false;
        if (typeof (ValidatorOnLoad) == "function")
            ValidatorOnLoad();
    },

    _attachRuleToDOMElement: function(ruleName, ruleParams, errorText, element) {
        var ruleConfig = this._getAspNetRuleConfig(ruleName, ruleParams, errorText);
        if (ruleConfig == null)
            return;

        // Find parent form and ensure it's enabled for validation
        var parentForm = element;
        while (parentForm.tagName != "FORM") {
            parentForm = parentForm.parentNode;
            if (parentForm == null)
                alert("Error: Element " + element.id + " is not in a form");
        }
        this._ensureValidationEnabledOnForm(parentForm);

        var messageContainer = this._createMessageContainer(element, ruleConfig.errorMessage);

        Page_Validators[Page_Validators.length] = messageContainer;

        messageContainer.controltovalidate = element.id;
        messageContainer.errormessage = ruleConfig.errorMessage;
        messageContainer.display = "Dynamic";
        messageContainer.evaluationfunction = ruleConfig.evaluationFunction;
        for (var i = 0; i < ruleConfig.params.length; i++)
            messageContainer[ruleConfig.params[i].name] = ruleConfig.params[i].value;
    },

    _getAspNetRuleConfig: function(ruleName, ruleParams, fixedErrorText) {
        switch (ruleName) {
            case "Required":
                return {
                    evaluationFunction: "RequiredFieldValidatorEvaluateIsValid",
                    params: [{ name: "initialvalue", value: ""}],
                    errorMessage: fixedErrorText || "Please enter a value."
                };
            case "Range":
                var message;
                var min = null, max = null;
                if (ruleParams.Type != "datetime") {
                    min = typeof (ruleParams.Min) == 'undefined' ? null : ruleParams.Min;
                    max = typeof (ruleParams.Max) == 'undefined' ? null : ruleParams.Max;
                } else {
                    if (typeof (ruleParams.MinYear) != 'undefined')
                        min = new Date(ruleParams.MinYear, ruleParams.MinMonth - 1, ruleParams.MinDay, ruleParams.MinHour, ruleParams.MinMinute, ruleParams.MinSecond);
                    if (typeof (ruleParams.MaxYear) != 'undefined')
                        max = new Date(ruleParams.MaxYear, ruleParams.MaxMonth - 1, ruleParams.MaxDay, ruleParams.MaxHour, ruleParams.MaxMinute, ruleParams.MaxSecond);
                }
                if (min != null) {
                    if (max != null)
                        message = "Please enter a value between " + min + " and " + max + ".";
                    else
                        message = "Please enter a value of at least " + min + ".";
                }
                else
                    message = "Please enter a value no more than " + max + ".";

                var aspNetNativeType = ruleParams.Type == "string" ? "String" :
                                       ruleParams.Type == "integer" ? "Integer" :
                                       ruleParams.Type == "decimal" ? "Double" :
                                       ruleParams.Type == "datetime" ? "Date" : alert("Unknown range type:" + ruleParams.Type);
                if (aspNetNativeType != "Date") {
                    min = "" + (min || Number.MIN_VALUE);
                    max = "" + (max || Number.MAX_VALUE);
                }
                return {
                    evaluationFunction: ruleParams.Type != "datetime" ? "RangeValidatorEvaluateIsValid" : "xVal_AspNetNative_Range_DateTime",
                    params: [{ name: "decimalchar", value: "." },
                             { name: "type", value: aspNetNativeType },
                             { name: "minimumvalue", value: min },
                             { name: "maximumvalue", value: max}],
                    errorMessage: fixedErrorText || message
                };
            case "RegEx":
                return {
                    evaluationFunction: "xVal_AspNetNative_RegEx",
                    params: [{ name: "pattern", value: ruleParams.Pattern },
                             { name: "options", value: typeof (ruleParams.Options) == 'undefined' ? "" : ruleParams.Options}],
                    errorMessage: fixedErrorText || "Please enter a valid value."
                };
            case "StringLength":
                var min = typeof (ruleParams.MinLength) == 'undefined' ? null : ruleParams.MinLength;
                var max = typeof (ruleParams.MaxLength) == 'undefined' ? null : ruleParams.MaxLength;
                var pattern = "^.{" + (min || "0") + "," + (max || "") + "}$";
                var message;
                if (min != null) {
                    if (max != null)
                        message = "Please enter a value between " + min + " and " + max + " characters long.";
                    else
                        message = "Please enter a value at least " + min + " characters long.";
                }
                else
                    message = "Please enter a value no more than " + max + " characters long.";

                return {
                    evaluationFunction: "xVal_AspNetNative_RegEx",
                    params: [{ name: "pattern", value: pattern },
                             { name: "options", value: ""}],
                    errorMessage: fixedErrorText || message
                };

            case "DataType":
                if (ruleParams.Type == "CreditCardLuhn") {
                    return {
                        evaluationFunction: "xVal_AspNetNative_CreditCardLuhn",
                        params: [],
                        errorMessage: fixedErrorText || "Please enter a valid credit card number."
                    };
                }

                var pattern, message;
                switch (ruleParams.Type) {
                    case "EmailAddress":
                        pattern = "^[\\w\\.=-]+@[\\w\\.-]+\\.[\\w]{2,}$";
                        message = "Please enter a valid email address.";
                        break;
                    case "Integer":
                        pattern = "^\\-?\\d+$";
                        message = "Please enter a number.";
                        break;
                    case "Decimal":
                        pattern = "^\\-?\\d+(\\.\\d+)?$";
                        message = "Please enter a decimal number.";
                        break;
                    case "Date":
                        pattern = "^(\\d{1,2}[/\\-\\.\\s]\\d{1,2}[/\\-\\.\\s](\\d{2}|\\d{4}))|((\\d{2}|\\d{4})[/\\-\\.\\s]\\d{1,2}[/\\-\\.\\s]\\d{1,2})$";
                        message = "Please enter a valid date.";
                        break;
                    case "DateTime":
                        pattern = "^(\\d{1,2}[/\\-\\.\\s]\\d{1,2}[/\\-\\.\\s](\\d{2}|\\d{4}))|((\\d{2}|\\d{4})[/\\-\\.\\s]\\d{1,2}[/\\-\\.\\s]\\d{1,2})\\s+\\d{1,2}\\:\\d{2}(\\:\\d{2})?$";
                        message = "Please enter a valid date and time.";
                        break;
                    case "Currency":
                        pattern = "^\\-?\\D?\\s?\\-?\\s?([0-9]{1,3},([0-9]{3},)*[0-9]{3}|[0-9]+)(.[0-9][0-9])?$";
                        message = "Please enter a currency value.";
                        break;
                }
                return {
                    evaluationFunction: "xVal_AspNetNative_RegEx",
                    params: [{ name: "pattern", value: pattern },
                             { name: "options", value: "i"}],
                    errorMessage: fixedErrorText || message
                };
        }
        return null;
    },

    _hideElementOnChange: function(elementToWatch, elementToHide) {
        var handler = function() { elementToHide.style.display = "none"; };
        if (elementToWatch.addEventListener)
            elementToWatch.addEventListener('change', handler, false);
        else
            elementToWatch.attachEvent('onchange', handler);
    },

    _createMessageContainer: function(element, initialText) {
        // Is there an existing message container with htmlfor="elementid"?
        // If so, we'll put the messages next to it
        var insertAfterElem = element;
        var spans = document.getElementsByTagName("SPAN");
        for (var i = 0; i < spans.length; i++) {
            if (spans[i].getAttribute("htmlfor") == element.id) {
                insertAfterElem = spans[i];
                this._hideElementOnChange(element, insertAfterElem);
                break;
            }
        }

        var result = document.createElement("span");
        result.id = element.id + "_Msg";
        result.innerHTML = initialText;
        result.style.color = "Red";
        result.style.display = "none";
        if (insertAfterElem.nextSibling)
            insertAfterElem.parentNode.insertBefore(result, insertAfterElem.nextSibling);
        else
            insertAfterElem.parentNode.appendChild(result);
        return result;
    },

    _ensureValidationEnabledOnForm: function(formElement) {
        if (!formElement._xVal_ValidationEnabledOnForm) {
            formElement._xVal_ValidationEnabledOnForm = true;

            formElement.onsubmit = function() {
                return (Page_ValidationActive ? ValidatorCommonOnSubmit() : false);
            };

            var inputControls = formElement.getElementsByTagName("INPUT");
            for (var i = 0; i < inputControls.length; i++) {
                if (inputControls[i].type && (inputControls[i].type.toLowerCase() == 'submit')) {
                    inputControls[i].onclick = function() {
                        WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(this.name || this.id || "", "", true, "", "", false, false));
                    };
                }
            }
        }
    }
};

function xVal_AspNetNative_RegEx(val) {
    var value = ValidatorGetValue(val.controltovalidate);
    if (ValidatorTrim(value).length == 0)
        return true;
    var regex = val.options == "" ? new RegExp(val.pattern) : new RegExp(val.pattern, val.options);
    return regex.test(value);
}

function xVal_AspNetNative_Range_DateTime(val) {
    var value = ValidatorGetValue(val.controltovalidate);
    if (ValidatorTrim(value).length == 0)
        return true;
    var min = val.minimumvalue;
    var max = val.maximumvalue;

    var parsedValue = Date.parse(value);
    if (isNaN(parsedValue))
        return false;
    else
        parsedValue = new Date(parsedValue);
    if (min != null)
        if (parsedValue < min) return false;
    if (max != null)
        if (parsedValue > max) return false;
    return true;
}

function xVal_AspNetNative_CreditCardLuhn(val) {
    var value = ValidatorGetValue(val.controltovalidate);
    if (ValidatorTrim(value).length == 0)
        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;
}