(function($, window, document, undefined) {
	$.fn.quicksearch = function (target, opt) {
		
		var timeout, cache, rowcache, jq_results, val = '', e = this, options = $.extend({ 
			delay: 100,
			selector: null,
			stripeRows: null,
			loader: null,
			noResults: '',
			bind: 'keyup',
			onBefore: function () { 
				return;
			},
			onAfter: function () { 
				return;
			},
			show: function () {
				this.style.display = "";
			},
			hide: function () {
				this.style.display = "none";
			},
			prepareQuery: function (val) {
				return val.toLowerCase().split(' ');
			},
			testQuery: function (query, txt, _row) {
				for (var i = 0; i < query.length; i += 1) {
					if (txt.indexOf(query[i]) === -1) {
						return false;
					}
				}
				return true;
			}
		}, opt), prevVal;
		
		this.go = function () {

			if (prevVal) {
				for (var i = 0, len = rowcache.length; i < len; i++) {
					jbUnHighlightNode(rowcache[i]);
				}
			}
			
			var i = 0, 
			noresults = true, 
			query = options.prepareQuery(val),
			val_empty = (val.replace(' ', '').length === 0);
			
			for (var i = 0, len = rowcache.length; i < len; i++) {
				//if (val_empty || options.testQuery(query, cache[i], rowcache[i])) {
				if (!val_empty && options.testQuery(query, cache[i], rowcache[i])) {
					options.show.apply(rowcache[i]);
					noresults = false;
				} else {
					options.hide.apply(rowcache[i]);
				}
			}
			
			//if (noresults) {
			if (noresults && !val_empty) {
				this.results(false);
			} else {
				if (!val_empty) {
					for (var i = 0, len = rowcache.length; i < len; i++) {
						jbHighlightNode(rowcache[i], val);
					}
				}
				prevVal = val;

				this.results(true);
				this.stripe();
			}
			
			this.loader(false);
			options.onAfter();
			
			return this;
		};
		
		this.stripe = function () {
			
			if (typeof options.stripeRows === "object" && options.stripeRows !== null)
			{
				var joined = options.stripeRows.join(' ');
				var stripeRows_length = options.stripeRows.length;
				
				jq_results.not(':hidden').each(function (i) {
					$(this).removeClass(joined).addClass(options.stripeRows[i % stripeRows_length]);
				});
			}
			
			return this;
		};
		
		this.strip_html = function (input) {
			var output = input.replace(new RegExp('<[^<]+\>', 'g'), "");
			output = $.trim(output.toLowerCase());
			return output;
		};
		
		this.results = function (bool) {
			if (typeof options.noResults === "string" && options.noResults !== "") {
				if (bool) {
					$(options.noResults).hide();
				} else {
					$(options.noResults).show();
				}
			}
			return this;
		};
		
		this.loader = function (bool) {
			if (typeof options.loader === "string" && options.loader !== "") {
				 (bool) ? $(options.loader).show() : $(options.loader).hide();
			}
			return this;
		};
		
		this.cache = function () {
			
			jq_results = $(target);
			
			if (typeof options.noResults === "string" && options.noResults !== "") {
				jq_results = jq_results.not(options.noResults);
			}
			
			var t = (typeof options.selector === "string") ? jq_results.find(options.selector) : $(target).not(options.noResults);
			cache = t.map(function () {
				return e.strip_html(this.innerHTML);
			});
			
			rowcache = jq_results.map(function () {
				return this;
			});
			
			return this.go();
		};
		
		this.trigger = function () {
			this.loader(true);
			options.onBefore();
			
			window.clearTimeout(timeout);
			timeout = window.setTimeout(function () {
				e.go();
			}, options.delay);
			
			return this;
		};
		
		this.cache();
		this.results(true);
		this.stripe();
		this.loader(false);
		
		return this.each(function () {
			$(this).bind(options.bind, function () {
				val = $(this).val();
        //if (val.length < 3) return;
				e.trigger();
			});
		});
		
	};


	// highlighting stuff


/**
 * Highlighting
 */
function jbHighlightNode(el, phrase) {

	if (!phrase) return;

	var node = el;
	var lPhrase = String(phrase).toLowerCase();

	var incr = 0;

	if (node.nodeType == 3) {

		var pos = node.data.toLowerCase().indexOf(lPhrase);

		if (pos >= 0) {
			// make the search phrase a separate text node
			var searchTextNode = node.splitText(pos);
			searchTextNode.splitText(phrase.length);
			var clone = searchTextNode.cloneNode(searchTextNode);

			// replace the text node with a span
			var spanNode = document.createElement('span');
			spanNode.className = 'x-highlight';
			spanNode.appendChild(clone);
			searchTextNode.parentNode.replaceChild(spanNode, searchTextNode);

			incr = 1;
		}

	} else {

if ($(node).hasClass('showanswer')) return;

		if (node.nodeType == 1 && node.childNodes) {

			for (var i = 0, len = node.childNodes.length; i < len; i++) {
				i += jbHighlightNode(node.childNodes[i], phrase);
			}
		}

	}

	return incr;
}

function jbUnHighlightNode(el) {
	$('span.x-highlight', el).each(function() {
		var domEl = this;
		var p = domEl.parentNode;
		p.replaceChild(domEl.firstChild, domEl);
		normalizeNode(p);
	});
}

function normalizeNode(node) {
	var chlds = node.childNodes;

	for (var i = 0, len = chlds.length; i < len; i++) {
		var child = chlds[i];

		if (child.nodeType == 1) {
			normalizeNode(child);
			continue;
		}

		// text node
		if (child.nodeType == 3) {
			var next = child.nextSibling;

			if (next && next.nodeType == 3) {
				var newText = child.nodeValue + next.nodeValue;
				var newNode = document.createTextNode(newText);

				node.insertBefore(newNode, child);

				node.removeChild(child);
				node.removeChild(next);

				i--;
				len--;
			}
		}

	}

}

}(jQuery, this, document));



