/**
 * @param {String} input				the name of the input field that trigger suggestions
 * @param {String} action				the Ajax method to call
 * @param {Number} minNrOfCharacters 	optional: sets the number of characters a user has to type before the listbox is popped up; defaults to 3
 * @param {String} engineUrl			The Ajax backend CFC
 * @return {Suggestor}
 * 
 * @projectDescription
 * <p>
 * <b>Usage:</b>  this Suggestor observes form input fields, so the DOM needs to be fully loaded <u>before</u> you instantiate it.
 * The proper way to do this is by listening for a window.onload event. <br>
 * Also, as ID's for the input field you should use the names of the Java classes you want them to search with.
 * </p>
 * 
 * <p>
 * <b>Example: </b> <br>
 * <code>
 * Event.observe(window, 'load', init); <br><br>
 * function init() {<br>
 *	var suggestor = new Suggestor('search-film', 'suggestMovieTitles', 2, 'suggest.cfc'); <br>
 *	suggestor.filterByCountry('B'); <br>
 *	suggestor.setAutoSubmit('results.cfm'); <br>
 * }
 * </code>
 * </p>
 * 
 * <p>
 * <b>Compatibility:</b> IE7/PC, IE6/PC, FireFox 2.0, Opera 8.0, Safari, Camino <br>
 * <b>Not tested:</b> IE5.5/PC and below, FireFox 1.5 <br>
 * <b>Breaks</b> on IE5.2/Mac 
 * </p>
 * 
 * @version built on Prototype 1.5
 * @author Maxime Cowez
 * @copyright <a href="http://www.edge.be">Edge.be</a>
 */
