Autocomplete = Class.create({
	initialize: function(options){
		this.inputField = options.inputField ? $(options.inputField) : false;
		if(this.inputField) {
			// disable the browser's autocompleter
			this.inputField.setAttribute("autocomplete", "off");
			this.chars = typeof options.chars == "undefined" ? 1 : options.chars;
			this.data = options.data || null;
			this.dosearchnext = true;
			this.url = options.url || null;
			this.hasFocus = false;
			this.resultBox  = new Element("div", {"class": "autocompleteResult"});
			this.resultBox.query = "";
			this.request = null; // the current Ajax request
			
			// this.inputField.insert({'after': this.resultBox});
			document.body.appendChild(this.resultBox);
			var padding = 2;
			var border = 1;
			
			this.resultBox.hide();
			this.resultBox.items = [];			
			
			this.showResultBox = function() {
				var totalOffset = this.resultBox.cumulativeOffset();
				var totalX = totalOffset[0];
				var totalY = totalOffset[1];
				var posOffset = this.resultBox.positionedOffset();
				var posX = posOffset[0];
				var posY = posOffset[1];
				var targetOffset = this.inputField.cumulativeOffset();
				var targetDimensions = this.inputField.getDimensions();
				var targetX = targetOffset[0];
				var targetY = targetOffset[1];
				var newX = targetX - (totalX - posX);
				var newY = targetY - (totalY - posY) + targetDimensions.height;
				var newWidth = targetDimensions.width - padding*2 - border*2;
				this.resultBox.setStyle({top: newY+'px', left: newX+'px', width: newWidth+'px'});
				this.resultBox.show();
				var height = 0;
				this.resultBox.items.each(function(li) {
					height += li.getHeight();
				});
				if(height > 300) {
					this.resultBox.setStyle({'height': '300px', 'overflow': 'auto'});
				} else {
					this.resultBox.setStyle({'height': 'auto', 'overflow': 'auto'});
				}
				
			}
			
			this.inputFieldFocusHandler = function(ev, el) {
				this.hasFocus = true;
				this.focusChanged();
				if(this.dosearchnext)
					this.dosearch();
				else
					this.dosearchnext = true;
			}.bindAsEventListener(this, this.inputField);
			this.inputField.observe("focus", this.inputFieldFocusHandler);
			this.inputField.observe("keydown", function(ev) {
				// if(this.hasFocus && this.resultBox.visible() && this.resultBox.items && this.resultBox.items.length) {
					// var TAB = ev.keyCode == 9 && !ev.shiftKey;
					// if(TAB) {
						// this.resultBox.items[0].focus();
						// Event.stop(ev);
					// }
				// }
			}.bind(this));
			
			this.focusChanged = function() {
				(function() { 
					if(!this.hasFocus) 
						this.resultBox.hide(); 
				}).bind(this).defer();
			}
			
			// true for up, down for false
			this.switchItem = function(up) {
				if(this.resultBox.visible()) {
					if(up) {
						var found = false;
						var el = this.resultBox.select("div.hasFocus").first();
						if(el) {
							if(el.previousSibling) {
								var found = true;
								el.previousSibling.focus();
							}
							if(!found) {
								// this.inputFieldFocusHandler();
								this.inputField.focus();
							}
						}
					} else {
						var el = this.resultBox.select("div.hasFocus").first();
						if(el) {
							if(el.nextSibling) {
								el.nextSibling.focus();
							}
						} else {
							this.resultBox.firstChild.focus(); // focus the first item
						}
					}	
				}
			}
			
			this.chooseItem = function(result) {
				this.inputField.value = result.text;
				this.dosearchnext = false;
				this.inputField.focus();
				Event.fire(this.inputField, "ac:chooseItem");
				Event.fire(this.inputField, "ac:textChange");
				this.resultBox.hide();
			}.bind(this);
			
			this.focusNext = function(next) {
				if(this.inputField.form) {
					if(arguments.length < 1) next = true;
					var o = next?1:-1;
					var els = $A(this.inputField.form.elements);
					var i = els.indexOf(this.inputField)+o;
					if(els[i]) els[i].focus();
				}
			}
			this.focusPrevious = function() { this.focusNext(false); }
			
			this.setText = function(text) {
				text = text || "";
				this.inputField.value = text;
				this.resultBox.update("");
				this.resultBox.hide();
				Event.fire(this.inputField, "ac:textChange");
			}
			
			this.inputField.observe("keyup", function(ev, el) {
				var UP = ev.keyCode == 38;
				var DOWN = ev.keyCode == 40;
				if(UP || DOWN) {
					this.switchItem(UP); // will be false for down
					Event.stop(ev);
				} else {
					this.dosearch();
				}
			}.bindAsEventListener(this));
			
			this.inputField.observe("blur", function(ev, el) {
				this.hasFocus = false;
				this.focusChanged();
			}.bindAsEventListener(this, this.inputField));
		}
		
	},
	dosearch: function() {
					if(this.inputField.value.length >= this.chars) {
						if(this.inputField.value.strip() != this.query || this.inputField.value.strip() == '') {
							this.search(this.inputField.value, function(results) {
								this.query = this.inputField.value.strip();
								this.resultBox.items = [];
								this.resultBox.update("");
								if(results.length > 0 && !(results.length == 1 && results[0].text == this.inputField.value)) {
									results.each(function(result) {
										var li = new Element("div", {'class':'autocompleteItem'});
										li.update(result.display || result.text);
										if(result.extra) {
											li.insert((new Element('div', {'class':'autocompleteExtra'})).update(result.extra));
										}
										li.tabIndex = 0;
										li.resultItem = result;
										li.observe("focus", function(li) {
											this.hasFocus = true;
											li.addClassName("hasFocus");
											this.showResultBox();
											this.focusChanged();
										}.bind(this, li));
										li.observe("mouseover", function(li) {
											this.resultBox.select(".hasHover").each(function(i) { 
												i.removeClassName("hasHover"); 
											});
											li.addClassName("hasHover");
										}.bind(this, li));
										li.observe("mouseout", function(li) {
											li.removeClassName("hasHover");
										}.bind(this, li));
										li.observe("blur", function(li) {
											this.hasFocus = false;
											li.removeClassName("hasFocus");
											this.focusChanged();
										}.bind(this, li));
										li.observe("keydown", function(ev, result) {
											var UP = ev.keyCode == 38;
											var DOWN = ev.keyCode == 40;
											var RETURN = ev.keyCode == 13;
											var SPACE = ev.charCode == 32 || ev.keyCode == 32;
											var TAB = ev.keyCode == 9 && !ev.shiftKey;
											var SHIFTTAB = ev.keyCode == 9 && ev.shiftKey;
											if(UP || DOWN) {
												this.switchItem(UP ); // will be false for down
												Event.stop(ev);
											} else if(RETURN || SPACE || TAB || SHIFTTAB) {
												this.chooseItem(result);
												if(TAB) this.focusNext();
												else if(SHIFTTAB) this.focusPrevious();
												Event.stop(ev);
											}
										}.bindAsEventListener(this, result));
										
										/**
										 * An item is chosen
										 */
										li.observe("click", function(result) {
											this.chooseItem(result);
										}.bind(this, result));
										this.resultBox.insert(li);
										this.resultBox.items.push(li);
									}.bind(this));
									this.showResultBox();
								} else {
									this.resultBox.hide();
									this.inputField.fire("ac:noResults");
								}
							}.bind(this));
						}
					} else {
						this.query = "";
						this.resultBox.update(""); 
						this.resultBox.hide();
					}
					Event.fire(this.inputField, "ac:textChange");
			  },
	// data must be objects with properties:
	// text: [required] the text that will be filled in the input
	// display: [optional] what will be shown in the dropdown (text will be used if this is missing or blank)
	// extra: [optional] more text that will be shown as an extra line in the dropdown (in addition to text or display)
	search: function(str, callback){
		str = str.toLowerCase();
		var results = [];
		if(this.data) {
			results = this.data.findAll(function(item) {
				return item.text.toLowerCase().indexOf(str) >= 0; 
			});
			results.sort(function(a, b) {
				var at = a.text.toLowerCase();
				var bt = b.text.toLowerCase();
				var astart = at.indexOf(str) == 0;
				var bstart = bt.indexOf(str) == 0;
				if(astart && !bstart) return -1;
				else if(bstart && !astart) return 1;
				else {
					if(at < bt) return -1;
					else if(at > bt) return 1;
					else {
						return 0;
					}
				}
			});
			callback.apply(this, [results]);
		} else if(this.url) {
			// do an ajax request to get the data array
			if(this.request) {
				this.request.transport.abort();
			}
			(function() {
				this.request = new Ajax.Request(this.url+escape(str), {
					method: "GET",
					onComplete: (function(response) {
						if(this.request && this.request.transport === response.transport) {
							var results = response.responseJSON;
							callback.apply(this, [results]);
							delete this.request;
						}
					}).bind(this)
				});
			}).bind(this)();
		}
	}
});

