/** 
 * @fileoverview 
 * overview
 *
 * @author $Author: kuehnel $
 * @version $Rev: 7415 $ 
 * @date $Date: 2011-10-20 19:19:50 +0200 (Thu, 20 Oct 2011) $
 *
 */
(function ($) {
	var validator = null,
		methods = {
			init: function (options) {
				return this.each(function () {
					var $form,
						marker	= options && options.requiredMarker || '*',
						v;
					
					// check if current selector is a form element or if we're on a asp.net page
					if (this.tagName && this.tagName.toLowerCase() == 'form') {
						$form = $(this);
						$.smaValidateOptions.isAspDotNet = false;
					}
					else {
						$form = $(this).closest('form');
						
						$.smaValidateOptions.fieldset = $.smaValidateOptions.fieldset ? $.smaValidateOptions.fieldset : $(this);
						
						if (!$form.length) {
							console.error('Der angegebene Selector ist kein <form>-Element und ist auch nicht in einem Formular enthalten.');
							
							return false;
						}
						
						if ($form.parent().is('body')) {
							$.smaValidateOptions.isAspDotNet = true;
						}
					}						
					
					if ($.browser.msie && parseInt($.browser.version, 10) <= 8) {
						$form.addClass($.smaValidateOptions.ieFormClass);
					}
					
					// set ajax Submit flag
					$.smaValidateOptions.ajaxSubmit	= $form.hasClass($.smaValidateOptions.ajaxSubmitClass);
	
					// add required-marker
					$('input, textarea, select', $form).filter('.required:not(.noLabel)').each(function (i, element) {
						var $this = $(this),
							$label = $this.is(':checkbox, :radio') ? $this.parent().siblings('legend') : $('label[for=' + $this.attr('id') + ']', $form);
	
						if (!$.trim($this.attr('id')).length || !$('label[for=' + $this.attr('id') + ']', $form).length) {
							console.error('Please fill in the id-attribute of %o and the for-attribute of the associated label.', $this);
							console.info('If the field doesn\'t need a label, add the class "noLabel" to the Field.');
						}
						else if($label.text().indexOf(marker) === -1) {
							$label.append(' ' + $.trim(marker));
						}
					});
					
					// save $form in settings
					$.smaValidateOptions.currentForm = $form;
	
					// add validate-plugin
					validator = $form.validate($.extend({}, $.smaValidateOptions, options));
				});
			},
		
			/**
			 * returns the validator object
			 */
			getValidator: function () {
				return validator !== null ? validator : false;
			}
		};
	
	/**
	 * @memberOf jQuery
	 * @name smaValidateOptions
	 * @namespace
	 * 
	 * @description
	 * Options for {@link $.fn.smaValidate} (SMA validation plugin). These options extend and override the default options of the <a href="http://docs.jquery.com/Plugins/Validation"jQuery.Validate>jQuery Validation plugin</a>.
	 */
	$.smaValidateOptions = {
			/**
			 * @name errorElement
			 * @fieldOf $.smaValidateOptions
			 * @type String
			 * @default span
			 *
			 * @ignore
			 *
			 * @description
			 * <a href="http://docs.jquery.com/Plugins/Validation/validate#toptions">jQuery Validation Docs</a>
			 */
			errorElement	: 'span',
			/**
			 * @name meta
			 * @fieldOf $.smaValidateOptions
			 * @type String
			 * @default validate
			 *
			 * @description
			 * In case you use metadata for other plugins, too, you want to wrap your validation rules into their own object that can be specified via this option.
			 * <br /><a href="http://docs.jquery.com/Plugins/Validation/validate#toptions">jQuery Validation Docs</a>
			 */
			meta			: 'validate',
			/**
			 *
			 */
			ieFormClass		: 'ie',
			ieErrorClass	: 'ieError',
			/**
			 * Send Form via Ajax
			 *
			 * @requires jquery.form.plugin.js ($.fn.ajaxSubmit)
			 */
			ajaxSubmit		: false,
			ajaxSubmitClass	: 'ajaxSubmit',
			/**
			 * container for ajax messages
			 */
			ajaxSubmitTarget: false,
			
			/**
			 * ASP.NET flag
			 */
			isAspDotNet: false,
			
			/**
			 * Only Fields in this Selecor/Object will be validated
			 */
			fieldset: null,
			$form	: null,

			/**
			 *
			 *
			 */
			ajaxSubmitTargetClasses			: 'messageBox eight-col last-col',
			ajaxSubmitTargetErrorClass		: 'warning',
			ajaxSubmitTargetSuccessClass	: 'note success',
			
			ajaxSubmitOnSucess	: function (responseText, statusText, xhr, $form) {
				var data = $form.data('validator'),
					statusToClass = function (status) {
						var klass;
						
						switch (status) {
							default:
							case 'error':
								klass = data.settings.ajaxSubmitTargetErrorClass;
								break;
							
							case 'success':
								klass = data.settings.ajaxSubmitTargetSuccessClass;
								break;
						};
						
						return klass;
					},
					$message;
					
				// create a target if nesessary
				if (!data.settings.ajaxSubmitTarget) {
					
					targetSelector = 'formTarget' + Math.round(Math.random() * 1000000);
					$message = $('<div />', {
						id			: targetSelector,
						className	: data.settings.ajaxSubmitTargetClasses
					}).insertBefore($form);
				}
				else {
					$message = $(data.settings.ajaxSubmitTarget);
				}
				
				// style message
				$message
					.addClass(statusToClass(statusText))
					.hide()
					.html('<p><strong>' + responseText + '</strong></p>')
					.stop(true, true)
					.slideDown();
			},
			/**
			 *
			 */
			tooltipIcon		: true,
			tipTipOpts		: {
				position	: 'top left',
				delay		: 0,
				activation	: 'hover focus',
				edgeOffset	: '0 15'
			},
			/**
			 * @name errorPlacement
			 * @methodOf $.smaValidateOptions
			 *
			 * @ignore
			 *
			 * @description
			 * Customize placement of created error labels.
			 *
			 * @param {jQuery-Object} error The created error label as a jQuery object
			 * @param {jQuery-Object} placement The invalid element as a jQuery object.
			 */
			errorPlacement	: function (error, placement) {
				var $form	= $(this.currentForm),
					data	= $form.data('validator'),
					$insertInlineErrorAfter = false,
					$tooltipHolder = placement.data('tooltipHolder') || placement;

				// init element events
				if (!placement.data('hasValidationEvents')) {
				
					// add Tooltip-Support for checkbox/radio-groups
					if (placement.is(':checkbox, :radio')) {
						$tooltipHolder = false;
						$insertInlineErrorAfter = placement.parent().siblings('legend:first');

						// wrong (fallback): // check if container has more than one checkbox/radio children
						if ((placement.is(':' + placement[0].type) && placement.closest('.' + placement[0].type).siblings('.' + placement[0].type).length) && !(placement.parents('.checkboxgroup, .radiogroup').length)) {
							console.error('The container of the field %s > %s lacks the class "%sgroup"', placement.parent().siblings('legend:first').text(), $('[for=' + placement[0].id + ']').text(), placement[0].type);

							// fix: add group-class
							console.info('I will add the class "%s" temporarily!', placement[0].type + 'group');
							placement.closest('.' + placement[0].type).parent().addClass(placement[0].type + 'group');
						}
					}
					/* 
					 * wrap container around input fields 
					 * because of ie < 9 not supporting css outline property
					 */
					else if ($.browser.msie && parseInt($.browser.version, 10) <= 8) {
						placement.wrap('<div class="' + this.ieErrorClass + '"></div>');
					}

					// set or remove tooltipHolder
					if ($tooltipHolder) {
						placement.data('tooltipHolder', $tooltipHolder);
					}
					else {
						placement.removeData('tooltipHolder');
					}
					// set flag
					placement.data('hasValidationEvents', true);
				}

				// show error via tooltip
				if ($tooltipHolder) {
					if (!$tooltipHolder.data('tipTip')) {
						$tooltipHolder
							.tipTip($.extend({}, {
								content: error.text()
							}, $.smaValidateOptions.tipTipOpts))
							.unbind('mouseleave.tooltip');
					}
					// update text
//					tooltipData = $tooltipHolder.data('tipTip');
					if (error.text().length) {
						$tooltipHolder.tipTip('updateContent', error.text());
						$tooltipHolder.tipTip('updatePosition', $.smaValidateOptions.tipTipOpts.position);
						
						if (placement.is(':focus')) {
							$tooltipHolder.tipTip('open');
						}
					}

					// register and show
					if (data.tooltip === undefined) {
						data.tooltip = $tooltipHolder;
						$form.data('validator', data);
						
						if (error.text().length) {
							$tooltipHolder.tipTip('open');
						}
					}
				}
				// show error via inline element
				else if ($insertInlineErrorAfter) {
					error/*.prepend('<span class="icon"></span>')*/.insertAfter($insertInlineErrorAfter).hide();
				}
			},
			
			success: function (label, element) {
				var $form	= $(this.currentForm),
					forAttr	= label.attr('for'),
					data	= $form.data('validator'),
					$tooltipHolder;

				if (!forAttr) {
					console.error('Please fill in the attributes "id" and "for" of the current field and the associated label.');
				}
				else {
					// remove open tooltip from form data
					delete data.tooltip;
					$form.data('validator', data);

					// destroy DOM
					$tooltipHolder = $('#' + forAttr).data('tooltipHolder');
					if ($tooltipHolder) {
						label.text('');
						$tooltipHolder.tipTip('destroy');
					}
					else {
						if ($(element).is(':checkbox, :radio')) {
							label.siblings('legend').removeClass('error ' + this.ieErrorClass);
						}
						label.stop(true, true).slideUp('fast', function () {
							$(this).text('');
						});
					}
				}
			},
			
			highlight: function (element, errorClass, validClass) {
				var $form		= $(this.currentForm),
					$element	= $(element),
					$additionalElements = $('label[for=' + $element.attr('id') + ']'), // label for single checkboxes
					$error,
					settings = $form.data('validator').settings;
					
				$element.add($additionalElements).addClass(errorClass).removeClass(validClass);
				
				if ($.validator.prototype.checkable(element)) {
					$element.parent().siblings('legend').addClass(errorClass).removeClass(validClass);
					$error = $element.parent().siblings('span.error');
					
					/*if (!$error.find('> span.icon').length) {
						$error.prepend('<span class="icon"></span>');
					}*/
					$error.not(':animated').stop(true, true).slideDown();
				}
				/* 
				 * wrap container around input fields 
				 * because of ie < 9 not supporting css outline property
				*/
				else if ($.browser.msie && parseInt($.browser.version, 10) <= 8 && !$element.parent().hasClass(settings.ieErrorClass)) {
					$element.wrap('<div class="' + settings.ieErrorClass + '"></div>');
				}
				
				$element.closest('div.' + settings.ieErrorClass).addClass(errorClass).removeClass(validClass);
			},
			
			unhighlight: function (element, errorClass, validClass) {
				var $form		= $(this.currentForm),
					data		= $form.data('validator'),
					$element	= $(element),
					$additionalElements = $('label[for=' + $element.attr('id') + ']'),
					$tooltipHolder,
					settings = $form.data('validator').settings;

				//.add($(element).filter(':checkbox, :radio').parent().siblings('legend')); // legend for checkbox/radio-groups
				$element.add($additionalElements).removeClass(errorClass).addClass(validClass);
				
				/* 
				 * unwrap container around input fields 
				 * because of ie < 9 not supporting css outline property
				 */
				if ($.browser.msie && parseInt($.browser.version, 10) <= 8 && $element.parent().hasClass(settings.ieErrorClass)) {
					$element.closest('div.' + settings.ieErrorClass).addClass(validClass).removeClass(errorClass);
				}

				$tooltipHolder = $(element).data('tooltipHolder');
				if ($tooltipHolder && $tooltipHolder.length && $tooltipHolder.data('tipTip')) {
					$tooltipHolder.tipTip('destroy');
				}

				if (data.tooltip) {
					delete data.tooltip;
				}
				$form.data('tooltip', data);
			},
			
			showErrors: function () {
				var error, i;
				
				for (i = 0; this.errorList[i]; i++) {
					error = this.errorList[i];
					this.showLabel(error.element, error.message);
					this.settings.highlight && this.settings.highlight.call(this, error.element, this.settings.errorClass, this.settings.validClass);
				}
				if (this.errorList.length) {
					this.toShow = this.toShow.add(this.containers);
				}
				if (this.settings.success) {
					for (i = 0; this.successList[i]; i++) {
						this.showLabel(this.successList[i]);
					}
				}
				if (this.settings.unhighlight) {
					for (i = 0, elements = this.validElements(); elements[i]; i++) {
						this.settings.unhighlight.call(this, elements[i], this.settings.errorClass, this.settings.validClass);
					}
				}
				this.toHide = this.toHide.not(this.toShow);
				this.hideErrors();
				this.addWrapper(this.toShow);
			},
			
			onfocusout: function (element) {
				$(element).valid();
			},
		
			submitHandler: function (form) {
				var targetSelector;
				
				if (this.settings.ajaxSubmit) {
					if (!$.fn.ajaxSubmit) {
						return $.error('You try to send the form via Ajax. But $.ajaxSubmit is not available.');
					}
					
					$(form).ajaxSubmit({
						success: this.settings.ajaxSubmitOnSucess
					});
				}
				else {
					form.submit();
				}
			}
		};

	/**
	 * Override Validator init
	 * add optional fieldset to validateDelegate.
	 */
	$.validator.prototype.init = function() {
		this.labelContainer = $(this.settings.errorLabelContainer);
		this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm);
		this.containers = $(this.settings.errorContainer).add( this.settings.errorLabelContainer );
		this.submitted = {};
		this.valueCache = {};
		this.pendingRequest = 0;
		this.pending = {};
		this.invalid = {};
		this.reset();
		
		var groups = (this.groups = {});
		$.each(this.settings.groups, function(key, value) {
			$.each(value.split(/\s/), function(index, name) {
				groups[name] = key;
			});
		});
		var rules = this.settings.rules;
		$.each(rules, function(key, value) {
			rules[key] = $.validator.normalizeRule(value);
		});
		
		function delegate(event) {
			var validator = $.data(this[0].form, "validator"),
				eventType = "on" + event.type.replace(/^validate/, "");
			
			validator.settings[eventType] && validator.settings[eventType].call(validator, this[0] );
		}
		
		var $fieldset;
		
		if (this.settings && this.settings.fieldset) {
			
			if (typeof this.settings.fieldset === 'string') {
				$fieldset = $($(this.settings.fieldset), $(this.currentForm));
			
			} else {
				$fieldset = this.settings.fieldset;
			}
		}
		else {
			$fieldset = $(this.currentForm);
		}
		
		$fieldset
			.validateDelegate(":text, :password, :file, select, textarea", "focusin focusout keyup", delegate)
			.validateDelegate(":radio, :checkbox, select, option", "click", delegate);

		if (this.settings.invalidHandler)
			$fieldset.bind("invalid-form.validate", this.settings.invalidHandler);
	};
	
	/**
	 * Override Validator elements
	 * add optional fieldset to validateDelegate.
	 */
	$.validator.prototype.elements = function() {
		var validator = this,
			rulesCache = {},
			$elements = this.settings && this.settings.fieldset ? this.settings.fieldset : this.currentForm;
		
		// selector to jQuery-Object
		if (typeof $elements === 'string') {
			$elements = $($elements, this.currentForm);
			
		} else if ($elements.tagName && $elements.tagName.toLowerCase() === 'form') {
			$elements = $($elements);
		}

		// select all valid inputs inside the form (no submit or reset buttons)
		// workaround $Query([]).add until http://dev.jquery.com/ticket/2114 is solved
		return $([]).add($elements.find('*'))
		.filter(":input")
		.not(":submit, :reset, :image, [disabled]")
		.not( this.settings.ignore )
		.filter(function() {
			!this.name && validator.settings.debug && window.console && console.error( "%o has no name assigned", this);
		
			// select only the first element for each name, and only those with rules specified
			if ( this.name in rulesCache || !validator.objectLength($(this).rules()) )
				return false;
			
			rulesCache[this.name] = true;
			return true;
		});
	};
	
	/**
	 * Override Validator focusInvalid
	 * add scrollTo Element.
	 */
	$.validator.prototype.focusInvalid = function () {
		if (this.settings.focusInvalid) {
			try {
				var $element = $(this.findLastActive() || (this.errorList.length && this.errorList[0].element) || []).filter(':visible');

				$('html,body').animate({
					scrollTop: $element.offset().top - 100
				}, 500, function () {
					$element.focus()
					// manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find
					.trigger('focusin');
				});
			} catch (e) {
				// ignore IE throwing errors when focusing hidden elements
			}
		}
	};

	/** 
	 * Override Validator showLabel
	 */
	$.validator.prototype.showLabel = function (element, message) {
		var label = this.errorsFor(element);
		if (label.length) {
			// refresh error/success class
			label.removeClass().addClass(this.settings.errorClass);

			// check if we have a generated label, replace the message then
			label.attr('generated') && label.html(message);
		}
		else {
			// create label
			label = $('<' + this.settings.errorElement + '/>').attr({
				'for': this.idOrName(element),
				generated: true
			}).addClass(this.settings.errorClass).html(message || '');
			if (this.settings.wrapper) {
				// make sure the element is visible, even in IE
				// actually showing the wrapped element is handled elsewhere
				label = label.hide().show().wrap('<' + this.settings.wrapper + '/>').parent();
			}
			if (!this.labelContainer.append(label).length) {
				this.settings.errorPlacement ? this.settings.errorPlacement(label, $(element)) : label.insertAfter(element);
			}
		}
		if (!message && this.settings.success) {
			//label.attr('generated') && label.text('');
			typeof this.settings.success === 'string' ? label.addClass(this.settings.success) : this.settings.success(label, element);
		}
		this.toShow = this.toShow.add(label);
	};
	
	/**
	 * Reset Form
	 */
	 $.validator.prototype.resetForm = function() {
		var $form	= $(this.currentForm),
			self	= this;
				
		if ( $.fn.resetForm ) {
			$(this.currentForm).resetForm();
		}
		this.submitted = {};
		this.prepareForm();
		this.hideErrors();
		this.elements().removeClass(this.settings.errorClass);
		
		this.elements().each(function (i) {
			var data = $form.data('validator'),
				$element = $(this),
				$tooltipHolder = $element.data('tooltipHolder');
				
			// ie
			if ($.browser.msie && parseInt($.browser.version, 10) <= 8) {
				$('div.' + self.ieErrorClass, $form).removeClass(self.settings.errorClass);
			}
			
			// Destroy Tooltip
			if ($tooltipHolder) {
				$tooltipHolder.tipTip('destroy');
			}
				
			//delete data.tooltip;
			$form.data('tooltip', data);
		});
	};
	
	$.extend($.fn, {
		validateDelegate: function(delegate, type, handler) {
			var $form	= $(this),
				data	= $form.data('validator');

			return this.bind(type, function(event) {
				var target = $(event.target);
				if (target.is(delegate)) {
					return handler.apply(target, arguments);
				}
			});
		}
	});
	 
	/**
	 * Override the stinking build in validate date method.
	 * Accepting dates from 1900 to 2099 in english or german format:
	 * - DD.MM.YYYY
	 * - MM/DD/YYYY
	 */
	$.validator.addMethod('date', function (value, element) {
		return this.optional(element) || /^(0[1-9]|[12]\d|3[01])\.(0[1-9]|1[012])\.(19|20)\d{2}$/.test(value) || /^(0[1-9]|1[012])\/(0[1-9]|[12]\d|3[01])\/(19|20)\d{2}$/.test(value);
	}, 'test');
	
	/**
	 * @memberOf jQuery.fn
	 * @name smaValidate
	 * @namespace
	 * 
	 * @description 
	 * The SMA Validation plugin. Based on <a href="http://docs.jquery.com/Plugins/Validation"jQuery.Validate>jQuery Validation</a>.
	 * <br />Options and examples are listed at {@link $.smaValidateOptions}
	 *
	 * @param {String/Object} method The name of the method or an object with options
	 *
	 * @requires $.Metadata
	 * <br /><a href="http://docs.jquery.com/Plugins/Metadata">http://docs.jquery.com/Plugins/Metadata</a>
	 * <br /><a href="http://github.com/jquery/jquery-metadata">http://github.com/jquery/jquery-metadata</a>
	 *
	 * @see $.smaValidateOptions
	 * @see <a href="http://docs.jquery.com/Plugins/Validation/validate#options">http://docs.jquery.com/Plugins/Validation/validate#options</a>
	 *
	 * @example
	 * $('form').smaValidate();
	 */
	$.fn.smaValidate = function (method) {
		// sma validate methods
		if (methods[method]) {
			return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
		}
		// validation methods
		else if (validator !== null && validator[method]) {
			validator[method].apply(validator, Array.prototype.slice.call( arguments, 1 ));
			return this;
		}
		// init
		else if (typeof method === 'object' || !method) {
			return methods.init.apply(this, arguments);
		}
		else {
			$.error('Method "' +  method + '" doesn\'t exist on jQuery.smaValidate');
		}
	};
}(jQuery));
