﻿/// <reference path="jquery-latest-vsdoc.js" />
/*
Generic field validation popup.

To use:

$(field).vldpopup(url, func, [options]);

options:
	className : the CSS class name to use for the popup
	update : this function can be used to dynamically show a message as the user types;
	validate : this function will will be called when the field is blurred and should be used to validate
	service : can be used instead of change to make an ajax request (only called on change)
	minChars : validate or service require the the field has atleast [minChars] length in the value to execute
	args : option extra arguments to send to func or service
*/

(function ($) {
	$.fn.vldpopup = function (options) {
		var opts = $.extend({}, $.fn.vldpopup.defaults, options);

		return this.each(function () {
			var field = $(this);
			var key = "vldpopup";
			if (field.data(key) == null) {
				field.data(key, new vldpopup(field));
			}
			field.data(key).criteria.push(opts);
		});
	};

	$.fn.vldpopup.defaults = {
		className: "vp-popup",
		message: null,
		valid: true,
		minChars: 0
	};

	$.fn.vldpopup.levels = {
		good: 0,
		warning: 1,
		bad: 2
	};

	vldpopup = function (field) {
		if (typeof JSON == "undefined" || typeof JSON.stringify == "undefined") throw "$.vldpopup requires JSON";

		var self = this;
		var hide_timeout = null;
		var pos_timeout = null;

		// Public variables
		this.popup = $("<span style='position: absolute'></span>");
		this.field = field;
		this.lastValue = null;
		this.criteria = [];
		this.valid = true;
		this.isCheckbox = self.field.is(":checkbox");
		this.label = self.isCheckbox ? field.parent().siblings("label") : field.siblings("label");

		if (self.isCheckbox) {
			field.click(update);
		} else if (field.is("select")) {
			field.keyup(validate);
			field.keypress(validate);
		} else {
			field.keyup(update);
			field.keydown(update);
			field.blur(validate);
		}
		field.change(service);
		field.change(validate);

		function update(event) {
			var val = self.field.val();

			if (self.isCheckbox || val != self.lastValue) {
				hide();
				setLabel(null);

				if (self.lastValue == null) {
					self.lastValue = val;
					return;
				} else self.lastValue = val;

				for (var i = 0; i < self.criteria.length; i++) {
					var criterion = self.criteria[i];
					if (criterion.update == null) continue;

					var result = criterion.update(self.field, criterion.args);
					if (typeof result == "string") {
						criterion.message = result;
						criterion.level = $.fn.vldpopup.levels.bad;
					} else {
						criterion.message = result.message;
						criterion.level = result.level;
					}

					if (criterion.message != null) show(criterion);
					else hide();
				}
			}
		}

		function validate(event) {
			hide();
			setLabel(null);

			for (var i = 0; i < self.criteria.length; i++) {
				var criterion = self.criteria[i];
				if (criterion.validate != null && enoughChars(criterion)) {
					criterion.message = criterion.validate(self.field, criterion.args);
					criterion.level = criterion.message != null ? $.fn.vldpopup.levels.bad : $.fn.vldpopup.levels.good;
				}
				if (criterion.request == null && criterion.message != null) show(criterion);
			}
		}

		function service(event) {
			for (var i = 0; i < self.criteria.length; i++) {
				var criterion = self.criteria[i];
				if (criterion.service == null || !enoughChars(criterion)) continue;

				var data = {
					method: criterion.args.method,
					args: JSON.stringify(criterion.args.args),
					strict: criterion.args.strict,
					value: self.field.val()
				};

				criterion.request = $.ajax({
					url: criterion.service,
					data: data,
					success: function (data) {
						if (ch.hasJsonError(data)) return;
						criterion.level = data.level;
						criterion.message = data.message;
						criterion.request = null
						if (criterion.message != null) show(criterion);
					},
					type: "post",
					dataType: "json"
				});
			}
		}

		function show(criterion) {
			hide();
			if (criterion.message == null || criterion.message == "") return;

			if (!self.isCheckbox) {
				if (!self.popup.parent().length) {
					$(document.body).append(self.popup);
					var $parent = self.field.parent();
					var zIndex = null;

					while ($parent != null && $parent[0] != document.body && (zIndex = $parent.css("z-index")) == "auto") $parent = $parent.parent();
					self.popup.css("z-index", isNaN(zIndex) ? "auto" : parseFloat(zIndex) + 1);
				}

				var className = "vp-bad";
				switch (criterion.level) {
					case $.fn.vldpopup.levels.good: className = "vp-good"; break;
					case $.fn.vldpopup.levels.warning: className = "vp-warning"; break;
				}

				self.popup.attr("class", "vp-popup " + className).show();
				self.popup.html(criterion.message);

				var pos = function () {
					var field_offset = self.field.offset();
					self.popup.css({
						"z-index": parseInt(self.field.css("z-index") + 1),
						top: field_offset.top - self.popup.outerHeight(),
						left: field_offset.left + self.field.outerWidth() - self.popup.outerWidth()
					});

					//					pos_timeout = window.setTimeout(function () {
					//						pos();
					//					}, 100);
				}

				pos();

				hide_timeout = window.setTimeout(function () {
					self.popup.fadeOut(200, function () {
						window.clearTimeout(pos_timeout);
					});
				}, 2000);
			}

			if (!criterion.valid) setLabel($(criterion.message).text());
		}

		function hide() {
			window.clearTimeout(hide_timeout);
			window.clearTimeout(pos_timeout);
			self.popup.stop(true, true).hide();
		}

		function setLabel(message) {
			if (message == null) {
				self.label.removeClass("invalid");
				self.label.removeAttr("title");
			} else {
				self.label.addClass("invalid");
				self.label.attr("title", self.label.text() + " " + message);
			}
		}

		function enoughChars(criterion) {
			var val = self.field.val()
			if (criterion.minChars > 0 && (val == null || val.length < criterion.minChars)) {
				criterion.valid = true;
				criterion.message = null;
				return false;
			} else return true;
		}

		this.toString = function () {
			return "[vldpopup]";
		};
	};
})(jQuery);

