//Autocomplete component with "tag" optimized default parameters.
//KB.Autocomplete(inputboxid,action,confirmfunction,inputfilter,outputfilter,navigatefunction,itemtag)
//Example;
//	new KB.Autocomplete
//	(
//		"cityinput",
//		"location",
//		function(reslist,idx)
//		{
//			$("cityId").value=/\s*(\d+)\s*/.exec(reslist[idx].down(".autocompleteid").innerHTML)[1];
//			$("location").value=reslist[idx].innerHTML.replace(/<span[^>]*>[^<]*</span>/gi,"").replace(/<[^>]*>/g,"");
//		},
//		function(value)
//		{
//			return value.length<3?"":value;
//		}
//	);
//
//	new KB.Autocomplete("taginput");
//
//Parameters;
//String inputboxid:
//	Editable input element id.
//
//String action:
//	AutocompleteController action to execute.
//	(Optional, default "tag")
//
//void function confirmfunction(reslist,idx):
//	Function to execute when user selects an item by pressing ENTER.
//	(Optional, default adds matched tag to comma seperated values in the input element)
//	Parameters;
//	NodeSet reslist:
//		Html element list as put in the result element. See also itemtag parameter.
//
//	Integer idx:
//		Index of selected element in the reslist.
//
//String function inputfilter(value):
//	Filters and returns input element's value whenever it is needed inside the component. See also getinputfilter property.
//	(Optional, default gets last comma seperated tag in the input element)
//	Parameters;
//	String value:
//		Inpu-t element's value.
//
//String function outputfilter(response,regexpbuilder):
//	Filters and returns server response.
//	(Optional, default wraps the part of the tags matched with the filtered input value with <b>.
//	Tags to be filtered should be wrapped with <span class="highlightmatch"> but <span> is removed in the filter.)
//	Parameters;
//	String response:
//		Response text.
//	String function regexpbuilder():
//		Default regular expression builder for higlighting to be called in the outputfilter by user.
//
//void function navigatefunction(reslist,fromidx,toidx):
//	Function to execute when user presses up ad down.
//	(Optional, default highlights the selected item)
//	Parameters;
//	NodeSet reslist:
//		Html element list as put in the result element. See also itemtag parameter.
//
//	Integer fromidx:
//		Index of the previous element. -1 indicates that there is no previous element.
//
//	Integer toidx:
//		Index of the current element.
//
//String itemtag:
//	Tag name of elements in server's response.
//	(Optional, default "LI")
//


