// todo: wohin die labels im manual-fall kommen

(function($){
	$.fn.astonishinputs = function(settings) {
		// variable-defaults
		var defaults = {
			labelBottomOffset: 5, //px
			labelMove: 0, //px
			speed: 800, //ms
			useInlineBlock: false, //true or false
			inputDownEasing: 'swing',
			inputUpEasing: 'swing',
			labelDownEasing: 'swing',
			labelUpEasing: 'swing',
			excludeInputs: ([]),
			manualInputs: ([]),
			manualInputsHoldLabel: ([])
		};
		// set default-settings if there is no user-entry
		var settings = $.extend(defaults, settings);

		// settings.excludeInputs is an array of input-names to be excluded (it has the syntax "excludeInputs: (['input-name1','input-name2'])" (also possible with more or less than 2 items)). The following line converts settings.excludeInputs to a string, that is used together with other :not-selectors afterwards
		settings['excludeInputs'] = ':not([name='+settings.excludeInputs.join(']):not([name=')+'])';

		// do something similar for settings.manualInputs:
		// first a string to prevent changes in the CSS of inputs wrapped by elements in settings.manualInputs (it is used in an if-query afterwards)
		// An if-query is needed here aswell
		if (settings.manualInputs.length) {
		// if settings.manualInputs was specified some helping-parameters have to be set
			// a string "settings.themanualInputs" has to be created to be called by CSS (it looks like this: ", ManualContainer1 input, ManualContainer2 input" (also possible with more or less than 2 items))
			settings['themanualInputs'] = ', '+settings.manualInputs.join(' input, ')+' input';
			// basically the same as in the line above, but this time there is no input after each element
			settings['themanualInputsParents'] = ', '+settings.manualInputs.join(', ');
		}
		// and now something similar for "settings.manualInputsHoldLabel" (if there are any)
		if (settings.manualInputsHoldLabel.length) {
			// for "settings.manualInputsHoldLabel" the same rules should be applied as for "settings.manualInputs"
			settings['themanualInputs'] = settings.themanualInputs+', '+settings.manualInputsHoldLabel.join(' input, ')+' input';
			settings['themanualInputsParents'] = settings.themanualInputsParents+', '+settings.manualInputsHoldLabel.join(', ')
			// additionally, the Labels of "settings.manualInputsHoldLabel" have to be identified
			settings['manualInputsHoldLabel'] = settings.manualInputsHoldLabel.join(', ');
		}

		return this.each(function() {
			var theobject = $(this);

			// functions used later

			// find the corresponding label
			// find the corresponding label
			function findCorrespondingLabel () {
				// sadly, theinput is not an input, when called from the "manual-loop", therefore this if-clause is needed
				if ($(this)[0].tagName.toLowerCase() != 'input') {
					return $(this).parent().find('label');
				}
				var inputid = $(this).attr('id');
				var inputclass = $(this).attr('class');
				var inputname = $(this).attr('name');
				// first try to find a label with for = input-id
				var theCorrespondingLabel = theobject.find('label[for='+inputid+']');
				// if there is no, try to find a label with for = input-class
				if (theCorrespondingLabel.length == 0) {
					var theCorrespondingLabel = theobject.find('label[for='+inputclass+']');
				}
				// if there is no, try to find a label with for = inputname
				if (theCorrespondingLabel.length == 0) {
					var theCorrespondingLabel = theobject.find('label[for='+inputname+']');
				}
				// return the corresponding label
				return theCorrespondingLabel;
			}

			// now the other way arround - find the corresponding input
			function findCorrespondingInput () {
				// sadly, $(this) is not an input, when called from the "manual-loop", therefore this if-clause is needed
				if ($(this)[0].tagName.toLowerCase() != 'label') {
					return $(this).parent().find('input');
				}
				var labelname = $(this).attr('for');
				// first try to find an input with id = labelname
				var theCorrespondingInput = theobject.find('input[id='+labelname+']');
				// if there is no, try to find an input with class = labelname
				if (theCorrespondingInput.length == 0) {
					var theCorrespondingInput = theobject.find('input[class='+labelname+']');
				}
				// if there is no, try to find a label with name = labelname
				if (theCorrespondingInput.length == 0) {
					var theCorrespondingInput = theobject.find('input[name='+labelname+']');
				}
				// return the corresponding label
				return theCorrespondingInput;
			}

			// get dimensions
			function GetDimensions (theelement,thedimension) {
				return parseInt(theelement.css(thedimension), 10) || 0;
			}
			
			// store the initial margin-top of each element that is moved afterwards in the field "initialMarginTop"
			function StoreInitialMarginTop () {
				$(this).data('initialMarginTop',GetDimensions($(this),'margin-top'));
			}

			// store the initial top of each label that is moved afterwards in the field "initialTop"
			function StoreInitialTop (theelement) {
				theelement.data('initialTop',theelement.position().top);
			}

			// How far should the input move
			function StoreMoveTargetInput (){
				$(this).data('moveTarget',findCorrespondingLabel.call($(this)).offset().top - $(this).offset().top + findCorrespondingLabel.call($(this)).outerHeight(true) + $(this).data('initialMarginTop') + settings.labelBottomOffset - settings.labelMove);
			}

			// How far should the label move
			function StoreMoveTargetLabel (parentdiv){
				if (settings.manualInputsHoldLabel.length && $(this).parents(settings.manualInputsHoldLabel).length){
					$(this).data('moveTarget',$(this).data('initialTop') - parentdiv.data('moveTarget') - settings.labelMove);
				} else {
					$(this).data('moveTarget',$(this).data('initialTop') - settings.labelMove);
				}
			}

			// wrap input with <div class="astonishinputs-div astonishinputs-[input-name]">...</div>
			function WrapInput () {
				var inputname = $(this).attr('name');
				$(this).wrap('<div class="astonishinputs-div astonishinputs-'+inputname+'"></div>');
			}

			// if there is a label with "for" corresponding with an input, get it inside the div in front of the input
			function PutLabelInFrontOfInput (){
				var inputname = $(this).attr('name');
				var parentdiv = theobject.find('div.astonishinputs-'+inputname);
				findCorrespondingLabel.call($(this)).prependTo(parentdiv);
			}

			// set essential css-rules
			function SetCssRule (property,value){
				var cssrule = {};
				cssrule[property] = value;
				$(this).css(cssrule);
			}

			// calculating the labels position (left) according to the corresponding inputs position aswell as padding and border left
			function CalcLabelHomePosLeft (){
				var theCorrespondingInput = findCorrespondingInput.call($(this));
				// Getting the corresponding inputs position
				var theposition = theCorrespondingInput.position();
				// Getting the corresponding inputs left margin
				var themarginleft = GetDimensions(theCorrespondingInput,'margin-left');
				// Getting the corresponding inputs left padding
				var thepaddingleft = GetDimensions(theCorrespondingInput,'padding-left');
				// Getting the corresponding inputs left border
				var theborderleft = GetDimensions(theCorrespondingInput,'border-left-width');
				// Calculating the position and return the results
				return (theposition.left + themarginleft + thepaddingleft + theborderleft);
			}

			// calculating the labels position (top) according to the corresponding inputs border-top
			function CalcLabelHomePosTop (){
				var theCorrespondingInput = findCorrespondingInput.call($(this));
				// Getting the corresponding inputs position
				var thelabelposition = $(this).position();
				// Getting the corresponding inputs top margin
				var themargintop = GetDimensions(theCorrespondingInput,'margin-top');
				// Getting the corresponding inputs left border
				var thebordertop = GetDimensions(theCorrespondingInput,'border-top-width');
				// Calculating the position and return the results
				return (thelabelposition.top + themargintop + thebordertop);
			}

			// setting the labels home position
			function SetLabelHomePos (){
				$(this).css({
					'left': CalcLabelHomePosLeft.call($(this)), // Calculate left offset
					'top': CalcLabelHomePosTop.call($(this)) // Calculate top offset
				});
			}

			function Moving (theelement,thecss,thetarget,thespeed,theeasing,cursorCssValue) {
				var csstarget = {};
				csstarget[thecss] = thetarget;
				if (cursorCssValue) {
					var docallback = SetCssRule(theelement,'cursor',cursorCssValue);
				}
				theelement.stop().animate(
					csstarget, thespeed,
					theeasing,
					docallback
				);
			}
				
			// executing functions
			// functions executed when the document is loaded
			// When the document is loaded, do WrapInputs and PutLabelInFrontOfInputs for all inputs
			$(theobject).find('input:not(:submit):not(:reset):not(:button):not(:checkbox):not(:radio):not(:file):not(:image):not(:hidden):not([class=notastonishinputs])'+settings.excludeInputs).each(function(){
				// If the Input is wrapped with a style element of class "manualastonishinputs" or another class/ID specified in settings.manualInputsNoCss no changes are made in the css code
				if (!($(this).parents('.manualastonishinputs,'+settings.themanualInputsParents).length)) {
					StoreInitialMarginTop.call($(this));
					WrapInput.call($(this));
					PutLabelInFrontOfInput.call($(this));
					SetCssRule.call($(this),'position','relative'); // all Inputs need position: relative;
				}
			});

			$(theobject).find('div.astonishinputs-div').each(function(){
				if(settings.useInlineBlock == true){
					SetCssRule.call($(this),'display','inline-block'); // the divs need display: inline-Block (older Browsers don't support that (up to IE7))
				} // per default, this option is unused
				SetCssRule.call($(this),'position','relative'); // the divs also need position: relative;
			});

			$(theobject).find('div.astonishinputs-div label').each(function(){
				SetCssRule.call($(this),'position','absolute'); // the Labels need position: absolute;
				SetCssRule.call($(this),'zIndex',99); // the Labels should be in the front
				SetLabelHomePos.call($(this));
				StoreInitialTop($(this));
				SetCssRule.call($(this),'cursor','text');
			});

			$(theobject).find('div.astonishinputs-div input').each(function(){
				StoreMoveTargetInput.call($(this));
			});

			$(theobject).find('div.astonishinputs-div label').each(function(){
				StoreMoveTargetLabel.call($(this));
			});

			// functions that should run always (no need to exclude anything here as all exclusions have taken place before)
			$(theobject).find('div.astonishinputs-div input').each(function(){
				//when there is something inside the input
				if($(this).val() !== ''){
					Moving ($(this),'marginTop',$(this).data('moveTarget'),0,settings.inputDownEasing);
					Moving(findCorrespondingLabel.call($(this)),'top',findCorrespondingLabel.call($(this)).data('moveTarget'),0,settings.labelUpEasing,'default');
				}
				//on Focus
				$(this).focus(function(){
					Moving ($(this),'marginTop',$(this).data('moveTarget'),settings.speed,settings.inputDownEasing);
					Moving(findCorrespondingLabel.call($(this)),'top',findCorrespondingLabel.call($(this)).data('moveTarget'),settings.speed,settings.labelUpEasing,'default');
				});
				//on Blur
				$(this).blur(function(){
					var inputvalue = $(this).val();
					if(inputvalue == ''){
						Moving ($(this),'marginTop',$(this).data('initialMarginTop'),settings.speed,settings.inputUpEasing);
						Moving(findCorrespondingLabel.call($(this)),'top',findCorrespondingLabel.call($(this)).data('initialTop'),settings.speed,settings.labelDownEasing,'text');
					}
				});
			});

			// now we go for all inputs wrapped in manual wrappers
			$(theobject).find('.manualastonishinputs input'+settings.themanualInputs+':not(:submit):not(:reset):not(:button):not(:checkbox):not(:radio):not(:file):not(:image):not(:hidden):not([class=notastonishinputs])'+settings.excludeInputs).each(function(){
				// Variables to make life easier
				var contentnotlabel = $(this).parents('.manualastonishinputs'+settings.themanualInputsParents).children(':not(label)');
				var contentjustlabel = $(this).parents('.manualastonishinputs'+settings.themanualInputsParents).find('label');
				// again, store the initial margin-top of each element that is moved afterwards in the array "initialMarginTop"
				StoreInitialMarginTop.call(contentnotlabel);
				StoreInitialTop(contentjustlabel);
				SetCssRule.call(contentjustlabel,'cursor','text');
				StoreMoveTargetInput.call(contentnotlabel);
				StoreMoveTargetLabel.call(contentjustlabel,contentnotlabel);
				//when there is something inside the input
				if($(this).val() !== ''){
					//:not(label) is added in the following animation-functions as moving the input and keeping the label in position is the intention of this script
					Moving (contentnotlabel,'marginTop',contentnotlabel.data('moveTarget'),0,settings.inputDownEasing);
					Moving(contentjustlabel,'top',contentjustlabel.data('moveTarget'),0,settings.labelUpEasing,'default');
				}
				//on Focus
				$(this).focus(function(){
					Moving (contentnotlabel,'marginTop',contentnotlabel.data('moveTarget'),settings.speed,settings.inputDownEasing);
					Moving(contentjustlabel,'top',contentjustlabel.data('moveTarget'),settings.speed,settings.labelUpEasing,'default');
				});
				//on Blur
				$(this).blur(function(){
					var inputvalue = $(this).val();
					if(inputvalue == ''){
						Moving (contentnotlabel,'marginTop',contentnotlabel.data('initialMarginTop'),settings.speed,settings.inputUpEasing);
						Moving(contentjustlabel,'top',contentjustlabel.data('initialTop'),settings.speed,settings.labelDownEasing,'text');
					}
				});
			});
		});
	};
})(jQuery);
