/**
* Suggestion object.
* @class
* @scope public
*/

function $autocomplete(id) {
	return eval('o' + id);
}

function Suggestion(inVal, inText) {

	this.value = inVal;  // the numeric value
	this.text = inText; // the text value
	this.searchText = searchText;

	function searchText() {
		return this.text.toLowerCase();
	}

}


function Suggestions(arrSuggestions /*:array of Suggestion objects*/ ) {

	this.textValues = arrSuggestions;
	this.requestSuggestions = requestSuggestions;
	this.getSuggestionById = getSuggestionById;
	
	/**
    * Request suggestions for the given autosuggest control. 
    * @scope protected
    * @param oAutoSuggestControl The autosuggest control to provide suggestions for.
	* @param bTypeAhead boolean
    */
	function requestSuggestions(oAutoSuggestControl /*:AutoSuggestControl*/,
		bTypeAhead /*:boolean*/) {
		var aSuggestions = [];
		var sTextboxValue = oAutoSuggestControl.textbox.value;

		if (sTextboxValue.length < this.minLenghtToStartSearch) {
			return;
		}

		//search for matching textValues
		for (var i = 0; i < this.textValues.length; i++) {
			var suggestion = this.textValues[i];
			if (suggestion.searchText().indexOf(sTextboxValue.toLowerCase()) >= 0) {
				aSuggestions.push(this.textValues[i]);
			}
		}

		//provide suggestions to the control
		oAutoSuggestControl.autosuggest(aSuggestions, false);
		return;

	}
	
	function getSuggestionById(idVal) {
		var ret;
		if (idVal) {
			for (var i = 0; i < this.textValues.length; i++) {
				var suggestion = this.textValues[i];
				if (suggestion.value == idVal) {
					ret = suggestion;
					break;
				}
			}
		}
		return ret;
	}

}

/**
* An autosuggest textbox control.
* @class
* @scope public
*/
function AutoSuggestControl(oTextbox /*:HTMLInputElement*/,
	oHiddenValueTextbox /*:HTMLInputElement*/,
	arrSuggestions /*:array of Suggestion objects*/,
	inAllowNew /*:boolean*/,
	inListWidth /*:int*/,
	inValueChangedHandler/*:function*/) {

	/**
    * The textbox to capture.
    * @scope private
    */
	if (!oTextbox) {
		alert('Error: TextBox is null!!');
		return;
	}
	
	/**
    * The currently selected suggestions.
    * @scope private
    */
	this.currentNodeIndex /*:int*/ = -1;
	this.bAllowNew = false;
	this.oldValue = '';
	this.minLenghtToStartSearch = 2;
	
	if (inAllowNew != null) {
		this.bAllowNew = inAllowNew;
	}
    
	if(inListWidth && inListWidth>-1) {
        
		this.listWidth= inListWidth;
	}

    
	this.ValueChangedHandler = null; // callback function to handle value changed!
	if(inValueChangedHandler)this.ValueChangedHandler=inValueChangedHandler;


	this.provider /*:SuggestionProvider*/ = new Suggestions(arrSuggestions);
	this.textbox /*:HTMLInputElement*/ = oTextbox;
	// the text box id =  <id of the control>+"Descr"
	this.id = this.textbox.id.substring(0, this.textbox.id.length-5);
    if(!$(this.id)) {
        // we may have descrEn or DescrGr, so also try -7
        this.id = this.textbox.id.substring(0, this.textbox.id.length-7);
    }
	this.hiddenValueTextbox = oHiddenValueTextbox;
	this.keepSuggestions = false ; // keep suggestions visible on textbox blur
	/**
	* Textbox initial value.  To avoid checking for validity of 
	* selected value during blur.  See processing of blur event
	*/
	this.initValue = this.textbox.value;
	
	//initialize the control
	this.init();
	this.handlingKeyDown = false;

}

// allowas for change of suggestions
AutoSuggestControl.prototype.setSuggestions = function(arrNewSuggestions /*:Array*/ ) {
										
	this.provider /*:SuggestionProvider*/ = new Suggestions(arrNewSuggestions);

};