KB.Autocomplete = Class.create(
{
    initialize:function(inputboxid,action,confirmfunction,inputfilter,outputfilter,navigatefunction,itemtag)
    {

        var lastLength=0;
        var resultlist;
        var selectedIndex=0;

	    if(!inputboxid)
		    return null;

	    if(!itemtag)
		    itemtag="LI";

	    if(!action)
		    action="tag";

        /* create result box */
		var resultBox=new Element
		(
			"div",
			{
				"class":"auto-complete-results",
				id:inputboxid+"AutoCompleteResults"
			}
		).hide();
		$(inputboxid).up().insert(resultBox);

	    if(typeof navigatefunction!="function")
	    {
		    navigatefunction=function(reslist,toidx)
		    {
                var fromidx = KB.Autocomplete.selectedIndex || 0;
			    if(fromidx!=-1)
				    reslist[fromidx] && reslist[fromidx].removeClassName('selected');
			    reslist[toidx].addClassName('selected');
                KB.Autocomplete.selectedIndex = toidx;
		    };
	    }

	    if(typeof confirmfunction!="function")
	    {
		    confirmfunction=function(reslist,idx)
		    {
			    var tmp=reslist[idx].innerHTML.replace(/<[^>]*>/g,"");
			    $(inputboxid).value=$(inputboxid).value.replace(/((?:.*,\s*)*)[^,]*/,"$1")+tmp+", ";
		    };
	    }

	    if(typeof inputfilter!="function")
	    {
		    inputfilter=function(value)
		    {
			    var tmp=value.replace(/(?:.*,\s*)*([^,]*)/,"$1");
			    if(tmp.length<1)
				    return "";
			    return tmp;
		    };
	    }

	    function defaultregexpbuilder()
	    {
		    //contains combining diacriticals
		    var remove	= "[\\s\u0001-\u002f\u003a-\u0040\u005b-\u0060\u007b-\u007f\u02b0-\u036f]";
		    var charexp	=	[
							    "[aA\u00c0-\u00c6\u00e0-\u00e6\u0100-\u0105\u018f\u01cd\u01ce\u01dd-\u01e3\u01fa-\u01fd\u0200-\u0203\u0226\u0227\u0259\u1e00\u1e01\u1ea0-\u1eb7]",
							    "[cC\u00c7\u00e7\u0106-\u010d\u1e08\u1e09]",
							    "[dD\u00d0\u00f0\u010e-\u0111\u1e0a-\u1e13]",
							    "[eE\u00c8-\u00cb\u00e8-\u00eb\u0112-\u011b\u018e\u018f\u01dd\u0204-\u0207\u0228\u0229\u1e14-\u1e1d\u1eb8-\u1ec7]",
							    "[gG\u011c-\u0123\u01e4-\u01e7\u01f4\u01f5\u1e20\u1e21]",
							    "[iI\u00cc-\u00cf\u00ec-\u00ef\u0128-\u0131\u01cf\u01d0\u0208-\u020b\u1e2c-\u1e2f\u1ec8-\u1ecb]",
							    "[kK\u0136-\u0138\u01e8\u01e9\u1e30-\u1e35]",
							    "[lL\u0139-\u0142\u1e36-\u1e3d]",
							    "[nN\u00d1\u00f1\u0143-\u014b\u01f8\u01f9\u1e44-\u1e4b]",
							    "[oO\u00d2-\u00d6\u00d8\u00f2-\u00f6\u00f8\u014c-\u0153\u01a0\u01a1\u01d1\u01d2\u01ea-\u01ed\u01fe\u01ff\u020c-\u020f\u022a-\u0231\u1e4c-\u1e53\u1ecc-\u1ee3]",
							    "[rR\u0154-\u0159\u1e58-\u1e5f]",
							    "[sS\u00df\u015a-\u0161\u1e60-\u1e69]",
							    "[tT\u0162-\u0167\u1e6a-\u1e71]",
							    "[uU\u00d9-\u00dc\u00f9-\u00fc\u0168-\u0173\u01af\u01b0\u01d3-\u01dc\u0214-\u0217\u1e72-\u1e7b\u1ee4-\u1ef1]",
							    "[yY\u00dd\u00fd\u00ff\u0176-\u0178\u1ef2-\u1ef9]",
							    "[zZ\u0179-\u017e\u01b5\u01b6\u1e90-\u1e95]"
						    ];
		    var temp;
		    //put sentinel value '0' to prevent matching syntax itself
		    //if user somehow typed '0' then he's looking for trouble
		    var locexp=inputfilter($(inputboxid).value).replace(/(.)/g,"$1\0");
		    temp=new RegExp(remove+"\\0","g");//match with '0'
		    locexp=locexp.replace(temp,"");

		    for(var idx=0;idx<charexp.length;++idx)
		    {
			    //corresponding expression of char.
			    temp=new RegExp(charexp[idx]+"\\0","g");
			    //add '0' after expression since it will become "remove" for highlight
			    locexp=locexp.replace(temp,charexp[idx]+"\0");
		    }
		    return "(?:"+remove+"*)"+locexp.replace(/\0/g,"(?:"+remove+"*)");
	    }

	    if(typeof outputfilter!="function")
	    {
		    outputfilter=function(response,regexpbuilder)
		    {	//i don't need span
			    return response.replace(new RegExp("<span\\s+class=\"highlightmatch\">\\s*("+regexpbuilder()+")([^<]*)</span>","gi"),"<b>$1</b>$2");
		    };
	    }

	    $(inputboxid).observe
	    (
		    'blur',
		    function(event)
		    {
                window.setTimeout(function(){resultBox.hide();}, 200);
		    }
	    );
	    $(inputboxid).observe
	    (
		    'keydown',
		    function(event)
		    {
                //RRV this wasn't mandatory. probably only 'keypress' matters
			    if(/*event.keyCode==40 || event.keyCode==38 || */event.keyCode==Event.KEY_RETURN)
			    {
				    Event.stop(event);
			    }
		    }
	    );
	    $(inputboxid).observe
	    (
		    'keypress',
		    function(event)
		    {
                //RRV why filter bracket '(' (40), ampersand '&' (38) ? 20090410
			    if(/*event.keyCode==40 || event.keyCode==38 || */event.keyCode==Event.KEY_RETURN)
			    {
				    Event.stop(event);
			    }
		    }
	    );
	    $(inputboxid).observe
	    (
		    'keyup',
		    function(event)
		    {
			    var inpqry=inputfilter($(inputboxid).value);
			    if(lastLength!=inpqry.length)
			    {
				    if(!inpqry)
				    {
					    lastLength=0;
					    resultBox.hide();
					    return;
				    }

				    new Ajax.Request
				    (
					    action.replace('__SEARCH__',encodeURIComponent(inpqry)),
					    {
						    method:'get',
						    onSuccess:function(result)
						    {
							    resultBox.update(outputfilter(result.responseText,defaultregexpbuilder));
                                lastLength=inpqry.length;
                                if(resultBox.empty())
								{
									resultBox.hide();
									return;
                                }
                                resultBox.show();
							    resultlist=resultBox.select(itemtag);
							    selectedIndex=0;
							    navigatefunction(resultlist,0);

                                var options=$(resultBox).down('ul').childElements();
                                for(var ind in options){
                                    if(!isNaN(ind)) {
                                        var link = options[ind].down('a');
                                        link && link.observe('mouseover',function(event){
                                            //RRV currentTarget.id in IE7&8: "currentTarget.id" is null or not object
                                            //Firefox, Safari, Chrome, Opera working fine without it
                                            var target = /*event.currentTarget.id ||*/ event.target.id || event.target.parentNode.id;
                                            target=Number(target);
                                            navigatefunction(resultlist,target);
                                        });
                                        link && link.observe('click',function(event){
                                            var target = /*event.currentTarget.id ||*/ event.target.id || event.target.parentNode.id;
                                            target=Number(target);
                                            confirmfunction(resultlist,target);
                                        });
                                    }
                                }
						    },
						    onComplete:function(transport){}
					    }
				    );
			    }
			    else if(event.keyCode==40 || event.keyCode==38)
			    {
				    Event.stop(event);
				    var dif=event.keyCode-39;
				    resultlist = resultBox.select(itemtag);
				    if((dif==1&&selectedIndex<resultlist.length-1)||(dif==-1&&selectedIndex>0))
				    {
					    navigatefunction(resultlist,selectedIndex+dif);
					    selectedIndex+=dif;
				    }
			    }
			    else if(event.keyCode==Event.KEY_RETURN)
			    {
				    Event.stop(event);
				    resultlist=resultBox.select(itemtag);
				    if(resultlist[selectedIndex])
				    {
					    confirmfunction(resultlist,selectedIndex);
				    }
				    resultBox.hide();
				    lastLength=0;
			    }
		    }
	    );
    }
});
