/**
 *	Reisplanner autocomplete
 *	--------------------------
 */

function AutoComplete(gui) {
	this.gui = gui;
	this.modes = [];
	this.changeHandlers = [];
	this.autoCompleteLists = [];
	this.isOpen = false;
	this.minBuffer = 2;

	this.bufferMode = /buffer/i;
	this.changeMode = /change/i;
	
	this.container = document.createElement('select');
	this.container.size = 7;
	this.container.id = 'autoComplete';
	document.getElementById("canvas").appendChild(this.container);

	EventListener.addEvent(this.container, 'click', this.scope(this.selectValue));
	EventListener.addEvent(this.container, 'blur', this.scope(this.selectValue));
	EventListener.addEvent(this.container, 'keydown', this.scope(this.navigate));
	EventListener.addEvent(document, 'click', this.scope(this.tryClose));
}

AutoComplete.prototype = {
	ATTR_COMPLETE:	 'ns:complete',
	ATTR_DEPENDENCY: 'ns:dependency',
	ATTR_LOADING:		'ns:loading',
	MODE_DEFAULT:		'change',

	// search a node for autocomplete inputs
	addElements:function(node) {
		if(!node) return;
		if(/form/i.test(node.nodeName)) {
			node.setAttribute('autocomplete', 'off');
		}
		var elements = node.getElementsByTagName("input");
		for (var i=0; i<elements.length; i++) {
			if(elements[i].getAttribute(this.ATTR_COMPLETE)) {
				this.addElement(elements[i]);
			}
		}

		var selects = node.getElementsByTagName("select");
		for (var i=0; i<selects.length; i++) {
			if(selects[i].getAttribute(this.ATTR_COMPLETE)) {
				this.addSelect(selects[i]);
			}
		}
	},
	
	// clean up events for removed nodes
	removeElements:function(node) {
		var elements = node.getElementsByTagName("input");
		for (var events, i=0; i<elements.length; i++) {
			if(elements[i].getAttribute(this.ATTR_COMPLETE)) {
				events = EventListener.getEvents(elements[i]);
				EventListener.removeEvents(events);
			}
		}
	},
	
	// add element for autocompletion
	addElement:function(element) {
		var completion = element.getAttribute(this.ATTR_COMPLETE);
		var dependency = element.getAttribute(this.ATTR_DEPENDENCY);
		
		if(completion) {
			element.setAttribute('autocomplete', 'off');
			EventListener.addEvent(element, 'keyup', this.scope(this.getValues));
			EventListener.addEvent(element, 'keydown', this.scope(this.setFocus));
			EventListener.addEvent(element, 'click', this.scope(this.activate));
		}

		if(dependency) {
			this.setMode(completion, 'change');
		}

		if(!this.modes[completion]) {
			this.setMode(completion, this.MODE_DEFAULT);
		}
	},

	// add select for autocompletion, adds options once
	addSelect:function(select) {
		if(select.options.length > 1) return;
		var completion = select.getAttribute(this.ATTR_COMPLETE);
		var dependency = select.getAttribute(this.ATTR_DEPENDENCY);

		if(!this.autoCompleteLists[completion]) {
			var postURL = this.gui.getProperty('POST_AUTOCOMPLETE', select.form);
			var self = this;
			var post = 'type=' + completion + (dependency? ('&' + dependency + '=' + escape(select.form[dependency].value)):'');
			XMLHttp.sendAndLoad(post, postURL, function(xml) {
				var list = self.autoCompleteLists[completion] = new AutoCompleteList(xml);
				self.suggestOptions(list, select);
			});
		} else {
			var list = this.autoCompleteLists[completion];
			this.suggestOptions(list, select);
		}
	},

	addDynamicSelect:function(select, fieldset) {
		var elements = fieldset.getElementsByTagName("*");
		var inputs = [], inputReg = /(text|select|hidden)/i;

		for(var i=0; i<elements.length; i++) {
			if(inputReg.test(elements[i].type) && elements[i] != select) {
				inputs.push(elements[i]);
				
				if(/text/i.test(elements[i].type) && elements[i].getAttribute(this.ATTR_COMPLETE)) {
					elements[i].relatedSelect = select;
				} else {
					this.linkDynamicSelect(elements[i]);
				}
			}
		}

		fieldset.dynamicElements = inputs;
		fieldset.dynamicSelect = select;
	},
		linkDynamicSelect:function(select) {
			var handler = this.scope(function(){ this.updateDynamicSelect(select); })
			EventListener.addEvent(select, 'change', handler);
		},
	
	updateDynamicSelect:function(origin) {
		var field = getParentByTagName(origin, 'fieldset');
		var typeReg = /text/i;
		for(var el,i=0; i<field.dynamicElements.length; i++) {
			el = field.dynamicElements[i];
			if((typeReg.test(el.type) && !el.value) || el.selectedIndex < 1) {
				var select = field.dynamicSelect;
				var first = select.getElementsByTagName("option")[0].cloneNode(true);
				select.innerHTML = '';
				select.appendChild(first);
				this.gui.displaySubject(select, false);
				return;
			}
		}

		var select = field.dynamicSelect;
		select.options[1] = new Option(select.getAttribute(this.ATTR_LOADING));
		select.selectedIndex = 1;

		var post = this.gui.getFormValues(field), self = this;
		XMLHttp.sendAndLoad(post, Globals.POST_ALT_TRAVEL, function(xml) {
			var list = new AutoCompleteList(xml);
			self.suggestOptions(list, field.dynamicSelect);
			self.gui.requestMethod();
		});
	},

	registerChange:function(input, handler) {
		this.changeHandlers.push({input:input, handler:handler});
	},
		handleOnChange:function(input) {
			var change, element, handler;
			for(var i=0; i<this.changeHandlers.length; i++) {
				change = this.changeHandlers[i];
				element = change.input;
				if(element == input && change.handler) {
					change.handler(input);
					break;
				}
			}
		},

	activate:function(e) {
		var input = EventListener.getTarget(e, 'input');
		if(input && input.getAttribute(this.ATTR_COMPLETE)) {
			this.currentInput = input;
		}
	},

	// sets autocomplete mode per type; buffer, change, keyup
	setMode:function(type, mode) {
		this.modes[type] = mode;
	},

	// keyboard mappings for input area
	setFocus:function(e) {
		var input = this.currentInput;
		var key = e.keyCode;

		switch (key) {
			case 40: this.open(input, true); break;		// arrow down, open autocomplete
			case 38: this.close(); break;				// arrow up, close autocomplete
			case 9:  this.close(true); break;			// tab away, close
		}
	},
	
	// keyboard mappings for autocomplete area
	navigate:function(e) {
		var input = this.currentInput;
		var key = e.keyCode;
		switch (key) {
			case 27: this.close(); break;				// escape key, close autocomplete
			case 9:  this.selectValue(e); break;		// tab away, select and close
			case 13: this.selectValue(e); break;		// enter (or click), select value from autocomplete
		}
	},

	// onkeyup or onchange autocompletion
	getValues:function(e) {
		var key = e.keyCode;
		if(key < 65 && (key != 8)) return;

		var input = EventListener.getTarget(e, 'input');
		this.currentInput = input;

		var completion = input.getAttribute(this.ATTR_COMPLETE);
		var dependency = input.getAttribute(this.ATTR_DEPENDENCY);
		var change = this.changeMode.test(this.modes[completion]);
		var buffered = (change || this.bufferMode.test(this.modes[completion]))? true:false;

		if(change) {
			buffered = true;
			value = input.value;
			
			// if value is too short, don't do anything
			if(value.length < this.minBuffer) return;
			
			// otherwise, check buffered value
			var buffer = input.buffer;
			var value = value.substring(0, this.minBuffer);

			if(buffer != value) {
				buffered = false;
				input.buffer = value;
			}
		}

		if(!buffered || !this.autoCompleteLists[completion]) {
			// if its not a buffered type, or there's no list yet, load one
			var dependent;
			if(dependency) {
				dependent = input.form[dependency];
				if(dependent && !dependent.value) {
					this.gui.displayError(dependent, true);
					this.gui.displayErrorMessage(dependent.form, '<br />- '+Globals.MSG_DEPENDENT);
					return;
				}
			}

			if(!this.loadingState) {
				var postURL = this.gui.getProperty('POST_AUTOCOMPLETE', input.form);
				var post = 'type=' + completion + '&value=' + escape(input.value) + 
					(dependency? ('&' + dependency + '=' + escape(dependent.value)):'');

				XMLHttp.sendAndLoad(post, postURL, this.scope(this.suggestValues));
				this.loadingState = true;
			}
		} else {
			// otherwise, open the autocomplete
			this.open(input);
		}
	},

	suggestValues:function(xml){
		this.loadingState = false;
		var type = this.currentInput.getAttribute(this.ATTR_COMPLETE);
		var list = this.autoCompleteLists[type] = new AutoCompleteList(xml);
		this.open(this.currentInput);
	},

	suggestOptions:function(list, select) {
		// Can't set options.length to 1 since select may contain optgroups
		var first = select.getElementsByTagName("option")[0].cloneNode(true);
		select.innerHTML = '';
		select.appendChild(first);

		if(list.options.length < 1) {
			this.gui.displayError(select, true);
			this.gui.displayErrorMessage(select.form,  '<br />- '+ list.error);
			this.gui.displaySubject(select, false);
		} else {
			this.gui.displayError(select, false);
			this.gui.displayErrorMessage(select.form, false);
			if(list.subject) {
				this.gui.displaySubject(select, list.subject);
			}
		}
		
		var opt, optGroup, group, currentGroup;

		for(var option,j,i=0; i<list.options.length; i++) {
			option = list.options[i];
			group = option.group;
			j = select.options.length;

			opt = document.createElement('option');
			opt.setAttribute('value', (option.value || option.label));
			opt.innerHTML = option.label;
			
			if(group) {
				if(!optGroup || group != currentGroup) {
					optGroup = document.createElement('optgroup');
					optGroup.setAttribute('label', group);
					select.appendChild(optGroup);
				}
				optGroup.appendChild(opt);
				currentGroup = group;
			} else {
				optGroup = null;
				select.appendChild(opt);
			}

			if(option.label.length > 20) {
				opt.title = option.label;
			}

			if(option.selected) {
				select.selectedIndex = select.options.length-1;
			}
		}
	},

	selectValue:function(e) {
		var select = EventListener.getTarget(e, 'select');
		if(select && this.currentInput && select.selectedIndex > -1) {
			this.currentInput.value = select.value;
			this.currentInput.focus();
			this.handleOnChange(this.currentInput);
		}

		this.close();
		EventListener.cancelEvent(e);
	},

	open:function(input, focus){
		this.isOpen = true;
		var result = this.filterSuggestions(input);
		if(!result) return;
		
		var css = this.container.style;
		var x = calculateLeft(input);
		var y = calculateTop(input) + input.offsetHeight;
		var w = input.offsetWidth;

		css.left = x + 'px';
		css.top = y + 'px';
		css.width = ((w > 200)? w : 200) + 'px'; 		
		css.display = 'block';

		if(focus) {
			this.container.focus();
		}
	},

	filterSuggestions:function(input) {
		var value = input.value;

		this.gui.displayError(this.currentInput, false);
		var type = input.getAttribute(this.ATTR_COMPLETE);
		var list = this.autoCompleteLists[type];
		var options = this.container.options;

		options.length = 0;
		
		if(!list) {
			return false;
		}

		var result = list.filter(value);		
		
		if(result.length > 0) {
			options.length = 0;
			for (var i=0; i<result.length; i++) {
				options[options.length] = new Option(result[i].label, (result[i].value || result[i].label));
			}

			this.suggestedValue = result[0].label;
		}
		
		return true;
	},

	close:function(suggest) {
		if(!this.isOpen) return;
		this.isOpen = false;
		this.container.style.display = 'none';
		try {
			if(suggest && this.suggestedValue && 
			  (this.suggestedValue != this.currentInput.value)) {
				this.currentInput.value = this.suggestedValue;
				this.handleOnChange(this.currentInput);
			}

			if(this.currentInput.relatedSelect) {
				this.updateDynamicSelect(this.currentInput);
			}
		
			this.suggestedValue = null;
			this.currentInput.focus();
			this.currentInput.buffer = '';
			this.currentInput = null;
		} catch (e) {}
	},

	tryClose:function(e) {
		if(!this.isOpen) return;
		var select = EventListener.getTarget(e, 'select');
		if(!select || select != this.container) {
			this.close();
		}
	},

	unload:function() {
		this.container.options.length = 0;
		if (!document.all) {
			document.getElementById("canvas").removeChild(this.container);
		}
		this.container = null;
	},

	scope:function(method) {
		var scope = this;
		return function() {
			return method.apply(scope, arguments);
		}
	}
}