/**
* Autosuggests one or more suggestions for what the user has typed.
* If no suggestions are passed in, then no autosuggest occurs.
* @scope private
* @param aSuggestions An array of suggestion strings.
* @param bTypeAhead If the control should provide a type ahead suggestion.
*/
AutoSuggestControl.prototype.autosuggest = function(aSuggestions /*:Array*/,
	bTypeAhead /*:boolean*/) {

	//make sure there's at least one suggestion
	if (aSuggestions.length > 0) {
		if (bTypeAhead) {
			this.typeAhead(aSuggestions[0]);
		} else {

		}

		this.showSuggestions(aSuggestions);

	} else {
		this.hideSuggestions();
	}

};

/**
* Creates the dropdown layer to display multiple suggestions.
* @scope private
*/
AutoSuggestControl.prototype.setupDropDown = function() {

	var oThis = this;

	//create the layer and assign styles
	var layer = this.getAutosuggestDiv(); 
	if(this.listWidth) {
		layer.style.width = this.listWidth + "px";
	// allow users to specify list width different than 
	// the default!
	} else {
		layer.style.width = this.textbox.parentElement.offsetWidth;
	// default list width: the textbox's width plus the image
	}
        
	//when the user clicks on the a suggestion, get the text (innerHTML)
	//and place it into a textbox
	layer.onscroll =
	layer.onmousedown =
	layer.onmouseup =
	layer.onkeydown = layer.onkeyup =
	layer.onmouseover = function(oEvent) {
		oEvent = oEvent || window.event;
		oTarget = oEvent.target || oEvent.srcElement;
		if($('__asdebug'))$('__asdebug').value='Autosuggest event:' + oEvent.type;
        
		if (oEvent.type == "scroll") {
		// nothing doin here...
            
		} else if (oEvent.type == "mousedown") {
            
			if(oTarget.className=='current') {
				if($('__asdebug'))$('__asdebug').value='oTarget.className:' + oTarget.className;
				if (oTarget.firstChild){
					oThis.textbox.value = oTarget.firstChild.nodeValue;
					oThis.hiddenValueTextbox.value = $j(oTarget).attr('selectedvalue');
				} 
				oThis.hideSuggestions();
				oThis.textbox.focus();
			} else {
				if($('__asdebug'))$('__asdebug').value='oTarget.className not current:' + oTarget.className;
			}
            
        
		} else if (oEvent.type == "keyup") {
			
			if (oThis.handleF4(oEvent))return; // just exit if we handled F4
			
			switch (oEvent.keyCode) {
				case 38: //up arrow
					cancelEvent(oEvent);
					break;

				case 40: //down arrow 
					cancelEvent(oEvent);
					break;
				case 13: //enter key.. 
					cancelEvent(oEvent);
					break;
			}
		} else if (oEvent.type == "keydown") {
			switch (oEvent.keyCode) {
				case 38: //up arrow
					oThis.previousSuggestion();
					cancelEvent(oEvent);
					break;

				case 40: //down arrow 
					oThis.nextSuggestion();
					cancelEvent(oEvent);
					break;
				case 13: //enter key.. select suggestion and close
					oThis.hideSuggestions();
					oThis.textbox.focus();
					cancelEvent(oEvent);
					break;
			}
		
		} else if (oEvent.type == "mouseup") {
            
		} else if (oEvent.type == "mouseover") {
			oThis.highlightSuggestion(oTarget);

		} else {
			oThis.textbox.focus();
		}
	};


    
};

/**
* Gets the left coordinate of the textbox.
* @scope private
* @return The left coordinate of the textbox in pixels.
*/
AutoSuggestControl.prototype.getLeft = function() /*:int*/{

	var oNode = this.textbox;
	var iLeft = 0;

	while (oNode && oNode.tagName != "BODY") {
		iLeft += oNode.offsetLeft;
		oNode = oNode.offsetParent;
	}

	return iLeft;
};

/**
* Gets the top coordinate of the textbox.
* @scope private
* @return The top coordinate of the textbox in pixels.
*/
AutoSuggestControl.prototype.getTop = function() /*:int*/{

	var oNode = this.textbox;
	var iTop = 0;

	while (oNode && oNode.tagName != "BODY") {
		iTop += oNode.offsetTop;
		oNode = oNode.offsetParent;
	}

	return iTop;
};

