(function($) {
	$.fn.extend( {
		typeahead : function(options) {
			return this.each(function() {
				new $.typeaheadz(this, options);
			});
		},
		typeahead_configure : function(options) {
			return this.trigger("typeahead_configure", [ options ]);
		},
		typeahead_load : function() {
			return this.trigger("typeahead_load");
		},
		typeahead_clear : function() {
			return this.trigger("typeahead_clear");
		}
	});

	$.typeaheadz = function(input, options) {

		var inputbox = input;
		var defaults = {
			field : 'tags' // id of input control (textbox or text area)
			,url : 'jsontags.php' // the remote url to get the suggestion list from
			,tagsep : ',' // multi-value delimiter of field
			,enclose : '' // character to enclose multi-word filters
			,max : 10 // maximum number of results to show in the suggestion list
			,cache : true // cache results from suggestion list or not
			,delay : 500 // pause after which the suggestion list is loaded
			,charMin : 1 // minimum number of chars for filter before a lookup is done
			,dblClick : true // activate suggestion list on double click?
			,postData : null // extra post data specified in object notation
			,visible : true // indicates whether the lookup list will be shown when there are suggestions
			,dataType : 'jsonp' // datatype of return results
			,jsonp : 'jsonp_callback'
			,method : 'GET'
			,onRenderItem : function(row) {
					return decodeURIComponent(row);
				}
				,onSelectItem : function(val) {
					return true;
				}
				,onSelectedItem : function(index, char_count, chars_typed, val, e) {
					return true;
				}
				,onLoadList : function(filter) {
					return true;
				}
				,onLoadedList : function(results) {
					return true;
				}
				,onClear: function() {
					return true;
				}
			};
		var options = $.extend(defaults, options);
		var input = $('#' + options.field);
		var chars_typed = '';
		var char_count = 0;
		$(input).attr("autocomplete", "off");

		var lkup = document.createElement('div');
		$(lkup, inputbox).show();
		input.after(lkup);

		var lkuplst = document.createElement('ol');
		$(lkup, inputbox).append(lkuplst);

		var cursor = -1; // keyboard arrow cursor in suggestion list (0=first position, -1 = no position)
		var length = 0; // length of last suggestion list
		var loading = false; // loading indicator of suggestion list
		var loaded = false; // loaded indicator of suggestion list
		var cacheLst = null; // in-memory suggestion list, used for containing the rich objects inside
		var inserted = false; // state variable to prevent double suggestion list after inserting a value

		var preg_escape = function(str) {
			return (str + '').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!<>\|\:])/g, "\\$1");
		};

		var hideLkup = function() {
			$(lkuplst, inputbox).empty();
			$(lkup, inputbox).hide();
			loaded = false;
			cacheLst = null;
			inserted = false;
		};

		var insertTag = function(filter, tag) {
			var cur = input.val();
			var words = tag.split(' ').length;
			var enclose = (words > 1) ? options.enclose.length > 0 ? options.enclose: '': '';
			cur = cur.replace(eval('/' + preg_escape(filter) + '$/i'), enclose + tag + enclose);
			input.val(cur);
			//cursor = -1;
		};


		var addItem = function(val, filter, index) {
			if (!options.visible) return;

			var row = val;
			var val = options.onRenderItem(val, index, length, filter);

			var li = document.createElement('li');
			lkuplst.appendChild(li);
			
			$(li).bind('mouseover', function(e){
                var e = e || window.event;
				chars_typed = document.getElementById('txtBookSearch').value;
				char_count = chars_typed.length;
                if (cursor > -1)
                    $('li:eq(' + cursor + ')', inputbox).removeClass('hl');
                cursor = index;
                $('li:eq(' + cursor + ')', inputbox).addClass('hl');
            });
			
			var aLink = document.createElement('a');
			$(aLink).attr( {'href' : '#'});
			$(aLink, inputbox).text(val);
			$(aLink, inputbox).addClass(index % 2 == 0 ? 'td-odd' : 'td-even');
			$(aLink, inputbox).html($(aLink, inputbox).text().replace(eval('/(' + preg_escape(filter) + ')/gi'),"<em>$1</em>"));
			li.appendChild(aLink);
			

			$(aLink).click(function(e) {
				options.onSelectItem(row);
				var cur = input.val();
				insertTag(filter, val);
				options.onSelectedItem(index, cur.length, cur, row, e);
				e.preventDefault();
				hideLkup();
				options.onClear();
				inserted = true;
				//input.focus();
			});
		};

		var loadList = function() {
			inserted = false;
			
			var filter = parseFilter(input.val());
			if (options.onLoadList(filter)){
				$(lkuplst, inputbox).empty();
				$.ajax( {
					type : options.method,
					url : options.url,
					data : $.extend({
								ahead : encodeURIComponent(filter),
								total : options.max
							}, options.postData),
					dataType : options.dataType,
					jsonp : options.jsonp,
					cache : options.cache,
					success : function(json) {
						if (filter != parseFilter(input.val())) {
							loadList();
						} else {
							$(lkuplst, inputbox).empty();
							length = json.length;
							cacheLst = json;
							cursor = -1;
							for (i = 0; i < json.length && i < options.max; i++) {
								addItem(json[i], filter, i);
							}
							if (options.visible) {
								$(lkup, inputbox).show();
							}
							loading = false;
							loaded = true;
							options.onLoadedList(json);
						}
					},
					error : function(XMLHttpRequest, textStatus, errorThrown) {
						length = 0;
						cacheLst = null;
						loading = false;
						loaded = false;
						options.onLoadedList(false);
					}
				});
			};
		};

		var parseFilter = function(val) {
			if (options.tagsep.length == 0)
				return val;

			if (val.indexOf(options.tagsep) > -1) {
				if (options.tagsep == ' ')
					val = val.substring(val.lastIndexOf(options.tagsep) + 1,val.length);
				else
					val = jQuery.trim(val.substring(val.lastIndexOf(options.tagsep) + 1, val.length));
			}
			return val;
		};

		var triggerLoad = function() {
			if (inserted) return false;
			else {
				var filter = parseFilter(input.val());

				if (filter.length >= options.charMin) {
					loading = true;
					setTimeout(function() {loadList()},options.delay);
				} else { hideLkup(); options.onClear();}
			}
		}

		$("*",input.form).focus(function(e){
			//if (this.id == options.field) {triggerLoad();} else {hideLkup(); options.onClear();};
		});
		input.dblclick(function(e){
			if (options.dblClick && !loading) triggerLoad();
		});

		$(lkuplst,inputbox).blur(function(e) {
			hideLkup();
			options.onClear();
		});

		var handleSpecials = function(e) {
			var cur = input.val();
			var e = e || window.event;
			var key = e.charCode || e.keyCode;

			if (!loaded){
				switch (key) {
					case 40: { //Down key pressed
							triggerLoad();
						}
						break;
				}
				return true;
			}

			switch (key) {
			case 9: {// TAB key pressed
					cursor = ((cursor + 1) < length) ? cursor + 1 : cursor;
					if (cursor < length) {
						$('li:eq(' + cursor + ')', inputbox).addClass('hl');
						if ((cursor - 1) > -1)
							$('li:eq(' + (cursor - 1) + ')', inputbox).removeClass('hl');
							e.preventDefault();
					}
				}
				break;
			case 40: {// DOWN key pressed

					if ((cursor+1) < length) {
						cursor++;
						$('li:eq(' + cursor + ')', inputbox).addClass('hl');
						if ((cursor - 1) > -1)
							$('li:eq(' + (cursor - 1) + ')', inputbox).removeClass('hl');
						insertTag(parseFilter(input.val()), $('li:eq(' + (cursor) + ')', inputbox).text());
						e.preventDefault();
					}else if((cursor+1) == length){
						$('li:eq(' + cursor + ')', inputbox).removeClass('hl');
						cursor = 0;
						$('li:eq(' + cursor + ')', inputbox).addClass('hl');
						insertTag(parseFilter(input.val()), $('li:eq(' + (cursor) + ')', inputbox).text());
						e.preventDefault();
					}
				}
				break;
			case 38: {// UP key pressed
					if ((cursor - 1) >= 0) {
						cursor--;
						$('li:eq(' + cursor + ')', inputbox).addClass('hl');
						$('li:eq(' + (cursor + 1) + ')', inputbox).removeClass('hl');
						insertTag(parseFilter(input.val()), $('li:eq(' + (cursor) + ')', inputbox).text());
						e.preventDefault();
					}else if (cursor == 0){
						hideLkup();
					}
				}
				break;
			case 13: {// ENTER key pressed
					if (input[0].type != "textarea")
						//e.preventDefault();
					if (cursor >= 0 && cursor < length) {
						var row = cacheLst[cursor];
						
						options.onSelectItem(row);
						insertTag(parseFilter(input.val()), $('li:eq(' + (cursor) + ')', inputbox).text());
						options.onSelectedItem(cursor, cur.length, cur, row, e);
						//e.preventDefault();
						hideLkup();
						options.onClear();
					}
				}
				break;
			case 27: {// ESC key pressed
					hideLkup();
					options.onClear();
					e.preventDefault();
				}
				break;
			}
		};

		var handleKey = function(e) {
			var e = e || window.event;
			var key = e.charCode || e.keyCode;

			if (key == 13) return true;
			if (key > 8 && key < 46 && key != 32) { return false; }

			if (loading == false) { triggerLoad(); }
			if (options.visible) { $(lkup, inputbox).show(); }
		};

		$(input).keyup(handleKey);
		$(input).keydown(handleSpecials);
		$(inputbox).bind("typeahead_configure", function() { $.extend(options, arguments[1]); });
		$(inputbox).bind("typeahead_load", function() { triggerLoad(); });
		$(inputbox).bind("typeahead_clear", function() { 
			hideLkup(); 
			options.onClear();
		});
	};
})(jQuery);