/**
 * Search javascript
 */
wsod_js.search = {
	
	/**
	 * Initialize the search
	 */
	init: function(inputID,config) {
		var self = this;
		
		if(!inputID) { console.warn("wsod_js.search: No input ID provided."); return false; }
		
		// The search input at the top of the header
		this.searchInputId = inputID;
		var $input = jQuery("#" + inputID);
		
		if(!$input || $input.length < 1) { console.warn("wsod_js.search: No input#"+inputID+" found on the DOM."); return false; }
		
		$input.search(); //attach events for blur/focus (see jquery.extensions)
		
		$input.attr("autocomplete","off"); //disable this for all search boxes
		
		//search URL
		this.searchURLPrefix = "http://markets.money.cnn.com/";
		
		//this.searchURLPrefix = "/cnnmoney/";
		
		//starts with "http://", otherwise relative
		this.urlPrefix = (urlPre) ? urlPre : "";
		
		//indexes
		this.cnnUrlMap=[];
		this.cnnUrlMap["INDU"] 	= "data/markets/dow/";
		this.cnnUrlMap["COMP"] 	= "data/markets/nasdaq/";
		this.cnnUrlMap["SPX"] 	= "data/markets/sandp/";
		this.cnnUrlMap["XAX"] 	= "data/markets/amex/";
		this.cnnUrlMap["OIX"] 	= "data/markets/cboe_oil/";
		this.cnnUrlMap["DJT"] 	= "data/markets/dowtrans/";
		this.cnnUrlMap["DJU"] 	= "data/markets/dowutil/";
		this.cnnUrlMap["BKX"] 	= "data/markets/kbw_bank/";
		this.cnnUrlMap["NYA"] 	= "data/markets/nyse/";
		this.cnnUrlMap["SOX"] 	= "data/markets/philsemi/";
		this.cnnUrlMap["RUT"] 	= "data/markets/russell/";
		this.cnnUrlMap["XAO"] 	= "data/world_markets/asx100/";
		this.cnnUrlMap["ATH"] 	= "data/world_markets/athens_composite/";
		this.cnnUrlMap["IBOV"] 	= "data/world_markets/bovespa/";
		this.cnnUrlMap["CAC40"] = "data/world_markets/cac40/";
		this.cnnUrlMap["DAX"] 	= "data/world_markets/dax/";
		this.cnnUrlMap["N100"] 	= "data/world_markets/euronext/";
		this.cnnUrlMap["UKX"] 	= "data/world_markets/ftse100/";
		this.cnnUrlMap["HSI"] 	= "data/world_markets/hang_seng/";
		this.cnnUrlMap["MXX"] 	= "data/world_markets/ipc/";
		this.cnnUrlMap["IPSA"] 	= "data/world_markets/ipsa/";
		this.cnnUrlMap["JKSE"] 	= "data/world_markets/jakarta_composite/";
		this.cnnUrlMap["KOPSI"] = "data/world_markets/kopsi/";
		this.cnnUrlMap["MERV"] 	= "data/world_markets/merval/";
		this.cnnUrlMap["NZ50"] 	= "data/world_markets/newzealand50/";
		this.cnnUrlMap["NKY"] 	= "data/world_markets/nikkei225/";
		this.cnnUrlMap["OMXS"] 	= "data/world_markets/omx30/";
		this.cnnUrlMap["SHCOMP"]= "data/world_markets/se_composite/";
		this.cnnUrlMap["SENSEX"]= "data/world_markets/sensex/";
		this.cnnUrlMap["SXXP"] 	= "data/world_markets/stoxx600/";
		this.cnnUrlMap["SSMI"] 	= "data/world_markets/swiss/";
		this.cnnUrlMap["SETIDX"]= "data/world_markets/thailand_se/";
		this.cnnUrlMap["TS50"] 	= "data/world_markets/tsec50/";
		this.cnnUrlMap["OSP60"] = "data/world_markets/tsx60/";	
		
		if (common && common.isRunningAtCNN()){
			this.urlPrefix 	= "http://money.cnn.com/";
			if (common._getEnv()=="QAI"){
				this.urlPrefix 	= "http://money.qai.cnn.com/";
			}
			var quotePage 	= "quote/quote.html?symb=";
			var mfPage 		= "quote/mutualfund/mutualfund.html?symb=";
			var etfPage 	= "quote/etf/etf.html?symb=";
		} else {
			var quotePage = mfPage = etfPage = "research/quote/snapshot.asp?symb=";
		}
		
		/**
		 * Search config -- unique config for multiple search controls
		 */
		var defaults = {
			linkTo					: this.urlPrefix + quotePage, 	//default destination
			linkToMF				: this.urlPrefix + mfPage,		//MF page
			linkToETF				: this.urlPrefix + etfPage,		//ETF page
			symReference			: "displaySymbol",				//"displaySymbol" or "WSODIssue"
			symToUpperCase			: true,							//toUpperCase() the result?
			autoSelectFirstResult	: false,						//allow user to press "Enter" key and pre-select first result, regardless if their entry is a valid match
			resultAction			: "returnSymbol", 				//or "returnResult" - return match or entire result of possible matches
			target					: "", 							//"parentWindow" if a search box is inside an iframe
			hideResultsOnSubmit		: false,						//if a user searches and presses enter, just kill the search and any results
			multiquoteAllowed		: true							//allows users to search for multiple symbols (delimited by space or comma). nothing actually gets searched, but the results are hidden/cleared.
		};
		
		//check if exists, don't overwrite
		if (!this.config){ this.config = {}; }
		
		//unique config for each search
		this.config[inputID] = jQuery.extend(defaults, config);
		
		/**
		 * Events
		 */
		this.attach($input, {
			cache: {},
			onClose: function(evt, $el) {

			},
			onOpen: function(evt, $el) {

			},
			onSelect: function(evt, $el, result) {
				//selected specific item
				self.goToResult(evt, $el, result);
			},
			onGo: function(evt, $el, result) {
				//press enter
				self.goToResult(evt, $el, result);
			},
			align: self.config[inputID].align || "left"
		});
	},
	
	/**
	 * Go to result when user selects result or presses enter
	 *
	 * @param evt {Event} The Event
	 * @param $el {jQuery} The Element
	 * @param result {Object} The Xref result
	 */
	goToResult: function(evt, $el, result){
		
		var inputID = $el.get(0).id;
		
		this.removeButtonData($el);
		
		//send back symbol or wsodissue ... or else send string (which is the input value)
		var symbol = ("WSODIssue" == this.config[inputID].symReference && result.w) ? result.w : ((result && result.s) ? result.s : result);
		
		//return result?
		if ("returnResult" == this.config[inputID].resultAction){
			dataBack = result;//single result (object, includes wsodissue, name, etc)
		} else {
			dataBack = symbol;//single symbol (string)
		}
		
		if (result && dataBack) {
			if (this.config[inputID].symToUpperCase && "returnResult" != this.config[inputID].resultAction) {
				dataBack = dataBack.toUpperCase();
			}
			
			//we need to go to different pages at CNN based on issueType
			if (!result.it){ result.it = ""; }
			var href;
			var isIndex = false;
			switch(result.it){
				case "MF":
				case "MMF":
					href = this.config[inputID].linkToMF;
					break;
				case "ETF":
				case "ETN":
				case "LETF":
					href = this.config[inputID].linkToETF;
					break;
				case "IN":
					isIndex = true;
					href = this.urlPrefix + this.cnnUrlMap[dataBack];
					break;
				default:
					href = this.config[inputID].linkTo;
					break;
			}
			
			//indexes have special URLs with no querystring, so don't append anything..otherwise append symbol
			var append = (isIndex) ? "" : dataBack;
			
			if ("function" == typeof(this.config[inputID].linkTo)){	
				this.config[inputID].linkTo(dataBack); //callback function instead of HREF
			} else if (this.config[inputID].target == "parentWindow"){
				parent.location.href = href + append;
			} else {
				location.href = href + append;
			}
			
			//call close here, fixes strange IE7 issue where results stayed on the page in certain conditions
			this._close(evt, $el, result);
		}
	},
	
	/**
	 * Attach the search handler to an input
	 *
	 * @param $input {jQuery} The input element
	 * @param data {Object} The data
	 */
	attach: function($input, data) {
	
		var self = this;
		var inputID = $input.attr("id");
		var $body = jQuery("body");
		
		data.cache = data.cache || {};

		var bodyEvents = {
			"click": function(evt) {
				self.onClick(evt, jQuery(this), data);
			}
		};
		var inputEvents = {
			"blur": function(evt) {
				for (var t in bodyEvents) {
					$body.unbind(t, bodyEvents[t]);
				}
				self.onBlur(evt, jQuery(this), data);
			},
			"focus": function(evt) {
				for (var t in bodyEvents) {
					$body.bind(t, bodyEvents[t]);
				}
				self.onFocus(evt, jQuery(this), data);
			},
			"keyup": function(evt) {
				self.onKeyUp(evt, jQuery(this), data);
			},
			"keydown": function(evt) {
				self.onKeyDown(evt, jQuery(this), data);
			}
		};

		for (var t in inputEvents) {
			$input.bind(t, inputEvents[t]);
		}
		
	},
	/**
	 * Perform a quick search
	 *
	 * @param $input {jQuery} the input
	 * @param q {String} The query
	 * @param onload {Function} The function to perform onload
	 */
	quickSearch: function($input, q, onload) {
		var self = this;
		var inputID = $input.get(0).id;
		
		var requestData = {
			q: q,
			render: "JSON"
		};
		
		//if issueType(s) specified, pass it along
		if (self.config[inputID] && self.config[inputID].searchIssueType){
			requestData.issueType = self.config[inputID].searchIssueType;
		}
		
		//beforeSend{} doesn't work with jsonp.
		//do this now
		$input.addClass("loading");
		
		//this doesn't really return the XMLHTTPRequest object b/c type=jsonp (it returns nothing)
		return jQuery.ajax({
			url: self.searchURLPrefix+"common/symbolLookup/getSymbols.asp",
			dataType: "jsonp",
			jsonp: "jsoncallback",
			data: requestData,
			success: function(results) {
				onload(results);
				self.removeLoadingIcon($input);
			},
			error:function(){
				self.removeLoadingIcon($input);
			}
		});
	},
	
	/**
	 * Remove the loading icon
	 */
	removeLoadingIcon: function($input){
		$input.removeClass("loading");
	},
	
	/**
	 * Remove the loading icon
	 */
	removeButtonData: function($input){
		var $btn = $input.data("closestButton") || [];
		if ($btn.length){
			$btn.data('results',-1);//clear
		}
	},
	
	/**
	 * Close the options
	 *
	 * @param $input {jQuery} The Element
	 * @param data {Object} The data
	 */
	_closeOptions: function(evt, $input, data) {
		if (data._div) {
			data._div.remove();
			data._div = null;
		}
	},
	/**
	 * Draw the options
	 *
	 * @param $input {jQuery} The Element
	 * @param data {Object} The data
	 * @param q {String} The query
	 * @param results {Array} The results
	 */
	_drawOptions: function($input, data, q, results, isCached) {
		
		var self = this;
		
		if (jQuery("#wsod_symbolSearchErrorMsg").length > 0){
			jQuery("#wsod_symbolSearchErrorMsg").remove();
		}
		
		// If the div doesn't exist, create it
		if (!data._div) {
			var id = $input.attr("id") + "Results";//unique ID
			var $div = data._div = jQuery("<div>").attr("id",id).addClass("wsod_symbolSearch");
			$div.appendTo('body');
		} else {
			data._div.empty();//destroy table of results inside <div wsod_symbolSearch>
			$div = data._div;
		}
		
		//grab JSON property
		results = results.data || [];
		
		var $btn = $input.data("closestButton") || [];
		if ($btn.length){
			if (results.length < 1) {
				$btn.data("results",-1);//no results
			} else {
				$btn.data("results",results);//results!
			}
		}
		
		this.removeLoadingIcon($input);

		//enable this for IE6 (if needed)
		//$div.empty().bgiframe();
		
		var resultType = ['SYMBOL MATCHES', 'COMPANY MATCHES', 'SYMBOL STARTS WITH'];
		
		//create this to attach <tr>'s
		var $tbody = jQuery("<tbody>");
		
		// Create the options for each result	
		for (var i=0,group; i<results.length; i++) {
			
			group = results[i];
			
			if (group.length) {
				
				for (var j=0,c; j<group.length; j++) {
					
					c = group[j];
					if (c.n.indexOf("<b>") > -1) {
						//check company name here for HTML bold tag. 
						//if found, use cached value, do not highlight text again.
					} else {
						c = this._highlightText(q,c);
					}
					
					//create bucket labels
					if(j == 0){
						var $tr = jQuery("<tr>").addClass("resultType").appendTo($tbody);
						var $arrowImg = jQuery("<img>").attr("src","http://i.cdn.turner.com/money/.element/img/3.0/data/iconDownArrow.png").attr("width",12).attr("height",4);
						
						if(c.lt == 'Symbol.5.0'){
							jQuery("<td colspan='3'>" +resultType[0]+ "</td>").append($arrowImg).appendTo($tr);
						} else if(c.lt == 'SymbolStart.5.0'){
							jQuery("<td colspan='3'>" +resultType[2]+ "</td>").append($arrowImg).appendTo($tr);
						} else if(c.lt == 'IssueNameFast.5.0'){
							jQuery("<td colspan='3'>" +resultType[1]+ "</td>").append($arrowImg).appendTo($tr);
						}
					}
					
					//results table rows (and cells)
					var $resultRow = jQuery("<tr>").addClass("result").appendTo($tbody);				
					var $cell_name = jQuery("<td>"+c.n+"</td>");
					var $cell_sym = jQuery("<td>"+c.d+"</td>").addClass("wsod_sym");
					//var countryCode = group[j].c.toLowerCase();
					//var $flag_div = jQuery("<div>").addClass("wsod_flag").addClass("flag-"+countryCode);
					//var $cell_country = jQuery("<td>").append($flag_div);
					
					$resultRow.append($cell_name).append($cell_sym);//.append($cell_country);

					//events on result row
					$resultRow
						.data('result', c)
						.bind('mouseenter', function() {
							jQuery(this).siblings('tr').removeClass('hover');
							jQuery(this).addClass('hover');
						})
						.bind('mouseleave', function() {
							jQuery(this).removeClass('hover');
						})
						.bind('mousedown', function(evt) {
							self._select(evt, $input, data, jQuery(this).data('result'));
						});	
				}
			} 
		}
		
		//no results
		if (results.length < 1) {
			var $tr = jQuery("<tr>").addClass("resultType last").appendTo($tbody);
			jQuery("<td colspan='3'>THAT TICKER IS NOT VALID. PLEASE TRY AGAIN.</td>").appendTo($tr);
		}
		
		//create results table, append tbody
		var $resultTable = jQuery("<table>").append($tbody);
		//add "nameMatches" class to name matches row row
		$resultTable.find("tr.resultType:eq(1)").addClass('nameMatches');
		//add "last" class to very last row
		$resultTable.find("tr:last").addClass('last');
		//append to div
		$div.append($resultTable);

		// Position the div
		var offset = $input.offset(),
			top = offset.top + $input.outerHeight(),
			width = $input.outerWidth();
		$div.css({
			top: parseFloat(top),
			minWidth: parseFloat(width-2)
		});

		// Set the left position after we figure out the total width we're dealing with
		$div.css('left', (data.align === 'right' ? offset.left - ($div.outerWidth() - width) : offset.left) + 'px');
	},
	/**
	 * Close the options, call the onClose handler
	 *
	 * @param evt {Event} The Event
	 * @param $input {jQuery} The Element
	 * @param data {Object} The data
	 */
	_close: function(evt, $input, data) {
		this._closeOptions(evt, $input, data);
		// Call onClose
		if (data.onClose) {
			data.onClose(evt, $input);
		}
	},
	/**
	 * If you hit enter without selecting something
	 *
	 * @param evt {Event} The Event
	 * @param $input {jQuery} The Element
	 * @param data {Object} The data
	 * @param q {String} The query
	 */
	_go: function(evt, $input, data, q) {
		if (data.onGo) {
			data.onGo(evt, $input, q, data);
		}
		this._close(evt, $input, data);
	},
	/**
	 * Open the options, call the onOpen handler
	 *
	 * @param evt {Event} The Event
	 * @param $input {jQuery} The Element
	 * @param data {Object} The data
	 * @param q {String} The query
	 * @param results {Array} The results
	 * @param isCached {boolean} Whether an ajax request was made 
	 */
	_open: function(evt, $input, data, q, results, isCached) {
		this._drawOptions($input, data, q, results, isCached);
		// Call onOpen
		if (data.onOpen) {
			data.onOpen(evt, $input, q, results);
		}
	},
	/**
	 * Search
	 *
	 * @param evt {Event} The Event
	 * @param $input {jQuery} The Element
	 * @param data {Object} The data
	 * @param q {String} The query
	 */
	_search: function(evt, $input, data, q) {
		var self = this;
		var inputID = $input.attr("id");
		// Clear any existing timer
		if (data._timer) {
			window.clearTimeout(data._timer);
		}

		/** We can't abort requests in JSONP
		
		// If there's an active request, abort it
		if (data._active) {
			data._active.abort();
			data._active = null;
			this.removeButtonData($input);
		}*/
		
		this.removeButtonData($input);
		
		//bind an event to the form (if there is one)...
		//..so when it submits (user presses Enter or clicks Get Quote), the results are hidden.
		if (this.config[inputID].hideResultsOnSubmit){
			var $p = $input.parent();
			if($p.get(0).tagName == "FORM"){
				$p.submit(function(){
					self._close(evt, $input, data);
				});	
			}	
		}
		
		//clean it up...
		q = xss.filter(q);
		
		//check if user is searching for more than 1 symbol
		if(this.config[inputID].multiquoteAllowed !== false && (q.indexOf(",") > -1 || q.indexOf(" ") > -1)){
			this.removeButtonData($input);
			this._close(evt, $input, data);
			return;
		}

		// If we've got this value cached, then
		//   don't bother with the ajax call
		if (data.cache[q]) {
			self._open(evt, $input, data, q, data.cache[q], true);
		} else {
			// Delay the search by 250ms
			data._timer = window.setTimeout(function() {
				// Set the active pointer to a new ajax request,
				//   onload we store the value in a cache, and call the _open
				//   function
				data._active = self.quickSearch($input, q, function(results) {
					data.cache[q] = results;
					self._open(evt, $input, data, q, results, false);
				});
			}, 250);
		}
	},
	/**
	 * Select the passed option
	 *
	 * @param evt {Event} The Event
	 * @param $input {jQuery} The Element
	 * @param data {Object} The data
	 * @param option {Object} The option
	 */
	_select: function(evt, $input, data, option) {
		if (data.onSelect) {
			data.onSelect(evt, $input, option, data);
		}
	},

	/**
	 * The on blur event
	 *
	 * @param evt {Event} The Event
	 * @param $el {jQuery} The Element
	 * @param data {Object} The data
	 */
	onBlur: function(evt, $el, data) {
		this._close(evt, $el, data);
		// If this input is empty, reset it to the original value
		if (!$el.val()) {
			$el.val($el.attr("alt"));
			this.removeButtonData($el);
		}
	},
	/**
	 * The on click event
	 *
	 * @param evt {Event} The Event
	 * @param $el {jQuery} The Element
	 * @param data {Object} The data
	 */
	onClick: function(evt, $el, data) {
	},
	/**
	 * The on focus event
	 *
	 * @param evt {Event} The Event
	 * @param $el {jQuery} The Element
	 * @param data {Object} The data
	 */
	onFocus: function(evt, $el, data) {
		if ($el.val() == $el.attr("alt")) {
			$el.val("");
			this.removeButtonData($el);
		} else if ($el.val()) {
			this._search(evt, $el, data, $el.val());
		}
	},
	/**
	 * The on key down event
	 *
	 * @param evt {Event} The Event
	 * @param $el {jQuery} The Element
	 * @param data {Object} The data
	 */
	onKeyDown: function(evt, $el, data) {
		if (!data._div) return;
		
		var inputID = $el.attr("id");
		
		this.removeLoadingIcon($el);

		// Get the selected element
		var $selected = data._div.find("tr.hover:first");
		
		switch(evt.keyCode) {
		case 13: // ENTER
			// Nothing selected, call the go event
			if (!$selected.length) {
				if (this.config[inputID].autoSelectFirstResult===true){
					//user hovered on nothing, select first result for them.
					$selected = data._div.find("tr.result:first")			
					if ($selected.length){
						this._go(evt, $el, data, $selected.data('result'));
					} else {
						return false;
					}
				} else {
					//don't want to auto-select first result, just let user go with what they have in the input
					// this._go(evt, $el, data, $el.val());
					/*==============================================
					Steven Weinberger's fix
					================================================
					if the user hits return, don't call, this._go().
					Return true, passing the event back up the line,
					so the form's action will be called.
					================================================*/
					return true;
				}
			// Select the currently hovered option
			} else {
				this._select(evt, $el, data, $selected.data('result'));
			}
			// Don't want to submit the form
			evt.preventDefault();
			break;
		case 27: // ESC
			// Close the options
			this._close(evt, $el, data);
			break;
		case 40: // DOWN
			var $next = $selected.nextAll("tr:not('.resultType'):first");

			// Get the first one if we've reached the end
			if (!$next.length) {
				$next = data._div.find("tr:not('.resultType'):first");
			}
			$selected.removeClass("hover");
			$next.addClass("hover");
			evt.preventDefault();
			break;
		case 38: // UP
			var $next = $selected.prevAll("tr:not('.resultType'):first");

			// Get the last one if we've reached the beginning
			if (!$next.length) {
				$next = data._div.find("tr:last");
			}
			$selected.removeClass("hover");
			$next.addClass("hover");
			evt.preventDefault();
			break;
		}
	},
	/**
	 * The on key up event
	 *
	 * @param evt {Event} The Event
	 * @param $el {jQuery} The Element
	 * @param data {Object} The data
	 */
	onKeyUp: function(evt, $el, data) {
		var self = this;

		var v = $el.val();

		// Only call the function if something has changed
		if (v != data._lastValue)  {
			data._lastValue = v;

			if (!v) {
				// Close the options
				this._close(evt, $el, data);
			} else {
				// Perform the search
				this._search(evt, $el, data, v);
			}
		}
	},
	
	/**
	 * Bolds search term within results
	 *
	 * @param q {String} search query
	 * @param c {String} result name or result symbol
	 */
	_highlightText: function(q,c) {
		var self = this;
		var sTermArray = String(q).split(" ");
		var sTermLen = sTermArray.length;
		
		if (c.n.indexOf(" ")) {
			var coName = c.n.split(" ");
			var coNameLen = coName.length;
		} else {
			var coName = c.n;
			var coNameLen = 1;
		}
		
		if(sTermLen == 1){
			var replacement = '<b>$1</b>';
			var re = new RegExp("\\b(" + sTermArray + ")", "gi");
			c.d = String(c.s).replace(re, replacement);
			c.n = String(c.n).replace(re, replacement);
		} else {
			for (var i = 0; i < coNameLen; i++) {
				var coNamePart = coName[i];
				if(!coNamePart.length){
					continue;
				}
				for(var z = 0; z < sTermLen; z++){
					var term = sTermArray[z].replace(/\s/g, "");
					var termLen = term.length;
					if (termLen && term.toLowerCase() == coNamePart.toLowerCase().substring(0, termLen)) {
						coName[i] = '<b>' + coNamePart.substring(0, termLen) + '</b>' + coNamePart.substring(termLen);
						z = sTermLen;
					}
				}
				
				c.n = coName.join(' ');
			}
		}
		
		return c;
	}
};

