 /******************************************************************************
	Autocompleter Class
 ******************************************************************************/
Autocomplete = Class.create();

Object.extend(Object.extend(Autocomplete.prototype), {

	/*** subclass constructor ***/
	initialize: function(element, update, callback, options) 
	{
		this.baseInitialize(element + "_value", update, options);
		this.keyElement = $(element);
		this.options.onShow = function(element, update)
		{ 
			if(! update.style.position || update.style.position == 'absolute') 
			{
				update.style.position = 'absolute';
			    Position.clone(element, update, 
				{
					setWidth: false, 
					setHeight: false, 
					offsetTop: element.offsetHeight
			  	});
			  	
			}
			//Effect.Appear(update, {duration:0.15});
			Element.show(update);
		};
		
		this.options.onHide = function(element, update)
		{ 
		    //new Effect.Fade(update,{duration:0.15}) 
		    Element.hide(update);
        };

		this.update.style.height = "200px";
		this.update.style.width = this.options.width || "200px";
		
		this.options.frequency = 0.0;
		this.options.asynchronous  = true;
		this.options.onComplete    = this.onComplete.bind(this);
		this.options.defaultParams = this.options.parameters || null;
		this.options.onIntellisense = this.options.onIntellisense || null;
		this.options.onAfterBlur = this.options.onAfterBlur || null;
		this.options.afterUpdateElement = this.options.afterUpdateElement || null;
		
		this.callback              = callback;
		this.data = null;
		this.masterData = null;
		this.toggled = false;
		this.localData = this.options.data || null;
		this.local = this.options.local || null;
		this.allowInvalidInput = this.options.allowInvalidInput || false;
		this.loaded = false;
        this.current = null;
        this.selectedValue = this.options.selectedValue || null;
        this.lastIndex = 0;
        
		if(this.local && this.localData)
			this.initLocal();
			
		if(this.selectedValue)
		    this.setValue(this.selectedValue, true);
		   
	    Event.observe(this.element, "keydown", this.onKeyDown.bindAsEventListener(this));
	    //Event.observe(this.element, "change", this.onChange.bindAsEventListener(this));
        Event.observe(this.element, "focus", this.onFocus.bindAsEventListener(this));
        Event.observe(document, "click", this.onDocumentClick.bindAsEventListener(this), false);
	},
	
	/*** custom functions ***/
	
    onDocumentClick: function(event)
    {
        if(! this.visible)
        {
            this.hide();
	    }

	    this.visible = false;
    },
	
	initLocal: function()
	{
		this.data = this.localData;
		var html = new StringBuilder();
		
		html.append("<div>");
		
		for(var i = 0; i < this.data.length; i++)
		{
			if(typeof this.data[i].html == "undefined" || this.data[i].html == null)
			{
				html.append("<div>");
				html.append(this.data[i].value);
				html.append("</div>");
			}
			else
				html.append(this.data[i].html.unescapeHTML2());
		}
	
	    html.append("</div>");
	   
		this.update.innerHTML = html.toStr();
		
      	Element.cleanWhitespace(this.update);
      	Element.cleanWhitespace(this.update.firstChild);

      	if(this.update.firstChild && this.update.firstChild.childNodes) 
		{
        	this.entryCount = this.update.firstChild.childNodes.length;
//			for (var i = 0; i < this.entryCount; i++) 
//			{
//          		var entry = this.getEntry(i);
//          		entry.autocompleteIndex = i;
//          		//this.addObservers(entry);
//        	}
      	} 
		else 
		{ 
        	this.entryCount = 0;
      	}
	},
	
	onComplete: function(res) 
	{
		if(Object.contains(res, "__type"))
    	    this.data = eval(res.value);
	    else
	        this.data = res;
	
		var html = "";

        if (this.data != null) {
		    for(var i = 0; i < this.data.length; i++)
		    {
			    if(typeof this.data[i].html == "undefined" || this.data[i].html == null)
				    html += "<div>" + this.data[i].value + "</div>";				
			    else
				    html += this.data[i].html.unescapeHTML2();
		    }
		}
		
		this.updateChoices("<div>" + html + "</div>");
	},
	
	getUpdatedChoices: function() 
	{
		if(this.local)
		{
			if(! this.toggled && ! this.hasFocus) this.options.onComplete(this.localData);
			
			var current = this.intellisense();
			this.options.onComplete(current);
		}
		else
		{
		    eval(this.callback + "(this.getToken(), this.options.onComplete);");
		}
	},
	
	intellisense: function()
	{
	    if(this.options.onIntellisense)
	       return this.options.onIntellisense(this);
	       
		var match = this.getToken().trim().toUpperCase();
		var perfectMatch = false;
		
		if(match == "")
			return this.localData;
			
		var data = [];
		var value = "";
	    var localDataLength = this.localData.length;
		for (var i = 0; i < localDataLength; i++)
		{
			value = this.localData[i].value.toUpperCase();
			
			if (value.indexOf(match) == 0)
			{
				if(value == match) perfectMatch = true;
				data.push(this.localData[i]);
			}
		}	

		if(perfectMatch) return this.localData;
		
		return data;
	},
		
	onFocus: function(event)
	{
	    this.hasFocus = true;   
	},
	
	ctrlDown: false,
	inCopy: false,
	inPaste: false,
	
	onKeyDown: function(event)
	{
        switch(event.keyCode) 
		{
			case 17:
			    this.ctrlDown = true;
			    break;
			
			case 86: // paste
			    if(this.ctrlDown) 
			    {  
			        this.inPaste = true;
			        this.ctrlDown = false;
			    }
			    break;
			    
			case 67: // copy
			    if(this.ctrlDown) 
			    {
			        this.inCopy = true;
			        this.ctrlDown = false;
			    }
			    break;
			    
			default:
			    this.ctrlDown = false;
			    break;
		} 
	},

	/*** overriding functions ***/
	onKeyPress: function(e) 
	{
	    if(this.inCopy)
	    {
	        this.inCopy = false;
	        return;
        }
	    else if(this.inPaste)
	    {
	        this.inPaste = false;
	        return;
	    }
	
		if(this.active)
		{
			switch(e.keyCode) 
			{
				case Event.KEY_TAB:
				    if(this.options.autoSelectItemOnTab == true){
					    this.selectEntry();
					}
					this.hide();
					this.active = false;
					return;
				case Event.KEY_RETURN:
					this.selectEntry();
					Event.stop(e);
				case Event.KEY_ESC:
					this.hide();
					this.active = false;
					Event.stop(e);
					return;
				case Event.KEY_LEFT:
				case Event.KEY_RIGHT:
					return;
				case Event.KEY_UP:
				   	this.markPrevious();
					this.render();
					if(navigator.appVersion.indexOf('AppleWebKit') > 0) 
						Event.stop(e);
	
					return;
				case Event.KEY_DOWN:
				    if(! this.allowInvalidInput)
                    {
                        if(this.keyElement.value == null || this.keyElement.value.length == 0)
                            this.element.value = "";
                    }
					this.markNext();
					this.render();
					if(navigator.appVersion.indexOf('AppleWebKit') > 0) 
						Event.stop(e);
					return;
			}
		}
		else 
		{
		    if(e.keyCode == Event.KEY_RETURN){
		        return;
		    }
		    else if(e.keyCode == Event.KEY_TAB || (navigator.appVersion.indexOf('AppleWebKit') > 0 && e.keyCode == 0)){
				return;
			} else {
			    this.setHeight();
    			
			    if(this.local)
				    this.options.onComplete(this.localData);
			}
		}		
		
		this.changed = true;
	    this.hasFocus = true;

    	if(this.observer) clearTimeout(this.observer);
		      this.observer = setTimeout(this.onObserverEvent.bind(this), this.options.frequency * 100);
	},
	
	markPrevious: function() 
	{
	    this.lastIndex = this.index;
        if(this.index > 0) 
            this.index--
        else 
            this.index = this.entryCount - 1;
        
        this.updateScrollPosition(true);
        //this.getEntry(this.index).scrollIntoView(true);
    },
      
    markNext: function() 
    {
        this.lastIndex = this.index;
        if(this.index < this.entryCount - 1) 
            this.index++
        else 
            this.index = 0;
        
        this.updateScrollPosition(false);
        //this.getEntry(this.index).scrollIntoView(false);
    },
    
    updateScrollPosition: function(direction)
    {
        var pos = (direction) ? 10 : 11;
        
        if(this.index == 0)
            this.update.scrollTop = 0;
        else if (this.index == (this.entryCount - 1))
            this.update.scrollTop = this.update.scrollHeight;
        else if(this.index % pos == 0)
            this.update.scrollTop = (direction) ? (17 * this.index) - 170 : (17 * this.index) - 17;
    },
	
	getEntry: function(index) 
	{
        return this.update.firstChild.childNodes[index];
    },
  
	onHover: function(event) 
	{
		var element = Event.element(event);// Event.findElement(event, "LI");
		
		if(this.index != element.autocompleteIndex) 
		{
		    this.lastIndex = this.index;
			this.index = element.autocompleteIndex;
			this.render();
		}
		Event.stop(event);
	},
	
	onClick: function(event) 
	{
		var element =  Event.element(event); //Event.findElement(event, "LI");
		this.index = element.autocompleteIndex;
		this.lastIndex = this.index;
		this.selectEntry();
		this.hide();
	},	
	
	selectEntry: function() 
	{
    	this.active = false;
	    this.updateElement(this.data[this.index]);
  	},
  	
  	setValue: function(key, stopEvents)
  	{
  	    if(! this.data || this.data.length == 0)
  	        this.data = this.localData;
  	
  	    for(var i = 0; i < this.data.length; i++)
  	    {
  	        if(this.data[i].key == key)
  	        {
                this.updateElement(this.data[i], stopEvents);
  	            return;
  	        }
  	    }
  	},
  	
  	setValueBasedOnInput: function(value, stopEvents)
  	{
  	    for(var i = 0; i < this.data.length; i++)
  	    {
  	        if(this.data[i].value == value)
  	        {
                this.updateElement(this.data[i], stopEvents);
  	            return;
  	        }
  	    }
  	},
	
	updateElement: function(selectedElement, stopEvents) 
	{
		var data = selectedElement;
		this.current = data;
		
		if (this.options.updateElement && ! stopEvents) 
		{
			this.options.updateElement(selectedElement);
			return;
		}
		
	    var value = data.value;
		var key = data.key;
		var lastTokenPos = this.findLastToken();
		
    	if (lastTokenPos != -1) 
		{
			var newValue = this.element.value.substr(0, lastTokenPos + 1);
			var newKey = this.keyElement.value.substr(0, lastTokenPos + 1);

			var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
			var keyWhitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
			
			if (whitespace)
		        newValue += whitespace[0];
			if (keyWhitespace)
				newKey += keyWhitespace[0];
				
      		this.element.value = newValue + value;
			this.keyElement.value = newKey + key;
    	} 
		else 
		{
      		this.element.value = value;
			this.keyElement.value = key;
	    }
    
        this.element.value = this.element.value.unescapeHTML2();
        if(this.element.visible()) {
            try {
                this.element.focus();
            } catch (e){
            
            }
        }
	
		if (this.options.afterUpdateElement && ! stopEvents)
			this.options.afterUpdateElement(this, this.element, selectedElement);
	},
	
	toggle: function(event)
	{
		if(this.local)
			this.data = this.localData;

		if(this.toggled)
		{
			this.hide();
		}
		else
		{
		    //this.element.focus();
		    this.hasFocus = true;
		    this.options.onComplete(this.localData);
		   // for (var i = 0; i < this.entryCount; i++)
		    //    this.index == i ? Element.addClassName(this.getEntry(i), "selected") : Element.removeClassName(this.getEntry(i), "selected");
            this.show();			
            
			//this.render(event);
		   // this.setHeight();
	    }
	    
	    Event.stop(event);
	},

	
	clear: function()
	{
	    this.keyElement.value = null;
	    this.current = null;
	},
	
	onObserverEvent: function() 
	{
	    this.clear();
    	this.changed = false;
		
		if(this.local &&  this.getToken().length < this.options.minChars)
		{
			this.getUpdatedChoices();
			return;
		}

	    if(this.getToken().length >= this.options.minChars) 
		{
	      this.startIndicator();
    	  this.getUpdatedChoices();
	    } 
		else 
		{
	      this.active = false;
    	  this.hide();
    	}
	},
	
	hide: function() 
	{
	//Debug.log("hide:" + this.hasFocus);
	
		this.stopIndicator();
		if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
		if(this.iefix) Element.hide(this.iefix);
		
		this.toggled = false;
	},
  		
  	show: function() 
	{
	//Debug.log("show:" + this.hasFocus);
	
    	if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
	    
	    if(! this.iefix && 
		  (navigator.appVersion.indexOf('MSIE')>0) &&
		  (navigator.userAgent.indexOf('Opera')<0) &&
		  (Element.getStyle(this.update, 'position')=='absolute')) 
		{
		    /* Include a bit or margin at top to allow user to click on the close button */
      		new Insertion.After(this.update, 
			   '<iframe id="' + this.update.id + '_iefix" '+
			   'style="display:none;position:absolute;margin-top:18px;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
			   'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
      		this.iefix = $(this.update.id+'_iefix');
	    }
    	if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
		
		this.toggled = true;
	},
	
	render: function(event) 
	{
    	if(this.entryCount > 0) 
		{
		    Element.removeClassName(this.getEntry(this.lastIndex), "selected");
            Element.addClassName(this.getEntry(this.index), "selected");
      		//for (var i = 0; i < this.entryCount; i++)
		     //   this.index == i ? Element.addClassName(this.getEntry(i), "selected") : Element.removeClassName(this.getEntry(i), "selected");
        
      		if(this.hasFocus) 
			{ 
		        this.show();
        		this.active = true;
			}
			
			this.setHeight();
			
		} 
		else 
		{
      		this.active = false;
      		this.hide();
    	}
  	},
	
	setHeight: function()
	{
	    if(this.update.offsetHeight < 200)
		    this.update.style.height = "auto";
	    else
		    this.update.style.height = "200px";
		   
	},
	
	resetData: function()
	{	    
	    if (this.masterData != undefined)
	    {
	        this.localData = this.masterData.slice();
  	    }
  	},
  	
  	removeAt: function(index)
  	{
  	    // Make a copy of the data first so it can be restored using resetData()
  	    if (this.masterData == undefined)
  	    {
  	        this.masterData = this.localData.slice();
  	    }  	     	    
  	    
  	    this.localData.splice(index, 1);
  	},	    
	
	onBlur: function(event) 
	{
	    if(! this.allowInvalidInput)
	    {  
	        if(this.keyElement.value == null || 
	            this.keyElement.value.length == 0 ||
	            this.keyElement.value == "null")
	        {  
	            if(this.element.value.trim().length != 0)
	            {
	                this.setValueBasedOnInput(this.element.value, false);
	                
	                if(this.keyElement.value == null || 
	                    this.keyElement.value.length == 0 ||
	                    this.keyElement.value == "null")
                    {
                        this.keyElement.value = "";
	                    this.element.value = "";
	                }
	            }
	            else
	            {
	                this.keyElement.value = "";
	                this.element.value = "";
	            } 
            }
           
	    }
	    
	    if (Element.getStyle(this.update, 'display') == 'none') { return; }
	
	/*
        // needed to make click events working
        setTimeout(this.hide.bind(this), 250);
        this.hasFocus = false;
        this.active = false;     
     */
        if (this.options.onAfterBlur)
            this.options.onAfterBlur(this);
    }
});
