/**
 * NS Autocomplete controller
 *
 * @version   1.00.090525
 * @author    LBI Lost Boys
 */
(function($){

	var TYPE_TEXT		= /text$/i,
		TYPE_SELECT		= /select/i;

	var Autocomplete = function() {
		this.modes = [];
		this.lists = [];
		this.settings = Autocomplete.Defaults;
	}

	Autocomplete.prototype = {
		parseNodes:function(nodes, settings) {
			if(!this.container) {
				this.container = $('<select id="autoComplete" size="7"></select>');
				this.container.css({
					position:'absolute', display:'none'
				})
				$('body').append(this.container);

				this.container.bind('click', this.select.bind(this));
				this.container.bind('blur', this.select.bind(this));
				this.container.bind('keydown', this.navigate.bind(this));
			}

			if(settings) {
				this.settings = $.extend(this.settings, settings);
			}

			this.addElements(nodes);
		},
		
		// enables text inputs for autocompletion
		addElements:function(inputs){
			inputs.each(function(index, input){
				if(TYPE_TEXT.test(input.type)) {
					var jInput = $(input);
					input.setAttribute('autocomplete', 'off');
					jInput.bind('keyup', this.keyup.bind(this));
					jInput.bind('keydown', this.focus.bind(this));
					jInput.bind('click', this.focus.bind(this));
				}
			}.bind(this));
		},

		keyup:function(e) {
			var key = e.keyCode;
			if(key < 65 && (key != 8)) {
				return;
			}
			this.getValues(e.target);
		},

		// main loader for autocomplete lists, either loads a response via ajax,
		// or uses a buffered list based on the first n chars of input.
		getValues:function(input){
			var value = input.value, 
				completion = input.name,
				minimal = this.settings.buffer;

			if(value.length < minimal && !this.lists[completion]) {
				return;
			} 

			var buffer = value.substring(0, minimal);
			if(buffer.length == minimal && (input.buffer != buffer || !this.lists[completion])) {
				input.buffer = buffer;
				
				var url = this.settings.path;
				var data = {};
				
				data[input.name] = encodeURIComponent(value);
				
				$.post(url, data, function(response){
					this.handleResponse(response, input);
				}.bind(this));
			} else {
				this.suggestValues(input, this.lists[completion]);
			}
		},

		handleResponse:function(response, input) {
			var completion = input.name;
				list = new AutoCompleteList(response);
			
			this.lists[completion] = list;
			this.suggestValues(input, list);
		},
		
		suggestValues:function(input, list){
			var type = input.type,
				select = this.container[0],
				filtered = list.filter(input.value);

			if(filtered.length == 0) {
				return;
			} else {
				select.options.length = 0;
			}

			for(var i=0; i<filtered.length; i++) {
				var item = filtered[i];
				var opt = document.createElement('option');
				opt.value = item.value || item.label;
				opt.innerHTML = item.label;

				select.appendChild(opt);
			}

			this.open();
		},

		select:function(e){
			var select = this.container[0], index = Math.max(select.selectedIndex, 0);
			this.currentInput.value = select[index].text;
			this.close();
		},

		focus:function(e){
			var input = e.target, key = e.keyCode;
			this.currentInput = input;
			switch (key) {
				case 40: this.open(e, true); break; // down
				case 38: this.close(); break; // up
				case 27: this.close(); break; // escape
				case 9:  this.select(); break; // tab
			}
		},
		
		navigate:function(e){
			var input = this.currentInput, key = e.keyCode;
			switch (key) {
				case 27: this.close(); break;	// escape
				case 9:  this.select(e); break;	// tab
				case 13: this.select(e); break;	// enter
			}
		},

		open:function(e, focus){
			var input = this.currentInput,
				offset = $(input).offset(),
				width = input.offsetWidth,
				height = input.offsetHeight;

			this.container.css({
				left: offset.left + 'px',
				top: (offset.top + height) + 'px',
				width: Math.max(width, this.settings.minWidth) + 'px'
			}).show();

			if(focus) {
				this.container[0].focus();
			}
		},
		
		close:function(e){
			var related = e && e.relatedTarget;
			if(related == this.currentInput || related == this.container) {
				return;
			}

			this.container.hide();
		}
	};

	/**
	 * Private Autocomplete list, parses the xml into a more usable object format, and
	 * provides a filter method to reduce the amount of matches when typing.
	 */
	function AutoCompleteList(xml) {
		this.options = [];
		if(xml) this.parse(xml);
	}

	AutoCompleteList.prototype = {
		parse:function(xml) {
			this.options = [];
			var items = xml.getElementsByTagName("item");
			for(var i=0; i<items.length; i++) {
				var item = items[i],
					label = item.firstChild.nodeValue,
					value = item.getAttribute('value');

				this.options.push({ 
					label:label, value:value
				});
			}
		},

		filter:function(value) {
			var result = [];
			try {
				var reg = new RegExp('^'+value, 'i');
				result = this.filterOptions(value, reg);
				if(result.length == 0) {
					reg = new RegExp(value, 'i');
					result = this.filterOptions(value, reg);
				}
			} catch(e){}
			return result;
		},

		filterOptions:function(value, reg) {
			var result = [], options = this.options;
			for(var i=0; i<options.length; i++) {
				var option = options[i];
				if(reg.test(option.label)) {
					result[result.length] = option;
				}
			}
			return result;
		}
	}

	// defaults
	Autocomplete.Defaults = {
		path: 'autocomplete.xml',
		minWidth: 150,
		buffer: 2
	}

	// local instance
	var autocomplete = new Autocomplete();
	
	// expose to jQuery
	$.fn.autocomplete = function(settings) {
		if(this.length) {
			autocomplete.parseNodes(this, settings);
		}
		return this;
	}

})(jQuery);