(function($) {
	$.fn.more = function(settings) {
		var config = {
			element 		: 	'li',
			limit			:	6,
			moreText 		:	'More +',
			lessText		:	'Less -',
			linkClass		:	'', // additional class(es) for the links
			elementWrapper	:	'',  // wrap the hidden elements in any text compatible with the jQuery .wrapAll with a wrappingElement - the whole new structure will be hidden
			mode			:	'element', // valid values: element, text - whether to search the parent element for the child elements defined in config.element or for text
										   // maintains all html in the children 
			click			:	''		   // function that is executed on when the show/hide link is clicked
		};
		
		if (settings) {
			$.extend(config,settings);
		}
		
		var linkCss = {
				cursor	: 'pointer'
		};
		
		$('.more-Link').live('mouseover',function(event){
			$(event.target).addClass('more-LinkHover');
		});
		
		$('.more-Link').live('mouseout',function(event){
			$(event.target).removeClass('more-LinkHover');
		});
		
		var og_selector = this.selector;
		var link = null;
		this.each(function(selectorIndex){
			if (config.mode == 'element') {
				var elementCount = $(this).children(config.element).length;
				if (elementCount > config.limit) {
					var cutoffElement = $(this).children(config.element + ':eq(' + (config.limit - 1) + ')');
					if (config.elementWrapper) {
						var wrapper = $(config.elementWrapper);
						var firstWrapper = wrapper.find('*:eq(0)').parent();
						firstWrapper.addClass('more-wrapperElement');
						$(this).children(config.element + ':gt(' + (config.limit - 1) + ')')
							.wrapAll(firstWrapper);
						$(this).find('.more-wrapperElement').hide();
					} else {
						$(this).children(config.element + ':gt(' + (config.limit - 1) + ')')
							.css('display','none');
					}
					link = cutoffElement.clone().removeClass();
				}
			} else if (config.mode == 'text') {
				var text = $(this).text();
				if  (text.length > config.limit) {
					cutoffText = text.substring(0,config.limit);
					myCutoff = locateTextCutoff(cutoffText,'',$(this).contents());
					cutoffNode = myCutoff.node;
					cutoffString = myCutoff.text;
					cutoffNodeIndex = myCutoff.index;
					cutoffFound = false;
					hideMe = $('<span class="more-wrapperElement"></span>');
					cutoffNodeText = cutoffNode.text();
					var foundNode = $();
					var oldestParent = null;
					nextSiblings = new Array();
					checkcheck = $(og_selector + ':eq(' +  selectorIndex + ')');
					
					if (cutoffNode[0] !== checkcheck[0]) {
						$(':contains(' +  cutoffString + '):last',this).each(function(index,element){
							foundNodeText = $(this).text();
							if (foundNodeText == cutoffNodeText) {
								foundNode = $(this);
								cutText = foundNode.contents().filter(function(){
									if (this.nodeType == 3) {
										return this;
									}
								});
								
								if (foundNodeText.length == 1) {
									hidden = foundNode.clone();
								} else {
									hidden = foundNode.clone();
									hidden.html($(cutText).text().substring(cutoffString.length));
									foundNode.children().appendTo(hidden);
								}
								foundNode.parentsUntil(og_selector + ':eq(' + selectorIndex + ')').each(function(){
									hidden = $(this).clone().html('').append(hidden);
									oldestParent = $(this);
								});
	
								foundNode.html(cutoffString);
								hidden.appendTo(hideMe);
							}
							
						});
						
						if (!$.isEmptyObject(oldestParent)) {
							foundNode = oldestParent;
						}
						
						siblingAfter = false;
						$(this).contents().each(function(index,element){
							if (siblingAfter) {
								nextSiblings.push($(this));
							}
							
							if ($(this).text() == foundNode.text()) {
								siblingAfter = true;
							}
						});
					} else {
						$(this).contents().each(function(index){
							if (index == cutoffNodeIndex) {
								cutText = $(this).text().substring(cutoffString.length);
								newText = $(this).text().substring(0,cutoffString.length);
								if (index == 0) {
									$(this).parent().html(newText);
								} else {
									$(this).text(newText);
								}
								hideMe.append(cutText);
							}
							if(index > cutoffNodeIndex) {
								nextSiblings.push($(this));
							}
						});
					}

					$.each(nextSiblings,function(index,element){
						this.appendTo(hideMe);
					});
					
					hideMe.css('display','none');
					$(this).data('state','hidden');
					$(this).append(hideMe);
					link = $('<span></span>');
				}
			}
		});
		
		if (link !== null) {
			link.addClass('more-Link more-displayLink ' + config.linkClass)
				.html(config.moreText)
				.css(linkCss)
				.bind('click',function(event){
					target = $(event.target);
					theParent = target.parent();
					if (config.elementWrapper || config.mode == 'text') {
						subject = theParent.find('.more-wrapperElement');
					} else {
						subject = theParent.children(config.element + ':gt(' + (config.limit - 1) + ')');
					}
					
					if (target.hasClass('more-displayLink')) {
						subject.show();
						target.html(config.lessText)
							.removeClass('more-displayLink')
							.addClass('more-hideLink');
						theParent.data('state','displayed');
						
					} else if (target.hasClass('more-hideLink')) {
						subject.hide();
						target.html(config.moreText)
							.removeClass('more-hideLink')
							.addClass('more-displayLink');
						target.show();
						theParent.data('state','hidden');
					}
					
					if (config.click) {
						config.click(parent);
					}
				})
				.appendTo(this);
		}
		
		return this;
		
	};
	
	function locateTextCutoff(flatText,matchString,contentsList) {
		contentsList.each(function(index,element){
			if (this.nodeType == 3) {
				tempMatchString = matchString + $(this).text();
				if (tempMatchString.length >= flatText.length) {
					ogMatchStringLen = matchString.length;
					matchString = tempMatchString.substring(0,(flatText.length));
					if (matchString == flatText) {
						response = {
								text : matchString.substring(ogMatchStringLen),
								node : $(this).parent(),
								index : index
						};
						return false;
					} else {
						response = false;
					}
				} else {
					matchString = matchString + $(this).text();
					response = false;
				}
			} else {
				response = locateTextCutoff(flatText,matchString,$(this).contents());
				matchString = matchString + $(this).text();
				if (response !== false) {
					return false;
				}
			}
		});
		return response;
	};
})(jQuery);
