jQuery.searchBox = {
	globalOptions: {
		onSearchError: function() {}
	},
	clear: function($container) {
		if (!$container instanceof jQuery) {
			throw ("$container argument must be instance of jQuery!");
		}
		
		var func = $container.data("clearSearchBox");
		if($.isFunction(func)) {
			func();
		} else {
			throw ("The function \"clearSearchBox\" does not exist for this element!");
		}
	},
	initCloseButton: function($container) {
		if (!$container instanceof jQuery) {
			throw ("$container argument must be instance of jQuery!");
		}
		
		var func = $container.data("initCloseButton");
		if($.isFunction(func)) {
			func();
		} else {
			throw ("The function \"initCloseButton\" does not exist for this element!");
		}
	},
	isResultBoxVisible: function($container) {
		if (!$container instanceof jQuery) {
			throw ("$container argument must be instance of jQuery!");
		}
		
		var func = $container.data("isResultBoxVisible");
		if($.isFunction(func)) {
			return func();
		}
		return false;
	}
};

jQuery.fn.searchBox = function(options) {
	var KEYCODE = {
		CYRILLIC_LETTER: 0,
		BACKSPACE: 8,
		ENTER: 13,
		ESCAPE: 27,
		SPACE: 32,
		UP: 38,
		DOWN: 40,
		DELETE: 46,
		MAIN_BEGIN: 48,
		MAIN_END: 90,
		NUMPAD_BEGIN: 96,
		NUMPAD_END: 105					
	};
	
	// Default options ============================================================================
	var defaultOptions = {
		url: "/bg/search/index/",
		setSearchContext: function() {
			return "item";
		},	
		resultBoxClass: "result_box",
		resultItemClass: "result_item",
		resultActiveItemClass: "result_item_active",
		loaderClass: "search_loader",
		insertLoader: function($container, $loader) {
			$container.after($loader);
		},
		closeBtnClass: "search_engine_cancel",
		insertCloseBtn: function($container, $closeBtn) {
			$container.after($closeBtn);
		},			
		insertResultBox: function($container, $resultBox) {
			$container.after($resultBox);
		},
		searchDelay: 500,
		continuousTraversing: false,
		valueMode: false,
		keepCloseBtnWhenNoResults: false,
		onKeyUp: function(containerVal) {},
		onSearchError: function(XMLHttpRequest, textStatus, errorThrown) {},
		onShowResultBox: function(payload) {},
		onSelectResult: function($element) {},
		onCloseBtnClicked: function() {},
		onEscapeClicked: function() {}
	};
	var opts = $.extend(defaultOptions, jQuery.searchBox.globalOptions);
	opts = $.extend(opts, options);
	
    var $container = $(this[0]); // It's your element
    
    // get the default container value
    var defaultContainerVal = $container.attr("title");
    $container.removeAttr("title");
    $container.data("defaultValue", defaultContainerVal);
    
    // fields =====================================================================================
	var $resultBox = null;
	var $loader = null;
	var $closeButton = null;
    var searchResults;
    var resultCount;
	var $lastActiveResult = $();
    
	// init =======================================================================================
	initContainer();
	initLoader();
	initCloseButton();
	initResultBox();

	// functions to handle the container (search box) =============================================
	function initContainer() {
		$container.bind("keydown.searchBox", function(event) {
			var keyCode = getKeyCode(event);
			if(keyCode == KEYCODE.ESCAPE) {
				event.preventDefault();
				hideResultBox();
				hideCloseButton();
				clearContainer();
				$container.blur();
				opts.onEscapeClicked();
			} else if(keyCode == KEYCODE.UP) {
				startScrolling("up");
			} else if(keyCode == KEYCODE.DOWN) {
				startScrolling("down");
			} else if(keyCode == KEYCODE.ENTER) {
				var $activeResult = null;
				if(resultCount == 1) {
					$activeResult = $(searchResults[0]);
				} else if(resultCount > 1) {
					var activeResultIdx = getActiveResultIdx();
					if(activeResultIdx != -1) {
						$activeResult = $(searchResults[activeResultIdx]);
					}
				}
				if($activeResult != null) {
					onSelectResult($activeResult);
				}
			}
		});
		
		$container.bind("keyup.searchBox", function(event) {
			stopScrolling();
			
			var containerVal = getContainerVal();
			opts.onKeyUp(containerVal);
			
			var keyCode = getKeyCode(event);
			if(containerVal == "") {
				abortXHR();
				hideResultBox();
				hideCloseButton();
			} else if(shouldPerformSearch(keyCode)) {
				startTimeout();
			}
		});		
		
		$container.bind("focus.searchBox", function() {
			if(getContainerVal() == defaultContainerVal) {
				clearContainer();
			}
		});
		
		$container.bind("blur.searchBox", function() {
			if(getContainerVal() == "") {
				setContainerVal(defaultContainerVal);
			}
			deactivateLastActiveResult();
		});
		
		// define functions to call outside the plugin
		$container.data("clearSearchBox", function() {
			hideResultBox();
			clearContainer();
			hideCloseButton();
			$container.blur();
		});
		
		$container.data("isResultBoxVisible", function() {
			return isResultBoxVisible();
		});
	};
	
	function getContainerVal() {
		return $.trim($container.val());
	};
	
	function setContainerVal(value) {
		$container.val(value);
	};
	
	function clearContainer() {
		setContainerVal("");
	};	
	
	// functions to handle the timer for sending the request for results ==========================
	var isTimerRunning = false;
	function startTimeout() {
		if(isTimerRunning === false) {
			setTimeout(function() {
				isTimerRunning = false;
				var containerVal = getContainerVal();
				updateResultBox(containerVal);							
			}, opts.searchDelay);
			isTimerRunning = true;
		}
	};
	
	// functions to handle the loader =============================================================
	function initLoader() {
		$loader = $("<div class='" + opts.loaderClass + "'></div>");
		$loader.css("display", "none");
		opts.insertLoader($container, $loader);
	};
	
	function showLoader() {
		$loader.css("display", "block");
	};
	
	function hideLoader() {
		$loader.css("display", "none");
	};
	
	// functions to handle the close button =======================================================
	function initCloseButton() {
		$closeButton = $("<a class='" + opts.closeBtnClass + "'></a>");
		$closeButton.css("display", "none");
		opts.insertCloseBtn($container, $closeButton);
		
		if(opts.valueMode) {
			// define function that show the close button on init
			var initCloseBtn = function() {
				var val = getContainerVal();
				if(val != "" && val != defaultContainerVal) {
					showCloseButton();
				}				
			};
			
			initCloseBtn();
			
			// define a function to call outside the plugin after init
			$container.data("initCloseButton", function() {
				initCloseBtn();
			});
		}			
		
		$closeButton.bind("click.searchBox", function(event) {
			event.preventDefault();
			hideResultBox();
			hideCloseButton();
			clearContainer();				
			opts.onCloseBtnClicked();
			$container.blur();
		});
	};
	
	function showCloseButton() {
		$closeButton.css("display", "block");
	};
	
	function hideCloseButton() {
		$closeButton.css("display", "none");
	};
	
    // functions to handle the result box =========================================================
	function initResultBox() {
		$resultBox = $("<div class='" + opts.resultBoxClass + "'></div>");
		$resultBox.css("display", "none");
		opts.insertResultBox($container, $resultBox);

		$resultBox.bind("click.searchBox", function(event){
			event.preventDefault();
			var $target = $(event.target);
			if($target.hasClass(opts.resultItemClass) || 
			   $target.hasClass(opts.resultActiveItemClass)) {
				onSelectResult($target);
			}
		});		
		
		$resultBox.bind("mouseover.searchBox", function(event) {
			var $target = $(event.target);
			if($target.hasClass(opts.resultItemClass) || 
			   $target.hasClass(opts.resultActiveItemClass)) {
				deactivateLastActiveResult();
				activateResult($target);				
			}			
		});
		
		$resultBox.bind("mouseout.searchBox", function(event) {
			var $target = $(event.target);
			if($target.hasClass(opts.resultItemClass) || 
			   $target.hasClass(opts.resultActiveItemClass)) {
				deactivateLastActiveResult();				
			}			
		});		
	};
	
	function showResultBox() {
		opts.onShowResultBox({
			container: $container,
			defaultContainerVal: defaultContainerVal,
			resultBox: $resultBox,
			closeButton: $closeButton
		});
		if($.browser.msie) {
			$resultBox.css("display", "block");
		} else {
			$resultBox.fadeIn(100);
		}		
	};
	
	function hideResultBox() {
		if($.browser.msie) {
			$resultBox.css("display", "none");
			clearResultBox();
		} else {
			$resultBox.fadeOut(100, function() {
				clearResultBox();
			});
		}
	};
	
	function clearResultBox() {
		$resultBox.html("");
	};
	
	function isResultBoxVisible() {
		return $resultBox.is(":visible");
	};
	
	function updateResultBox(containerVal) {
		abortXHR();
		if(containerVal != "") {
			hideCloseButton();
			showLoader();
			_xhr = $.ajax({
				url: opts.url,
				type: "POST", 
				data: {
				   	searchPhrase: containerVal,
				   	context: opts.setSearchContext()
			   	}, 
			   	success: function(response){
			   		_xhr = null;
					clearResultBox();
					$resultBox.html(response);
					searchResults = $("." + opts.resultItemClass, $resultBox);
					resultCount = searchResults.length;
					if(resultCount > 0) {
						if(!isResultBoxVisible()) {
							showResultBox();
						}
						showCloseButton();
					} else if(resultCount == 0) {
						hideResultBox();
						if(opts.keepCloseBtnWhenNoResults) {
							showCloseButton();
						} else {
							hideCloseButton();
						}
					}
					hideLoader();
			   	},
			   	error: function(XMLHttpRequest, textStatus, errorThrown) {
			   		clearResultBox();
					if(opts.keepCloseBtnWhenNoResults) {
						showCloseButton();
					} else {
						hideCloseButton();
					}
			   		hideLoader();
			   		hideResultBox();
			   		clearContainer();
			   		opts.onSearchError(XMLHttpRequest, textStatus, errorThrown);
			   	}
			});
		} else {
			hideResultBox();
			hideCloseButton();
			clearContainer();
		}
	};
	
	var _xhr = null;
	function abortXHR() {
		if(_xhr != null) {
			_xhr.abort();
			_xhr = null;
		}
	};
	
	function onSelectResult($result) {
		if(opts.valueMode) {
			setContainerVal($result.text());
		}
		$result = $result.clone();
		hideResultBox();
		if(!opts.valueMode) {
			hideCloseButton();
			clearContainer();
		}
		$container.blur();
		
		opts.onSelectResult($result);
	};	
	
	// function to handle keycodes ================================================================
	function getKeyCode(event) {
		if (event.which != "") {
			return event.which;
		} else if (event.charCode != "") {
			return event.charCode;
		} else if (event.keyCode != "") {
			return event.keyCode;
		}		
		return 0;
	};
	
	function shouldPerformSearch(keyCode) {
		if(
		   (keyCode >= KEYCODE.MAIN_BEGIN && keyCode <= KEYCODE.MAIN_END) 
		   ||
		   (keyCode >= KEYCODE.NUMPAD_BEGIN && keyCode <= KEYCODE.NUMPAD_END)
    	   ||
    	   (keyCode == KEYCODE.CYRILLIC_LETTER)
    	   ||
    	   (keyCode == KEYCODE.SPACE)
    	   ||
		   ((keyCode == KEYCODE.BACKSPACE || keyCode == KEYCODE.DELETE))) {
			return true;
		}
		return false;
	};	
	
	// functions to handle scrolling of the results ===============================================
	var isScrolling = false;
	var scrollTimer = null;
	function startScrolling(direction) {
		if(isScrolling) {
			return;
		}
		isScrolling = true;
		changeActiveResult(direction);
		scrollTimer = setInterval(function() {
			changeActiveResult(direction);				
		}, 100);		
	};
	
	function stopScrolling() {
		clearInterval(scrollTimer);
		scrollTimer = null;
		isScrolling = false;		
	};
	
	function changeActiveResult(direction) {
		var itemToDeactivateIdx = getActiveResultIdx();
		var itemToActivateIdx = -1;
		var $itemToActivate;
		if(resultCount == 1) {
			itemToActivateIdx = itemToDeactivateIdx == -1 ? 0 : -1;
		} else if(resultCount > 1) {
			if(direction == "down") {
				if(itemToDeactivateIdx + 1 > resultCount -1) { 
					if(opts.continuousTraversing === true) {
						itemToActivateIdx = 0;
					} else {
						itemToActivateIdx = itemToDeactivateIdx;
					}
				} else {
					itemToActivateIdx = itemToDeactivateIdx + 1;
				}
			} else if(direction == "up") {
				if(itemToDeactivateIdx - 1 < 0) { 
					if(opts.continuousTraversing === true) {
						itemToActivateIdx = resultCount - 1;
					} else {
						itemToActivateIdx = itemToDeactivateIdx;
					}
				} else {
					itemToActivateIdx = itemToDeactivateIdx - 1;
				}
			}
			
			if(itemToDeactivateIdx != -1) {
				var $itemToDeactivate = $(searchResults[itemToDeactivateIdx]);
				deactivateResult($itemToDeactivate);
			}
		}
		
		if(itemToActivateIdx != -1) {
			$itemToActivate = $(searchResults[itemToActivateIdx]);
			activateResult($itemToActivate);
			setContainerVal($itemToActivate.text());
		}
	};
	
	function getActiveResultIdx() {
		return searchResults.index($("[class*='" + opts.resultActiveItemClass + "']"));
	};
	
	function activateResult($result) {
		$result.removeClass(opts.resultItemClass);
		$result.addClass(opts.resultActiveItemClass);
		$lastActiveResult = $result;		
	};
	
	function deactivateResult($result) {
		$result.removeClass(opts.resultActiveItemClass);
		$result.addClass(opts.resultItemClass);
	};
	
	function deactivateLastActiveResult() {
		deactivateResult($lastActiveResult);
	};
};
