/**
 * @version 0.2.2
 * @lastUpdated 12/10/2009
 * @classDescription SearchPreview provides and manages listing of search results below a text input box.
 * @return {Object}	Returns an instance of class.
 * @projectDescription The class depends on Prototype v1.6+, Scriptaculous v1.8.1+ and is the couterpart to popajax's AjaxSearchResults.ascx control.
 * Markup dependencies:
 *		-An associated text input element
 *		-A containing/wrapper div for result items
 *		-The results items need to be within a <dl> or <ul>, depending on if categorization is used.
 *		-Supporting css
 */
var SearchPreview = Class.create({
	/**
	 * Constructor
	 * @param {String} clientID ClientID of custom control to reference dom node.
	 * @param {String} uniqueID .NET UniqueID of custom control (used for resultContainer and for __EVENTTARGET).
	 * @param {String} textInput ID/element of text search box.
	 * @param {Object} options Hash of preferences and optional callback functions.
	 */
	initialize: function(clientID, uniqueID, textInput, options){
		this.clientID = clientID;
		this.uniqueID = uniqueID;
		this.txtSearch = $(textInput);
		this.resultsContainer = $(clientID); //resultsContainer is the element of containing <div>

		if(!this.txtSearch && !this.resultsContainer){	return;} //element check

		this.cachedResults = $H();		// stores cached responses for backspacing
		this.hasFocus = false;			// does the control have focus?
		this.changed = false;			// has the value of the textbox changed?
		this.active  = false;			// is the containing div active/showing?
		this.index = 0;					// index of currently "selected" result item
		this.resultItemCount = 0;		// number of item elements <dd>/<li> in the list
		
		// set options and defaults
		this.options = Object.extend({
			showEmptyResults: true,		// show results when there are no result items?
			frequency: 0.4,				// pausing time after key pressing
			minChars: '1',					// minimum characters for making a request
			usingCategories: true,		// is this a categorized <dl>, or flat list <ul>?
			onShow: function(txtSearch, resultsContainer){
				if(!resultsContainer.style.position || resultsContainer.style.position=='absolute') {
					resultsContainer.style.position = 'absolute';
					// having ie7 issues due to Element.getOffsetParent: see http://prototype.lighthouseapp.com/projects/8886/tickets/90-cloneposition-problem-in-ie7
					Element.clonePosition(resultsContainer, txtSearch, {
						setHeight: false,
						setWidth: false,
						offsetTop: txtSearch.getHeight()
					});
					/*
					var searchOffset = txtSearch.cumulativeOffset();
					var resultsLeft = (searchOffset.left );
					var resultsTop = searchOffset.top + txtSearch.getHeight();
					resultsContainer.setStyle({left: resultsLeft + 'px', top: resultsTop + 'px'});
					*/
				}
				Effect.Appear(resultsContainer,{duration:0.15});
			},
	      
			onHide: function(txtSearch, resultsContainer){ 
				new Effect.Fade(resultsContainer,{duration:0.15});
			},
			
			onSelectResultItem: function(elSelected){
				// default to redirecting to first link in selected resultItem
				if(Object.isElement(elSelected) && elSelected.select('A').size() > 0){
					var lnk = elSelected.select('A').first();
					window.location = lnk.href;
				}
			}
			
		}, options || {});		
		
		this.observer = null;		// timerid for keypress pause
		this.txtSearch.setAttribute('autocomplete','off');
		this.itemTagName = (this.options.usingCategories) ? 'DD' :'LI';
		
		
		this.resultsContainer.hide();
		if(this.options.spinner){this.options.spinner.hide();}

		// attach events
		this.txtSearch.observe('blur', this.__onBlur.bindAsEventListener(this));
		this.txtSearch.observe('keydown', this.__onKeyPress.bindAsEventListener(this));

		var beforeHandle = this.__onBeforeRequest.bindAsEventListener(this);
		var afterHandle = this.__onAfterResponse.bindAsEventListener(this);
		document.observe('searchresults:beforeXhr', beforeHandle);
		document.observe('searchresults:afterXhr', afterHandle);

	},
	/**
	* Either loads the container with results from the cache, or sends a xhr request.
	*/
	//getResults: function(e){
	getResults: function(){
		// being fired on keypress by onObserverEvent
		var txtValue = this.txtSearch.value.strip();
		if(this.cachedResults.get(txtValue)){
			// get from the cache
			this.resultsContainer.update(this.cachedResults.get(txtValue));
			this.updateResults();
		}
		else{
			// not in cache, make request
			if(!$('__EVENTTARGET')){
				// HACK:  create the eventtarget node for doAsyncPostBack method. 
				this.txtSearch.up('form').appendChild(new Element('input', {'type': 'hidden', value: this.uniqueID, id: '__EVENTTARGET', name: '__EVENTTARGET'}));
			}
			Page.post({
				target: this.uniqueID, // e.g. "uxSearchResults"
				argument: txtValue,
				before: "searchresults:beforeXhr",
				after: "searchresults:afterXhr"
			});
		}
	},
	
	__onBeforeRequest: function(e, requestOptions){
		//console.log('before');
		this.startSpinner();
		// send xhr
		document.fire(e.memo.eKey, e.memo); 
	},
	
	__onAfterResponse: function(e){// e has memo with pbOptions and transport.
		//console.log('after');
		//Find the text value was beforeRequest and put into cache.
		this.cachedResults.set(e.memo.parameters.get("__EVENTARGUMENT"), this.resultsContainer.innerHTML);// results are also available in e.memo.transport.responseXML
		
		this.updateResults();
		
		this.stopSpinner();
		
	},	

	/**
	* Updates the result item list
	* @param {String} results A string of html to update the list with
	*/
	updateResults: function(){ // logic from updateChoices
		if(!this.changed && this.hasFocus) {
			// The container contents should have been updated via pop.ajax response or from cache
			Element.cleanWhitespace(this.resultsContainer);
			Element.cleanWhitespace(this.resultsContainer.down());
			
			if(this.resultsContainer.firstChild && this.resultsContainer.down(this.itemTagName)) {
				var elItems =  this.resultsContainer.select(this.itemTagName);
				this.resultItemCount = elItems.length;
				for (var i = 0; i < this.resultItemCount; i++) {
					var resultItem = elItems[i];
					resultItem.resultIndex = i;
					this.addObservers(resultItem)
				}
			} else { 
				this.resultItemCount = 0;
			}

			//this.stopSpinner();
			
			this.index = 0;
			
			this.render();
			this.resultsContainer.fire('searchresults:update');
			// RAH specific attach a click observer to the full results link 
			// so it can invoke click on the Go button.
			var lnkFullResults = this.resultsContainer.down('a#lnk_full_results');
			if(lnkFullResults){
				lnkFullResults.observe('click', this.__fullResultsClick.bindAsEventListener(this));
			}
			
		}		
	},	

	/**
	* Setup the list item classes and show or hide the list.
	*/
	render: function() {
		if(this.resultItemCount > 0 || this.options.showEmptyResults) {
			for (var i = 0, count = this.resultItemCount; i < count; ++i) {
		 		this.index == i ? 
					Element.addClassName(this.getResultItem(i), "selected") : 
					Element.removeClassName(this.getResultItem(i), "selected");
				i == (this.resultItemCount -1) ? 
					Element.addClassName(this.getResultItem(i), "last") : 
					Element.removeClassName(this.getResultItem(i), "last");
				(i == 0 && (count > 1)) ? 
					Element.addClassName(this.getResultItem(i), "first") : 
					Element.removeClassName(this.getResultItem(i), "first");
		 	}
			if(this.hasFocus) { 
				this.show();
				this.active = true;
			}
		} else {
			if(!this.options.showEmptyResults){
				this.active = false;
				this.hide();
			}
			
		}
	},
	
	/**
	* Mark the previous result item as selected
	*/  
	markPrevious: function() {
		if(this.index > 0){this.index--;}
		else{this.index = this.resultItemCount-1;}
		//this.getResultItem(this.index).scrollIntoView(true);
		this.getResultItem(this.index);
	},
	
	/**
	* Mark the next result item as selected
	*/  
	markNext: function() {
		if(this.index < this.resultItemCount-1) {this.index++}
		else {this.index = 0;}
		//this.getResultItem(this.index).scrollIntoView(false);
		this.getResultItem(this.index);
	},
	
	/**
	* Get a result item <dd> or <li>
	* @param {Integer} index The node index of the list item to retrieve
	*/  
	getResultItem: function(index) {
		return this.resultsContainer.down(this.itemTagName, index);
	},
	
	/**
	* Get a result item <dd> or <li> that is currently selected.
	*/   
	getCurrentResultItem: function() {
		return this.getResultItem(this.index);
	},
	
	/**
	* Action taken when a result item is selected by the user via ENTER or click.
	* Can be extended through options.onSelectResultItem.
	*/   
	selectResultItem: function() {
		this.active = false;
		var elSelected = this.getCurrentResultItem();
		if (this.options.onSelectResultItem) {
			this.options.onSelectResultItem(elSelected);
		}
	},

	show: function() {
		if(!this.resultsContainer.visible()){
			this.options.onShow(this.txtSearch, this.resultsContainer);
		    this.resultsContainer.fire("searchresults:show");
		}
	},

	hide: function() {
		this.stopSpinner();
		if(this.resultsContainer.visible()) {
		    this.options.onHide(this.txtSearch, this.resultsContainer);
		    this.resultsContainer.fire("searchresults:hide");
		}
		
	},

	startSpinner: function() {
		if (this.options.spinner) {
	  		new Effect.Appear(this.options.spinner,{duration:0.15}); 
		}
	},

	stopSpinner: function() {
		if(this.options.spinner) {
			new Effect.Fade(this.options.spinner,{duration:0.25}); 
		}
	},
	
	addObservers: function(element) {
		Event.observe(element, "mouseover", this.__onHover.bindAsEventListener(this));
		Event.observe(element, "click", this.__onClick.bindAsEventListener(this));
	},
	
	onObserverEvent: function() {
		this.changed = false;
		if(this.txtSearch.value.strip().length >= this.options.minChars) {
			this.getResults();
		} else {
			this.active = false;
			this.hide();
		}
	},
		
	/**
	* Event handlers
	*/	
	__onKeyPress: function(e){
		if(this.active){
			switch(e.keyCode) {
			 //case Event.KEY_TAB:
			 case Event.KEY_RETURN:
				e.stop();
				this.gotoFullResults();
			 case Event.KEY_ESC:
				this.hide();
				this.active = false;
				e.stop();
				return;
			 case Event.KEY_LEFT:
			 case Event.KEY_RIGHT:
				return;
			 case Event.KEY_UP:
				this.markPrevious();
				this.render();
				e.stop();
				return;
			 case Event.KEY_DOWN:
				this.markNext();
				this.render();
				e.stop();
				return;
			}
		}
		else {
		    if (e.keyCode == Event.KEY_RETURN) {
				e.stop();
		        this.gotoFullResults();
            } else		    
			if(e.keyCode==Event.KEY_TAB || (Prototype.Browser.WebKit > 0 && e.keyCode == 0)) return;
		}
		
		this.changed = true;
		this.hasFocus = true;

		if(this.observer){clearTimeout(this.observer);}
		this.observer = setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
		
	},

	__onBlur: function(e) {
		// needed to make click outside events work
		setTimeout(this.hide.bind(this), 250);
		this.hasFocus = false;
		this.active = false;
	},
	
	__onHover: function(e) {
		var element = Event.findElement(e, this.itemTagName);
		if(this.index != element.resultIndex) {
			this.index = element.resultIndex;
			this.render();
		}
		e.stop();
	},
	
	__onClick: function(e){
		var el = Event.findElement(e, this.itemTagName);
		this.index = el.resultIndex;
		this.selectResultItem();
		this.hide();
	},
	
	// RAH specific event handler
	__fullResultsClick: function(e){
		e.stop(e);
		this.gotoFullResults();
	},
	
	gotoFullResults: function(){
		// find go button and invoke it's click event
		//var btnGo = $$('#header fieldset#searchbox input[id$=uxSearch]').first();
		//btnGo.click();
		
		// redirect to: /search/?term=XXX&startD=today's date&endD=date one year out
		var params = $H();
		var dtNow = new Date();
		var dtYearFromNow = new Date();
		dtYearFromNow.setFullYear(dtNow.getFullYear()+1);
		params.set('term', this.txtSearch.value);
		params.set('startD', this._formatDate(dtNow));
		params.set('endD', this._formatDate(dtYearFromNow));
		
		window.location.href = '/search/?' + params.toQueryString();	
	},
	
	//yyyy-mm-dd
	_formatDate: function (dt){
		var y = dt.getFullYear();
		var m = String(dt.getMonth()+1);
		if(m.length === 1){m = '0' + m;}
		var d = String(dt.getDate());
		if(d.length === 1){d = '0' + d;}
		return (y + '-' + m + '-' + d);
	}
	
});

