/**
 * jQuery-Plugin zur Formularvalidierung.
 *
 * Attribute:
 *
 * Trennzeichen bei mehreren Werten: "|" = Oder, "," = Und
 *
 * v-validate="true|false":
 * Legt fest ob das Feld validiert werden soll.
 *
 * v-required="true|false":
 * Eingabe obligatorisch (leerer Wert nicht erlaubt).
 *
 * v-match="alphabetic|name|numeric|email|url|/regex/":
 * Legt das Format der Eingabe fest.
 * 
 * v-purge="html|spaces|chars|numbers":
 * Entfernt die angegebenen Inhalte aus der Eingabe.
 *
 * v-classes="error:class[{selector}]|ok:class[{selector}]":
 * Legt die anzuzeigenden Klassen fuer den Fehler- und Ok-Fall fest.
 * "error" bzw. "ok" beschreiben den Anwendungsfall, "{selector}" ist ein
 * optionaler jQuery-kompatibler (also CSS-kompatibler) Selektor fuer das
 * Element, welches die durch "class" spezifizierte Klasse erhalten soll.
 * Wichtig: Der Selektor wird IMMER nach "aussen" angewandt, d.h. es wird in den
 * Elternelementen des angesprochenen Formularelements danach gesucht.
 *
 * v-message="message":
 * Legt die Fehlermeldung fest, die im Fehlerfall angezeigt werden soll.
 *
 * @author Kristian Kriehl
 * @version 1.0.2
 * @history
 * 1.0.2 | 2010-09-01:
 * - Fix im Domain-Char-Regex: Vermeidung von "-" vor und nach der Domain durch
 *   negative lookahead und statt mindestens 2 chars mindestens 1 gefolgt von
 *   nicht-"-".
 * - Neues Attribut "v-empty-message" für eine extra-Nachricht fuer benoetigte
 *   nicht-ausgefuellte Felder hinzugefuegt
 * - Validierungszustaende eingefuehrt: "empty", "error" und "valid"
 * - Event-Option "onErrorFree" hinzugefuegt
 * 1.0.1 | 2010-08-31:
 * - Option "errorParent" hinzugefuegt
 * - Event-Option "onError" hinzugefuegt
 **/