/**
* Handles the drop down image of the combo box keydown events.
* We do not want to allow Up or Down keys because the page scrolls up and down
* Also, we do not want to allow ENTER key because it may submit the page
* @scope private
* @param oEvent The event object for the keydown event.
*/
AutoSuggestControl.prototype.dropDownImageKeyDown = function(oEvent /*:Event*/) {
		
	switch (oEvent.keyCode) {
		case 38: //up arrow
			cancelEvent(oEvent);
			break;
			
		case 40: //down arrow 
			cancelEvent(oEvent);
			break;
        
		case 13: //enter
			cancelEvent(oEvent);
			
	}
	
};

/**
* Handles three keydown events.
* @scope private
* @param oEvent The event object for the keydown event.
*/
AutoSuggestControl.prototype.textboxKeyDown = function(oEvent /*:Event*/) {
	
	if(this.handlingKeyDown)return;
	// put this variable as a flag that the control is 
	// currently handling the key down event.
	
	if (this.handleF4(oEvent))return; // just exit if we handled F4
	
	this.handlingKeyDown = true;
    
	switch (oEvent.keyCode) {
		case 38: //up arrow
			this.previousSuggestion();
			cancelEvent(oEvent);
			break;
			
		case 40: //down arrow 
			cancelEvent(oEvent);
			if(this.listIsDown()) {
				this.nextSuggestion();
			} else {
				// drop the list and select first entry!
				this.showSuggestions(this.provider.textValues);
				//now auto select selected item!
				if (this.hiddenValueTextbox.value) {
					this.highlightSuggestionByValue();
				}
			}
            
			break;
		case 27: //escape key
			this.hideSuggestions();
			cancelEvent(oEvent);
			break;

		case 13: //enter
			if(this.listIsDown()) {
				this.hideSuggestions();
				cancelEvent(oEvent);
			}
			break;
	}
	// reset flag
	this.handlingKeyDown = false;
};

/**
* Handles keyup events.
* @scope private
* @param oEvent The event object for the keyup event.
*/
AutoSuggestControl.prototype.handleKeyUp = function(oEvent /*:Event*/) {

	var iKeyCode = oEvent.keyCode;

	//for backspace (8) and delete (46), shows suggestions without typeahead
	if (iKeyCode == 8 || iKeyCode == 46) {
		this.provider.requestSuggestions(this, false);

	//make sure not to interfere with non-character keys
	} else if (iKeyCode < 32 || (iKeyCode >= 33 && iKeyCode < 46) || (iKeyCode >= 112 && iKeyCode <= 123)) {
	//ignore
	} else {
		//request suggestions from the suggestion provider with typeahead
		this.provider.requestSuggestions(this, true);
	}
};

/**
* Hides the suggestion dropdown.
* @scope private
*/
AutoSuggestControl.prototype.hideSuggestions = function() {

	var valFound = false;
	var layer = this.getAutosuggestDiv();
	layer.style.visibility = "hidden";
	
	if (!this.textbox.value) {
		this.hiddenValueTextbox.value = '';
		this.autosuggestValChange(this.id, '', '');

	} else {
		if(this.initValue==this.textbox.value && layer.childNodes.length==0) {
			// this is to avoid check for value when 
			// we blur away from textbox and the layer.childNodes.length = 0 , ie we 
			// havent yet shown the suggestions.
			
			// this happens when we open the page and the autosuggest has a value
			// and we click away from the control (blur) w/o showing the 
			// drop down list layer
			valFound = new Suggestion(this.hiddenValueTextbox.value, this.textbox.value);
			
		} else {
			//make sure that the text thing matches an item in the list of suggestions
			valFound = this.provider.getSuggestionById(this.hiddenValueTextbox.value);
			
		}

		if (!valFound) {

			// the text typed in the text box does not exist!!
			this.hiddenValueTextbox.value = '';
			if (!this.bAllowNew) {
				this.textbox.value = '';
			}
			this.autosuggestValChange(this.id, '', '');
		} else {
			this.textbox.value = valFound.text;
			this.autosuggestValChange(this.id, valFound.value, valFound.text);
		}

	}


};
/**
* gets the value of the control (the one oin the hidden text box)
* @scope private
*/
AutoSuggestControl.prototype.getValue = function() {
	return this.hiddenValueTextbox.value;
}

