/**
 * Class for manipulating GoogleSuggest for input form items.
 * 
 * @param input form <input> item that suggest will be available for
 * @param datasource ajax url to request data from
 * @return void
 */
function GoogleSuggest(input, datasource) {
	var datasource = datasource;
	var input = $(input);
	var layer = null;
	var visible = false;
	var keyIgnore = false;
	var lastPosition = null;
	var limit = 8;
	
	var selectedValue = false;
	var hasFocus = false;
	
	/* map input events */
	input
		.blur(function() { hasFocus = false; setTimeout(hide, 200); })
		.click(keyup)
		.focus(keyup)
		.keyup(keyup)
		.keydown(keydown)
		.keypress(keypress)
	;
	
	/* create layer with hints */
	var p = input.offset();
	var width = parseInt(input.width());
	var height = parseInt(input.height());
	var left = parseInt(p.left);
	var top = parseInt(p.top) + 3;
	
	layer = $("<ul/>").addClass('googlesuggest-box').css({
		'display': 'none',
		'left': left+'px',
		'top': top+'px',
		'width': width+'px'
	}).click(onEnter).appendTo('body');
	
	/**
	 * Check if we should ignore keyboard input.
	 * 
	 * @param event
	 * @param restore
	 * @return bool
	 */
	function isKeyIgnored(event, restore) {
		restore = restore || false;
		if(keyIgnore) event.preventDefault();
		
		var result = keyIgnore;
		if(restore) keyIgnore = false;
		
		return result;
	}
	
	/**
	 * Handle keypress event on input
	 * @param event
	 * @return bool
	 */
	function keypress(event) {
		if(isKeyIgnored(event)) return false;
		if(event.keyCode == 13 && visible) return false;
		
		return true;
	}
	
	/**
	 * Handle keypu event on input
	 * @param event
	 * @return bool
	 */
	function keyup(event) {
		hasFocus = true; 
		if(isKeyIgnored(event, true)) return false;
		if(event.keyCode == 13) return onEnter(event);
		
		var text = input.val();
		if(text.length > 2 && text != selectedValue) {
			selectedValue = false;
			setTimeout(function() {
				if(hasFocus && input.val() == text) {
					$.post(datasource, { word: text, limit: limit }, update, 'json');
				}
			}, 200);
		}
		
		return true;
	}
	
	/**
	 * Handle keydown event on input
	 * @param event
	 * @return bool
	 */
	function keydown(event) {
		if(visible) {
			switch(event.keyCode) {
				/* Enter */
				case 13:
					keyIgnore = true;	
					return onEnter(event)
				/* escape */
				case 27:
					keyIgnore = true;
					hide();
					
					event.preventDefault();
					return false;
				/* up arrow */
				case 38:
					keyIgnore = true;
					var prev = layer.find("li.active").removeClass('active').prev();
					if(!prev.length) prev = layer.find("li:last");
					prev.addClass('active');
					
					event.preventDefault();
					return false;
				/* down arrow */
				case 40:
					keyIgnore = true;
					var next = layer.find("li.active").removeClass('active').next();
					if(!next.length) next = layer.find("li:first");
					next.addClass('active');
					
					event.preventDefault();
					return false;
			}
		}
		
		hide(event);
		return true;
	}
	
	/**
	 * Creates list of hints and shows hint layer as needed
	 * @param words
	 * @return void
	 */
	function update(words) {
		words.length ? show() : hide();
		layer.empty();
		
		var separator = input.val().length;
		
		$.each(words, function(key, value) {
			var myText = value.substr(0, separator) + '<strong>' + value.substr(separator) + '</strong>';
			
			$("<li/>").html(myText).mouseover(function() {
				layer.find("li").removeClass('active');
				$(this).addClass('active');
			}).appendTo(layer);
		});
		
		layer.find("li:first").addClass('active');
	}
	
	/**
	 * Show hints layer
	 * @return bool
	 */
	function show() {
		if(visible) return false;
		
		var p = input.offset();
		var width = parseInt(input.outerWidth(false));
		var height = parseInt(input.outerHeight(false));
		var left = parseInt(p.left)+parseInt();
		var top = parseInt(p.top) + height + 3;
		
		layer.css({left: left+'px', top: top+'px', width: width+'px'});
		layer.show();
		
		visible = true;
		return true;
	}
	
	/**
	 * Hide hints layer
	 * @return bool
	 */
	function hide() {
		if(!visible) return false;
		layer.hide();
		visible = false;
		return true;
	}
	
	/**
	 * Handle enter event (this is also called when user click a hint-list item)
	 * @param event
	 * @return bool
	 */
	function onEnter(event) {
		selectedValue = layer.find("li.active").text();
		insert(selectedValue);
		hide();
		
		event.preventDefault();
		return false;
	}
	
	/**
	 * Update input with this word
	 * @param word
	 * @return void
	 */
	function insert(word) {
		input.val(word);
	}
}
