// 
// Copyright (c) 2008 Beau D. Scott | http://www.beauscott.com
// 
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
// 

/**
 * AutoComplete.js
 * Prototype/Scriptaculous based _suggest tool
 * @version 1.2
 * @requires prototype.js <http://www.prototypejs.org/>
 * @author Beau D. Scott <beau_scott@hotmail.com>
 * 6/5/2008
 */
var ischange=false;
var AutoComplete = Class.create();
AutoComplete.prototype = {
	/**
	 * The select object
	 * @var HTMLSelectElement
	 */
	selector: null,
	ishavelist:false,
	Conselector: null,
	blurtimeout: null,
	done:false,
	/**
	 * The component that triggers the suggest
	 * @var HTMLInputElement
	 */
	input: null,
	/**
	 * The timeout between lookups
	 * @var Integer
	 * @private
	 */
	_timeout: null,
	/**
	 * Visisbility status of the selector object
	 * @var Boolean
	 */
	visible: false,
	/**
	 * Flag indicating whether the components have been laid out
	 * @var Boolean
	 */
	drawn: false,
	
	/**
	 * Hide timeout
	 * @var Integer
	 * @private
	 */
	_hideTimeout: null,
	/**
	 * @param Object input ID of form element, or dom element,  to _suggest on
	 * @param String URL of dictionary
	 * @param Object options
	 */
	inputid:'',
	initialize: function(input, action, options)
	{
		this.action = action;
		this.input = $(input);
		this.inputid=input;
		this.options = new AutoComplete.Options();
		Object.extend(this.options, options || {});
		
		if(!this.input)
			alert('No input field/binding field given or found')

		if(!this.action)
			alert('No action url specified');
		this.Conselector=document.createElement("DIV");	
		this.Conselector.style.backgroundColor="white";
		this.selector = document.createElement('UL');
		this.selector.style.listStyle="none"; 
		this.selector.style.padding="0px";
		this.selector.style.margin="0px";
		document.body.appendChild(this.Conselector);
		Event.observe(this.input, 'focus', this._onInputFocus.bindAsEventListener(this));
		Event.observe(this.input, 'keyup', this._onInputKeyUp.bindAsEventListener(this));
		Event.observe(this.input, 'keydown', this._onInputKeyDown.bindAsEventListener(this));
		Event.observe(this.input, 'blur', this._onInputBlur.bindAsEventListener(this));
		//Event.observe(this.selector, 'blur', this._onSelectorBlur.bindAsEventListener(this));
		//Event.observe(this.selector, 'focus', this._onSelectorFocus.bindAsEventListener(this));
		//Event.observe(this.selector, 'change', this._onSelectorChange.bindAsEventListener(this)); 
		Event.observe(window, 'resize', this._reposition.bind(this));
		Event.observe(window, 'scroll', this._reposition.bind(this));
	},
	
	/**
	 * The input fields focus event handler
	 */
	_onInputFocus: function(event)
	{
	    this.inputid=this.input.id;
		this._onSelectorFocus(event);
	},
	
	/**
	 * The selector's blur event handler
	 * @param Event event
	 * @private
	 */
	_onSelectorBlur: function(event)
	{
		this._onInputBlur(event);
	},
	/**
	 * The input's blur event handler
	 * @param Event event
	 * @private
	 */
	 
	_onInputBlur: function(event)
	{   
	    /*  if(!this.ishavelist)
	      {
	          if(this.input.value.length < this.options.threshold)
		     {
			    return true;
		     }
	         this._timeout = setTimeout(this._sendRequest.bind(this), 1000 * this.options.delay);
			  ischange=true;			
			  this.blurtimeout=setTimeout(this._onInputBlur.bind(this), 100); return ;
	      }
	      */
	     
		 var parms=$$('li.sel'); 
		 if(parms!='')
		 	{
		 	 // alert(parms[0].lang);
		 	   if(parms[0].lang !=this.inputid)
		 	   parms='';
		 	   
		 	   
		 	}
		 if(parms=='' && !this.ishavelist )
		 {		   
		     this.ishavelist=false;
		    if(this.input.value.length < this.options.threshold)
		     {
			    return true;
		     }
		    else
		    {
			  this._timeout = setTimeout(this._sendRequest.bind(this), 1000 * this.options.delay);
			  ischange=true;			
			  this.blurtimeout=setTimeout(this._onInputBlur.bind(this), 100); return ;
		     }
		   
		 }
		 else if(ischange)
		 {
		    this._timeout = setTimeout(this._sendRequest.bind(this), 1000 * this.options.delay);
		   this.blurtimeout=setTimeout(this._onInputBlur.bind(this), 100); return ;
		 }
		 this.ishavelist=true;
		 this._cancelTimeout();
		 clearTimeout(this.blurtimeout);
		 this.blurtimeout=null;
		 if(parms && parms.length>0)
		 this.input.value=parms[0].title;this.done=true;
		 this._hideTimeout = null;		 
		this.hide();this.clearlists();
	},
	
	 
	/**
	 * Complete's the blur event handlers. Used as a proxy to avoid event collisions when blurring from the input
	 * and focusing on the selector during a mouse navigation
	 * @private
	 */
	_checkOnBlur:function()
	{
		this._hideTimeout = null
		this.hide();
	},
	/**ishavelist
	 * The input's key-up event handler
	 * @param Event event
	 * @private
	 */
	_onInputKeyUp: function(event)
	{
		this._suggest(event);
	},
	/**
	 * The input's key-down event handler
	 * @param Event event
	 * @private
	 */
	_onInputKeyDown: function(event)
	{
	   this.done=false;
		this._suggest(event);
	},
	/**
	 * The selectors's focus event handler.
	 * @param Event event
	 * @private
	 */
	_onSelectorFocus: function(event)
	{
		if(this._hideTimeout)
		{
			clearTimeout(this._hideTimeout);
			this._hideTimeout = null;
		}
	},
	/**
	 * The selector's change event handler
	 * @param Event event
	 * @private
	 */
	_onSelectorChange: function(event)
	{
		this.select();
	},
	/**
	 * Lays the UI elements of the control out, sets interaction options
	 * @param {Object} event Event
	 */
	draw: function()
	{
		if(this.drawn) return;
		if(this.options.cssClass)
			this.selector.className = this.options.cssClass;
		Element.setStyle(this.Conselector, {
			display: 'none',
			position: 'absolute',
			width: this.input.offsetWidth + 'px'
		});
		//this.selector.size = this.options.size;
		this.Conselector.appendChild(this.selector);
		this.input.autocomplete = 'off';
		this.drawn = true;
	},

	/**
	 * Hides the option box
	 */
	hide: function()
	{
	    
		if(!this.drawn || !this.visible) return;
		this.visible = false;
		if(window.Scriptaculous)
		{
			new Effect.BlindUp(this.Conselector, {
				duration: this.options.delay,
				queue: 'end',
				afterFinish: function(event){
					Element.setStyle(this.Conselector,{
						display: 'none'
					});
					this.clearlists();
					//setTimeout(this._restoreFocus.bind(this),50);
				}.bind(this)
			});
		}
		else
		{
			Element.setStyle(this.Conselector,{
				display: 'none'
			});
			this.clearlists();
			// FF hack, wasn't selecting without this small delay for some reason
			//setTimeout(this._restoreFocus.bind(this),50);
		}
	},
	/**
	 * Resores the focus to the input control to avoid the cursor getting lost somewhere.
	 * @private
	 */
	_restoreFocus: function() {
		this.input.focus();
	},
	/**
	 * Displays the select box
	 */
	show: function()
	{
	    if(this.done)return;
		if(!this.drawn) this.draw();
		var trigger = null;
		if(this.Conselector)
		{
			if(window.Scriptaculous)
			{
				new Effect.BlindDown(this.Conselector,{
					duration: this.options.delay,
					queue: 'end'
				});
			}
			else
			{
				Element.setStyle(this.Conselector,{
					display: 'inline'
				});
			}
			
			this._reposition();// this.ishavelist=true;
			this.visible = true;
		}
	},

	/**
	 * Removes the timeout function set by a suggest
	 * @private
	 */
	_cancelTimeout: function()
	{
		if(this._timeout)
		{
			clearTimeout(this._timeout);
			this._timeout = null;
		}
	},

	/**
	 * Triggers the suggest interaction
	 * @param {Object} event The interaction event (keyboard or mouse)
	 * @return Boolean Whether to continue the event
	 * @private
	 */
	_suggest: function(event)
	{
		this._cancelTimeout();
		var key = Event.keyPressed(event);
		var ignoreKeys = [
			20, // caps lock
			16, // shift
			17, // ctrl
			91, // Windows key
			121, // F1 - F12
			122,
			123,
			124,
			125,
			126,
			127,
			128,
			129,
			130,
			131,
			132,
			45, // Insert
			36, // Home
			35, // End
			33, // Page Up
			34, // Page Down
			144, // Num Lock
			145, // Scroll Lock
			44, // Print Screen
			19, // Pause
			93, // Mouse menu key
			
			
		];
		if(ignoreKeys.indexOf(key) > -1)
			return true;
        
		if(event.type == 'keydown')
		{
			if(Event.KEY_ESC == key)
			{
				this.cancel();
				Event.stop(event);
				return false;
			}
			
			if(!(key == Event.KEY_UP || key == Event.KEY_DOWN))
				return true;
				
		if(Event.KEY_TAB == key)
			{
				this.clearlists();				
				return false;
			}
			
		}

		switch(key)
		{
			case Event.KEY_LEFT:
			case Event.KEY_RIGHT:
				return true;
				break;
			//case Event.KEY_TAB:
			// this.select();
			//	return false;
			//    break;
			//case Event.KEY_BACKSPACE:
			case 46: //Delete
				this.cancel();
				return true;
				break;
			case Event.KEY_RETURN:
				this.select();
				return true;
				//return false;
				break;
			case Event.KEY_ESC:
				this.cancel();
				return false;
				break;
			case Event.KEY_UP:
			case Event.KEY_DOWN:
				if(event.type=='keydown')
					this._interact(event);
				Event.stop(event);
				return false;
				break;
			default:
				break;
		}
		
		if(this.input.value.length < this.options.threshold)
		{
			return true;
		}
		else
		{
		     this.ishavelist=false;
			this._timeout = setTimeout(this._sendRequest.bind(this), 1000 * this.options.delay);
			 ischange=true;
			return false;
		}

	},

	/**
	 * Sends the suggestion request
	 * @private
	 */
	_sendRequest: function()
	{ 
		this._request = new Ajax.Request(this.action + this.input.value, {
			onComplete: this._process.bind(this),
			method: this.options.requestMethod
		});
	},

	/**
	 * Repositions the selector (if visible) to match the new
	 * coords of the input.
	 * @private
	 */
	_reposition: function()
	{
		if(!this.drawn) return;
		var pos = Position.cumulativeOffset(this.input);
		pos.push(pos[0] + this.input.offsetWidth);
		pos.push(pos[1] + this.input.offsetHeight);
		Element.setStyle(this.Conselector,{
			left: pos[0] + 'px',
			top: pos[3] + 'px'
		});
	},

	/**
	 * Processes the resulting  from a suggestion request, adds options to the suggestion box.
	 * @param XMLHTTPRequest objXML The XMLHTTPRequest created by _sendRequest
	 * @param String jsonHeader the string of json commands to execute if the return type is json
	 * @private
	 */
	_process: function(objXML, jsonHeader)
	{
		//this.selector.options.length = 0;
	   // this.selector.children.length=0;
		switch(this.options.resultFormat)
		{
			case AutoComplete.Options.RESULT_FORMAT_XML:
				this._parseXML(objXML.responseXML);
				break;
			case AutoComplete.Options.RESULT_FORMAT_JSON:
				if(!jsonHeader)
				{
					jsonHeader = objXML.responseText && objXML.responseText.isJSON() ?
						objXML.responseText.evalJSON() : null;
				}
				this._parseJSON(jsonHeader);
				break;
			case AutoComplete.Options.RESULT_FORMAT_TEXT: 
				this._parseText(objXML.responseText);
				break;
			default:
				alert("Unable to parse result type. Make sure you've set the resultFormat option correctly");
				break;
		}
		
		this.show();ischange=false;
	/*	if(this.selector.options.length > (this.options.size))
			this.selector.size = this.options.size;
		else
			this.selector.size = this.selector.options.length > 1 ? this.selector.options.length : 2;
			
		if(this.selector.options.length)
		{
			//none selected by default
			this.selector.selectedIndex = -1;
			this.show();
		}
		else
			this.cancel();
			*/
	},
	
	/**
	 * Parses the XML result, adds options
	 * @param XML xml the XML of suggestions to parse
	 */
	_parseXML: function(xml)
	{
		var suggestions = null;
		for(var i = 0; i < xml.childNodes.length; i++)
		{
			if(xml.childNodes[i].tagName)
			{
				suggestions = xml.childNodes[i].childNodes;
			}
		}
		if(!suggestions)
		{
			alert("Could not parse response XML.");
			return;
		}
		for(i = 0; i < suggestions.length; i++)
		{
			suggestion = suggestions.item(i).firstChild.nodeValue;
			this._addOption(suggestion);
		}
	},
	/**
	 * Parses the JSON result, adds options 
	 * @param String json The json response to parse
	 */
	_parseJSON: function(json)
	{
		if(!json) json = [];
		for(i = 0; i < json.length; i++)
			this._addOption(json[i]);
	},
	/**
	 * Parses the TEXT result, adds options
	 * @param String text The text response to parse
	 */
	_parseText: function(text)
	{
	    
		var suggestions = (text||"").split(/\n/);
		/*alert(suggestions.length);
		if(suggestions.length>0 && text!="")
		{
		haveChilds=true;
		}
		*/
		 this.clearlists();
	 if(suggestions.length>0 && text!="")
	  {
		for(i = 0; i < suggestions.length; i++)
			this._addOption(suggestions[i]);
	  }
			if(this.selector.childNodes.length>0)
			{
			  this.selector.childNodes[0].className="sel";
			}
	},
	/**
	 * Creates a suggestion option for the given suggestion,
	 * adds it to the selector object.
	 * @param String suggestion The suggestion 
	 */
	_fixlowercase: function(orgtext,lowertext)
	{
	    
	},
	_addOption: function(suggestion)
	{
		//var opt = new Option(suggestion, suggestion);
		//Prototype.Browser.IE ? this.selector.add(opt) : this.selector.add(opt, null);
		var opt=document.createElement("li"); 
		var re = new RegExp(this.input.value,"i");
		opt.title=suggestion;
		r = suggestion.match(re);  
		var html=suggestion;
		if(r)
		 html=suggestion.replace(re,"<b>"+r[0]+"</b>");  
		            		
		opt.innerHTML=html;
		opt.style.margin="0px";
		opt.style.padding="0px";
		opt.style.listStyle="none"; 
		opt.language=this.selector.childNodes.length;
		opt.lang=this.inputid;
		this.selector.appendChild(opt); 
		Event.observe(opt, 'mouseover', this._onSelectorMover.bindAsEventListener(this)); 
	},
    _onSelectorMover: function(event)
    {
      this.clearsel();
      var opt=event.srcElement ?  event.srcElement : event.target;
      opt.className="sel"; 
      
    },
	/**
	 * Clears and hides the suggestion box.
	 */
	cancel: function()
	{
		this.hide();
	},
	/**
	*cleare suggetions lists
	*/
    clearlists: function()
    {
       
       for(var i=0;i<this.selector.childNodes.length;)
       {
        this.selector.removeChild(this.selector.childNodes[i]);
       }
    },
    /**
    * clear sel option
    */
    clearsel: function()
    {
      $$('li.sel').each(function(item){item.className=""}); 
		 
    },
	/**
	 * Captures the currently selected suggestion option to the input field
	 */
	select: function()
	{
	   this._onInputBlur();
		/*if(this.selector.options.length)
			this.input.value = this.selector.options[this.selector.selectedIndex].value;
		this.cancel();
		if(typeof this.options.onSelect == 'function')
		{
			this.options['onSelect'](this.input);
		}
		*/
	},

	/**
	 * Processes key interactions with the input field, including navigating the selected option
	 * with the up/down arrows, esc cancelling and selecting the option.
	 * @param Event event The interaction event
	 * @private
	 */
	_interact: function(event)
	{
		if(!this.visible) return;

		var key = Event.keyPressed(event);
		if(key != Event.KEY_UP && key != Event.KEY_DOWN) return;
		//var mx = this.selector.options.length;
        var index=0;
         var parms=$$('li.sel'); 
		 if(parms && parms.length>0)
		 index=parms[0].language;
		  
		if(key == Event.KEY_UP)
		{
			if(index== 0)
				index = this.selector.childNodes.length - 1;
			else
				index--;
		}
		else
		{
			if(index == this.selector.childNodes.length - 1)
				index = 0;
			else
				index++;
		}
		this.clearsel();
		this.selector.childNodes[index].className="sel";
	}
};

