/*
Script: TextboxList.Autocomplete.js
	TextboxList Autocomplete plugin

	Authors:
		Guillermo Rauch
	
	Note:
		License purchased to use in Atmail for unlimited domains
*/

(function(){
	
TextboxList.Autocomplete = function(textboxlist, _options){

	var available_height, element_height = -1, total_height = 0, index, prefix, method, container, list, values = [], searchValues = [], results = [], placeholder = false, current,  currentInput, hidetimer, doAdd, currentSearch, currentRequest, currentRequestTimeout = -1;
	var options = $.extend(true, {
		debounceMs: 500,
		minLength: 1,
		maxResults: 10,
		insensitive: true,
		highlight: true,
		highlightSelector: null,
		mouseInteraction: true,
		onlyFromValues: false,
		minRemoteLength: 3,
		queryRemote: false,
		cacheSize: 250,
    remote: {
			url: '',
			param: 'search',
			extraParams: {},
			loadPlaceholder: 'Please wait...'
    },
		method: 'standard',
		placeholder: 'Type to receive suggestions'
	}, _options);
	
	var init = function(){
		textboxlist.addEvent('bitEditableAdd', setupBit)
			.addEvent('bitEditableFocus', search)
			.addEvent('bitEditableBlur', hide)
			.setOptions({bitsOptions: {editable: {addKeys: 188, stopEnter: false}}});
		if ($.browser.msie) textboxlist.setOptions({bitsOptions: {editable: {addOnBlur: false}}});
		if (textboxlist.getOptions().unique){
			index = [];
			textboxlist.addEvent('bitBoxRemove', function(bit){
				if (bit.autoValue && $.inArray(bit.autoValue, index) != -1) index = index.splice($.inArray(bit.autoValue, index) + 1, 1);
			}, true);
		}
		prefix = textboxlist.getOptions().prefix + '-autocomplete';
		method = TextboxList.Autocomplete.Methods[options.method];
		container = $('<div class="'+ prefix +'" />').appendTo(textboxlist.getContainer());
		if (chk(options.placeholder)) placeholder = $('<div class="'+ prefix +'-placeholder" />').html(options.placeholder).appendTo(container);		
		list = $('<ul class="'+ prefix +'-results" />').appendTo(container).click(function(ev){
			ev.stopPropagation(); ev.preventDefault();
		});
	};
	
	var setupBit = function(bit){
		bit.toElement().keydown(navigate).keyup(function(){ search(); });
	};
	

	var unique_array = function(vals)
	{
	    var uniques = [];
	    for(var i=vals.length;i--;){
	        var val = vals[i];
		
		uniques = $.grep(uniques, function(n) { return n[0] != val[0]; })
		uniques.unshift(val);
	    }
	    return uniques;
	} 

	var search = function(bit){
		
		if (bit) currentInput = bit;
		//**values undef here** if (!values.length) return;
		var search = $.trim(currentInput.getValue()[1]);
		if (search.length < options.minLength) showPlaceholder();
		if (search == currentSearch) return;
		currentSearch = search;
		list.css('display', 'none');
		if (search.length < options.minLength) return;
		if (options.queryRemote && (search.length >= options.minRemoteLength)){
			if (!searchValues[search])
			{
				var data = options.remote.extraParams;
				data[options.remote.param] = search;
				if (currentRequest)
				{
					currentRequest.abort();
				}
				if(currentRequestTimeout == -1)
				{
					currentRequestTimeout = true;
					currentRequest = $.ajax({
						url: options.remote.url,
						data: data,
						dataType: 'json',
						success: function(r){
							searchValues[search] = r;
							values = $.merge(values, r)
							if(values.length > options.cacheSize)
							{
								values = values.slice(0, -1);
							}
							values = unique_array(values);
							showResults(search);
						}
					});
				}
				else
				{
					clearTimeout(currentRequestTimeout);
					
					currentRequestTimeout = setTimeout(function() { currentRequest = $.ajax({
						url: options.remote.url,
						data: data,
						dataType: 'json',
						success: function(r){
							searchValues[search] = r;
							values = $.merge(values, r)
							if(values.length > options.cacheSize)
							{
								values = values.slice(0, -1);
							}
							values = unique_array(values);						
							showResults(search);
						}
					}); currentRequestTimeout = -1;}, options.debounceMs);
				}
			}
		}
		showResults(search);
	};
	
	var showResults = function(search)
	{
		available_height = $(document).height() - container.position().top;
		
		if(values.length > options.cacheSize)
		{
			values = values.slice(0, -1);
		}
		
		if(values.length > 0)
		{
			values = values.sort(function(a, b) { return a[4]<b[4]; });
		}
			
		var results = method.filter(values, search, options.insensitive, options.maxResults);
		if (index) results = $.grep(results, function(v){ return $.inArray(v, index) == -1; });
		hidePlaceholder();
		if (!results.length) return;
		reselect = false;
		currentValue = null
		if(current != null)
		{
			currentValue = current.data('textboxlist:auto:value');
			$.each(results, function(i, r){ if(r == currentValue) reselect = true; });
		}
		if(!reselect)
			blur();
		list.empty().css('display', 'block');
		total_height = 0;
		if(!reselect)
		{
			$.each(results, function(i, r){ addResult(r, search); });
		}
		else
		{
			$.each(results, function(i, r){ element = addResult(r, search); if(r == currentValue) focus(element); });	
		}
		if (options.onlyFromValues) focusFirst();
		results = results;
	};
	
	var addResult = function(r, searched){
		var element = $('<li class="'+ prefix +'-result" />').html(r[3] ? r[3] : r[1]).data('textboxlist:auto:value', r);

		if(element_height == -1)
		{
			element.appendTo(list);
			element_height = element.height();
			total_height += element_height;
		}
		else
		{
			if(total_height + element_height > available_height)
				return false;
		
			element.appendTo(list);
			total_height += element_height;
			
		}
		
		if (options.highlight) $(options.highlightSelector ? element.find(options.highlightSelector) : element).each(function(){
			if ($(this).html()) method.highlight($(this), searched, options.insensitive, prefix + '-highlight');
		});
		if (options.mouseInteraction){
			element.css('cursor', 'pointer').hover(function(){ focus(element); }).mousedown(function(ev){
				ev.stopPropagation(); 
				ev.preventDefault();
				clearTimeout(hidetimer);
				doAdd = true;
			}).mouseup(function(){
				if (doAdd){
					addCurrent();
					currentInput.focus();
					search();
					doAdd = false;
				}
			});
			if (!options.onlyFromValues) element.mouseleave(function(){ if (current && (current.get(0) == element.get(0))) blur(); });	
		}
		
		return element;
	};
	
	var hide = function(){
		hidetimer = setTimeout(function(){
			hidePlaceholder();
			list.css('display', 'none');
			currentSearch = null;
			if (currentRequest) 
			{
				currentRequest.abort();
				php.complete();
				clearTimeout(currentRequestTimeout);
			}
		}, $.browser.msie ? 150 : 0);
	};
	
	var showPlaceholder = function(){
		if (placeholder) placeholder.css('display', 'block');		
	};
	
	var hidePlaceholder = function(){
		if (placeholder) placeholder.css('display', 'none');
	};
	
	var focus = function(element){
		if (!element || !element.length) return;
		blur();
		current = element.addClass(prefix + '-result-focus');
	};
	
	var blur = function(){
		if (current && current.length){
			current.removeClass(prefix + '-result-focus');
			current = null;
		}
	};
	
	var focusFirst = function(){
		return focus(list.find(':first'));
	};
	
	var focusRelative = function(dir){
		if (!current || !current.length) return self;
		return focus(current[dir]());
	};
	
	var addCurrent = function(){
		if(current != null)
		{
			if (currentRequest) 
			{
				currentRequest.abort();
				php.complete();
				clearTimeout(currentRequestTimeout);
			}
			var value = current.data('textboxlist:auto:value');
			var b = textboxlist.create('box', value.slice(0, 3));
			if (b){
				b.autoValue = value;
				//index.push(value);
				currentInput.setValue([null, '', null]);
				b.inject(currentInput.toElement(), 'before');
			}
			blur();
		}
		return self;
	};
	
	var navigate = function(ev){
		var evStop = function(){ ev.stopPropagation(); ev.preventDefault(); };
		switch (ev.which){
			case 38:
				evStop();
				(!options.onlyFromValues && current && current.get(0) === list.find(':first').get(0)) ? blur() : focusRelative('prev');
				break;
			case 40:
				evStop();
				(current && current.length) ? focusRelative('next') : focusFirst();
				break;
			case 8:
					// backspace hit, cancel any remote calls pending
					if (currentRequest) 
					{
						currentRequest.abort();
						php.complete();
						clearTimeout(currentRequestTimeout);
					}

					break;
			case 13:
				evStop();
				if (currentRequest) 
				{
					currentRequest.abort();
					php.complete();
					clearTimeout(currentRequestTimeout);
				}
				if (current && current.length) addCurrent();
				else if (!options.onlyFromValues){
					//var value = htmlentities(currentInput.getValue());				
					var value = currentInput.getValue();
					//make [1] with htmlentities
					value[1] = htmlentities(value[1]);
					var b = textboxlist.create('box', value);
					if (b){
						b.inject(currentInput.toElement(), 'before');
						currentInput.setValue([null, '', null]);
					}
				}
				break;
		}
	};
	
	this.setValues = function(v){
		values = v;
	};
	
	this.setOptions = function(opt){
		options = $.extend(true, options, opt);
	};
	
	this.getOptions = function(){
		return options;
	};
	
	init();
};

TextboxList.Autocomplete.Methods = {
	
	standard: {
		filter: function(values, search, insensitive, max){
			var newvals = [], regexp = new RegExp('\\b' + escapeRegExp(search), insensitive ? 'i' : ''); 
			for (var i = 0; i < values.length; i++){
				if (newvals.length === max) break;
				if (regexp.test(values[i][0]) || regexp.test(values[i][5]))
				{
					newvals.push(values[i]);
				}
			}
			return newvals;
		},
		
		highlight: function(element, search, insensitive, klass)
		{
			var regex = new RegExp('(<[^>]*>)|(\\b'+ escapeRegExp(search) +')', insensitive ? 'ig' : 'g');
			return element.html(element.html().replace(regex, function(a, b, c)
			{
				return (a.charAt(0) == '<') ? a : '<strong class="'+ klass +'">' + c + '</strong>';
			}));
		}
	}
	
};

var chk = function(v){ return !!(v || v === 0); };
var escapeRegExp = function(str){ return str.replace(/([-.*+?^${}()|[\]\/\\])/g, "\\$1"); };

})();