/**
* gets the value of the control (the one oin the hidden text box)
* @scope private
*/
AutoSuggestControl.prototype.getText = function() {
	return this.textbox.value;
}
/**
* Sets and highlites the node in the suggestions dropdown with value==idVal.
* @scope private
*/
AutoSuggestControl.prototype.setValue = function(idVal) {
	// we check if it exists because we may have called the setValue 
	// while we are loading the page
	if ($(this.getAutosuggestDivId())) { 
		$(this.getAutosuggestDivId()).style.visibility = "hidden";
	}
	
	this.textbox.value = '';
	this.hiddenValueTextbox.value = '';
	
	var suggestion = this.provider.getSuggestionById(idVal);
	
	// if idVal not passed, just clear the control
	// and raise the autosuggestValChange event
	if ( suggestion) {
		this.textbox.value = suggestion.text;
		this.hiddenValueTextbox.value = suggestion.value;
	}
	
	this.autosuggestValChange(this.id, this.hiddenValueTextbox.value, this.textbox.value);
};

/**
* Highlights the node in the suggestions dropdown with value==val.
* @scope private
*/
AutoSuggestControl.prototype.highlightSuggestionByValue = function() {
	
	var layer = this.getAutosuggestDiv();
	for (var i = 0; i < layer.childNodes.length; i++) {
		var oNode = layer.childNodes[i];
		if (this.textbox.value == oNode.firstChild.nodeValue) {
			this.scrollToNode(layer, oNode);
			$j(layer).focus();//added 19/4 to focus the selected item
			oNode.className = "current";
			this.currentNodeIndex = i; // set current node index
			break;
		}

	}
};

/**
* Scrolls to the selected node, if it not visible
* @scope private
*/
AutoSuggestControl.prototype.scrollToNode = function(layer, oNode) {
	// from: http://stackoverflow.com/questions/14261296/scroll-if-div-is-hidden-by-overflow
	var it = $j(oNode);
	var jlayer = $j(layer);
	var top = it.position().top; 
	var bd = top + it.height();     
	var ch = jlayer.height();     
	
	if( bd > ch ){  //if focused item isn't visible, (scroll to item is below the parent div's lower boundary
		$j(layer).scrollTop(top); 
	}
	if( bd < 0 ){  //if focused item isn't visible, (scroll to item is before the parent div's first visible item
		$j(layer).scrollTop(top); 
	}
	
}
/**
* Highlights the given node in the suggestions dropdown.
* @scope private
* @param oSuggestionNode The node representing a suggestion in the dropdown.
*/
AutoSuggestControl.prototype.highlightSuggestion = function(oSuggestionNode) {
	var layer = this.getAutosuggestDiv();
	for (var i = 0; i < layer.childNodes.length; i++) {
		var oNode = layer.childNodes[i];
		if (oNode == oSuggestionNode) {
			oNode.className = "current"

		} else if (oNode.className == "current") {
			oNode.className = "";
		}
		
	}
};

/**
* Initializes the textbox with event handlers for
* auto suggest functionality.
* @scope private
*/
AutoSuggestControl.prototype.init = function() {

	//save a reference to this object
	var oThis = this;

	//assign the onkeyup event handler
	this.textbox.onkeyup = function(oEvent) {

		//check for the proper location of the event object
		if (!oEvent) {
			oEvent = window.event;
		}

		//call the handleKeyUp() method with the event object
		oThis.handleKeyUp(oEvent);
	};

	//assign onkeydown event handler
	this.textbox.onkeydown = function(oEvent) {

		//check for the proper location of the event object
		if (!oEvent) {
			oEvent = window.event;
		}

		//call the handleKeyDown() method with the event object
		oThis.textboxKeyDown(oEvent);
	};

	//assign onblur event handler (hides suggestions)    
	this.textbox.onblur = function() {
		if (oThis.keepSuggestions) {
			oThis.keepSuggestions = false; // reset flag for next time
		} else {
			oThis.hideSuggestions();
		}
		
		
	};

};

