(function($, window, undefined) {
	//regex
	var rtextshadow = /([\d+.\-]+[a-z%]*)\s+([\d+.\-]+[a-z%]*)(?:\s+([\d+.\-]+[a-z%]*))?(?:\s+(#[\da-f]{3,6}|(?:rgb|hsl)a?\(.*?\)))?(?:\s*,)?/g,
		rcolorvalues = /(rgb|hsl)a?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\.\d]+))?/,
		filter = "progid:DXImageTransform.Microsoft.";
	
	// create a plugin
	$.fn.textshadow = function(value, options) {
		var matches, values = [], x, y, blur, color, opacity;
		do {
			matches = rtextshadow.exec(value);
			if (!matches) {
				break;
			}
			
			// capture the values
			x = parseFloat(matches[1]); // TODO: handle units
			y = parseFloat(matches[2]); // TODO: handle units
			blur = matches[3] !== undefined ? parseFloat(matches[3]) : 0; // TODO: handle units
			color = matches[4] !== undefined ? toHex(matches[4]) : 'inherit';
			opacity = getAlpha(matches[4]);
			values.push([x, y, blur, color, opacity]);
		} while (matches)
		
		values.reverse(); // reverse the array
		
		// loop the found items
		return this.each(function() {
			var $elem = $(this);
			
			// create all of the elements				
			$.each(allWords(this), function() {replaceWord(this, values.length)});
			
			$.each(values, function(i) {
				var $copy = $elem.find('.ui-text-shadow-copy:nth-child(' + (i + 2) + ')'),
					x = this[0],
					y = this[1],
					blur = this[2],
					color = this[3],
					opacity = this[4];
								
				// style the elements
				$copy.css({
					color: color,
					left: (x - blur) + 'px',
					top: (y - blur) + 'px'
				});
				
				$copy.each(function() {
					var copy = this;
					
					// try to prevent selection
					copy.unselectable = "on";
					copy.onselectstart = function(){return false;};
					
					// add in the filters
					copy.style.filter = [
						filter + "Alpha(",
							"opacity=" + parseInt(opacity * 100, 10),
						") ",
						filter + "Blur(",
							"pixelRadius=" + blur,
						")"
					].join('');
				});
			});
			
		});
	};
	
	function replaceWord(text, copies) {
		if (!text.parentNode) { // IE 9
			return;
		}
		var copy = '',
			i = 0;
		
		while (i < copies) {
			copy += '<span class="ui-text-shadow-copy">' + text.nodeValue + '</span>';
			i++;
		}
		
		$(text).replaceWith([
			'<span class="ui-text-shadow">',
				'<span class="ui-text-shadow-original">',
					text.nodeValue,
				'</span>',
				copy,
			'</span>'
		].join(''));
	}

	function allWords(elem, otherwords) {
		var words = otherwords || [];
		$(elem).contents().each(function() {
			var $elem = $(this);
			if (this.nodeType === 3 && this.data) {
				words = makeWords(this, words);
			} else if (this.nodeType === 1 && 
					!$elem.hasClass('ui-text-shadow') &&
					!$elem.hasClass('ui-text-shadow-original') &&
					!$elem.hasClass('ui-text-shadow-copy')
				) {
				words = allWords(this, words);
			}
		});
		return words;
	}
	
	function makeWords(textNode, otherwords) {
		var words = otherwords || [],
			split = textNode.nodeValue.split(/\s/),
			text = textNode, length;
		words.push(textNode);
		$.each(split, function() {
			length = this.length;
			if (!length) { // IE 9
				return false;
			}
			text = text.splitText(length + (/\s/.test(text.nodeValue.charAt(length)) ? 1 : 0));
			words.push(text);
		});
		return words;
	}
				
	// http://haacked.com/archive/2009/12/29/convert-rgb-to-hex.aspx
	function toHex(color) {
		// handle rgb
		var matches = rcolorvalues.exec(color), rgb;
		
		// handle hsl
		if (matches && matches[1] === 'hsla') {
			rgb = hsl2rgb(matches[2], matches[3], matches[4]);
			matches[2] = rgb[0];
			matches[3] = rgb[1];
			matches[4] = rgb[2];
		}
		
		// convert to hex
		return matches ? '#' + (1 << 24 | matches[2] << 16 | matches[3] << 8 | matches[4]).toString(16).substr(1) : color;
	}
	
	function getAlpha(color) {
		var matches = rcolorvalues.exec(color);
		if (matches) {
			return matches[5] !== undefined ? matches[5] : 1; 
		}
		return 1;
	}
	
	// http://www.codingforums.com/showthread.php?t=11156
	function hsl2rgb(h, s, l) {
		var m1, m2, hue, r, g, b;
		s /=100;
		l /= 100;
		if (s === 0) {
			r = g = b = (l * 255);
		} else {
			if (l <= 0.5) {
				m2 = l * (s + 1);
			} else {
				m2 = l + s - l * s;
			}
			m1 = l * 2 - m2;
			hue = h / 360;
			r = hue2rgb(m1, m2, hue + 1/3);
			g = hue2rgb(m1, m2, hue);
			b = hue2rgb(m1, m2, hue - 1/3);
		}
		return [r, g, b];
	}
	
	function hue2rgb(m1, m2, hue) {
		var v;
		if (hue < 0) {
			hue += 1;
		} else if (hue > 1) {
			hue -= 1;
		}

		if (6 * hue < 1) {
			v = m1 + (m2 - m1) * hue * 6;
		} else if (2 * hue < 1) {
			v = m2;
		} else if (3 * hue < 2) {
			v = m1 + (m2 - m1) * (2/3 - hue) * 6;
		} else {
			v = m1;
		}

		return 255 * v;
	}
})(jQuery, this);