/**
 * Helper class for defining options for the AutoComplete object
 * @version 1.2
 * @requires prototype.js <http://www.prototypejs.org/>
 * @author Beau D. Scott <beau_scott@hotmail.com>
 * 6/5/2008
 */
AutoComplete.Options = Object.extend(Class.create(), {
	/**
	 * Enumeration for XML result format
	 * Be sure your response type is application/xml or text/xml
	 * @static
	 */
	RESULT_FORMAT_XML: 'xml',
	/**
	 * Enumeration for JSon result format
	 * @static
	 */
	RESULT_FORMAT_JSON: 'json',
	/**
	 * Enumeration for text result format
	 * @static
	 */
	RESULT_FORMAT_TEXT: 'text'
});
AutoComplete.Options.prototype = {
	/**
	 * Number of options to display before scrolling
	 * @var Number
	 */
	size: 10,
	/**
	 * CSS class name for autocomplete selector
	 * @var String
	 */
	cssClass: null,
	/**
	 * JavaScript callback function to execute upon selection
	 * @var Function
	 */
	onSelect: null,
	/**
	 * Minimum characters needed before an suggestion is executed
	 * @var Number
	 */
	threshold: 3,
	/**
	 * Time delay between key stroke and execution
	 * @var Number
	 */
	delay: .2,
	/**
	 * The request method to use when getting the suggestions
	 * @var String
	 */
	requestMethod: 'GET',
	/**
	 * The format of the results retrieved (xml, text or json)
	 * @var String
	 */
	resultFormat: AutoComplete.Options.RESULT_FORMAT_XML,
	/**
	 * Constructor
	 * @var Object overrides Overriding properties
	 */
	initialize: function(overrides)
	{
		if(overrides)
		{
			for(var item in overrides)
				this[item] = overrides[item];
		}
	}
}