/**
* Highlights the next suggestion in the dropdown and
* places the suggestion into the textbox.
* @scope private
*/
AutoSuggestControl.prototype.nextSuggestion = function() {
	var layer = this.getAutosuggestDiv();
	var cSuggestionNodes = layer.childNodes;

	if (cSuggestionNodes.length > 0 ) {
		var oNode = null;
        
		if( this.currentNodeIndex < cSuggestionNodes.length - 1) {
			oNode = cSuggestionNodes[++this.currentNodeIndex];
		} else {
			//27/6/09:goto first item if already on last!
			oNode = cSuggestionNodes[0];
			this.currentNodeIndex = 0;
		}
        
		if(!oNode)return;
        
		this.scrollToNode(layer, oNode);
		this.highlightSuggestion(oNode);
		this.textbox.value = oNode.firstChild.nodeValue;
		this.hiddenValueTextbox.value = $j(oNode).attr('selectedvalue'); //oNode.attributes['selectedvalue'].nodeValue;
		this.autosuggestValChange(this.id, this.hiddenValueTextbox.value, this.textbox.value);
	}
};

/**
* Highlights the previous suggestion in the dropdown and
* places the suggestion into the textbox.
* @scope private
*/
AutoSuggestControl.prototype.previousSuggestion = function() {
	var layer = this.getAutosuggestDiv();	
	var cSuggestionNodes = layer.childNodes;

	if (cSuggestionNodes.length > 0 ) {
		var oNode = null;
		if( this.currentNodeIndex > 0) {
			oNode = cSuggestionNodes[--this.currentNodeIndex];
		} else {
			//27/6/09:goto last item if already on first!
			oNode = cSuggestionNodes[cSuggestionNodes.length - 1];
			this.currentNodeIndex = cSuggestionNodes.length - 1;
		}
        
		if(!oNode)return; // fix: this was sometimes giving an error!
        
		this.scrollToNode(layer, oNode);
		this.highlightSuggestion(oNode);
		this.textbox.value = oNode.firstChild.nodeValue;
		this.hiddenValueTextbox.value = $j(oNode).attr('selectedvalue'); //oNode.attributes['selectedvalue'].nodeValue;
		this.autosuggestValChange(this.id, this.hiddenValueTextbox.value, this.textbox.value);
	}
};


/**
* Selects a range of text in the textbox.
* @scope public
* @param iStart The start index (base 0) of the selection.
* @param iLength The number of characters to select.
*/
AutoSuggestControl.prototype.selectRange = function(iStart /*:int*/, iLength /*:int*/) {

	//use text ranges for Internet Explorer
	if (this.textbox.createTextRange) {
		var oRange = this.textbox.createTextRange();
		oRange.moveStart("character", iStart);
		oRange.moveEnd("character", iLength - this.textbox.value.length);
		oRange.select();

	//use setSelectionRange() for Mozilla
	} else if (this.textbox.setSelectionRange) {
		this.textbox.setSelectionRange(iStart, iLength);
	}

	//set focus back to the textbox
	this.textbox.focus();
};



/**
* Builds the suggestion layer contents, moves it into position,
* and displays the layer.
* @scope private
* @param aSuggestions An array of suggestions for the control.
*/
AutoSuggestControl.prototype.showSuggestions = function(aSuggestions /*:Array of Suggestion objects*/) {
	
	var oDiv = null;
	var layer = this.getAutosuggestDiv();
	layer.innerHTML = '';  //clear contents of the layer
	this.setupDropDown();

	for (var i = 0; i < aSuggestions.length; i++) {
		oDiv = document.createElement("div");
		oDiv.setAttribute("selectedValue", aSuggestions[i].value );
		
		//oDiv = document.createElement("<div selectedValue='" + aSuggestions[i].value + "'>");
		oDiv.appendChild(document.createTextNode(aSuggestions[i].text));
		layer.appendChild(oDiv);
	}

	layer.style.left = this.getLeft() + "px";
	layer.style.top = (this.getTop() + this.textbox.offsetHeight) + "px";
	layer.style.visibility = "visible";
	layer.style.height = "200px";
	layer.style.overflow = "auto";

};

