<?xml version="1.0"?>

<bindings   id="autocompleteBindings"
            xmlns="http://www.mozilla.org/xbl"
            xmlns:html="http://www.w3.org/TR/REC-html40"
            xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

<binding id="autocomplete" extends="chrome://global/content/xulBindings.xml#textfield">
    <content excludes="template,observes,menupopup">
        <xul:box class="textfield-internal-box" flex="1">
            <html:input class="textfield-input" flex="1" inherits="value,type,maxlength,disabled,size,readonly"/>
            <xul:popupset ignorekeys="true"
				oncommand="var me = this.parentNode.parentNode; me.privatefunc.onMenuCommand(me, this);"
			>
                <xul:popup oncreate="this.parentNode.parentNode.parentNode.menuOpen='true'" ondestroy="this.parentNode.parentNode.parentNode.menuOpen='false';">
                </xul:popup>
            </xul:popupset>
        </xul:box>
    </content>

    <interface>
        <property name="value"
            onset="return document.getAnonymousNodes(this)[0].firstChild.value = val;"
            onget="this.privatefunc.cleanupInputField(this); return document.getAnonymousNodes(this)[0].firstChild.value;"
		/>

        <property name="timeout"
            onset="return this.setAttribute('timeout', val);"
            onget="return this.getAttribute('timeout');"
		/>

        <property name="displayMenu"
            onset="return this.setAttribute('displayMenu', val);"
            onget="return this.getAttribute('displayMenu');"
		/>

		<property name="autoCompleteSession">
        <![CDATA[
            if (this.getAttribute('searchSessionType') != "") {
                searchSession = unescape('component:%2F%2Fnetscape%2FautocompleteSession%26type=');
                searchSession = searchSession + this.getAttribute('searchSessionType');
                try {
                    Components.classes[searchSession].getService(Components.interfaces.nsIAutoCompleteSession);
//                      var session = Components.classes[searchSession].createInstance();
//                      session.QueryInterface(Components.interfaces.nsIAutoCompleteSession);
                } catch (e) {dump("### ERROR, cannot create a search session. " + e + "\n");}
            }
        ]]>
        </property>
        
        <property name="disableAutocomplete"
            onset="return this.setAttribute('disableAutocomplete', val);"
            onget="return this.getAttribute('disableAutocomplete');"
        />

        <property name="autoCompleteTimer">
        <![CDATA[
            0;
        ]]>
        </property>

        <property name="lastResults">
        <![CDATA[
            var results = Components.classes["component://netscape/autocomplete/results"].createInstance();
            results.QueryInterface(Components.interfaces.nsIAutoCompleteResults);
        ]]>
        </property>

        <property name="autoCompleteListener">
        <![CDATA[
        ({
            onAutoComplete: function(result, status) {
				var me = this.param;
                if (status == Components.interfaces.nsIAutoCompleteStatus.failed)
                    return;
                
                if (me.disableAutocomplete == "true" || me.privatefunc.imeInProgress(me))
                    return;

                me.lastResults = result;
                if (status == Components.interfaces.nsIAutoCompleteStatus.ignored || 
                    status == Components.interfaces.nsIAutoCompleteStatus.noMatch)
                    return;
                
                if (result == null && result.items.Count() == 0)
                    return;

                if (result.defaultItemIndex > result.items.Count())
                    result.defaultItemIndex = 0;
                
                var inputElement = document.getAnonymousNodes(me)[0].firstChild;
                
                //Time to build the new edit field value
                //First, check if the search string correspond to the current value of the field, else ignore it
                if (result.searchString != inputElement.value)
                    return;

                var item = null;
				if (result.defaultItemIndex != -1)
				{
					item = result.items.QueryElementAt(result.defaultItemIndex, Components.interfaces.nsIAutoCompleteItem);
					var match = item.value.toLowerCase();
					var entry = inputElement.value.toLowerCase();
                
					if (entry != match)
					{
						if (match.substring(0, entry.length) == entry)
						{
							inputElement.value = inputElement.value + item.value.substring(entry.length, match.length);
							inputElement.setSelectionRange(entry.length, match.length);
							me.noDirectMatch = false;
						}
						else
						{
							inputElement.value = inputElement.value + " >> " + item.value;
							inputElement.setSelectionRange(entry.length, inputElement.value.length);
							me.noDirectMatch = true;
						}
					}
				}
                
                //Now, build the popup content
				if (me.displayMenu == "false")
					return;

                popupset = document.getAnonymousNodes(me)[0].childNodes[1];
                popupElement = popupset.firstChild.cloneNode(false);
                for (i = 0; i < result.items.Count(); i ++)
                {
                    item = result.items.QueryElementAt(i, Components.interfaces.nsIAutoCompleteItem);
                    menuitem = document.createElement("menuitem");
                    menuitem.setAttribute('data', i);
                    menuitem.setAttribute('value', item.value);
                    popupElement.appendChild(menuitem);
//					dump("  match=" + item.value + "\n");
                }
//				dump("  count=" + result.items.Count() + ", default=" + result.defaultItemIndex + "\n");
                popupset.replaceChild(popupElement, popupset.firstChild);
				if (result.defaultItemIndex != -1)
				{
//TODO: Select the default item
//                popupset.selectedItem = result.defaultItemIndex;
				}
				me.privatefunc.selectedItemIndex = result.defaultItemIndex;
				if (result.defaultItemIndex != 0 || result.items.Count() != 1)
				{
					me.privatefunc.closePopupMenu(me); //Close it first as openPopup seems to work as a toggle!
					popupset.firstChild.openPopup(document.getAnonymousNodes(me)[0].firstChild, -1, -1, "popup", "bottomleft", "topleft");
				}
            },

            param: this
        })
        ]]>
        </property>

        <property name="privatefunc">
        <![CDATA[
        ({
			imeInProgress: function(me) {
				var inputElement = document.getAnonymousNodes(me)[0].firstChild;
				//I hope ime will set an attribute on the input field when it's currently building a sequence
				//like that we can now when we can or not autocomplete.
				//Q: How can I know when ime is done? I need to kick the autocomplete timer?

				//if (inputElement.getAttribute('<TBD>') == "true")
				//	return true;

				return false;
			},

            onMenuCommand: function(me, popupSetElem) {
				var inputElement = document.getAnonymousNodes(me)[0].firstChild;
				var popupElem = popupSetElem.firstChild;
				for (var i = 0; i < popupElem.childNodes.length; i ++)
				{
					var menuitem = popupElem.childNodes[i];
					if (menuitem.getAttribute("menuactive") == "true")
					{
						inputElement.value = menuitem.getAttribute("value");
						var inputElement = document.getAnonymousNodes(me)[0].firstChild;
                        inputElement.setSelectionRange(inputElement.value.length, inputElement.value.length);
						needToAutocomplete = false;
						me.privatefunc.selectedItemIndex = i;
						return;
					}
				}
            },

			callListener: function(me, action) {
                switch (action) {
                    case 'startLookup':
                        if (me.disableAutocomplete == "true" || me.privatefunc.imeInProgress(me))
                            return;
						var inputElement = document.getAnonymousNodes(me)[0].firstChild;
                        if (!me.lastResults || inputElement.value != me.lastResults.searchString)
                            me.autoCompleteSession.onStartLookup(inputElement.value, me.lastResults, me.autoCompleteListener);
                        break;

                    case 'stopLookup':
//						me.privatefunc.closePopupMenu(me);
                        me.autoCompleteSession.onStopLookup();
                        break;

                    case 'autoComplete':
                        if (me.autoCompleteTimer) {
                            clearTimeout(me.autoCompleteTimer);
                            me.autoCompleteTimer = 0;
                        }
                        me.needToAutocomplete = false;
                        if (this.disableAutocomplete == "true" || me.privatefunc.imeInProgress(me))
                            return;
						var inputElement = document.getAnonymousNodes(me)[0].firstChild;
                        me.autoCompleteSession.onAutoComplete(inputElement.value, me.lastResults, me.autoCompleteListener);
                        break;
                }
			},

			finishAutoComplete: function(me, event) {
 			   me.privatefunc.closePopupMenu(me);
               if (me.disableAutocomplete == "true" || me.privatefunc.imeInProgress(me))
                    return;

                var inputElement = document.getAnonymousNodes(me)[0].firstChild;
				var value = inputElement.value;
                var entry = value.substring(0, inputElement.selectionStart) + value.substring(inputElement.selectionEnd, value.length);

                if (me.lastResults)
                {
                    if (me.lastResults.searchString == entry)
                    {
                        try {
                            inputElement.value = me.lastResults.items.QueryElementAt(me.lastResults.defaultItemIndex, Components.interfaces.nsIAutoCompleteItem).value;
                        } catch(e) {};
                        inputElement.setSelectionRange(inputElement.value.length, inputElement.value.length);
						return;
                    }
                }
                
                me.privatefunc.callListener(me, 'autoComplete');
			},

			closePopupMenu: function(me) {
				popup = document.getAnonymousNodes(me)[0].childNodes[1].firstChild;
				if (popup && me.menuOpen == "true")
					popup.closePopup();
			},

			cleanupInputField: function(me) {
				if (me.noDirectMatch)
				{
					var inputElement = document.getAnonymousNodes(me)[0].firstChild;
					var value = inputElement.value;
					var index = value.indexOf(">> ");
					if (index >= 0)
					{
						inputElement.value = value.substr(index + 3);
						inputElement.setSelectionRange(inputElement.value.length, inputElement.value.length);
					}
				}
			},

			keyNavigation: function(me, event, popup) {
				var inputElement = document.getAnonymousNodes(me)[0].firstChild;
				if (event.keyCode == 38 || event.keyCode == 40)
				{
					if (event.keyCode == 38)
					{
						if (me.privatefunc.selectedItemIndex <= -1)
							me.privatefunc.selectedItemIndex = me.lastResults.items.Count() - 1;
						else
							me.privatefunc.selectedItemIndex --;
					}
					else
					{
						me.privatefunc.selectedItemIndex ++;
						if (me.privatefunc.selectedItemIndex >= me.lastResults.items.Count())
							me.privatefunc.selectedItemIndex = -1
					}

					if (me.privatefunc.selectedItemIndex == -1)
						inputElement.value = me.lastResults.searchString;
					else
						inputElement.value = me.lastResults.items.QueryElementAt(me.privatefunc.selectedItemIndex, Components.interfaces.nsIAutoCompleteItem).value;
					inputElement.setSelectionRange(inputElement.value.length, inputElement.value.length);
					noDirectMatch = false;
					needToAutocomplete = false;

					//TODO: Need to change the menu selection
					if (popup)
					{
					}
					return;
				}
			},

			processKeyPress: function(me, event) {
				//Stop current lookup in case it's async.
				me.privatefunc.callListener(me, 'stopLookup'); 
				if (me.autoCompleteTimer) {
					clearTimeout(me.autoCompleteTimer);
					me.autoCompleteTimer = 0;
				}

				if (me.disableAutocomplete == "true" || me.privatefunc.imeInProgress(me))
					return;

				var popup = document.getAnonymousNodes(me)[0].childNodes[1].firstChild;
 				if (popup && me.menuOpen != "true")
					popup = null;

				switch (event.keyCode)
				{
					case 8: /*vk_back*/
					case 46: /*vk_delete*/
						me.privatefunc.closePopupMenu(me);
						me.lastResults.searchString='';
						return;

					case 13 /*vk_return*/:
						me.privatefunc.finishAutoComplete(me, event);
						return;

					case 27 /*vk_escape*/:
						if (popup) {
							me.privatefunc.closePopupMenu(me);
							event.preventDefault();
							event.preventBubble();
							return;
						}
						break;

					case 37 /*vk_left*/:
					case 39 /*vk_right*/:
						if (popup)
						{
							me.privatefunc.closePopupMenu(me);
							event.preventDefault();
							event.preventBubble();
						}
						me.privatefunc.cleanupInputField(me);
						break;

					case 38 /*vk_up*/:
					case 40 /*vk_down*/:
						me.privatefunc.keyNavigation(me, event, popup);
						event.preventDefault();
						event.preventBubble();
						me.privatefunc.cleanupInputField(me);
						break;

					default:
						me.needToAutocomplete = true;
						me.autoCompleteTimer = setTimeout(me.privatefunc.callListener, me.timeout, me, 'startLookup');
				}
			},

			selectedItemIndex: 0

        })
        ]]>
        </property>
        
	</interface>

	<handlers>
		<handler type="click" value="this.privatefunc.cleanupInputField(this);"/>
		<handler type="dblclick" value="this.privatefunc.cleanupInputField(this);"/>
        <handler type="keypress" value="this.privatefunc.processKeyPress(this, event);"/>
        <handler type="focus" value="this.needToAutocomplete = false; this.lastResults.searchString=''"/>
        <handler type="blur" value="
			this.privatefunc.closePopupMenu(this);
            if (this.needToAutocomplete)
                this.privatefunc.finishAutoComplete(this, event);
			this.privatefunc.cleanupInputField(this);
            "/>
    </handlers>

</binding> 
</bindings> 