if (!Edge) var Edge = {};
Edge.Suggestor = Class.create();
Edge.Suggestor.prototype = {
	
	/**
	 * @constructor
	 * @param {String} input				the name of the input field that trigger suggestions
	 * @param {String} action				the Ajax method to call
	 * @param {Number} minNrOfCharacters 	optional: sets the number of characters a user has to type before the listbox is popped up; defaults to 3
	 * @param {String} engineUrl			The Ajax backend CFC
	 * @return {Suggestor}
	 */
	initialize: function(input, action, engineUrl, options) {
		this.minNrOfCharacters = options.minNrOfCharacters || 3;
		this.input = $(input) || Edge.Tools.Debug.error("You need to pass in an text input elm as 1st parameter");
		this.action = action || Edge.Tools.Debug.error("You need to pass in an ajax method as 2nd parameter");
		this.engineUrl = engineUrl || Edge.Tools.Debug.error("You need to pass in an ajax url as 3d parameter");
				
		this.countryCode = options.countryFilter || null;
		this.languageCode = options.languageFilter || null;
		this.autoSubmit = $(options.autoSubmit) || false;		
		this.idIndex = options.overrideIdIndex || 0;
		
		var idStoreID = options.overrideIdStoreName || this.input.id + "_id";		
		if (!$(idStoreID)) new Insertion.After(this.input, '<input type="hidden" id="'+idStoreID+'" name="'+idStoreID+'" />');	//1.5	
		this.idStore = $(idStoreID);
		
		if (!$('suggestion-box')) {
			new Insertion.Top(document.body, '<div id="suggestion-box" ></div>');		//1.5
			$('suggestion-box').hide();
			Event.observe(document, 'click', this.hideOptions.bind(this));	//1.5
		}
		this.listbox = $('suggestion-box');
		
		this.selectionEL = this.boxClicked.bind(this);
		this.inputClickEL = this.searchAsYouType.bind(this);
		this.input.observe('click', this.inputClickEL);
		this.input.observe('keyup', this.inputClickEL); 			//IE hack: the 'enter' key is only recognized on keypress,
		this.input.observe('keydown', this.checkKey.bind(this));	//while a keyup event doesn't contain a character keyCode yet
	},
	
	/**
	 * @method checkKey
	 * Navigate if the trigger is an arrow key,
	 * else call the suggestion backend search.
	 * @param {Event} event	the event that triggered the search request
	 */
	checkKey: function(event) {
		if (event.keyCode) {
			switch (event.keyCode) {
				case Event.KEY_UP:	
					var selectedA = this.listbox.getElementsByClassName('selected')[0]; 
					if (selectedA) {
						var previousLI = selectedA.up(0).previous('li'); 					
						if (previousLI) {
							selectedA.removeClassName('selected');
							previousLI.down(0).addClassName('selected');
							this.listbox.scrollTop -= previousLI.getHeight();
						}
					}
					Event.stop(event);
					break;
				case Event.KEY_DOWN:
					var selectedA = this.listbox.getElementsByClassName('selected')[0]; 
					if (selectedA) {
						var nextLI = selectedA.up(0).next('li'); 					
						if (nextLI) {
							selectedA.removeClassName('selected');
							nextLI.down(0).addClassName('selected');
							var ypos = Position.cumulativeOffset(nextLI)[1] - Position.cumulativeOffset(this.listbox)[1];
							var liHeight = nextLI.getHeight();
							if (ypos > (this.listbox.getHeight() - liHeight)) this.listbox.scrollTop += liHeight;
						}
					}
					Event.stop(event);
					break;
				case Event.KEY_RETURN:
					var selected = this.listbox.getElementsByClassName('selected')[0]; 
					this.selectOption(selected, event);
					Event.stop(event);
					break;
				default: break;
			}
		}
	},
	
	/**
	 * @method searchAsYouType
	 * Looks for the input field that's being written in
	 * and launches a search if it finds 3 or more characters 
	 * @param {Event} event	the event that triggered the search request
	 */
	searchAsYouType: function(event) {
		if (event.keyCode != Event.KEY_UP && event.keyCode != Event.KEY_DOWN && event.keyCode != Event.KEY_RETURN) {
			var userString = $F(this.input);

			if (userString.toArray().length >= this.minNrOfCharacters) {			
				new Ajax.Request(this.engineUrl, 
					{parameters: {
						method: this.action, 
						term: userString, 
						countryCode: this.countryCode,
						languageCode: this.languageCode }, 
					onComplete: this.showOptions.bind(this), 
					onFailure: Edge.Tools.Debug.error});
			}
			else this.hideOptions(event);
		}
	},
	
	/**
	 * @method boxClicked
	 * Calls selectOption() when an option is clicked
	 * @param {Event} event	the event that triggered the selection
	 */
	boxClicked: function(event) {
		var option = Event.element(event);
		option.up(2).suggestor.selectOption(option, event);
	},
	
	/**
	 * @method selectOption
	 * By selecting an option the list disappears and the active input field is filled with the selected textvalue
	 * @param {Event} event	the event that triggered the selection
	 */
	selectOption: function(option, event) {
		Event.stop(event);
		
		if (option.tagName == "A") {
			this.input.value = option.text ? option.text : option.innerHTML.replace(/&amp;/ig, "&");	//Gecko OR IE7 and below
			this.hideOptions(event);
			this.idStore.value = option.id.split('_')[this.idIndex];
			
			if (this.autoSubmit) this.autoSubmit.submit();
		}
	},
	
	/**
	 * @method showOptions
	 * Shows the optionbox upon a successfull search
	 * and positions it underneath the triggering inputbox
	 * @param {XMLHttpObject} resp
	 */
	showOptions: function(resp) {
		this.listbox.update('<ul></ul>');
		var ul = this.listbox.getElementsByTagName('ul')[0];
		Edge.Tools.Ajax.get(resp, this.engineUrl).each(function(title) {new Insertion.Bottom(ul, decodeURIComponent(title))});	//1.5

		this.listbox.suggestor = this;
		this.listbox.stopObserving('click', this.selectionEL);
		this.listbox.observe('click', this.selectionEL);
		this.input.stopObserving('click', this.inputClickEL);
		
		var dim = this.input.getDimensions(); 
		var pos = Position.cumulativeOffset(this.input);
		$(ul.getElementsByTagName('a')[0]).addClassName('selected'); 

		if (Prototype.Browser.IE) {	//IE hack to simulate CSS hover	
			$A(ul.getElementsByTagName('li')).each(function(li) {
				li = $(li);
				li.observe('mouseover', this.ieHover);
				li.observe('mouseout', this.ieHoverOut.bind(this));
			}.bind(this));
		}
		
		try {
			var h = ul.getHeight();
		}
		catch (e) {var h = 300};		
		if (h > 300 || h == 0) h = 300;		
			this.listbox.setStyle({
			top:	(dim.height + pos[1]) + 'px',
			left:	pos[0] + 'px',
			width:	dim.width-2 + 'px',
			height:	h + 'px'
		});
		this.listbox.show();
		
		if (Prototype.Browser.IE && Edge.Tools.IErange(5.5, 6.0)) {
			document.getElementsByTagName('select').each(function(select) {
				var selectPos = Position.cumulativeOffset(select);
				var selectDim = select.getDimensions();
				if (Position.within(this.listbox, selectPos[0], selectPos[1]) || 
					Position.within(this.listbox, selectPos[0], selectPos[1] + selectDim[1]) || 
					Position.within(this.listbox, selectPos[0] + selectDim[0], selectPos[1]) || 
					Position.within(this.listbox, selectPos[0] + selectDim[0], selectPos[1] + selectDim[1]))
						select.style.visibility = 'hidden';
			}.bind(this));	//1.5
		}
	},
	
	/**
	 * @method ieHover
	 * IE specific method to simulate CSS hover: onMouseOver callback
	 * @param {MouseEvent} event	the mouse-over trigger Event
	 */
	ieHover: function(event) {
		Event.element(event).addClassName('selected'); 
	},
	
	/**
	 * @method ieHoverOut
	 * IE specific method to simulate CSS hover: onMouseOut callback
	 * @param {MouseEvent} event	the mouse-out trigger Event
	 */
	ieHoverOut: function(event) {
		Event.element(event).removeClassName('selected'); 
		var aTags = this.listbox.getElementsByTagName('ul')[0].getElementsByTagName('a');
		$(aTags[0]).addClassName('selected'); 
		for (var i=1; i<aTags.length; i++) $(aTags[i]).removeClassName('selected');
	},
	
	/**
	 * @method hideOptions
	 * Hides the optionbox if needed
	 * @param {Event} event
	 */
	hideOptions: function(event) {
		try {		
			if (this.listbox.visible()) {
				this.listbox.stopObserving('click', this.selectionEL);
				this.input.observe('click', this.inputClickEL);
				if (this.input.value.length < this.minNrOfCharacters) 	//STRANGE: after trigger is defined, activeInput is gone
					this.listbox.hide(); 						
				var trigger = Event.element(event);
				if (trigger != this.input || trigger != this.listbox) 
					this.listbox.hide();
			}
		}
		catch(ex) {}
	}
	
}