AutoSuggestControl.prototype.autosuggestValChange =
	function(autosuggestName, newId, newText) {
		
		// if old value is equal to new then just return
		if (this.oldValue==newId)return;
		
		// set "oldvalue" field to newid, so that 
		// the next time in we check against this value
		// if we have a change
		this.oldValue=newId;
				
		//alert('fire of change, listIsDown: '+this.listIsDown());
		//if (this.listIsDown()==true)return;
		//change 28/6: do not fire event if list is down!
		if (this.ValueChangedHandler) {
			this.ValueChangedHandler(autosuggestName, newId, newText);
		}
		
		if (typeof (__page__autosuggest_change) == 'function') {
			//allow custom page validation if function __page__validate is defined
			__page__autosuggest_change(autosuggestName, newId, newText);
		}

	};



/**
* Returns true if list if currently showing!
* @scope private
*/
AutoSuggestControl.prototype.listIsDown =
	function() {
		var layer = $(this.getAutosuggestDivId());
		if(!layer) return false;
		return layer.style.visibility == "visible";
	};


/**
* To keep compatibility between this and the classic combo box,
* we implement an "options" array property 
* @scope private
* @returns aSuggestions: array of suggestions for the control.
*/
AutoSuggestControl.prototype.options =
	function() {
		return aSuggestions;
        
	};
    
/**
* Toggles showing or hiding the suggestion layer 
* @scope private
* @param aSuggestions An array of suggestions for the control.
*/
AutoSuggestControl.prototype.toggleSuggestions =
	function() {

		if (this.listIsDown()==true) {
			this.hideSuggestions();
		} else {
			this.showSuggestions(this.provider.textValues);
			//now auto select selected item!
			if (this.hiddenValueTextbox.value) {
				this.highlightSuggestionByValue();
			}
		}
	};
    
/**
* Toggles showing or hiding the suggestion layer 
* @scope private
* @param aSuggestions An array of suggestions for the control.
*/
AutoSuggestControl.prototype.imgClick =
	function(e) {

		var target = e.srcElement ? e.srcElement : e.target;
		if (target && target.className != "dhx_combo_img") return;
		//exit if it is not the drop down image
        
		this.toggleSuggestions();
	};

/**
* Inserts a suggestion into the textbox, highlighting the 
* suggested part of the text.
* @scope private
* @param sSuggestion The suggestion for the textbox.
*/
AutoSuggestControl.prototype.typeAhead = function(sSuggestion /*:Suggestion Object*/) {

	//check for support of typeahead functionality
	if (this.textbox.createTextRange || this.textbox.setSelectionRange) {

		var iLen = this.textbox.value.length;
		this.textbox.value = sSuggestion.text;
		this.hiddenValueTextbox.value = sSuggestion.value;
		this.selectRange(iLen, sSuggestion.text.length);
		this.autosuggestValChange(this.id, sSuggestion.value, sSuggestion.text);
	} else {
		this.hiddenValueTextbox.value = '';
		this.autosuggestValChange(this.id, '', '');
	}

};

AutoSuggestControl.prototype.getAutosuggestDivId = function() {
	return "autoSuggestDropDown"+this.id;
}

AutoSuggestControl.prototype.handleF4 = function(oEvent) {
	
	if (oEvent.keyCode == 115) { //F4's keycode is 115.
		
		cancelEvent(oEvent);
		oEvent.stopPropagation();
		oEvent.cancelBubble=true;
		this.keepSuggestions = true;
		this.toggleSuggestions();
		
		return true;
		
	} else {
		return false;
	}
}


AutoSuggestControl.prototype.getAutosuggestDiv = function() {

	if(!$(this.getAutosuggestDivId())) {
		// 20/4: give the div a tabindex to enable focus in Safari/Chrome/Firefox
		var asdiv = "<div id=\"" + this.getAutosuggestDivId() + "\" tabindex=\"-1\" class=\"suggestions\" style=\"visibility: hidden;\"></div>";
		$j('body').append(asdiv);
	}
	
	return $(this.getAutosuggestDivId());
};