function validateCheckBox(field, args) {
	if (!field.prop("checked") && args.required) return "required";
	return null;
}

function validateEntity(field, args) {
	if(field.val() == ""){
		if (args.required) return "required";
		else return null;
	}
	var hiddenField = field.siblings("input:hidden");
	if (hiddenField.length == 0) {
		return null
	} else if (args.exists && hiddenField.val() == "") {
		return "no selection";
	} else return null
}

function validateNumber(field, args) {
	var val = field.val();
	if(val == ""){
		if(args.required) return "required";
		else return null;
	}
	var number;
	if (args.format == "Integer") {
		number = parseInt(val);
	} else if (args.format == "float") {
		number = parseFloat(val);
	}
	if (isNaN(number)) {
		return "invalid " + args.format;
	} else if (args.min != null && number < args.min) {
		return "min " + args.min;
	} else if (args.max != null && number > args.max) {
		return "max " + args.max;
	} else return null;
}

function regex_validate(field, args) {
	var val = field.val();
	if (val.length == 0) return null;
	var regex = new RegExp(args.expression);
	if (regex.test(val) != args.match) return args.message;
	else return null;
}

function list_validate(field, args) {
	var val = field.val();
	if (val == "-1" || val == null) {
		return "required";
	} else if (args.max > 0 && val.split(",").length > args.max) {
		return "max " + args.max + " selections";
	} else return null;
}

function time_validate(field, args) {
	var str = field.val();
	if (str == "dd-MMM-yyyy" || str == "") {
		if (args.required) return "required";
		else return null;
	}
	var t = time.parseString(str)
	if (t == null) {
		return "invalid time";
	} else if (args.min != null && t.compareTo(args.min) < 0) {
		return "min " + args.min.toString();
	} else if (args.max != null && t.compareTo(args.max) > 0) {
		return "max " + args.max.toString();
	} else return null;
}

function date_validate(field, args) {
	var str = field.val();
	if (str == "hh:mm am/pm" || str == "") {
		if (args.required) return "required";
		else return null;
	}
	var date = Date.parseDisplayString(str)
	if (date == null) {
		return "invalid date";
	} else if (date < args.min) {
		return "min " + args.min.toDisplayString();
	} else if (date > args.max) {
		return "max " + args.max.toDisplayString();
	} else return null;
}

