﻿// Common initialization
var xVal = xVal || {};
xVal.Plugins = xVal.Plugins || {};
xVal.Messages = xVal.Messages || {};
xVal.AttachValidator = function(elementPrefix, rulesConfig, options, pluginName) {
    if (pluginName != null)
        this.Plugins[pluginName].AttachValidator(elementPrefix, rulesConfig, options);
    else
        for (var key in this.Plugins) {
            this.Plugins[key].AttachValidator(elementPrefix, rulesConfig, options);
            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, options) {
			var self = this;
			self._ensureCustomFunctionsRegistered();
			$(function() {
				self._ensureValidationSummaryContainerExistsIfRequired(options);

				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 = self._makeAspNetMvcHtmlHelperID((elementPrefix ? elementPrefix + "." : "") + fieldName);
					var elem = document.getElementById(elemId);


					// SRB - added, based on example/suggestion - http://xval.codeplex.com/WorkItem/View.aspx?WorkItemId=1516
					//     - allows radio button groups to properly validate
					if (!elem) {
						var tmp = elemId.replace('_', '.');
						elem = $('[name=' + tmp + ']')[0];
					}


					if (elem) {
						for (var j = 0; j < fieldRules.length; j++) {
							var rule = fieldRules[j];
							if (rule != null) {
								var ruleName = rule.RuleName;
								var ruleParams = rule.RuleParameters;
								var errorText = (typeof (rule.Message) == 'undefined' ? null : rule.Message);
								self._attachRuleToDOMElement(ruleName, ruleParams, errorText, $(elem), elementPrefix, options);
							}
						}
					}
				}
			});
		},

		_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(/\./g, "_");
		},

		_attachRuleToDOMElement: function(ruleName, ruleParams, errorText, element, elementPrefix, options) {
			var parentForm = element.parents("form");
			if (parentForm.length != 1)
				alert("Error: Element " + element.attr("id") + " is not in a form");
			this._ensureFormIsMarkedForValidation($(parentForm[0]), options);
			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);
						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;

				case "Remote":
					var dataAccessor = {};
					parentForm.find("input[name], textarea[name], select[name]").each(function() {
						var input = this;
						dataAccessor[input.name] = function() { return $(input).val(); };
					});
					options.remote = {
						url: ruleParams.url,
						data: dataAccessor,
						type: 'post'
					};
					break;

				case "Custom":
					var ruleFunction = this._parseAsFunctionWithWarnings(ruleParams.Function);
					if (ruleFunction != null) {
						var customFunctionName = this._registerCustomValidationFunction(ruleFunction);
						var evaluatedParams = ruleParams.Parameters == "null" ? null : eval("(" + ruleParams.Parameters + ")");
						options[customFunctionName] = evaluatedParams || true;
						options.messages = [];
						options.messages[customFunctionName] = errorText;
					}
					break;
			}

			element.rules("add", options);
		},

		_parseAsFunctionWithWarnings: function(functionString) {
			var result;
			try { result = eval("(" + functionString + ")") }
			catch (ex) {
				alert("Custom rule error: Could not find or could not parse the function '" + functionString + "'");
				return null;
			}
			if (typeof (result) != 'function') {
				alert("Custom rule error: The JavaScript object '" + functionString + "' is not a function.");
				return null;
			}
			return result;
		},

		_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 an attribute called "htmlfor" 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, options) {
			if (!formElement.data("isMarkedForValidation")) {
				formElement.data("isMarkedForValidation", true);


				// SRB - added .parent() and useParentP variable
				var useParentPItem = null;
				if (typeof useParentP == "undefined") {
					useParentPItem = false;
				} else {
					useParentPItem = useParentP;
				}

				var validationOptions = {
					errorClass: "field-validation-error",
					errorElement: "span",

					// SRB - added .parent() and useParentP variable
					highlight: function(element) {
						if (useParentPItem) {
							$(element).parent().addClass("input-validation-error");
						} else {
							$(element).addClass("input-validation-error");
						}
					},
					// SRB - added .parent() and useParentP variable
					unhighlight: function(element) {
						if (useParentPItem) {
							$(element).parent().removeClass("input-validation-error");
						} else {
							$(element).removeClass("input-validation-error");
						}
					}
				};

				// SRB - global message placement handler
				if ((typeof validationHelper != "undefined" && typeof validationHelper.globalCustomErrorPlacement != "undefined") && (jQuery.isFunction(validationHelper.globalCustomErrorPlacement))) {
					validationOptions.errorPlacement = validationHelper.globalCustomErrorPlacement;
				}

				if (options.ValidationSummary) {
					validationOptions.wrapper = "li";
					validationOptions.errorLabelContainer = "#" + options.ValidationSummary.ElementID + " ul:first";
				}
				var validator = formElement.validate(validationOptions);
				if (options.ValidationSummary)
					this._modifyJQueryValidationElementHidingBehaviourToSupportValidationSummary(validator, options);
			}
		},

		_registerCustomValidationFunction: function(evalFn) {
			jQuery.validator.xValCustomFunctionCount = (jQuery.validator.xValCustomFunctionCount || 0) + 1;
			var functionName = "xVal_customFunction_" + jQuery.validator.xValCustomFunctionCount;
			jQuery.validator.addMethod(functionName, function(value, element, params) {
				if (this.optional(element))
					return true;
				return evalFn(value, element, params);
			});
			return functionName;
		},

		_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);
					}
				});

				$.expr[":"].displayableValidationSummaryMessage = function(object) {
					var span = $(object).find("span:first");
					if (span.length == 0)
						return true;
					return !(span.css("display") === "none") && !span.is(":empty");
				};
			}
		},

		_ensureValidationSummaryContainerExistsIfRequired: function(options) {
			// If we're using a validation summary, make sure the container contains an UL
			// (If there were no server-generated errors, there won't be until we create one)
			if (options.ValidationSummary) {
				var validationSummaryContainer = $("#" + options.ValidationSummary.ElementID);
				if (validationSummaryContainer.length == 0)
					alert("Cannot find validation summary element \"" + options.ValidationSummary.ElementID + "\". Make sure you've put an element with this ID into your HTML document.");
				if (!validationSummaryContainer.is(":has(ul)")) {
					validationSummaryContainer.append($("<span class='validation-summary-errors' />").text(options.ValidationSummary.HeaderMessage))
                                              .append($("<ul />"))
                                              .hide();
				}
			}
		},

		_modifyJQueryValidationElementHidingBehaviourToSupportValidationSummary: function(validator, options) {
			// Intercept the hideErrors() and showErrors() methods. Pity there isn't a proper API for this.
			var originalHideErrorsMethod = validator.hideErrors;
			var originalShowErrorsMethod = validator.showErrors;
			validator.hideErrors = function() {
				this.toHide = this.toHide.not("ul"); // Don't ever hide ULs, because these might still contain server-generated error messages
				originalHideErrorsMethod.apply(this, arguments);
				// If the summary container contains no displayable messages, hide the whole thing
				$("#" + options.ValidationSummary.ElementID + ":not(:has(li:displayableValidationSummaryMessage))").hide();
			};
			validator.showErrors = function() {
				originalShowErrorsMethod.apply(this, arguments);
				// If the summary container contains any displayable messages, show it
				$("#" + options.ValidationSummary.ElementID + ":has(li:displayableValidationSummaryMessage)").show();
			};
		}
	};
})(jQuery);