(function($){
	var options = {
		errorMessageClass : "v-error-message",
		animate           : true,
		animateFading     : true,
		animateSliding    : true,
		animationDuration : 250,
		errorParent       : null,
		onError           : null,
		onErrorFree       : null
	};

	$.fn.validator = function(_options) {
		options = $.extend(true, options, _options);

		var $elements = this;

		return this.each(function () {
			if(!$.data(this, 'classConf')) parseClassesConfig(this);

			$(this).bind("blur", null, function() {
				if($(this).attr($.fn.validator.attributes.validate) == "true") {
					var validationResult = validate(this);

					setState(this, validationResult, options);

					if($elements.filter(
						'[' + $.fn.validator.attributes.state + '="' + $.fn.validator.states.empty + '"],' +
						'[' + $.fn.validator.attributes.state + '="' + $.fn.validator.states.error + '"]'
					).length == 0) {
						if($.isFunction(options.onErrorFree)) {
							options.onErrorFree.apply(null);
						}
					} else {
						if($.isFunction(options.onError)) {
							options.onError.apply(this, [ validationResult ]);
						}
					}
				}
			})
		});
	}

	$.fn.isValid = function() {
			
		var isValid = true;
		var validationResult = $.fn.validator.states.valid;

		this.each(function () {
			if(!$.data(this, 'classConf')) parseClassesConfig(this);

			if($(this).attr($.fn.validator.attributes.validate) == "true") {
				validationResult = validate(this);
				setState(this, validationResult, options);
				
				if(validationResult != $.fn.validator.states.valid) {
					isValid = false;
					if($.isFunction(options.onError)) {
						options.onError.apply(this, [ validationResult ]);
					}
				}
			}
		});

		if(isValid && $.isFunction(options.onErrorFree)) {
			options.onErrorFree.apply(null);
		}
		
		return isValid;
	}

	$.fn.validator.attributes = {
		validate     : "v-validate",
		required     : "v-required",
		match        : "v-match",
		purge        : "v-purge",
		classes      : "v-classes",
		emptyMessage : "v-empty-message",
		errorMessage : "v-message",
		state        : "v-state"
	}

	var domainChars = '(?!-)([àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęěĝğ' +
	                  'ġģĥħĩīĭįıĵķĺļľŀłńņňŉŋōŏőœŕŗřśŝšťŧũūŭůųŵŷźżžșțΐάέήίΰαβγδεζ' +
	                  'ηθικλμνξοπρςστυφχψωϊϋόύώабвгдежзийклмнопрстуфхцчшщъыьэюяἀ' +
	                  'ἁἂἃἄἅἆἇἐἑἒἓἔἕἠἡἢἣἤἥἦἧἰἱἲἳἴἵἶἷὀὁὂὃὄὅὐὑὒὓὔὕὖὗὠὡὢὣὤὥὦὧὰάὲέὴή' +
	                  'ὶίὸόὺύὼώᾀᾁᾂᾃᾄᾅᾆᾇᾐᾑᾒᾓᾔᾕᾖᾗᾠᾡᾢᾣᾤᾥᾦᾧᾰᾱᾲᾳᾴᾶᾷῂῃῄῆῇῐῑῒΐῖῗῠῡῢΰῤῥῦ' +
	                  'ῧῲῳῴῶῷa-z0-9.-]{1,})[^-]+';

	$.fn.validator.regex = {
		purge : {
			html    : /<[^>]*>/g,
			spaces  : /\s+/g,
			chars   : /[a-zA-Z]+/g,
			numbers : /[0-9]+/g
		},
		match : {
			alphabetic : '/^[a-z]+$/i',
			name       : '/^[a-z0-9._\\s-()"\']+$/i',
			numeric    : '/^[0-9]+$/',
			email      : '/^[A-Za-z0-9.!#$%&\'*+\\/=?^_`{|}~-]+@' + domainChars + '\\.[a-z]{2,10}$/i',
			url        : '/^(ftp|http|https):\\/\\/' + domainChars + '\\.[a-z]{2,10}(\\/.*)?$/i'
		}
	}

	$.fn.validator.states = {
		error : 'error',
		empty : 'empty',
		valid : 'valid'
	}

	function setState(obj, state, options) {
		var classConf = $.data(obj,"classConf");
		var $obj = $(obj);

		var $errorTarget = $obj;
		var $okTarget = $obj;

		$obj.attr($.fn.validator.attributes.state, state);

		if(classConf.error.selector != null) {
			$errorTarget = $errorTarget.closest(classConf.error.selector);
		}

		if(classConf.ok.selector != null) {
			$okTarget = $okTarget.closest(classConf.ok.selector);
		}

		var messageID = 'error_for_' + $obj.attr("name");

		if(state == $.fn.validator.states.empty) {
			if(classConf.error.clazz) $errorTarget.addClass(classConf.error.clazz);
			if(classConf.ok.clazz)    $okTarget.removeClass(classConf.ok.clazz);
			showErrorMessage(obj, messageID, options, true);
		} else if(state == $.fn.validator.states.error) {
			if(classConf.error.clazz) $errorTarget.addClass(classConf.error.clazz);
			if(classConf.ok.clazz)    $okTarget.removeClass(classConf.ok.clazz);
			showErrorMessage(obj, messageID, options, false);
		} else if(state == $.fn.validator.states.valid) {
			if(classConf.error.clazz) $errorTarget.removeClass(classConf.error.clazz);
			if(classConf.ok.clazz)    $okTarget.addClass(classConf.ok.clazz);
			hideErrorMessage(obj, messageID, options);
		}
	}

	function showErrorMessage(obj, messageID, options, isEmpty) {
		var $obj = $(obj);
		var messageText = "";

		if(isEmpty) {
			messageText = $obj.attr($.fn.validator.attributes.emptyMessage);

			if(!messageText) {
				messageText = $obj.attr($.fn.validator.attributes.errorMessage);
			}
		} else {
			messageText = $obj.attr($.fn.validator.attributes.errorMessage);
		}

		if(messageText) {
			var $message = $('#' + messageID);

			if($('#' + messageID).size() == 0) {
				$message = $('<div style="display:none" id="' + messageID + '" class="' + options.errorMessageClass + '"></div>');

				if(options.errorParent) {
					$(options.errorParent).append($message);
				} else {
					$obj.after($message);
				}
			}

			$message.html(messageText);

			if(options.animate) {
				if(options.animateFading && options.animateSliding) {
					$message.fadeTo(0, 0.0).slideDown(options.animationDuration, function() {
						$message.fadeTo(options.animationDuration, 1.0);
					});
				} else if(options.animateFading && !options.animateSliding) {
					$message.fadeTo(0, 0.0).fadeTo(options.animationDuration, 1.0);
				} else if(options.animateSliding && !options.animateFading) {
					$message.slideDown(options.animationDuration);
				}
			} else {
				$message.show();
			}
		}
	}

	function hideErrorMessage(obj, messageID, options) {
		var $message = $('#' + messageID);

		if(options.animate) {
			if(options.animateFading && options.animateSliding) {
				$message.fadeTo(options.animationDuration, 0.0, function() {
					$(this).slideUp(options.animationDuration, function() {
						$(this).remove();
					});
				});
			} else if(options.animateFading && !options.animateSliding) {
				$message.fadeTo(options.animationDuration, 0.0, function() {
					$(this).remove();
				});
			} else if(options.animateSliding && !options.animateFading) {
				$(this).slideUp(options.animationDuration, function() {
					$(this).remove();
				});
			}
		} else {
			$('#' + messageID).hide().remove();
		}
	}

	function parseClassesConfig(obj) {
		var dataClassConf = $.data(obj, "classConf");

		if(dataClassConf != null && typeof dataClassConf == "object") {
			return dataClassConf;
		}

		var config = {
			error : {clazz : null, selector : null},
			ok    : {clazz : null, selector : null}
		};

		var classes = $(obj).attr($.fn.validator.attributes.classes);

		if(classes && classes != '') {
			var split = classes.split("|");
			var errorPart = "";
			var okPart = "";

			for(var key in split) {
				if(split[key].indexOf("error:") == 0) {
					errorPart = split[key].substr(6);
				} else if(split[key].indexOf("ok:") == 0) {
					okPart = split[key].substr(3);
				}
			}

			var matches;

			if(errorPart != "") {
				matches = /([^\{]+)({.*})?/.exec(errorPart);
				config.error.clazz    = matches[1];
				config.error.selector = matches[2] ? matches[2].replace(/[\{\}]/g,"") : null;
			}

			if(okPart != "") {
				matches = /([^\{]+)({.*})?/.exec(okPart);
				config.ok.clazz = matches[1];
				config.ok.selector = matches[2] ? matches[2].replace(/[\{\}]/g,"") : null;
			}
		}

		$.data(obj, "classConf", config);

		return config;
	}

	function validate(obj) {
		var $obj = $(obj);

		if($obj.attr($.fn.validator.attributes.purge)) {
			execPurge(obj, $obj.attr("v-purge"));
		}

		if($obj.attr($.fn.validator.attributes.required) == "true") {
			if(!validateRequired(obj)) {
				return $.fn.validator.states.empty;
			}
		} else {
			// wenn nicht obligatorisch & der wert "leer", dann ist das ok...
			if($obj.val() == "") {
				return $.fn.validator.states.valid;
			}
		}

		if($obj.attr($.fn.validator.attributes.match)) {
			if(!validateMatch(obj, $obj.attr("v-match"))) {
				return $.fn.validator.states.error;
			}
		}

		return $.fn.validator.states.valid;
	}

	function execPurge(obj, rules) {
		rules = rules.indexOf("|") > 0 ? rules.split("|") : rules.split(",");
		var value = $(obj).val();
		
		for(var key in rules) {
			value = value.replace($.fn.validator.regex.purge[rules[key]], "");
		}

		$(obj).val(value);
	}

	function validateRequired(obj) {
		var value = $(obj).val();

		if($(obj).attr('type') == 'checkbox') {
			return $(obj).is(':checked');
		}

		if(
			( typeof value == "string" && value != "" ) ||
			( typeof value == "object" && value.length && value.length > 0 )
		) {
			return true;
		} else {
			return false;
		}
	}

	function validateMatch(obj, match) {
		// alphabetic|numeric|email|url|/regex/

		match = match.indexOf("|") > 0 ? match.split("|") : match.split(",");

		var value = $(obj).val();
		var regex = null;
		var regexString = '';

		for(var key in match) {
			if($.fn.validator.regex.match[match[key]]) {
				regexString = $.fn.validator.regex.match[match[key]];

				if(/(\/i)$/.exec(regexString)) {
					regexString = regexString.substr(1, regexString.length - 3);
					regex = new RegExp(regexString, "i");
				} else {
					regexString = regexString.substr(1, regexString.length - 2);
					regex = new RegExp(regexString);
				}

				if(!regex.exec(value)) {
					return false;
				}
			} else if(
				match[key].indexOf('/') == 0 &&
				match[key].lastIndexOf('/') == match[key].length - 1
			) {
				regex = new RegExp(match[key].replace("/", ""), "i");
				if(!regex.exec(value)) {
					return false;
				}
			}
		}

		return true;
	}
})(jQuery)
