/**
 * Search handler
 *
 * @author Ollie Maitland <ollie@byng-systems.com>
 * @copyright Byng Systms LLP
 * 
 * @requires Mootools-1.11
 * 
 * @param DomElement input
 * @param String gateway AJAX gateway URL
 * @param String redirectUri URL to redirect to
 * @param Int minStrLength Minimum number of charaters to submit the search request
 * 
 */
 
/**
 * Holds a global reference to the active search object
 * 
 * @param Search ActiveSearchRequest
 */
var ActiveSearchRequest = null;

/**
 * Holds the default DomElement name for AJAX results
 * 
 * @param String
 */
var ACTIVE_SEARCH_CONTAINER = 'live-search';

var Search = new Class(
{
	
	Extends : ByngAjax,

	options : {
		
		/**
		 * Set a minimun length before the request is sent (default 3 chars)
		 * 
		 * @type Int
		 */ 		
		minStrLength : 3,
	
		/**
		 * Holds the AJAX gateway URL for the search
		 * 
		 * @type String
		 */ 
		gateway : null,
	
		/**
		 * Holds the input element
		 * 
		 * @type DomElement
		 */ 
		input : null,
	
		/**
		 * Holds the redirect listener handle value
		 * 
		 * @type String
		 */
		redirectUri : null,
		
		/**
		 * Holds the request delay in ms
		 * 
		 * @type Int
		 */
		requestDelay : 200,

		/**
		 * Holds the control keys for search navigation
		 * 
		 * @type Array 
		 */
		controlKeys : ['down','up','left','right','enter'],
		
		/**
		 * Element to attach the loading icon to
		 * 
		 * @type Element
		 */
		loaderHost : $empty
	},
	
	/**
	 * Request timeout
	 * 
	 * @param Float requestTimeout
	 */
	requestTimeout : null,	
		
	/**
	 * Holds the number index of the selected result
	 * 
	 * @type Integer
	 */
	selectedResult : null,	
	
	/**
	 * Holds the stack of events (onEnter, onLeave etc)
	 * 
	 * @type Object
	 */
	events : {},
	
	/**
	 * Initiate the FAYT search
	 * 
	 * @param Element options The input field
	 * @param String gateway The URL for the search results
	 * @param String redirectUri The URL to redirect to with results
	 * @param Integer minStrLength Minimum string length
	 * @param Float requestDelay 
	 */
	initialize : function (options, gateway, redirectUri, minStrLength, requestDelay, events)
	{
		if ($type(options) == 'element') {
			options = {	'input'        : options, 
						'gateway'      : gateway, 
						'redirectUri'  : redirectUri, 
						'minStrLength' : minStrLength, 
						'requestDelay' : requestDelay, 
						'events'       : events}
		}
		
		if ($type(options) == 'object') {
			// condition: we have a ByngRequest
			if ($type(options.gateway) == 'object' && options.gateway.type == 'byngrequest') {
				options.gateway = options.gateway.composeAsString();
			}
			this.setOptions(options);			
			this.parent(options);
			if (!this.options.onComplete) {
				this.addEvent('response', this.doDrop.bind(this));
			} else {
				this.addEvent('response', this.options.onComplete);
			}		
			this.setInput(options.input);
		}
	},
	
	/**
	 * Set the input field
	 * 
	 * @param DomElement input
	 */
	setInput : function (input)
	{
		this.input = input;
		this.input.onkeydown = this.navigateResults.bind(this);
		this.input.onkeyup   = this.update.bind(this);
		
		
		this.input.addEvent('focus', function(event) {
			// close the other searches
		});
		
		// condition: redirect option in use
		if (this.options.redirect) {
			this.addEvent('select', function(key) {
				Byng.ui.gotoLocation(this.options.redirect + key);
			}.bind(this));
		}
	},
	
	/**
	 * Navigate through results by event
	 * 
	 * @param Event event
	 */
	navigateResults : function (event)
	{
		// bind the key capture events
	    var event = new Event(event);
	    if (event.key == 'down') {
	    	this.shiftActiveResult(+1);
		} else if (event.key == 'up') {
			this.shiftActiveResult(-1);
		} else if (event.key == 'enter') {
			this.openSelectedResult();
		}
		
		// condition: key pressed was a control key
		if (this.options.controlKeys.contains(event.key) == true) {
			// let the event run
		}
	},

	/**
	 * Get the active result row (by "shift")
	 * 
	 * @param Integer shift
	 * @return DomElement
	 */
	shiftActiveResult : function ( shift )
	{
		var list = this.getContainer().getElements('ul li');
		var active = list.filter(function(li) {
				return li.hasClass('active');
		});

		// find the new result to be selected as active
		if (active.length < 1) this.selectedResult = 0;
		else this.selectedResult = list.indexOf(active.pop())+shift;
		
		// condition: selection is in range
		if (this.selectedResult >= 0 && this.selectedResult < list.length) {
			return this.setActiveResult(list[this.selectedResult], list);
		} else {
			return null;
		}
	},
	
	/**
	 * Get the selected result row from the Integer
	 * 
	 * @return DomElement
	 */
	getSelectedResultRow : function ()
	{
		var item = this.getContainer().getElements('ul li').filter(function(li,indexOf) {
			return (indexOf == this.getSelectedResult());
		}.bind(this));
		if (item.length > 0) return item.pop();
		else return null;
	},
			
	/**
	 * Set the active result row
	 * 
	 * @param DomElement item Active list item
	 * @param DomElement[] List of results at time of key press
	 * @return DomElement
	 */
	setActiveResult : function (item, list)
	{
		if ($type(item) != 'element') {
			var item = this.getSelectedResultRow();
			if (!item) return;
		}
		
		// get the list of results
		if (!list) var list = this.getContainer().getElements('ul li');
		
		var targetIndex = list.indexOf(item);
		if (targetIndex != this.getSelectedResult()) {
			this.selectedResult = targetIndex;
		}
		// remove active list items
		list.removeClass('active');
		try { item.addClass('active').focus(); } catch (e) {}
		return item; 
	},
	
	/**
	 * Handle a mouse over event
	 * 
	 * @param Event event
	 * @return DomElement
	 */	
	onMouseover : function (event)
	{
		var event = new Event(event);
		return this.setActiveResult(event.target.parentNode);
	},
	
	/**
	 * Handle a mouse out event
	 * 
	 * @param Event event
	 * @return DomElement
	 */
	onMouseout : function (event)
	{
		var event = new Event(event);
		return this.setActiveResult();
	},
	
	/**
	 * Get the selected result
	 * 
	 * @return Integer
	 */
	getSelectedResult : function ()
	{
		return this.selectedResult;
	},
	
	/** 
	 * Set the container which the results appear
	 * 
	 * @param DomElement element
	 */
	setContainer : function (element) 
	{
		this.options.container = $(element);
	},
	
	/**
	 * Get the result container
	 * 
	 * @return DomElement
	 */
	getContainer : function ()
	{
		return this.options.container;
	},
	
	/**
	 * Populate the result set with the search results
	 * 
	 */ 
	update : function ( event ) 
	{
		if ($type(event) == 'event') {
			var event = new Event(event);
			// condition: coming out with a control key
			if (this.options.controlKeys.contains(event.key) == true) {
		    	return event.stop();
			}
		}
		
		// condition: this isn't the active search
		if (ActiveSearchRequest != this) {
			ActiveSearchRequest = this;
			Byng.input.addInputHandler(this, this.input, BYNG_INPUT_FAYT);
		}

		// condition: request string not long enough
		if (this.input.value.length < this.options.minStrLength) return;
		
		// use the value of the input
		this.q = this.input.value;
		
		// current timestamp in ms
		var date = new Date();
		var time = date.getTime();
				
		// condition: active request pending and request timed-out
		if (this.timeoutId && this.options.requestDelay && (this.requestTimeout > time)) {
			window.clearTimeout(this.timeoutId);
			this.timeoutId = null;
		}
		
		// condition: time-out delay set
		if (this.options.requestDelay > 0) {
			this.timeoutId = window.setTimeout(this.submitRequest.bind(this), this.options.requestDelay);			
			this.requestTimeout = time + this.options.requestDelay;
			return this.timeoutId;
		} else {
			return this.submitRequest();
		}

	},
	
	/**
	 * Set the result set from the AJAX response
	 * 
	 * @param String html HTML containing results
	 */
	setResults : function (html)
	{
		// show the container
		if (!this.getContainer()) throw "Result container not found";
			
		this.getContainer().setStyle('display','block').set('html',html);
	
		// reselect the active result
		if (this.getSelectedResult()) {
			this.setActiveResult();
		}
		
		// set the mouseover/mouseout events for list items
		this.getContainer().getElements('ul li')
			.addEvent('mouseover', this.onMouseover.bind(this))
			.addEvent('mouseout',  this.onMouseout.bind(this));
		
		// fire the content event
		Byng.app.fireEvent('popup', this.getContainer());
	},
	
	/**
	 * Submit the active search request
	 * 
	 * @return ByngAjax
	 */
	submitRequest : function ()
	{
		if ($type(this.options.loaderHost) == 'element') {
			Byng.ui.dom.showLoader(this.options.loaderHost);
		}
		return this.get( {'q' : this.q} );
	},
	
	/**
	 * Drop the drop down
	 * 
	 * @param String html
	 */
	doDrop : function (html) 
	{
		// condition: the search container is not set
		if ($type(ActiveSearchRequest.getContainer()) != 'element') {
			ActiveSearchRequest.setContainer($(ACTIVE_SEARCH_CONTAINER));			
		}
		
		// condition: a loader is present then hide it
		if (Byng.ui.dom.loader) {
			Byng.ui.dom.hideLoader();
		}
		// update the results
		ActiveSearchRequest.setResults(html);
	},
	

	/**
	 * Remove a search container
	 * 
	 */
	looseDrop : function ( event ) 
	{	
		if (!isObject(this.options.container)) {
			this.options.container = $('live-search');			
		}			

		if ( !$chk(event) ) {
			this.getContainer().setStyle('display','none');
		} else {
			window.setTimeout( function() {	this.getContainer().setStyle('display','none'); }.bind(this), 200);
		}
	},
	
	/**
	 * Redirect based on a listener handle
	 * 
	 * @param Int id Parameterised identifier for the redirect
	 * @param String listener Listener handle name 
	 */
	redirect : function (id, listener)
	{
		// condition: listener is supplied
		if (!isUndefined(listener)) {
			if (isObject(Byng.ui.response) && isObject(Byng.ui.response.getListener(listener))) {
					return Byng.ui.response.getListener(listener)(id);
			}
		}
		
		this.doRedirect(id);
	},
	
	/**
	 * Open the selected result
	 * 
	 * Find the active link and perform a click event
	 * 
	 * @return DomElement Link clicked
	 */
	openSelectedResult : function ()
	{
		if (!this.getContainer()) return;
		var link = this.getContainer().getElement('li.active a');
		if (!link) return;		
		// condition: Link already fired
		if (this.linkFired == true) {
			if (this.events.onEnter) this.events.onEnter(link);
			else return;
		} else {
			eval(link.getAttribute('href'));
			this.linkFired = true;
			return link;
		}
	},	
	
	/**
	 * Set the redirect URI
	 * 
	 * @param String uri URI to redirect when doRedirect is called
	 */
	setRedirectUri : function (uri)
	{
		this.redirectUri = uri;
	},
	
	/**
	 * Do a redirect with an id and label
	 * 
	 * @param String id Parameter to append
	 * @param String value Label for entity
	 */
	doRedirect : function (id, value)
	{
		// otherwise redirect from a string
		this.redirectUri += id;
		document.location = this.redirectUri;
	}

});
