/**

 * Autocompleter

 *

 * @version		1.1.1

 *

 * @todo: Caching, no-result handling!

 *

 *

 * @license		MIT-style license

 * @author		Harald Kirschner <mail [at] digitarald.de>

 * @copyright	Author

 */

var Autocompleter = {};



Autocompleter.Base = new Class({



	options: {

		minLength: 3,

		markQuery: true,

		width: 'inherit',

		maxChoices:10,

		injectChoice: null,

		customChoices: null,

		className: 'autocompleter-choices',

		zIndex: 42,

		delay: 0,

		observerOptions: {},

		fxOptions: {},

		onOver: $empty,

		onSelect: $empty,

		onSelection: $empty,

		onShow: $empty,

		onHide: $empty,

		onBlur: $empty,

		onFocus: $empty,



		autoSubmit: true,

		overflow: false,

		overflowMargin: 25,

		selectFirst: false,

		filter: null,

		filterCase: false,

		filterSubset: false,

		forceSelect: false,

		selectMode: true,

		choicesMatch: null,



		multiple: false,

		separator: ', ',

		separatorSplit: /\s*[,;]\s*/,

		autoTrim: true,

		allowDupes: false,



		cache: true,

		relative: false

	},



	initialize: function(element, options) {

		this.element = $(element);

		this.setOptions(options);

		this.build();

		this.observer = new Observer(this.element, this.prefetch.bind(this), $merge({

			'delay': this.options.delay

		}, this.options.observerOptions));

		this.queryValue = null;

		if (this.options.filter) this.filter = this.options.filter.bind(this);

		var mode = this.options.selectMode;

		this.typeAhead = (mode == 'type-ahead');

		this.selectMode = (mode === true) ? 'selection' : mode;

		this.cached = [];

	},



	/**

	 * build - Initialize DOM

	 *

	 * Builds the html structure for choices and appends the events to the element.

	 * Override this function to modify the html generation.

	 */

	build: function() {

		if ($(this.options.customChoices)) {

			this.choices = this.options.customChoices;

		} else {

			this.choices = new Element('ul', {

				'class': this.options.className,
				
				'styles': {

					'zIndex': this.options.zIndex

				}

			}).inject(document.body);

			this.relative = false;

			if (this.options.relative) {

				this.choices.inject(this.element, 'after');

				this.relative = this.element.getOffsetParent();

			}

			this.fix = new OverlayFix(this.choices);

		}

		if (!this.options.separator.test(this.options.separatorSplit)) {

			this.options.separatorSplit = this.options.separator;

		}

		this.fx = (!this.options.fxOptions) ? null : new Fx.Tween(this.choices, $merge({

			'property': 'opacity',

			'link': 'cancel',

			'duration': 200

		}, this.options.fxOptions)).addEvent('onStart', Chain.prototype.clearChain).set(0);

		this.element.setProperty('autocomplete', 'off')

			.addEvent((Browser.Engine.trident || Browser.Engine.webkit) ? 'keydown' : 'keypress', this.onCommand.bind(this))

			.addEvent('click', this.onCommand.bind(this, [false]))

			.addEvent('focus', this.toggleFocus.create({bind: this, arguments: true, delay: 0}))

			.addEvent('blur', this.toggleFocus.create({bind: this, arguments: false, delay: 0}));

	},



	destroy: function() {

		if (this.fix) this.fix.destroy();

		this.choices = this.selected = this.choices.destroy();

	},



	toggleFocus: function(state) {

		this.focussed = state;

		if (!state) this.hideChoices(true);

		this.fireEvent((state) ? 'onFocus' : 'onBlur', [this.element]);

	},



	onCommand: function(e) {

		if (!e && this.focussed) return this.prefetch();

		if (e && e.key && !e.shift) {

			switch (e.key) {

				case 'enter':

					if (this.element.value != this.opted) return true;

					if (this.selected && this.visible) {

						this.choiceSelect(this.selected);

						return !!(this.options.autoSubmit);

					}

					break;

				case 'up': case 'down':

					if (!this.prefetch() && this.queryValue !== null) {

						var up = (e.key == 'up');

						this.choiceOver((this.selected || this.choices)[

							(this.selected) ? ((up) ? 'getPrevious' : 'getNext') : ((up) ? 'getLast' : 'getFirst')

						](this.options.choicesMatch), true);

					}

					return false;

				case 'esc': case 'tab':

					this.hideChoices(true);

					break;

			}

		}

		return true;

	},



	setSelection: function(finish) {

		var input = this.selected.inputValue, value = input;

		var start = this.queryValue.length, end = input.length;

		if (input.substr(0, start).toLowerCase() != this.queryValue.toLowerCase()) start = 0;

		if (this.options.multiple) {

			var split = this.options.separatorSplit;

			value = this.element.value;

			start += this.queryIndex;

			end += this.queryIndex;

			var old = value.substr(this.queryIndex).split(split, 1)[0];

			value = value.substr(0, this.queryIndex) + input + value.substr(this.queryIndex + old.length);

			if (finish) {

				var space = /[^\s,]+/;

				var tokens = value.split(this.options.separatorSplit).filter(space.test, space);

				if (!this.options.allowDupes) tokens = [].combine(tokens);

				var sep = this.options.separator;

				value = tokens.join(sep) + sep;

				end = value.length;

			}

		}

		this.observer.setValue(value);

		this.opted = value;

		if (finish || this.selectMode == 'pick') start = end;

		this.element.selectRange(start, end);

		this.fireEvent('onSelection', [this.element, this.selected, value, input]);

	},



	showChoices: function() {

		var match = this.options.choicesMatch, first = this.choices.getFirst(match);

		this.selected = this.selectedValue = null;

		if (this.fix) {

			var pos = this.element.getCoordinates(this.relative), width = this.options.width || 'auto';

			this.choices.setStyles({

				'left': pos.left,

				'top': pos.bottom,

				'width': (width === true || width == 'inherit') ? pos.width : width

			});

		}

		if (!first) return;

		if (!this.visible) {

			this.visible = true;

			this.choices.setStyle('display', '');

			if (this.fx) this.fx.start(1);

			this.fireEvent('onShow', [this.element, this.choices]);

		}

		if (this.options.selectFirst || this.typeAhead || first.inputValue == this.queryValue) this.choiceOver(first, this.typeAhead);

		var items = this.choices.getChildren(match), max = this.options.maxChoices;

		var styles = {'overflowY': 'hidden', 'height': ''};

		this.overflown = false;

		if (items.length > max) {

			var item = items[max - 1];

			styles.overflowY = 'scroll';

			styles.height = item.getCoordinates(this.choices).bottom;

			this.overflown = true;

		};

		this.choices.setStyles(styles);

		this.fix.show();

	},



	hideChoices: function(clear) {

		if (clear) {

			var value = this.element.value;

			if (this.options.forceSelect) value = this.opted;

			if (this.options.autoTrim) {

				value = value.split(this.options.separatorSplit).filter($arguments(0)).join(this.options.separator);

			}

			this.observer.setValue(value);

		}

		if (!this.visible) return;

		this.visible = false;

		this.observer.clear();

		var hide = function(){

			this.choices.setStyle('display', 'none');

			this.fix.hide();

		}.bind(this);

		if (this.fx) this.fx.start(0).chain(hide);

		else hide();

		this.fireEvent('onHide', [this.element, this.choices]);

	},



	prefetch: function() {

		var value = this.element.value, query = value;

		if (this.options.multiple) {

			var split = this.options.separatorSplit;

			var values = value.split(split);

			var index = this.element.getCaretPosition();

			var toIndex = value.substr(0, index).split(split);

			var last = toIndex.length - 1;

			index -= toIndex[last].length;

			query = values[last];

		}

		if (query.length < this.options.minLength) {

			this.hideChoices();

		} else {

			if (query === this.queryValue || (this.visible && query == this.selectedValue)) {

				if (this.visible) return false;

				this.showChoices();

			} else {

				this.queryValue = query;

				this.queryIndex = index;

				if (!this.fetchCached()) this.query();

			}

		}

		return true;

	},



	fetchCached: function() {

		return false;

		if (!this.options.cache

			|| !this.cached

			|| !this.cached.length

			|| this.cached.length >= this.options.maxChoices

			|| this.queryValue) return false;

		this.update(this.filter(this.cached));

		return true;

	},



	update: function(tokens) {
		
		this.choices.empty();

		this.cached = tokens;

		if (!tokens || !tokens.length) {
			
			this.hideChoices();

		} else {
			
			if (this.options.maxChoices < tokens.length && !this.options.overflow) tokens.length = this.options.maxChoices;

			tokens.each(this.options.injectChoice || function(token){

				var choice = new Element('li', {'html': this.markQueryValue(token)});

				choice.inputValue = token;

				this.addChoiceEvents(choice).inject(this.choices);

			}, this);

			this.showChoices();

		}

	},



	choiceOver: function(choice, selection) {

		if (!choice || choice == this.selected) return;

		if (this.selected) this.selected.removeClass('autocompleter-selected');

		this.selected = choice.addClass('autocompleter-selected');

		this.fireEvent('onSelect', [this.element, this.selected, selection]);

		if (!selection) return;

		this.selectedValue = this.selected.inputValue;

		if (this.overflown) {

			var coords = this.selected.getCoordinates(this.choices), margin = this.options.overflowMargin,

				top = this.choices.scrollTop, height = this.choices.offsetHeight, bottom = top + height;

			if (coords.top - margin < top && top) this.choices.scrollTop = Math.max(coords.top - margin, 0);

			else if (coords.bottom + margin > bottom) this.choices.scrollTop = Math.min(coords.bottom - height + margin, bottom);

		}

		if (this.selectMode) this.setSelection();

	},



	choiceSelect: function(choice) {
		
		if (choice) this.choiceOver(choice);

		this.setSelection(true);

		this.queryValue = false;

		this.hideChoices(); //alert("a");
		
		setTimeout(function() {document.getElementById('search_form').submit();}, "200");

	},

	





	filter: function(tokens) {

		var regex = new RegExp(((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp(), (this.options.filterCase) ? '' : 'i');

		return (tokens || this.tokens).filter(regex.test, regex);

	},



	/**

	 * markQueryValue

	 *

	 * Marks the queried word in the given string with <span class="autocompleter-queried">*</span>

	 * Call this i.e. from your custom parseChoices, same for addChoiceEvents

	 *

	 * @param		{String} Text

	 * @return		{String} Text

	 */

	markQueryValue: function(str) { //alert(this.options.filterCase);
		var gg=this.queryValue.split(" ");
		for (var h=0; h<gg.length; h++)
		{
			//alert(str); alert(gg[h]); 
			if(gg[h].length>3) str=str.replace(gg[h], '<span class="autocompleter-queried">'+gg[h]+'</span>');
			//alert(str);
		}
		//var rrr=new RegExp('(' + ((this.options.filterSubset) ? '' : '') + this.queryValue.escapeRegExp() + ')', (this.options.filterCase) ? '' : 'i');
		//var tt=rrr.split(" ");
		//alert(str);
		
		return (!this.options.markQuery || !this.queryValue) ? str

			: 

			//str.replace(new RegExp('(' + ((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp() + ')', (this.options.filterCase) ? '' : 'i'), '<span class="autocompleter-queried">$1</span>');

			
			str.replace(

						new RegExp('(' + ((this.options.filterSubset) ? '' : '') + this.queryValue.escapeRegExp() + ')', (this.options.filterCase) ? '' : 'i'), 

						'<span class="autocompleter-queried">$1</span>');

			

			



	},



	/**

	 * addChoiceEvents

	 *

	 * Appends the needed event handlers for a choice-entry to the given element.

	 *

	 * @param		{Element} Choice entry

	 * @return		{Element} Choice entry

	 */

	addChoiceEvents: function(el) {

		return el.addEvents({

			'mouseover': this.choiceOver.bind(this, [el]),

			'click': this.choiceSelect.bind(this, [el])

		});

	}

});



Autocompleter.Base.implement(new Events);

Autocompleter.Base.implement(new Options);



Autocompleter.Local = new Class({



	Extends: Autocompleter.Base,



	options: {

		minLength: 0,

		delay: 0

	},



	initialize: function(element, tokens, options) {

		this.parent(element, options);

		this.tokens = tokens;

	},



	query: function() {

		this.update(this.filter());

	}



});



Autocompleter.Ajax = {};



Autocompleter.Ajax.Base = new Class({



	Extends: Autocompleter.Base,



	options: {

		postVar: 'value',

		postData: {},

		ajaxOptions: {},

		onRequest: $empty,

		onComplete: $empty

	},



	initialize: function(element, options) {

		this.parent(element, options);

		var indicator = $(this.options.indicator);

		if (indicator) {

			this.addEvents({

				'onRequest': indicator.show.bind(indicator),

				'onComplete': indicator.hide.bind(indicator)

			}, true);

		}

	},



	query: function(){

		var data = $unlink(this.options.postData);

		data[this.options.postVar] = this.queryValue;

		this.fireEvent('onRequest', [this.element, this.request, data, this.queryValue]);

		this.request.send({'data': data});

	},



	/**

	 * queryResponse - abstract

	 *

	 * Inherated classes have to extend this function and use this.parent(resp)

	 *

	 * @param		{String} Response

	 */

	queryResponse: function() {

		this.fireEvent('onComplete', [this.element, this.request, this.response]);

	}



});



Autocompleter.Ajax.Json = new Class({



	Extends: Autocompleter.Ajax.Base,



	initialize: function(el, url, options) {

		this.parent(el, options);

		this.request = new Request.JSON($merge({

			'url': url,

			'link': 'cancel'

		}, this.options.ajaxOptions)).addEvent('onComplete', this.queryResponse.bind(this));

	},



	queryResponse: function(response) {
		
		this.parent();

		this.update(response);

	}



});



Autocompleter.Ajax.Xhtml = new Class({



	Extends: Autocompleter.Ajax.Base,



	initialize: function(el, url, options) {

		this.parent(el, options);

		this.request = new Request.HTML($merge({

			'url': url,

			'link': 'cancel',

			'update': this.choices

		}, this.options.ajaxOptions)).addEvent('onComplete', this.queryResponse.bind(this));

	},



	queryResponse: function(tree, elements) {

		this.parent();

		if (!elements || !elements.length) {

			this.hideChoices();

		} else {

			this.choices.getChildren(this.options.choicesMatch).each(this.options.injectChoice || function(choice) {

				var value = choice.innerHTML;

				choice.inputValue = value;

				this.addChoiceEvents(choice.set('html', this.markQueryValue(value)));

			}, this);

			this.showChoices();

		}



	}



});





var OverlayFix = new Class({



	initialize: function(el) {

		if (Browser.Engine.trident) {

			this.element = $(el);

			this.relative = this.element.getOffsetParent();

			this.fix = new Element('iframe', {

				'frameborder': '0',

				'scrolling': 'no',

				'src': 'javascript:false;',

				'styles': {

					'position': 'absolute',

					'border': 'none',

					'display': 'none',

					'filter': 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)'

				}

			}).inject(this.element, 'after');

		}

	},



	show: function() {

		if (this.fix) {

			var coords = this.element.getCoordinates(this.relative);

			delete coords.right;

			delete coords.bottom;

			this.fix.setStyles($extend(coords, {

				'display': '',

				'zIndex': (this.element.getStyle('zIndex') || 1) - 1

			}));

		}

		return this;

	},



	hide: function() {

		if (this.fix) this.fix.setStyle('display', 'none');

		return this;

	},



	destroy: function() {

		this.fix = this.fix.destroy();

	}



});



/**

 * @todo Clean that up or check if they exist already

 */

Element.implement({



	getOffsetParent: function() {

		var body = this.getDocument().body;

		if (this == body) return null;

		if (!Browser.Engine.trident) return $(this.offsetParent);

		var el = this;

		while ((el = el.parentNode)){

			if (el == body || Element.getComputedStyle(el, 'position') != 'static') return $(el);

		}

		return null;

	},



	getCaretPosition: function() {

		if (!Browser.Engine.trident) return this.selectionStart;

		this.focus();

		var work = document.selection.createRange();

		var all = this.createTextRange();

		work.setEndPoint('StartToStart', all);

		return work.text.length;

	},



	selectRange: function(start, end) {

		if (Browser.Engine.trident) {

			var range = this.createTextRange();

			range.collapse(true);

			range.moveEnd('character', end);

			range.moveStart('character', start);

			range.select();

		} else {

			this.focus();

			this.setSelectionRange(start, end);

		}

		return this;

	}



});