function charCount_validate(field, args) {
	restrictLength(field, args.max);
	var length = $.trim(field.val()).length;
	if (length == 0 && args.required == true)
		return "required";
	if (args.max > 0 && length > args.max) {
		return "too long";
	} else if (length > 0 && length < args.min) {
		return "too short";
	} else return null;
}

function abn_acn_update(field, args) {
	var val = field.val().replace(/[- ]/g, "");
	if(/\D/.test(val)) return { message: "invalid characters", level: $.fn.vldpopup.levels.bad };
	return { level: $.fn.vldpopup.levels.good };
}

function abn_acn_validate(field, args) {
	var val = field.val().replace(/[- ]/g, "");

	if (val.length == 11) {
		return abn_validate(field, args);
	} else if (val.length == 9) {
		return acn_validate(field, args);
	}

	return "too " + (val.length > 9 ? "long" : "short");
}

function abn_validate(field, args) {
	var weights = [10, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19];
	var sum = 0;
	var val = field.val().replace(/[- ]/g, "");

	if (/\D/.test(val)) return "invalid characters";
	if (val.length < 11) return "too short";
	if (val.length > 11) return "too long";

	for (var i = 0; i < weights.length; i++) {
		var digit = parseInt(val.charAt(i));
		if (i == 0) digit--;
		sum += digit * weights[i]
	}
	if (sum % 89 != 0) return "invalid ABN";

	field.val(val.substr(0, 2) + " " + val.substr(2, 3) + " " + val.substr(5, 3) + " " + val.substr(8, 3));
}

function acn_validate(field, args) {
	var weights = [8, 7, 6, 5, 4, 3, 2, 1];
	var sum = 0;
	var val = field.val().replace(/[- ]/g, "");

	if (/\D/.test(val)) return "invalid characters";
	if (val.length < 9) return "too short";
	if (val.length > 9) return "too long";

	for (var i = 0; i < weights.length; i++) {
		var digit = parseInt(val.charAt(i));
		sum += digit * weights[i]
	}

	var check = 10 - (sum % 10);
	check = check == 10 ? 0 : check;
	if (check != parseInt(val.charAt(8))) return "invalid ACN";

	field.val(val.substr(0, 3) + " " + val.substr(3, 3) + " " + val.substr(6, 3));
}

function compare_validate(field, args) {
	var $compare = $("#" + args.compareTo);
	if (field.val() != $compare.val()) return "values do not match";
}

function requiredGroup_validate(field, args) {
	var length = $.trim(field.val()).length;
	if (length) {
		$("#" + args.message).removeClass("message-required-invalid");
		for (var i in args.fields) {
			var id = args.fields[i];
			if (id == field.attr("id")) continue;
			var $f = $("#" + id);
			if (!$.trim($f.val()).length) {
				var $l = $f.is("input:checkbox") ? $f.next() : $f.prev();
				$l.removeClass("invalid").removeAttr("title");
			}
		}
	}
}

function countChars(field, args) {
	restrictLength(field, args.max);

	var min = args.min;
	var max = args.max;
	var length = field.val().length;

	var level = null;
	// Find the relevant class to indicate current length status.
	if (max > 0 && length >= max || length < min) {
		level = $.fn.vldpopup.levels.bad;
	} else if (max > 0 && length > max / 2 && length < max) {
		level = $.fn.vldpopup.levels.warning;
	} else {
		level = $.fn.vldpopup.levels.good;
	}

	var html = [];
	html.push(length);
	html.push(" /");

	if(min > 0) {
		html.push(" min " + min);
		if(max > 0) html.push(",");
	}

	if (max > 0) html.push(" max " + max);

	return { message: html.join(''), level: level };
}

// Restrict the user's input to the max characters allowed.
function restrictLength(field, max){
	var val = field.val();
	if(max > 0) {
		if(val.length > max){
			field.val(val.substr(0, max));
		}
	}
}
