jQuery.fn.ColumnSelector = function(options) {
	if (!jQuery.fn.Triggerable) {
		return;
	}
	if (!options instanceof Object || !options.url) {
		return;
	}

	var o = jQuery.extend({
			type: 'GET',
			url: '',
			popupClass: 'cs_popup',
			loadingClass: 'loading',
			helpClass: 'bubble',
			buttonClass: 'button_search',
			forceReload: false,
			searchParamsCallback: null,
			clickParamsCallback: null,
			selectCallback: null
		}, options);

	return this.each(function() {
		if (this.tagName != 'INPUT' && this.getAttribute('type') != 'text' ) {
			return;
		}
		var subject = this;
		var root, container, bubble, slider, button;
		var xml, data;
		var searchMode = false;

		container = jQuery('<div style="position:absolute;overflow:hidden"></div>')
			.addClass(o.popupClass)
			.insertAfter(subject);

		slider = jQuery('<div style="position:absolute;left:0;height:100%"></div>')
			.bind('resize', function() {
				resize();
			})
			.bind('select', function() {
				shift();
			})
			.appendTo(container);
		
		//	adjust slider width to ensure horizontal alignment
		var resize = function() {
			var w = 0;
			jQuery('>ul', slider).each(function() {
				w += jQuery(this).width();
			});
			jQuery(slider).width(w);
		}
		
		//	shift slider if necessary
		var shift = function() {
			var w = 0;
			jQuery('>ul', slider).each(function() {
				w += jQuery(this).width();
			});
			var diff = jQuery(slider).parent().width() - w;
			if (diff < 0) {
				slider.animate({left: diff}, 'fast');
			} else {
				var p = jQuery(slider).offset();
				if (p.left != 0) {
					slider.animate({left: 0}, 'fast');
				}
			}
		}

//		button = jQuery('<input type="button" />').addClass(o.buttonClass).insertAfter(subject);

		/**
		 * submit AJAX call and store the response to xml (raw XML) and data (flattened 'node' DOM nodes)
		 * @param {Object} callback
		 */
		var ajax = function(callback, selectedItem) {
			if (searchMode) {
				var params = o.submitParamsCallback instanceof Function? o.submitParamsCallback.apply(subject, [selectedItem]) : null;
			} else {
				var params = o.clickParamsCallback instanceof Function? o.clickParamsCallback.apply(subject, [selectedItem]) : null;
			}
			jQuery(subject).addClass(o.loadingClass);
			jQuery.ajax({
				type: o.type,
				url: o.url,
				data: params,
				dataType: 'xml',
				success: function(response) {
					jQuery(subject).removeClass(o.loadingClass);
					xml = response;
					data = filter();
					callback.apply(subject);
				}
			});
		};
		
		/**
		 * Select the level of nodes that are of the siblings of selector
		 * @param {Object} selector
		 */
		var selectData = function(selector) {
			if (typeof selector == 'string' && selector == '*') {
				return data;
			}
			//	default the first node as selector
			if (!selector) {
				selector = data[0];
			}
			var nodes = data
				.filter(function() {
					return this.parentNode == selector.parentNode;
				});
			return nodes;
		}
		
		var drawColumn = function(selector) {
			var column = jQuery('<ul></ul>');
			selectData(selector)
				.each(function() {
					var node_xml = this;
					var node_children = jQuery('>childnodes>node', node_xml);
	
					var item = drawItem(node_xml)
						.bind('click', function() {
							//	remove columns to the right
							jQuery(this).parent().nextAll().remove();
	
							//	update selection and set value
							if (o.selectCallback instanceof Function) {
								o.selectCallback.apply(subject, [this]);
							} else {
								jQuery(subject).val(jQuery(this).attr('data'));
							}
	
							//	update style
							if (this.parentNode.selectedItem) {
								jQuery(this.parentNode.selectedItem).removeClass('selected');
							}
							this.parentNode.selectedItem = this;
							jQuery(this).addClass('selected');
							subject.focus();
	
							//	click events
							if (node_children.size() > 0) {
								if (o.forceReload) {
									ajax(function() {
										drawColumn().appendTo(slider);
										slider.trigger('resize').trigger('select');
									}, item);
								} else {
									drawColumn(node_children[0]).appendTo(slider);
									slider.trigger('resize').trigger('select');
								}
							} else {
								slider.trigger('select');
								jQuery(subject).triggerCollapse();
							}
						})
						.appendTo(column);
	
						if (node_children.size() > 0) {
							item.addClass('more');
						}
				});
			return column;
		};
		
		var drawItem = function(xml) {
			var label = jQuery('>label', xml).text();
			var value = jQuery('>value', xml).text();
			var desc = jQuery('>desc', xml).text();

			var detail_popup = jQuery('<div></div>').append(jQuery('<h3></h3>').text(label));
			if (desc.length > 0) {
				detail_popup.append(desc);
			}
			
			var item = jQuery('<li style="cursor:default"></li>')
				.append(jQuery('<span></span>').text(label))
				.addClass('item')
				.attr('data', value)
				.HelpBubble(detail_popup, {delay: 500, className: o.helpClass});
			return item;
		};
		
		/**
		 * Filter cached XML and fetch only the nodes that contain "word"
		 * @param var word (optional)
		 */
		var filter = function(word) {
			if (word && word.length > 0) {
				return jQuery('node', xml)
					.filter(function() {
						return $('>label:contains('+word+')', this).size() > 0;
					})
					.clone()
					.each(function() {
						$('>childnodes', this).remove();
					});
			} else {
				return jQuery('node', xml);
			}
		};
		
		/**
		 * Trigger a search
		 */
		var search = function() {

			var word = subject.value;
			searchMode = (word.length > 0);

			slider.empty();
			if (searchMode) {
				if (o.forceReload) {
					ajax(function() {
						data = filter();
						drawColumn('*').appendTo(slider);
						slider.trigger('resize').trigger('select');
					});
				} else {
					data = filter(word);
					drawColumn('*').appendTo(slider);
				}
			} else {
				if (o.forceReload) {
					ajax(function() {
						drawColumn().appendTo(slider);
						slider.trigger('resize');
					});
				} else {
					data = filter();
					drawColumn().appendTo(slider);
				}
			}
			slider.trigger('resize');
			jQuery(subject).triggerExpand();
		};
	
		//	initial data load
		ajax(function() {
			drawColumn().appendTo(slider);
			slider.trigger('resize');
		});
		
		jQuery(subject)
			.bind('keypress', function(e) {
				if (e.keyCode == 13) {
					search();
					return false;
				}
			})
			.Triggerable(container, function(t) {
				var p = jQuery(this).offset();
				p.top = p.top + jQuery(this).height();
				jQuery(t).css(p);
			});
		
//		button.click(function() {
//			search();
//			return false;
//		});
		
		//	public functions
		subject.ColumnSelectorSearch = search;
	});
};
jQuery.fn.ColumnSelectorSearch = function() {
	return this.each(function() {
		if (this.ColumnSelectorSearch instanceof Function) {
			return this.ColumnSelectorSearch();
		}
	});
}