/**
 *	Bufferable list
 *	--------------------------
 */

function AutoCompleteList(xml) {
	this.options = [];
	this.error = null;
	if(xml) this.parse(xml);
}

AutoCompleteList.prototype = {
	parse:function(xml) {
		try { this.error = xml.getElementsByTagName("error")[0].firstChild.nodeValue; } catch(e){}
		try { this.subject = xml.getElementsByTagName("subject")[0].firstChild.nodeValue; } catch(e){}
		
		this.options = [];
		var items = xml.getElementsByTagName("item");
		var trueReg = /true/i;
		var groupReg = /group/i;

		for (var item,label,value,selected,group,i=0; i<items.length; i++) {
			group = null;
			item = items[i];
			label = item.firstChild.nodeValue;
			value = item.getAttribute('value');
			if(groupReg.test(item.parentNode.nodeName)) {
				group = item.parentNode.getAttribute('label');
			}
			selected = trueReg.test(items[i].getAttribute('selected'));
			this.addOption(label, value, selected, group);
		}
	},

	addOption:function(label, value, selected, group) {
		this.options[this.options.length] = { 
			label:label, 
			value:value,
			selected:selected,
			group:group
		};
	},

	filter:function(value) {
		var reg, result = [];
		try {
			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 = [];
		var options = this.options;
		for(var option,i=0; i<options.length; i++) {
			option = options[i];
			if(reg.test(option.label)) {
				result[result.length] = option;
			}
		}
		return result;
	}
}