//
// Various Prototype Event extensions
//
Object.extend(Event, {
	/**
	 * Enumeration for the backspace key code
	 * @static
	 */
	KEY_BACKSPACE: 8,
	/**
	 * Enumeration for the tab key code
	 * @static
	 */
	KEY_TAB:       9,
	/**
	 * Enumeration for the return/enter key code
	 * @static
	 */
	KEY_RETURN:   13,
	/**
	 * Enumeration for the escape key code
	 * @static
	 */
	KEY_ESC:      27,
	/**
	 * Enumeration for the left arrow key code
	 * @static
	 */
	KEY_LEFT:     37,
	/**
	 * Enumeration for the up arrow key code
	 * @static
	 */
	KEY_UP:       38,
	/**
	 * Enumeration for the right arrow key code
	 * @static
	 */
	KEY_RIGHT:    39,
	/**
	 * Enumeration for the down arrow key code
	 * @static
	 */
	KEY_DOWN:     40,
	/**
	 * Enumeration for the delete key code
	 * @static
	 */
	KEY_DELETE:   46,
	/**
	 * Enumeration for the shift key code
	 * @static
	 */
	KEY_SHIFT:    16,
	/**
	 * Enumeration for the cotnrol key code
	 * @static
	 */
	KEY_CONTROL:  17,
	/**
	 * Enumeration for the capslock key code
	 * @static
	 */
	KEY_CAPSLOCK: 20,
	/**
	 * Enumeration for the space key code
	 * @static
	 */
	KEY_SPACE:	  32,
	
	/**
	 * A simple interface to get the key code of the key pressed based, browser sensitive.
	 * @param Event event They keyboard event
	 * @return Integer the key code of the key pressed
	 */
	keyPressed: function(event)
	{
		return Prototype.Browser.IE ? window.event.keyCode : event.which;
	}
});
