!function($){

	var supported = null,
	    supportsSubpixelSpacing = null;
	
	// erstellt objekt mit den werten des
	// eingangsarrays als namen der schlüssel
	function flip(arr) {
		var newarr = {};
		for (var i in arr) {
			if (arr[i])
				newarr[arr[i]] = 0;
		}
		return newarr;
	}
	
	var _inlineElements = flip( (
	'a,abbr,acronym,b,bdo,big,button,cite,code,del,dfn,em,' +
	'i,ins,input,label,map,kbd,object,q,ruby,samp,select,' +
	'small,span,strong,sub,sup,textarea,tt,var'
	).toUpperCase().split(','));


	$.fn.autoJustify = function() {

		if ( !supported ) {
			// browser doesn't support CSS-property 'letter-spacing'
			if (console && console.log)
				console.log('"letter-spacing" wird vom browser nicht unterstützt.');
			return this;
		}

		$( this ).each(function() {
			var container = this,
					elements = $( container ).children(),
			    measure = $( '<span>l</span><br />' ),
			    lineHeight = measure.prependTo( container )[0].offsetHeight;
			
			$(container).css('text-align', 'left');
			
			measure.css({
				/*whiteSpace: 'nowrap !important',
				overflow: 'visible !important',*/
				color: 'transparent !important',
				background: 'none transparent !important'
			});
			
			function justifyChildNodes( elem ) {
				// elem als basisknoten
				// alle child nodes von elem durchgehen
				// wenn child node text ist, ersetzen
				// andernfalls diese funktion aufrufen
				
				for ( var i = 0; i < elem.childNodes.length; i++ ) {
					var child = elem.childNodes[i];
					
					if ( child == measure[0] ) {
						// Platzhalter überspringen
						continue;
					}
					if ( child.nodeType == 1 && !( child.nodeName in { IMG:0, HEAD:0, SCRIPT:0, META:0 } ) ) {
						// verschachteltes kindelement
						justifyChildNodes( child );
					}	
					else if (child.nodeType == 3) {
						// Text-Knoten -> verarbeiten
						var textLines = child.nodeValue
							.replace(/\xA0/g, ' ')
							.replace(/\ \ +/g, ' ')
							.split( '\n' )
							//.filter(function(t){ return !!t; })	// leere zeilen entfernen
							.map(function(t){ return t.trim() });	// whitespace links und rechts entfernen
						
						// newline vor oder nach dem Text entfernen
						if (!textLines[0])
							textLines.shift();
						if (!textLines[textLines.length-1])
							textLines.pop();
						
						for ( var j = 0; j < textLines.length; j++ ) {
							// zeichenabstände für jede textzeile berechnen
							var line = textLines[j];
							var span = document.createElement('span');
							span.appendChild(document.createTextNode(line));
							
							if (line) {
								/******* hier findet die tatsächliche Berechnung statt *******/
								var step = 256,
								    spacing = 0;
								measure.css('letter-spacing', '');
								measure.text(line);

								while (step >= 0.005) {
									// ausprobieren, ob der zeichenabstand um (step) erhöht werden kann
									measure.css('letter-spacing', (-128 + spacing + step) + 'px');
									if (measure[0].offsetHeight <= lineHeight)
										spacing += step;
									// in immer feineren schritten versuchen
									step /= 2;
								}
								spacing = -128 + spacing;
									
								/* berechnung, ende */

								// gültigen Wert gefunden
								span.style.letterSpacing = spacing + 'px';
							}
						
							elem.insertBefore(span, child);
							elem.insertBefore(document.createElement('br'), child);
						}

						// textknoten aus DOM entfernen
						elem.removeChild(child);
						
						// Zähler korrigieren
						i += textLines.length*2 - 1;
					}
				}
				
			
			}
	
			// element unsichtbar? das element und alle elternknoten anzeigen
			if (!this.offsetHeight) {
				var node = this;
				while (node.parentNode && !node.offsetHeight && node.nodeName != 'BODY') {
					node.oldDisplay = node.style.display;
					node.style.display = (node.nodeName in _inlineElements) ? 'inline' : 'block';
					node = node.parentNode;
				}
				var visibleParent = node;
				lineHeight = measure[0].offsetHeight;

				justifyChildNodes( this );

				// elternelemente wieder ausblenden, wenn sie das vorher waren
				node = this;
				while (node.parentNode && node != visibleParent) {
					node.style.display = node.oldDisplay;
					node = node.parentNode;
				}
			}
			else
				justifyChildNodes( this );		
			
			measure.remove();
		});
	
		return this;
	};
	
	$.autoJustify = function(el) { return $(el).autoJustify(); };
	$.fn.autoJustify.browserSupport = $.autoJustify.browserSupport = false;

	$(function(){
		// DOMReady: check browser feature support
	
		// check if letter-spacing is supported
		var el = document.createElement( 'div' );
		supported = 'letterSpacing' in document.createElement( 'div' ).style;
		el = null;
		
		// check if letter-spacing can use sub-pixel values
		// (webkit only rounds to nearest pixel values)
		// otherwise use margin-right all couple of words instead
		var tester = $('<span>XXXXXXXXXX</span>'),
				widthBefore, widthAfter;
		
		// check if letter-spacing is supported
		supported = 'letterSpacing' in tester[0].style;
		
		if (supported) {
			tester.css({
				display: 'inline',
				visibility: 'visible',
				'letter-spacing': '0.2px',
				overflow: 'visible',
				opacity: 0.01
			})
			.appendTo('body');
			
			widthBefore = tester[0].offsetWidth;
			tester.css('letter-spacing', '0.25px');
			widthAfter = tester[0].offsetWidth;
			
			tester.remove();
			tester = null;
			
			// finally set result
			$.fn.autoJustify.browserSupport =
				$.autoJustify.browserSupport =
				supportsSubpixelSpacing = (widthBefore != widthAfter);
		}
	});
	
	
	
	$.fn.autoJustifyCufon = function(targetWidth) {
	
		this.each(function(){
			
			var elem = $(this),
			    oldstyle = elem.attr('style');
			elem.css('width', 5000);
			
			$(this).children('span').each(function(){
				
				// this = <span> mit einer Zeile cufon text (bereits mit word-spacing)
				
				var wasInvisible, visibleParent;
				// element unsichtbar? das element und alle elternknoten anzeigen
				if (wasInvisible = !this.offsetHeight) {
					var node = this;
					while (node.parentNode && !node.offsetHeight && node.nodeName != 'BODY') {
						node.oldDisplay = node.style.display;
						node.style.display = (node.nodeName in _inlineElements) ? 'inline' : 'block';
						node = node.parentNode;
					}
					visibleParent = node;
				}

				var spaceNeeded,
						$this = $(this),
						words = $this.children(),
						wordCount = words.length,
						wordSpacing = 0,
						i;
						
				if (!targetWidth)
					// zielbreite automatisch bestimmen
					targetWidth = $this.parent().width();
				
				// vorheriges autoJustify deaktivieren
				words.css('margin-left', '');
				
				spaceNeeded = targetWidth - $(this).width();
				
				if (wordCount > 1 && spaceNeeded != 0) {
					// do the magic
					for (i = 1; i < wordCount; i++) {
						wordSpacing += spaceNeeded / (wordCount - 1);
						if (wordSpacing >= 1 || wordSpacing <= -1) {
							words.eq(i).css('margin-left', (wordSpacing-(wordSpacing%1)) + 'px');
							wordSpacing %= 1;
						}
						else
							words.eq(i).css('margin-left', '');
					}
				}

				if (wasInvisible) {
					// elternelemente wieder ausblenden, wenn sie das vorher waren
					node = this;
					while (node.parentNode && node != visibleParent) {
						node.style.display = node.oldDisplay;
						node = node.parentNode;
					}
				}

			});
			
			if (oldstyle)
				elem.attr('style', oldstyle);
			else
				elem.removeAttr('style');
		});
	}
	
}(jQuery);
