/**
 * Suggest Class
 *
 * Gets suggestions for a given query from a database and
 * shows them in a UL applied to a given textfield
 *
 * @package   Occhio Design Ajax Library
 * @copyright 2007 Occhio Design
 * @author    Gijs Wilbrink
 * @version   0.1
 */

function IMM_TagSuggest(id, oOptions)
{

	/**
 	 * Initialize function (JavaScript class constructor)
	 *
	 * Initialize the class: set all class properties and call the first necessary functions
	 * Tales one mandatory variable (id) and tons of optional variables
	 *
	 * @param	int    	id				The id of the textfield, on which the autosuggest tool should be applied
	 * @param	string 	url				Optional url of the server side script that fetches the results and returns them to JavaScript; default = "/lib/suggest/suggest.php"
	 * @param	bool   	enableAmount	Optional show the amount of results per suggestion (make sure you also enable this server-side); default = false
	 * @param	string	boxClass 		Optional CSS class of the suggestion box; default = "suggestions"
	 * @param	string	suggestionClass	Optional CSS class of eacht suggestion entry; = default = "suggestion"
	 * @param	int		idleTime		Optional waiting time before requesting suggestions, so that it only fires when user stops typing for a short while; default = 200
	 * @param	int		minLength		Optional minimum required amount of characters in the textfield before requesting suggestions; default = 2
	 * @return	bool	true
	 */ 

	this.init = function()
	{
		var OD_Suggest = this;
		
		this.id = id;
		this.iTimeoutId = null;
		this.oXmlHttp = false;
		this.oCurrentHighlight = false;
		this.textField = document.getElementById(this.id);
		this.includeJsFile("/static/lib/ajax/zxml.js");
		this.includeJsFile("/static/lib/ajax/json.js");
		this.includeJsFile("/static/lib/common/common.js");
		this.parentForm = $(this.textField).parents("form")[0];
		this.parentForm.setAttribute("autocomplete", "off");
		this.textFieldValue = null;
		this.boxShown = false;
		this.oOptions = this.augment({
			url: "/lib/suggest/suggest.php",
			enableAmount: false,
			boxClass: "",
			suggestionClass: "",
			idleTime: 100,
			minLength: 2,
			autoSubmit: true
		}, oOptions);
		
		this.suggestionsBox = this.prepareSuggestionsBox();
		this.setBoxClass(false);
		this.setTextfieldEvents();
		
		document.body.onclick = function()
		{
			OD_Suggest.hideSuggestionsBox();
		};
		
		return true;
	};

	/**
 	 * Prepare Suggestions Box
	 *
	 * Creates a UL which can contain keyword suggestions
	 *
	 * @return	object	ulSuggestions
	 */ 	 
	this.prepareSuggestionsBox = function()
	{
		var ulSuggestions = document.getElementById("OD_Suggestions");
		if(!ulSuggestions) {
    		ulSuggestions = document.createElement("ul");
    		ulSuggestions.id = "OD_Suggestions";
			$(ulSuggestions).addClass("OD_Suggestions");
			$(ulSuggestions).addClass(this.oOptions.boxClass);
			if(InternetExplorer()) {
				ulSuggestions.style.overflow =  "hidden";
			}
			document.body.appendChild(ulSuggestions);
		}
		return ulSuggestions;
	};
	
	/**
 	 * Hide Suggestions Box
	 *
	 * Empty and hide the UL with suggestions	
	 * 	 
	 * @return	bool	true
	 */ 
	 
	this.hideSuggestionsBox = function()
	{
		this.suggestionsBox.innerHTML = "";
		this.setBoxClass(false);
		this.boxShown = false;
		return true;
	};
	
	/**
 	 * Show Suggestions Box
	 *
	 * Show the UL with suggestions	
	 * 	 
	 * @return	bool	true
	 */ 
	 
	this.showSuggestionsBox = function()
	{
		var self = this;
		var extraMargin = 0;
		if (this.textField.marginLeft) {
    		extraMargin = this.textField.marginLeft;
    	}
		
		this.textField.parentNode.appendChild(this.suggestionsBox);
		this.suggestionsBox.style.left = this.textField.offsetLeft + extraMargin + "px";
		this.suggestionsBox.style.top = this.textField.offsetTop + extraMargin + this.textField.offsetHeight + "px";
	
		if(InternetExplorer()) {
			this.suggestionsBox.style.width = this.textField.offsetWidth + "px";
		} else {
			this.suggestionsBox.style.minWidth = this.textField.offsetWidth + "px";
		}
		
		this.setBoxClass(true);
		this.boxShown = true;
		return true;
	};
	
	
	/**
 	 * Set Box Class
	 *
	 * Sets the class of the Suggestions UL to "active" or "inactive" 
	 *
	 * @param	bool   	active  true = active, false = inactive	
	 * @return	bool	true
	 */ 
	 
	this.setBoxClass = function(active)
	{
	
		$(this.suggestionsBox).removeClass("OD_Suggestions-active");
		$(this.suggestionsBox).removeClass("OD_Suggestions-inactive");
		if(active) {		
			$(this.suggestionsBox).addClass("OD_Suggestions-active");

		} else {
			$(this.suggestionsBox).addClass("OD_Suggestions-inactive");
		}
		
		
		
		return true;
	};
	
	/**
 	 * Set Textfield Events
	 *
	 * Sets the OnKeyUp Event Handler for the textfield
	 * The handler checks which key was pressed and calls a suited function
	 *
	 * @return	bool	true or false
	 */ 
	 
	this.setTextfieldEvents = function()
	{
		var OD_Suggest = this;
		$(OD_Suggest.textField).attr("autocomplete", "off");
		OD_Suggest.textField.onkeyup = function(e)
		{
			// Cancel all previous calls
			clearTimeout(OD_Suggest.iTimeoutId);
			
			// Get key code		
			key = OD_Suggest.getKeyCode(e);
			
			
			var returnValue = true;
			// Check key and react
			switch (key) {
				case 27: // escape: hide suggestions
					OD_Suggest.enterPressed = false;
					OD_Suggest.hideSuggestionsBox();
					returnValue = false;
				break;
				case 13: // enter: hide suggestions 
					OD_Suggest.enterPressed = true;
					if(OD_Suggest.oCurrentHighlight) {
						OD_Suggest.passSuggestion(OD_Suggest.oCurrentHighlight);
						OD_Suggest.hideSuggestionsBox();
					} 
					
					returnValue = false;
					
				break;
				case 38: // up arrow: walk the list of suggestions upwards
					OD_Suggest.enterPressed = false;
					if (OD_Suggest.textField.value !== null && OD_Suggest.textField.value !== "") {
						OD_Suggest.walkList("up");
					}
					returnValue = false;
				break;
				case 40: // down arrow: walk the list of suggestions downwards
					OD_Suggest.enterPressed = false;
					if (OD_Suggest.textField.value !== null && OD_Suggest.textField.value !== "") {
						OD_Suggest.walkList("down");
					}
					returnValue = false;
				break;
				case 37:
				case 39:
					OD_Suggest.enterPressed = true;
					returnValue = true;
				break;
				default: //everything else: get suggestions from server
					OD_Suggest.enterPressed = true;
					if (OD_Suggest.textField.value.length < OD_Suggest.oOptions.minLength) {
						OD_Suggest.hideSuggestionsBox();		
					} else {					
						OD_Suggest.iTimeoutId = setTimeout(function () {
							OD_Suggest.getSuggestions();
						}, OD_Suggest.oOptions.idleTime);	
										
					}	
			}
			return returnValue;
		};
		
		OD_Suggest.textField.onkeypress = function(e)
		{
			var key = OD_Suggest.getKeyCode(e);
			if(key == 13) {
				if(OD_Suggest.oCurrentHighlight !== false) {
					return false;
				}
			}
		};
	};
	
	/**
 	 * Get Key Code
	 *
	 * Cross-browser way of checking which key was pressed and returning the key code in one format
	 * 	 
	 * @param	event	e			The onKeyUp event
	 * @return	string	keycode		The resulting key code of the event
	 */ 
	 
	this.getKeyCode = function(e)
	{
	    if (document.layers) {
	        return e.which;
	    } else if (document.all) {
	        return event.keyCode;
	    } else if (document.getElementById) {
	        return e.keyCode;
	    } else {
			return 0;
		}
	};
	
	/**
 	 * Walk List
	 *
	 * Walk through the list of suggestions, highlighting the selected suggestion	
	 * 	 
	 * @ param	string	direction	The direction of which the list should be walked through
	 * @return	bool	true
	 */ 
	  
	this.walkList = function(direction)
	{
		if (direction == "down") {
			if(!this.oCurrentHighlight) {
				this.oCurrentHighlight = this.suggestionsBox.firstChild;
			} else if(this.oCurrentHighlight.nextSibling) {
				this.oCurrentHighlight = this.oCurrentHighlight.nextSibling;
			} else {
				this.oCurrentHighlight = this.oCurrentHighlight;
			}
		}		
		else if(direction == "up") {			
			if(this.oCurrentHighlight && this.oCurrentHighlight.previousSibling) {
				this.oCurrentHighlight = this.oCurrentHighlight.previousSibling;
			} else {
				this.oCurrentHighlight = false;
			}	
		} 
		if(this.oCurrentHighlight) {
			this.highlightSuggestion(this.oCurrentHighlight);
		} else {
			this.highlightNone();
		}
		
		return true;
	};

	/**
 	 * Get Suggestions
	 *
	 * Ajax Request to oOptions.url to get the suggstions matching the value of the textfield	
	 * 	 
	 * @return	void	Calls showSuggestions() if results were found
	 */ 
	 
	this.getSuggestions = function()
	{
			
		var self = this;
	    
	    if (!self.oXmlHttp) {
	        self.oXmlHttp = zXmlHttp.createRequest();
	    } else if (self.oXmlHttp.readyState !== 0) {
	        self.oXmlHttp.abort();
	    }
	    
		// Get suggestions
		this.textFieldValue = self.GetValue();
		if(this.textFieldValue.length >= self.oOptions.minLength && this.textFieldValue != "%2C") {
    	    var queryString = "query=" + this.textFieldValue;
    	    self.oXmlHttp.open("get", self.oOptions.url + "?" + queryString, true);
    		
    	    self.oXmlHttp.onreadystatechange = function () {               
    	        
    			if (self.oXmlHttp.readyState == 4 && self.oXmlHttp.status == 200) {
    				self.hideSuggestionsBox();
    				if(self.oXmlHttp.responseText != "false" && (self.textField.value.length >= self.oOptions.minLength)) {
    					self.showSuggestions(self.oXmlHttp.responseText.parseJSON());	
    				}
    	        }
    	    };
    		self.oXmlHttp.setRequestHeader("X-Requested-With", "XMLHttpRequest");
    		
    		// Send request
		
		    self.oXmlHttp.send(null);
		} else {
			self.hideSuggestionsBox();
		}
	    
	};
	
	this.GetValue = function()
	{	
		var delimiter = ",";
		var value = this.textField.value;
		var cursor = this.getSelectionStart(this.textField);
		var prevComma = value.lastIndexOf(delimiter, cursor);
		if(prevComma == -1) {
			prevComma = 0;
		} else {
			prevComma = prevComma + 1;
		}
		
		if(cursor != 0) cursor = cursor - 1;
		var nextComma = value.indexOf(delimiter, cursor);
		if(nextComma == -1) nextComma = 99999;
		var fragment = encodeURIComponent(this.trim(value.substring(prevComma, nextComma)));
		return	encodeURIComponent(fragment);	
	};
	
	/**
 	 * Show Suggestions
	 *
	 * Fills the Suggestions UL Box with LI's containing the suggestions	
	 * 	 
	 * @return	void	Calls setBoxClass() function, setting the UL class to "active"
	 */ 
	 
	this.showSuggestions = function(aSuggestions)
	{
		var OD_Suggest = this;
	
		for(var i = 0; i<aSuggestions.length; i++) {
			var newSuggestion = document.createElement("li");
			newSuggestion.id = "suggestion-" + i;
			$(newSuggestion).addClass("OD_Suggestion");
			$(newSuggestion).addClass(this.oOptions.suggestionClass);
			newSuggestion.innerHTML = aSuggestions[i].result;
			newSuggestion.onmouseover = function()
			{
				OD_Suggest.highlightSuggestion(this);
						
			};
			
			newSuggestion.onclick = function()
			{			
				OD_Suggest.passSuggestion(this);
				OD_Suggest.hideSuggestionsBox();
				if(OD_Suggest.oOptions.autoSubmit) {
					OD_Suggest.parentForm.submit();
				}
			};
			
			this.suggestionsBox.appendChild(newSuggestion);
		}
		
		OD_Suggest.oCurrentHighlight = false;
		
		this.showSuggestionsBox();
	};
	
	
	
	/**
 	 * Pass Suggestion Suggestion
	 *
	 * Passes a suggestion to the textfield value	
	 * 	 
	 * @return	bool	true
	 */ 
	 
	this.passSuggestion = function(o)
	{
		var delimiter = ",";
		var value = this.textField.value;
		var cursor = this.getSelectionStart(this.textField);
		var prevComma = value.lastIndexOf(delimiter, cursor);
		if(prevComma == -1) {
			prevComma = 0;
		} else {
			prevComma = prevComma + 1;
		}
		
		var beginValue = this.textField.value.substr(0, prevComma);
		var endValue = this.textField.value.substr(prevComma);
		
		eval('endValue = endValue.replace(/' + this.textFieldValue + '/, o.innerHTML);');
		
		this.textField.value = beginValue + endValue;
		
		this.oCurrentHighlight = false;
		
		return true;
	};
	
	/**
 	 * Highlight Suggestion
	 *
	 * Give a suggestion a "hover" class	
	 * 
	 * @param	id		The ID of the LI that should be highlighted	 
	 * @return	bool	true
	 */ 
	 
	this.highlightSuggestion = function(o)
	{
		// First unhighlight everything
		this.highlightNone();
		
		// Then highlight the right LI
		$(o).addClass("hover");
		
		this.oCurrentHighlight = o;	
		
		return true;
	};
	
	/**
 	 * Highlight None
	 *
	 * Removes the "hover" class from eacht Suggestion LI	
	 * 	 
	 * @return	bool	true
	 */ 
	 
	this.highlightNone = function()
	{
		var aSuggestions = this.suggestionsBox.getElementsByTagName("li");
		for(var i = 0; i < aSuggestions.length; i++) {
			$(aSuggestions[i]).removeClass("hover");
		}	
		this.oCurrentHighlight = false;
	};
	
	/**
 	 * Find Form
	 *
	 * Find the parent form of the textfield	
	 * 	 
	 * @return	object	the form. returns "false" on failure
	 */ 
	 
	this.findForm = function()
	{
		var endLoop = false;
		var parent = this.textField;
		while(endLoop === false) {
			if(parent) {
				if(parent.nodeName.toLowerCase() == "form") {
					endLoop = true;
					return parent;				
				} else {
					parent = parent.parentNode;
				}
			} else {
				endLoop = true;
				return false;
			}			
		}	
	};
		
	/**
 	 * Include JavaScript File
	 *
	 * Ads a <script> tag to the document to include an external .js file
	 *
	 * @param	string 	sFilename		URL of the external JavaScript
	 * @return	bool	true
	 */ 
	 
	this.includeJsFile = function(sFilename) 
	{		
		var includeScript = document.createElement("script");
		includeScript.setAttribute("src", sFilename);
		includeScript.setAttribute("type", "text/javascript");
		document.getElementsByTagName("head")[0].appendChild(includeScript);
		return true;	
	};
	
	/**
 	 * Include CSS File
	 *
	 * Ads a <style> tag to the document to include an external .css file
	 *
	 * @param	string 	sFilename		URL of the external Styleshoeet
	 * @return	bool	true
	 */ 
	 
	this.includeCssFile = function(sFilename) 
	{		
		var includeStyle = document.createElement("link");
		includeStyle.setAttribute("href", sFilename);
		includeStyle.setAttribute("type", "text/css");
		includeStyle.setAttribute("rel", "stylesheet");
		document.getElementsByTagName("head")[0].appendChild(includeStyle);
		return true;	
	};
	
	/**
 	 * Augment
	 *
	 * This enables optional variables to be passed to a function	
	 * 	 
	 * @param	object	oSelf			The default values of the object
	 * @param	string 	oOther			The optional variables set by the user
	 * @return	object	oSelf			All variables put together into one object
	 */ 
		
	this.augment = function (oSelf, oOther) 
	{
		if (oSelf === null) {
			oSelf = {};
		}
				
		for (var i = 1; i < arguments.length; i++) {
			var o = arguments[i];
			if (typeof(o) != 'undefined' && o !== null) {
				for (var j in o) {
					oSelf[j] = o[j];
				}
			}
		}
		return oSelf;
	};
	
	this.getSelectionStart = function(o) {
    	
		if (o.createTextRange) {
    		var r = document.selection.createRange().duplicate()
    		r.moveEnd('character', o.value.length)
    		if (r.text == '') return o.value.length
    		return o.value.lastIndexOf(r.text)
    	} else return o.selectionStart;
	}
	
	this.trim = function(value) {
		value = value.replace(/^\s+/,'');
		value = value.replace(/\s+$/,'');
		return value;
	}

	// Call initialize function - DO NOT REMOVE THIS FUNCTION CALL
	this.init();
	
}



