/**
 * Autocompleter
 *
 * @version		1.0rc4
 *
 * @license		MIT-style license
 * @author		Harald Kirschner <mail [at] digitarald.de>
 * @copyright	Author
 */
var InstantSearch = {};

InstantSearch.Base = new Class({

	options: {
		minLength: 1,
		useSelection: true,
		markQuery: true,
		inheritWidth: true,
		maxChoices: 10,
		injectChoice: null,
		onSelect: Class.empty,
		onShow: Class.empty,
		onHide: Class.empty,
		customTarget: null,
		className: 'instantsearch-choices',
		zIndex: 42,
		observerOptions: {},
		fxOptions: {},
		overflown: []
	},

	initialize: function(el, options) {
		this.setOptions(options);
		this.element = $(el);
		this.build();
		this.observer = new Observer(this.element, this.prefetch.bind(this), $merge({
			delay: 400
		}, this.options.observerOptions));
		this.value = this.observer.value;
		this.queryValue = null;
	},

	/**
	 * 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.customTarget)) this.choices = this.options.customTarget;
		else {
			this.choices = new Element('ul', {
				'class': this.options.className,
				styles: {zIndex: this.options.zIndex}
			}).injectInside(document.body);
			this.fix = new OverlayFix(this.choices);
		}
		this.fx = this.choices.effect('opacity', $merge({
			wait: false,
			duration: 200
		}, this.options.fxOptions))
			.addEvent('onStart', function() {
				if (this.fx.now) return;
				this.choices.setStyle('display', '');
				this.fix.show();
			}.bind(this))
			.addEvent('onComplete', function()
			{
				if (this.fx.now) return;
				this.choices.setStyle('display', 'none');
				this.fix.hide();
			}.bind(this)).set(0);
		this.element.setProperty('autocomplete', 'off')
			.addEvent(window.ie ? 'keydown' : 'keypress', this.onCommand.bindWithEvent(this))
			.addEvent('mousedown', this.onCommand.bindWithEvent(this, [true]))
			.addEvent('focus', this.toggleFocus.bind(this, [true]))
			.addEvent('trash', this.destroy.bind(this));
			
			
		document.body.addEvent('click',this.documentclick.bindWithEvent(this,[true]));
	},
	
	documentclick: function(event,mouse)
	{
		var event = new Event(event);
		if(event.target.id!='query' && event.target.className!=this.options.className)
		{
			var parent		= event.target.parentNode;
			while(parent && parent.className!=this.options.className)
			{
				parent			= parent.parentNode;
			}
			
			if(!parent || parent.className!=this.options.className)
			{
				this.hideChoices();
			}
		}	
	},

	destroy: function() {
		this.choices.remove();
	},

	toggleFocus: function(state) {
		this.focussed = state;
		if (!state) this.hideChoices();
	},

	onCommand: function(e, mouse) {
		if (mouse && this.focussed) this.prefetch();
		if (e.key && !e.shift) switch (e.key)
		{
			case 'enter':
				e.preventDefault();
				e.stopPropagation();
				if (this.selected && this.visible)
				{
					this.choiceSelect(this.selected);					
					e.stop();					
				} return;
			case 'up': case 'down':
				if (this.observer.value != (this.value || this.queryValue)) this.prefetch();
				else if (this.queryValue === null) break;
				else if (!this.visible) this.showChoices();
				else {
					try{
						if(this.selected)
						{						
							if(e.key=='up')
							{											
								var prev		= (this.selected.getPrevious());	
								if(prev.tagName.toLowerCase()!="li")
								{	
									while(prev.tagName && prev.tagName.toLowerCase()!='li')
									{
										var prev		= (prev.getPrevious());
									}
									if(!prev.tagName)
									{
										var prev		= this.choices.getLast();
									}
								}										
								this.choiceOver(prev);									
							}else if(e.key=='down')
							{
								var next		= (this.selected.getNext());	
								if(!next || next.tagName.toLowerCase()!="li")
								{			
									while(next.tagName && next.tagName.toLowerCase()!='li')
									{
										var next		= (next.getNext());
									}
									if(!next.tagName)
									{
										var next		= this.choices.getFirst();
										while(next.tagName && next.tagName.toLowerCase()!='li')
										{
											var next		= (next.getNext());
										}
									}
								}										
								this.choiceOver(next);							
							}
						}					
					}catch(e){}
					this.setSelection();
				}
				e.stop(); return;
			case 'esc': this.hideChoices(); return;
		}
		this.value = false;
	},

	setSelection: function()
	{
		
	},

	hideChoices: function()
	{
		if (!this.visible) return;
		this.visible = this.value = false;
		this.observer.clear();
		//this.fx.start(0);
		this.choices.setStyle('opacity', '0');
		
		this.fireEvent('onHide', [this.element, this.choices]);
	},

	showChoices: function()
	{
		if (this.visible || !this.choices.getFirst()) return;
		this.visible = true;
		var pos = this.element.getCoordinates(this.options.overflown);
		this.choices.setStyles({
			left: pos.left,
			top: pos.bottom
		});
		if (this.options.inheritWidth)
		{
			this.choices.setStyle('width', pos.width);
		}else if(this.options.width)
		{
			this.choices.setStyle('width', this.options.width);
		}
		this.choices.setStyle('display', 'block');	
		//this.fx.start(1);
		
		this.choices.setStyle('opacity', '1');
		
		var lis		= this.choices.getElements('li');	
		this.choiceOver(lis[0]);
		this.fireEvent('onShow', [this.element, this.choices]);
	},

	prefetch: function()
	{
		if (this.element.value.length < this.options.minLength)
		{
			this.hideChoices();
		}else if (this.element.value == this.queryValue)
		{
			this.showChoices();
		}else
		{
			this.query();
		}
	},

	updateChoices: function(choices)
	{	
		this.choices.empty();
		this.selected = null;
		if (!choices || !choices.length) return;
		if (this.options.maxChoices < choices.length) choices.length = this.options.maxChoices;
		choices.each(this.options.injectChoice || function(choice, i)
		{
			var el = new Element('li').setHTML(this.markQueryValue(choice));
			el.inputValue = choice;
			this.addChoiceEvents(el).injectInside(this.choices);
		}, this);
		this.showChoices();
	},

	choiceOver: function(el,test)
	{
		if(test)
		{
			el			= test;
		}

		if (this.selected) this.selected.removeClass('instantsearch-selected');
		if(el)
		{
			this.selected = el.addClass('instantsearch-selected');
		}
	},

	choiceSelect: function(el,test)
	{	
		if(test)
		{
			el		= test;
		}
		//this.observer.value = this.element.value = el.inputValue;
		this.hideChoices();
		this.fireEvent('onSelect',el);
		
		//this.fireEvent('onSelect', [this.element], 20);
	},

	/**
	 * markQueryValue
	 *
	 * Marks the queried word in the given string with <span class="instantsearch-queried">*</span>
	 * Call this i.e. from your custom parseChoices, same for addChoiceEvents
	 *
	 * @param		{String} Text
	 * @return		{String} Text
	 */
	markQueryValue: function(txt)
	{
		return txt;
		return (this.options.markQuery && this.queryValue) ? txt.replace(new RegExp('^(' + this.queryValue.escapeRegExp() + ')', 'i'), '<span class="instantsearch-queried">$1</span>') : txt;
	},

	/**
	 * 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]),
			mousedown: this.choiceSelect.bind(this, [el])
		});
	}
});

InstantSearch.Base.implement(new Events);
InstantSearch.Base.implement(new Options);

InstantSearch.Local = InstantSearch.Base.extend({

	options: {
		minLength: 0,
		filterTokens : null
	},

	initialize: function(el, tokens, options) {
		this.parent(el, options);
		this.tokens = tokens;
		if (this.options.filterTokens) this.filterTokens = this.options.filterTokens.bind(this);
	},

	query: function() {
		this.hideChoices();
		this.queryValue = this.element.value;
		this.updateChoices(this.filterTokens());
	},

	filterTokens: function(token) {
		var regex = new RegExp('^' + this.queryValue.escapeRegExp(), 'i');
		return this.tokens.filter(function(token) {
			return regex.test(token);
		});
	}

});

InstantSearch.Ajax = {};

InstantSearch.Ajax.Base = InstantSearch.Base.extend({

	options: {
		postVar: 'searchfor',
		postData: {},
		ajaxOptions: {},
		onRequest: Class.empty,
		onComplete: Class.empty
	},

	initialize: function(el, url, options) {
		this.parent(el, options);
		this.ajax = new Ajax(url, $merge({
			autoCancel: true
		}, this.options.ajaxOptions));
		this.ajax.addEvent('onComplete', this.queryResponse.bind(this));
		this.ajax.addEvent('onFailure', this.queryResponse.bind(this, [false]));
	},

	query: function()
	{
		var data = $extend({}, this.options.postData);
		data[this.options.postVar] = this.element.value;
		this.fireEvent('onRequest', [this.element, this.ajax]);
		this.ajax.request(data);
	},

	/**
	 * queryResponse - abstract
	 *
	 * Inherated classes have to extend this function and use this.parent(resp)
	 *
	 * @param		{String} Response
	 */
	queryResponse: function(resp) {
		this.value = this.queryValue = this.element.value;
		this.selected = false;
		this.hideChoices();
		this.fireEvent(resp ? 'onComplete' : 'onFailure', [this.element, this.ajax], 20);
	}

});

