jQuery.fn.LiveSearch = function(options) {
	var o = jQuery.extend({
			popupClass: 'popup',
			loadingClass: 'loading',
			helpClass: 'bubble',
			itemClass: 'item',
			messageClass: 'message',
			url: '',
			type: 'POST',
			submitParamsCallback: null,
			afterSelectCallback: null,
			autoSearch: false,
			minLength: 3
		}, options);
	return this.each(function() {
		//	only works with text INPUT
		if (!jQuery(this).is(':text')) {
			return;
		}
		//	require:
		//	- Triggerable for dropdown behavior
		if (!jQuery.fn.Triggerable || !jQuery.fn.HelpBubble) {
			return;
		}
		var subject = this;
		subject.$livesearch = {
			options: o,
			value: subject.value,
			data: null,
			popup: jQuery('<div style="position:absolute"></div>').insertAfter(subject).addClass(o.popupClass),
			update: function() {
				var params = subject.$livesearch.options.submitParamsCallback instanceof Function?
					subject.$livesearch.options.submitParamsCallback.apply(subject) : {value: subject.$livesearch.value};
				if (params == false) {
					return;
				}
				jQuery(subject).addClass(o.loadingClass);
				jQuery.ajax({
					type: subject.$livesearch.options.type,
					url: subject.$livesearch.options.url,
					data: params,
					dataType: 'xml',
					success: function(data) {
						jQuery(subject).removeClass(subject.$livesearch.options.loadingClass);
						subject.$livesearch.data = data;
						subject.$livesearch.draw();
					}
				});
			},
			draw: function() {
				subject.$livesearch.popup.empty();
				if (subject.$livesearch.data == null) {
					jQuery('<div>empty</div>')
						.addClass(subject.$livesearch.options.messageClass)
						.appendTo(subject.$livesearch.popup);
				} else {
					var ul = jQuery('<ul></ul>')
						.addClass('inner')
						.appendTo(subject.$livesearch.popup);
					var response = jQuery('livesearch', subject.$livesearch.data);
					var status = response.attr('status');
					var total = response.attr('total');
					var nodes = jQuery('item', subject.$livesearch.data);
					nodes.each(function() {
						var data = {
							text: jQuery('text', this).text(),
							value: jQuery('value', this).text(),
							detail: jQuery('detail > di', this),
							cue: jQuery('cue', this).text()
						};
						
						if (data.cue.length != 0) {
							var detail_popup = jQuery('<div></div>').html(data.cue);
						} else {
							var detail_popup = jQuery('<div></div>');
							var detail_popup_table = jQuery('<table></table>').appendTo(detail_popup);
							data.detail.each(function() {
								detail_popup_table.append('<tr><th>'+jQuery(this).attr('name')+'</th><td>'+jQuery(this).text()+'</td></tr>');
							});
						}

						var li = jQuery('<li></li>')
							.addClass(subject.$livesearch.options.itemClass)
							.append('<span>'+data.text+'</span>')
							.attr('data', data.value)
							.bind('click', function() {
								if (subject.$livesearch.options.afterSelectCallback instanceof Function) {
									subject.$livesearch.options.afterSelectCallback.apply(subject, [data]);
								} else {
									subject.value = data.value;
								}
								subject.focus();
								jQuery(subject).triggerCollapse();
							})
							.HelpBubble(detail_popup, {delay: 500, className: subject.$livesearch.options.helpClass})
							.appendTo(ul);
					});
					var text = total + (total > 1? ' results' : ' result');
					if(status == 'truncated') {
						text = '(truncated) ' + text;
					}
					var statusbar = jQuery('<div></div>')
						.addClass(subject.$livesearch.options.messageClass)
						.text(text)
						.appendTo(subject.$livesearch.popup);
					jQuery(subject).triggerExpand();
				}
			}
		};

		subject.$livesearch.draw();

		jQuery(subject)
			.bind('keypress', function(e) {
				if (e.keyCode == 13) {
					if (jQuery(subject).val().length > 0) {
						subject.$livesearch.value = subject.value;
						subject.$livesearch.timer = subject.$livesearch.update();
					}
					return false;
				}
			})
			.bind('keyup', function() {
				if (subject.$livesearch.options.autoSearch == true && jQuery(subject).val().length >= subject.$livesearch.options.minLength) {
					subject.$livesearch.value = subject.value;
					subject.$livesearch.timer = subject.$livesearch.update();
				}
			})
			.Triggerable(subject.$livesearch.popup, function(t) {
				var p = jQuery(this).offset();
				p.top = p.top + jQuery(this).height();
				jQuery(t).css(p);
			});
	});
};
jQuery.fn.livesearchUpdate = function() {
	return this.each(function() {
		if (this.$livesearch instanceof Object && this.$livesearch.update instanceof Function) {
			return this.$livesearch.update();
		}
	});
};