/*
* Autocomplete - jQuery plugin 1.0 Alpha
*
* Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* Revision: $Id: jquery.autocomplete.js 1729 2007-04-17 20:04:10Z joern $
*
*/

jQuery.fn.extend({
	autocomplete: function(urlOrData, options) {
		var isUrl = typeof urlOrData == "string";
		options = jQuery.extend({}, jQuery.Autocompleter.defaults, {
			url: isUrl ? urlOrData : null,
			data: isUrl ? null : urlOrData,
			delay: isUrl ? jQuery.Autocompleter.defaults.delay : 10
		}, options);
		return this.each(function() {
			new jQuery.Autocompleter(this, options);
		});
	},
	result: function(handler) {
		return this.bind("result", handler);
	},
	search: function() {
		return this.trigger("search");
	}
});

jQuery.Autocompleter = function(input, options) {

	var KEY = {
		UP: 38,
		DOWN: 40,
		DEL: 46,
		TAB: 9,
		RETURN: 13,
		ESC: 27,
		COMMA: 188,
		//ja dodo
		LEFT: 37,
		RIGHT: 40,
		HOME: 36,
		END: 35
	};

	// Create jQuery object for input element
	var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
	var timeout;
	var previousValue = "";
	var cache = jQuery.Autocompleter.Cache(options);
	var hasFocus = 0;
	var lastKeyPressCode;
	var select = jQuery.Autocompleter.Select(options, input, selectCurrent);

	$input.keydown(function(event) {

		if ($input.val().length > 0)
			$("#google_"+$input.attr("id")+":hidden").fadeIn();
			$("#add_tag_label").text("Press Enter when done");

		// track last key pressed
		lastKeyPressCode = event.keyCode;
		switch(event.keyCode) {
		
			case KEY.UP:
				event.preventDefault();
				if ( select.visible() ) {
					select.prev();
				} else {
					onChange(0, true);
				}
				break;

			case KEY.DOWN:
				event.preventDefault();
				if ( select.visible() ) {
					select.next();
				} else {
					onChange(0, true);
				}
				break;

			// matches also semicolon
			case options.multiple && jQuery.trim(options.multipleSeparator) == "," && KEY.COMMA:
			case KEY.TAB:
				break;
			case KEY.RETURN:

				if( selectCurrent() ){
					// make sure to blur off the current field
					//if( !options.multiple )
						//$input.blur();
					event.preventDefault();
					$('#JT').remove();
				}
				break;

			case KEY.ESC:
				select.hide();
				break;

			case KEY.LEFT:
				break;

			case KEY.RIGHT:
				break;

			case KEY.HOME:
				break;

			case KEY.END:
				break;

			default:
				clearTimeout(timeout);
				timeout = setTimeout(onChange, options.delay);
				break;
		}
	}).keypress(function() {
		// having fun with opera - remove this binding and Opera submits the form when we select an entry via return
	}).focus(function(){
		// track whether the field has focus, we shouldn't process any
		// results if the field no longer has focus
		hasFocus++;
	}).blur(function() {
		hasFocus = 0;
		hideResults();
	}).click(function() {
		// show select when clicking in a focused field
		if ( hasFocus++ > 1 && !select.visible() ) {
			onChange(0, true);
		}
	}).bind("search", function() {
		function findValueCallback(q, data) {
			var result;
			if( data && data.length ) {
				for (var i=0; i < data.length; i++) {
					if (data[i].result.toLowerCase() == q.toLowerCase() ) {
						result = data[i];
						break;
					}
				}
			}
			$input.trigger("result", result && [result.data, result.value]);
		}
		jQuery.each(trimWords($input.val()), function(i, value) {
			request(value, findValueCallback, findValueCallback);
		});
	});
	
	hideResultsNow();


	function selectCurrent() {

		var selected = select.selected();
		if (!selected)
			return false;

		var v = selected.result;

		previousValue = v;
		if ( options.multiple ) {
			var words = trimWords($input.val());
			if ( words.length > 1 ) {
				v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
			}
			v += options.multipleSeparator;
		}

		var what = $input.attr("id");
		var tag_name = "";

		var keyword = lastWord($input.val());

		if (what == "subject") {
			$input.val(v);
		}
		else {
		//object, tags
			var ar = /((<em>(.*)<\/em>))(.*)/.exec(v);
			if (ar) {
				tag_name = unicode2string(ar[4].replace("; ", ""));
				tag_name = tag_name.replace(/ <b>[^<]*<\/b>/g, "");

				var tag_name_eng = unicode2string(ar[3].split('|||')[0]);
			}

			if (tag_name.search(/\(disambiguation\)/) != -1) {
				var new_tag_name = tag_name.replace(" (disambiguation)", "");
				$input.val(new_tag_name);
				tag_from = new_tag_name;
				googleSearch(new_tag_name);
			}
			else
				saveVote('autocomplete', what, tag_name, tag_name_eng);

			if (tag_name.search(/\(/) != -1) {
				var ar = /[^\(]*/.exec(tag_name);
				if (keyword.toUpperCase() == $.trim(ar[0]).toUpperCase()) {
					saveRedirect(keyword, tag_name);
				}
			}

			if (what == "convert_tag" || what == "global_name") {
				$input.val(tag_name_eng);
			}
		}

		hideResultsNow();
		$input.trigger("result", [selected.data, selected.value]);
		return true;
	}


	function onChange(crap, skipPrevCheck) {
		if( lastKeyPressCode == KEY.DEL ) {
			select.hide();
			return;
		}

		var currentValue = $input.val();

		if ( !skipPrevCheck && currentValue == previousValue )
			return;
		
		previousValue = currentValue;
		currentValue = lastWord(currentValue);

		if (currentValue.length >= options.minChars) {
			$input.addClass(options.loadingClass);
			if (!options.matchCase)
				currentValue = currentValue.toLowerCase();
			request(currentValue, receiveData, stopLoading);
		} else {
			stopLoading();
			select.hide();
		}
	};
	
	function trimWords(value) {
		if ( !value ) {
			return [""];
		}
		var words = value.split( jQuery.trim( options.multipleSeparator ) );
		var result = [];
		jQuery.each(words, function(i, value) {
			if ( jQuery.trim(value) )
				result[i] = jQuery.trim(value);
		});
		return result;
	}
	
	function lastWord(value) {
		if ( !options.multiple )
			return value;
		var words = trimWords(value);
		return words[words.length - 1];
	}
	
	// fills in the input box w/the first match (assumed to be the best match)
	function autoFill(q, sValue){
		// autofill in the complete box w/the first match as long as the user hasn't entered in more data
		// if the last user key pressed was backspace, don't autofill
		if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != 8 ) {
			// fill in the value (keep the case the user has typed)
			$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
			// select the portion of the value not typed by the user (so the next character will erase)
			jQuery.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
		}
	};

	function hideResults() {
		clearTimeout(timeout);
		timeout = setTimeout(hideResultsNow, 200);
	};

	function hideResultsNow() {
		select.hide();
		clearTimeout(timeout);
		stopLoading();
		// TODO fix mustMatch...
		if (options.mustMatch) {
			if ($input.val() != previousValue) {
				//selectCurrent();
			}
		}
	};

	function receiveData(q, data) {
		if ( data && data.length && hasFocus ) {
			stopLoading();
			select.display(data, q);
			autoFill(q, data[0].value);
			select.show();
		} else {
			hideResultsNow();
		}
	};

	function request(term, success, failure) {
		if (!options.matchCase)
			term = term.toLowerCase();
		var data = cache.load(term);
		// recieve the cached data
		if (data && data.length) {
			success(term, data);
		// if an AJAX url has been supplied, try loading the data now
		} else if( (typeof options.url == "string") && (options.url.length > 0) ){
			jQuery.ajax({
				url: options.url,
				data: jQuery.extend({
					q: lastWord(term),
					label: escape(string2unicode(lastWord(term))),
					limit: options.max
				}, options.extraParams),
				success: function(data) {
					//enableEnter = false;
					var parsed = options.parse && options.parse(data) || parse(data);
					cache.add(term, parsed);
					success(term, parsed);
				}
			});
		} else {
			failure(term);
		}
	}
	
	function parse(data) {
		var parsed = [];
		var rows = data.split("\n");
		for (var i=0; i < rows.length; i++) {
			var row = jQuery.trim(rows[i]);
			if (row) {
				row = row.split(";");
				parsed[parsed.length] = {
					data: row,
					value: row[0],
					result: options.formatResult && options.formatResult(row) || row[0]
				};
			}
		}
		return parsed;
	}

	function stopLoading() {
		$input.removeClass(options.loadingClass);
	}

}

jQuery.Autocompleter.defaults = {
	inputClass: "ac_input",
	resultsClass: "ac_results",
	loadingClass: "ac_loading",
	minChars: 1,
	delay: 400,
	matchCase: false,
	matchSubset: true,
	matchContains: false,
	cacheLength: 10,
	mustMatch: false,
	extraParams: {},
	selectFirst: true,
	max: 10,
	autoFill: false,
	width: 0,
	multiple: false,
	multipleSeparator: ", "
};


jQuery.Autocompleter.Cache = function(options) {

	var data = {};
	var length = 0;

	function matchSubset(s, sub) {
		if (!options.matchCase) 
			s = s.toLowerCase();
		var i = s.indexOf(sub);
		if (i == -1) return false;
		return i == 0 || options.matchContains;
	};

	function add(q, value) {
		if (length > options.cacheLength) {
			this.flush();
		}
		if (!data[q]) {
			length++;
		}
		data[q] = value;
	}

	// if there is a data array supplied
	if( options.data ){
		var stMatchSets = {},
			nullData = 0;

		// no url was specified, we need to adjust the cache length to make sure it fits the local data store
		if( !options.url ) options.cacheLength = 1;
		
		stMatchSets[""] = [];

		// loop through the array and create a lookup structure
		jQuery.each(options.data, function(i, rawValue) {
			// if row is a string, make an array otherwise just reference the array
			
			value = options.formatItem
				? options.formatItem(rawValue, i+1, options.data.length)
				: rawValue;
			var firstChar = value.charAt(0).toLowerCase();
			// if no lookup array for this character exists, look it up now
			if( !stMatchSets[firstChar] )
				stMatchSets[firstChar] = [];
			// if the match is a string
			var row = {
				value: value,
				data: rawValue,
				result: options.formatResult && options.formatResult(rawValue) || value
			}
			
			stMatchSets[firstChar].push(row);
			
			if ( nullData++ < options.max ) {
				stMatchSets[""].push(row);
			}
			
		});

		// add the data items to the cache
		jQuery.each(stMatchSets, function(i, value) {
			// increase the cache size
			options.cacheLength++;
			// add to the cache
			add(i, value);
		});
	}
	
	return {
		flush: function() {
			data = {};
			length = 0;
		},
		add: add,
		load: function(q) {
			if (!options.cacheLength || !length)
				return null;
			if (data[q])
				return data[q];
			if (options.matchSubset) {
				for (var i = q.length - 1; i >= options.minChars; i--) {
					var c = data[q.substr(0, i)];
					if (c) {
						var csub = [];
						jQuery.each(c, function(i, x) {
							if (matchSubset(x.value, q)) {
								csub[csub.length] = x;
							}
						});
						return csub;
					}
				}
			}
			return null;
		}
	};
};

jQuery.Autocompleter.Select = function (options, input, select) {
	var CLASSES = {
		ACTIVE: "ac_over"
	};
	
	//$(".ac_results").remove();

	// Create results
	var element = jQuery("<div>")
		.hide()
		.addClass(options.resultsClass)
		.appendTo("#"+jQuery(input).attr("id")+"_ac_holder");

	var list = jQuery("<ul>").appendTo(element).mouseover( function(event) {
		active = jQuery("li", list).removeClass(CLASSES.ACTIVE).index(target(event));

		if ($(input).attr("id") != "subject") {
			JT_show($(".ac_results li:eq("+active+")")[0], $(input).attr("id")+"_ac_holder .ac_results li");
		}

		jQuery(target(event)).addClass(CLASSES.ACTIVE);
	}).mouseout( function(event) {
		jQuery(target(event)).removeClass(CLASSES.ACTIVE);
	}).click(function(event) {
		jQuery(target(event)).addClass(CLASSES.ACTIVE);
		select();
		input.focus();
		return false;
	});

	var listItems,
		active = -1,
		data,
		term = "";
		
	if( options.width > 0 )
		element.css("width", options.width);
		
	function target(event) {

		var element = event.target;
		if (element)
			while(element.tagName != "LI")
				element = element.parentNode;

		return element;
	}
	
	function moveSelect(step) {
		active += step;
		
		if ($(input).attr("id") != "subject" && data[active]) {
			JT_show($(".ac_results li:eq("+active+")")[0], $(input).attr("id")+"_ac_holder .ac_results li");
		}

		wrapSelection();
		listItems.removeClass(CLASSES.ACTIVE).eq(active).addClass(CLASSES.ACTIVE);
	};


	function wrapSelection() {
		if (active < 0) {
			active = listItems.size() - 1;
		} else if (active >= listItems.size()) {
			active = 0;
		}
	}
	
	function limitNumberOfItems(available) {
		return (options.max > 0) && (options.max < available)
			? options.max
			: available;
	}
	
	function dataToDom() {
		var num = limitNumberOfItems(data.length);

		for (var i=0; i < num; i++) {
			if (data[i].value.search(/\(disambiguation\)/) != -1) {
				data[i].dis= true;
				var temp = data[num-1];
				data[num-1] = data[i];
				data[i] = temp;
			}
			else
				data[i].dis= false;
		}

		for (var i=0; i < num; i++) {
			if (!data[i])
				continue;
			function highlight(value) {
				return value.replace(new RegExp("(" + escapeRegex(value) + ")", "gi"), "<strong>$1</strong>");
			}
			var li = jQuery("<li>").html( options.formatItem 
					? highlight(options.formatItem(data[i].data, i+1, num))
					: highlight(data[i].value) )
			if (data[i].dis)
				li.addClass("d");
			li.appendTo(list);
		}

		listItems = list.find("li");

		if ( options.selectFirst ) {
			listItems.eq(0).addClass(CLASSES.ACTIVE);
			active = 0;
			//if ($(input).attr("id") != "subject")
				//JT_show($(".ac_results li:first")[0], $(input).attr("id")+"_ac_holder .ac_results li");
		}
	}
	
	return {
		display: function(d, q) {
			data = d;
			term = q;
			list.empty();
			if (enableEnter !== false) {
				dataToDom();
			}
			else {
				$(".ac_results").remove();
			}
		},
		next: function() {
			moveSelect(1);
		},
		prev: function() {
			moveSelect(-1);
		},
		hide: function() {
			element.hide();
			$('#JT').remove();
			active = -1;
		},
		visible : function() {
			return element.is(":visible");
		},
		current: function() {
			return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
		},
		show: function() {

			element.show();
/*
			var what = jQuery(input).attr("id");

			element.css({
				width: options.width > 0 ? options.width : jQuery(input).width(),
				top: (what == "object") ? 35 : 10,
				left: (what == "object") ? 313 : 10
			}).show();
			//active = -1;
			//listItems.removeClass(CLASSES.ACTIVE);
*/

		},
		selected: function() {
			return data && data[active];
		}
	};
}