InstantSearch.Ajax.Json = InstantSearch.Ajax.Base.extend({

	queryResponse: function(resp) {
		this.parent(resp);
		var choices = Json.evaluate(resp || false);
		if (!choices || !choices.length) return;
		this.updateChoices(choices);
	}

});

InstantSearch.Ajax.Xhtml = InstantSearch.Ajax.Base.extend({

	options: {
		parseChoices: null
	},

	queryResponse: function(resp)
	{	
		this.parent(resp);
		if (!resp) return;
		this.choices.setHTML(resp).getChildren().each(this.options.parseChoices || this.parseChoices, this);
		this.showChoices();
	},

	parseChoices: function(el)
	{
		if(el && el.tagName && el.tagName.toLowerCase()=="li")
		{
			el.addEvent('click',this.choiceSelect.bindWithEvent(this,el));
			el.addEvent('mouseover',this.choiceOver.bindWithEvent(this,el));		
			var value = el.innerHTML;
			el.inputValue = value;
		}		
		//el.setHTML(this.markQueryValue(value));
	}

});


var OverlayFix = new Class({

	initialize: function(el) {
		this.element = $(el);
		if (window.ie){
			this.element.addEvent('trash', this.destroy.bind(this));
			this.fix = new Element('iframe', {
				properties: {
					frameborder: '0',
					scrolling: 'no',
					src: 'javascript:false;'
				},
				styles: {
					position: 'absolute',
					border: 'none',
					display: 'none',
					filter: 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)'
				}
			}).injectAfter(this.element);
		}
	},

	show: function() {
		if (this.fix) this.fix.setStyles($extend(
			this.element.getCoordinates(), {
				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.remove();
	}

});