// RAH Search Event Tracking
var RAHSearchTracking = {
	
	// Track search flyout query terms
	Flyout: function(keyword){
		var args = {
			category: 'flyout',
			term: keyword
		}
		RAHSearchTracking._trackEvent(args);
	},
	
	// Private
	_trackEvent: function(oArgs) {
		//var _source = oArgs.source || document.location.pathname+document.location.search+document.location.hash;
		var s_source = oArgs.source || document.location.pathname+document.location.search;
		var s_category = oArgs.category || '';
		var s_term = oArgs.term || '';
		s_term = s_term.replace(/ /gi,"+");
		
		//lowercase`
		var s_source = s_source.toLowerCase();
		var s_category = s_category.toLowerCase();
		var s_term = s_term.toLowerCase();

		// Instantiate GA tracking call
		var qryStartChar = (document.location.search.length > 0) ? '&': '?';
		var sReport = encodeURI(s_source + qryStartChar + 'utm_key='+s_term+'&utm_search='+s_category);
		try{
			urchinTracker(sReport);
			// Only uncomment during debugging
			//console.log("urchinTracker being called with: %s", sReport);
			//console.dir(oArgs);
		}
		catch(e){}
	}
};
// track the search term after xhr response
document.observe('searchresults:afterXhr', function(e){
	RAHSearchTracking.Flyout(e.memo.argument);
});

document.observe('dom:loaded', function() {
	$('searchle').labelInput();
});	
