﻿/// <reference path="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5-vsdoc.js" />
/// <reference path="debugversions/ba-debug.js" />
/// <reference path="BondiTouch.js" />
/// <reference path="BondiTouch.Core.js" />
/// <reference path="BondiTouch.js" />
/// <reference path="debugversions/jquery.ba-throttle-debounce.js" />
/// <reference path="jquery.autocomplete.js" />
/// <reference path="debugversions/iscroll-4.0.aseemk.js" />

//verify that we have what we need to proceed
if (!(jQuery)) {
	throw new Error("This code requires jQuery");
}

BondiTouch.Search = (function (otherSelf) {

	/// <summary>
	/// A module encapsulating searching for Bondi projects
	///
	/// Uses specific elements & IDs:
	///
	/// div#searchResultsDiv: The div that contains the displayed search results (REQUIRED)
	/// div#hiddenSearchResultsDiv: The div that contains the retrieved search results to be displayed (REQUIRED)
	/// div#selectedSearchFilters: The div that  displays the SELECTED filters/facets (optional)
	/// div#searchResultsOptions: The div that displays the options/facet selections (optional)
	/// </summary>

	// private variables

	// TODO: use shared private variable technique to copy these from otherSelf

	// private variables
	var $ = jQuery;
	var _view = $('#searchContents');
	var _hiddenForm = null;
	var _menu = $('#menu_search');
	/* 	var _filterScroll = null; */
	var _resultScroll = null;
	var _readied = false;
	var _highlights = null;
	var _highlightsRequest = null;
	var _autocompleteControl = null;
	var _currentAjaxRequest = null; // used to hold pending requests
	var _maxRecords = 50; // max total records to pull down.
	var _debug = $.extend(true, {}, debug); // local deep-copy clone of the debug object used to be able to filter Search output
	var _autocompleteRequest = null;

	// PRIVATE METHODS HERE:

	/*______________________________________________________________________________________________________________________________________________________________________________________*/
	var _activateScroll = function () {
		/* Debug code for getting the multiple scroll zones to work. 
		TODO: remove when they're working
   
		function getHeight(id) {
		var elem = $(id);
		if(elem.length == 1)
		return elem.height();
				
		return -1;
		}
		
		alert("activate scroll: " 
		+ getHeight("#searchContents")
		+ ", " 
		+ getHeight("#searchBox")
		+ ", " 
		+ getHeight("#searchColumn")
		+ ", " 
		+ getHeight("#searchResultsDiv")
		);
		*/

		function activateOne(scroll, id) {
			if (scroll && scroll.destroy) {
				scroll.destroy();
				scroll = null;
			}
			if (BrowserDetect.useTouch) {
				scroll = new iScroll(id);
			}
			return scroll;
		}

		/* 		_filterScroll = activateOne(_filterScroll, "searchResultsOptions"); */
		_resultScroll = activateOne(_resultScroll, "searchBox");
	};

	/*______________________________________________________________________________________________________________________________________________________________________________________*/
	var _getAutocompleteTerms = function (val) {
		// check for config
		if (self.getAutocompleteTermsUrl === undefined) {
			var msg = '"getAutocompleteTermsUrl" is not defined';
			debug.error(msg);

			throw new {
				name: 'Search Setup Error',
				message: msg
			};
		}

		// cancel existing search
		if (_autocompleteRequest != null) {
			_autocompleteRequest.abort();
			_autocompleteRequest = null;
		}

		// execute search
		_autocompleteRequest = $.ajax({
			type: "POST",
			url: self.getAutocompleteTermsUrl,
			data: {
				'query': val
			},
			success: function (retval) {
				debug.debug('autocomplete callback, success = ' + retval.success);

				_autocompleteRequest = null;

				if (retval.success == false) {
					// TODO: display error...?
					debug.error('error retrieving autocomplete values' + retval.message);
				}
				else {
					var terms = retval.terms;
					debug.log('Autocomplete terms', terms);
				}
			},
			error: function (req, status, error) {
				_autocompleteRequest = null;
				// TODO: display error...?
				if (status !== 'abort' && error != undefined) {  // put this here because jQuery 1.4.4 has a bug w/ aborting that calls error
					//TODO: ???
					var msg = 'ERROR Retrieving Autocomplete Terms [' + status + ']';
					if (error) {
						msg += ': ' + error;
					}
					debug.error(msg);
				}
				else {
					debug.error('... aborted?');
				}
			}
		});
	};

	/*______________________________________________________________________________________________________________________________________________________________________________________*/
	var _searchImpl = function (start, rows) {
		/// <summary>
		/// The implementation of the search.  Gathers the parameters and calls to the server
		/// </summary>
		/// <param name="start" type="Number" integer="true" optional="false">
		/// the starting result index being requested
		/// </param>
		/// <param name="rows" type="Number" integer="true" optional="false">
		/// the number of rows being requested 
		/// </param>

		// declare default methods for success and error, but allow overrides to be passed in
		var successHandler = function (data) {
			_debug.debug('BondiTouch.Search: Success Handler');
			_currentAjaxRequest = null;
			self.displaySearchResults(data, start, rows);
		};

		var errorHandler = function (req, status, error) {
			_currentAjaxRequest = null;
			debug.debug('BondiTouch.Search: Error Handler');

			if (status !== 'abort' && error != undefined) {  // put this here because jQuery 1.4.4 has a bug w/ aborting that calls error
				//TODO: ???
				var msg = 'ERROR Retrieving Search Results [' + status + ']';
				if (error) {
					msg += ': ' + error;
				}
				_debug.error(msg);
				alert(msg);
			}
			else {
				debug.debug("BondiTouch.Search: ... was probably a result of an abort()");
			}
		};

		var queryData = {
			'query': self.searchPhrase,
			'sort': self.sort,
			'start': start,
			'rows': rows,
			'currOnly': self.isCurrentIssueSelected,
			'grid': self.asGrid
		};

		if (self.issueKey) {
			// if we have a current issue, add it to the query.
			queryData = $.extend(queryData, { 'issueKey': self.issueKey });
		}

		// add filters
		var filterDict = _filtersToDictionary();
		queryData = $.extend(queryData, filterDict);

		_debug.debug('BondiTouch.Search: sending search request: ', queryData);

		// make the call
		_currentAjaxRequest = $.ajax({
			type: "POST",
			url: _b.Utility.makeProxyUrl(self.searchUrl),
			data: queryData,
			success: successHandler,
			error: errorHandler
		});
	};

	/*______________________________________________________________________________________________________________________________________________________________________________________*/
	var _filtersToDictionary = function () {
		/// <summary>
		/// Converts the <c>filters</c> associative array into a set of variables in the format of
		/// "filters[index].Key = foo; filters[index].Value = bar, ..."
		/// This will allow ASP.NET MVC to put it in an IDictionary automatically
		/// </summary>

		// declare empty object for extending
		var queryData = {};


		_debug.debug('BondiTouch.Search: Converting filters to dictionary parameters');

		var index = 0;
		for (var i in self.filters) {

			// the values are arrays, so act accordingly
			var valArray = self.filters[i];

			for (var j = 0; j < valArray.length; ++j) {

				index = index + j;
				var root = 'filters[' + index + ']';
				var key = root + '.Key';
				var val = root + '.Value';

				var nvpair = {};

				_debug.debug('BondiTouch.Search: ', key + '=' + i, val + ' = ' + valArray[i]);

				nvpair[key] = i;
				nvpair[val] = valArray[j];

				// add these values to the querydata
				queryData = $.extend(queryData, nvpair);
			}
			++index;
		}
		return queryData;
	};

	/*______________________________________________________________________________________________________________________________________________________________________________________*/
	var _postProcessSearchResults = function (stillGettingResults) {
		/// <summary>
		/// runs through the DOM and does any tweaking of the UI self is data-sensitive
		/// </summary>
		$("<div style='clear:both'></div>").appendTo("#searchColumn");
		BondiTouch.Search.sizeToFit();
		_activateScroll();

		var currIssueDiv = $('div#currentIssueDiv');

		// CURRENT ISSUE RADIO BUTTONS
		$(':radio[name=CurrentIssue]').each(function () {

			// if one of the following is true, don't show the "current issue" selection:
			// 1) we don't have a selected issue
			// 2) The current issue is selected
			// 3) We have selected a decade facet
			if (self.issueKey == null
								|| self.isCurrentIssueSelected == true
								|| self.hasFilter(self.Config.get('DECADE_KEY'))) {

				currIssueDiv.hide();
			}
			else {
				currIssueDiv.show()

				// read the value of the selected radio
				var value = $(this).val().toLowerCase() == 'true';

				// force selected to be a boolean.
				var selected = self.isCurrentIssueSelected;
				if (typeof (selected) != 'boolean') {
					selected = (selected) ? selected.toLowerCase() == 'true' : false;
				}

				if (value === selected) {
					$(this).attr('checked', true);
				}
				else {
					$(this).removeAttr('checked');
				}
			}
		});

		// disable the current sort option
		$('a.searchSortLink').each(function () {
			if ($(this).data('sortoption') == self.sort) {
				// removing the href should make it unclickable
				$(this).removeAttr('href');
				$(this).addClass('selectedSearchSortLink');
			}
			else {
				// setting the href should make it clickable again
				$(this).attr('href', '#');
				$(this).removeClass('selectedSearchSortLink');
			}
		});


		// handle enable/disable of facet links
		$('a.facetLink').each(function () {
			var name = $(this).data('facetname');
			var val = $(this).data('facetvalue');

			// see if this is already in the search
			var curr = self.hasFilter(name, val);

			if (curr === true) {
				$(this).removeAttr('href');
				$(this).attr('disabled', true);
			}
			else {
				$(this).attr('href', '#');
				$(this).removeAttr('disabled');
			}
		})

		// if there are no remaining search options and the current issue div is hidden, show the "no options" div
		var showNoOptionsDiv = false;
		if (currIssueDiv.is(":visible") == false) {
			var searchOpts = $('div#searchOptionsList');
			if (searchOpts.children().length == 0) {
				showNoOptionsDiv = true;
			}
		}
		//TODO: Replace this when removing table
		//resize table containing results (necessary for some unknown reason)
		//		var resultsWidth = $("#searchContents").width() - $("#searchResultsOptions").width() - 40; //arbitrary margin 
		//		$("#searchResultsDiv").width(resultsWidth);

		var noOptsDiv = $('div#noSearchOptionsDiv');
		if (showNoOptionsDiv === true) {
			noOptsDiv.show();
		}
		else {
			noOptsDiv.hide();
		}
	};

	/*______________________________________________________________________________________________________________________________________________________________________________________*/
	var _executeSearch = function () {
		/// <summary>
		/// Executes the search with the current parameters
		/// </summary>
		/// <remarks>
		/// This method will cancel any existing pending search requests
		/// </remarks>
		_debug.info('BondiTouch.Search: Starting Search...');

		// DEBUG: empty the search contents.
		$('#searchContents').html('');

		$("#searchText").blur();

		if (self.searchUrl === undefined) {
			_debug.error('BondiTouch.Search: No searchUrl defined!');
			throw {
				name: "Configuration Error",
				message: "'searchUrl' needs to be set"
			};
		}

		// if we searched for new stuff while existing requests are pending, cancel the request
		if (_currentAjaxRequest != null) {
			_debug.info('aborting existing request...');
			// try to abort the current request
			_currentAjaxRequest.abort();
		}

		// tell the users we are searching
		// TODO: do something better than this.
		$('div#selectedSearchFilters').html('Searching...');

		// start search at index 0 with 10 rows
		_searchImpl(0, 10); // TODO: configurable?
	};

	// the actual object is created here
	/*______________________________________________________________________________________________________________________________________________________________________________________*/
	var self = {
		// PARAMETERS - taken from otherSelf, if present

		/// <field name="searchPhrase" type="string" mayBeNull="false">The phrase to search for</field>
		searchPhrase: (otherSelf) ? otherSelf.searchPhrase : '',

		/// <field name="isCurrentIssueSelected" type="Boolean">Whether or not the current issue is selected</field>
		isCurrentIssueSelected: (otherSelf) ? otherSelf.isCurrentIssueSelected : false,

		/// <field name="issueKey" type="Number" integer="true">The issue key for the "current" issue</field>
		issueKey: (otherSelf) ? otherSelf.issueKey : undefined,

		/// <field name="isCurrentIssueSelected" type="string" mayBeNull="false">the selected sort order</field>
		sort: (otherSelf) ? otherSelf.sort : 'relevance',

		/// <field name="asGrid" type="Boolean" mayBeNull="false">Whether to display results as a grid or as a list</field>
		asGrid: (otherSelf) ? otherSelf.asGrid : false,

		/// <field name="searchUrl" type="string" mayBeNull="false">The url to use for submitting searches</field>
		searchUrl: (otherSelf) ? otherSelf.searchUrl : undefined,

		/// <field name="getHighlightsUrl">the url to use to retrieve highlights</field>
		getHighlightsUrl: (otherSelf.getHighlightsUrl) ? otherSelf.getHighlightsUrl : undefined,

		/// <field name="getAutocompleteTermsUrl">the url to use to retrieve autocomplete terms</field>
		getAutocompleteTermsUrl: (otherSelf.getAutocompleteTermsUrl) ? otherSelf.getAutocompleteTermsUrl : undefined,

		/// <field name="filters">the filters to use on the search</field>
		filters: (otherSelf) ? otherSelf.filters : {}, // filters is a dictionary

		// FILTER METHODS
		/*______________________________________________________________________________________________________________________________________________________________________________________*/
		addFilter: function (key, value) {
			/// <summary>
			/// Adds a filter to the collection.
			/// The filters support multiple values per key, so this will append values if a duplicate key is passed
			/// </summary>
			/// <param name="key" type="string" optional="false">
			/// The key for indexing the dictionary
			/// </param>
			/// <param name="value" type="string" optional="false">
			/// The value to add to this key
			/// </param>

			_debug.info('BondiTouch.Search: Adding Filter [' + key + ' : ' + value + ']');

			// see if it's there first
			var vals = self.filters[key];

			if (vals === undefined) {
				vals = new Array();
			}

			vals.push(value);
			self.filters[key] = vals;
		},

		/*______________________________________________________________________________________________________________________________________________________________________________________*/
		removeFilter: function (key, value) {
			/// <summary>
			/// Removes a filter to the collection.
			/// </summary>
			/// <remarks>Note self, if the value is not passed, it removes all filters with self key</remarks>
			/// <param name="key" type="string" optional="false">
			/// The key for indexing the dictionary
			/// </param>
			/// <param name="value" type="string" optional="true">
			/// The value to remove.  If undefined, remove all values for this key
			/// </param>

			_debug.info('BondiTouch.Search: Removing Filter');

			// if we don't pass a specific value, remove all of them
			if (value === undefined) {
				_debug.debug('BondiTouch.Search: Removing ALL Values for [' + key + ']');
				return delete self.filters[key];
			}
			else {
				_debug.debug('BondiTouch.Search: Removing Filter [' + key + ' : ' + value + ']');

				// remove just the value from the collection
				var vals = self.filters[key];

				var newVals = $.grep(vals, function (v) { return v != value; });

				if (newVals.length === 0) {
					return delete self.filters[key];
				}
				else {
					self.filters[key] = newVals;
				}
			}
		},

		/*______________________________________________________________________________________________________________________________________________________________________________________*/
		hasFilter: function (key, value) {
			/// <summary>
			/// Checks to see if a filter is in the collection.
			/// </summary>
			/// <param name="key" type="string" optional="false">
			/// The key for indexing the dictionary
			/// </param>
			/// <param name="value" type="string" optional="true">
			/// The value to look for. if undefined, just test for the key
			/// </param>
			/// <returns>whether or not the key/value (or key) exists in the filters</returns>

			if (value === undefined) {
				return self.filters[key] !== undefined;
			}
			else {
				// remove just the value from the collection
				var vals = self.filters[key];

				var has = false;
				if (vals !== undefined) {
					var idx = $.inArray(value, vals);
					has = idx > -1;
				}
				return has;
			}
		},

		/*______________________________________________________________________________________________________________________________________________________________________________________*/
		displaySearchResults: function (resultsHtml, start, rows) {
			/// <summary>
			/// Diplays search results by grabbing data from the <c>resultsHtml</c> object passed in
			/// </summary>
			/// <param name="resultsHtml" type="HTMLDocument" domElement="true" elementMayBeNull="false" optional="false">
			/// The resulting HTML Document from the call to the server
			/// </param>
			/// <param name="start" type="Number" integer="true" optional="true">
			/// the starting result index being requested (default: 0)
			/// </param>
			/// <param name="rows" type="Number" integer="true" optional="true">
			/// the number of rows being requested (default: 10)
			/// </param>
			/// <remarks>
			/// This method sends additional requests for more and more results until running out or 
			/// reaching <c>_maxRecords</c> records.
			/// </remarks>

			if (resultsHtml == undefined) {
				var msg = 'BondiTouch.Search: No resultsHtml passed to displaySearchResults';
				_debug.error(msg);
				throw new {
					name: 'Search Error',
					message: msg
				};
			}
			var stillGettingResults = false;

			// grab the existing search control div
			// if we don't find it, use the one inside the resultsHtml
			// TODO: switch this so that people can hand in the container to use instead
			var $searchContents = $('#searchContents');

			if ($searchContents.children().length == 0) {
				// put the data inside the search contents
				$searchContents.html(resultsHtml);
			}

			start = start || 0;
			rows = rows || 10;

			_debug.info('BondiTouch.Search: Displaying Search Results [start: ' + start + ', rows: ' + rows + ']');

			$('#searchPhrase').text(self.searchPhrase);

			// get the results and put them into the existing divs
			// NOTE: this is a bit of a hack, since it's loading the entire HTML page 
			//       it should be refactored to use JSON in order to allow for partial loading of results.

			// update the filters if present
			var $filters = $(resultsHtml).find('div#selectedSearchFilters');
			if ($filters !== undefined) {
				_debug.debug('BondiTouch.Search: Updating Filters');

				$('div#selectedSearchFilters').html($filters.html());
			}

			// left side/facets
			var $leftSideResults = $(resultsHtml).find('div#searchResultsOptions');
			if ($leftSideResults !== undefined) {
				_debug.debug('BondiTouch.Search: Updating Facets/Options');
				$('div#searchResultsOptions').html($leftSideResults.html());
			}

			// results.
			var inputResultsDiv = $(resultsHtml).find('#hiddenSearchResultsDiv');
			if (inputResultsDiv === undefined) {
				_debug.error('BondiTouch.Search: NO RESULTS DIV');
			}

			var inputResults = inputResultsDiv.children();
			var numRows = inputResults.length;

			_debug.debug('BondiTouch.Search: Num Results = ' + numRows);

			var outputResultsDiv = $('#searchResultsDiv');
			if (outputResultsDiv === undefined) {
				debugger;
				_debug.error('BondiTouch.Search: NO OUTPUT DIV!');
			}

			if (numRows == 0) {
				// TODO: Something better (have a "no records div" that we show/hide?)
				outputResultsDiv.append($('<span>No Records Found</span>'));
			}
			else if (numRows > 0) {

				_debug.debug('BondiTouch.Search: Copying/Appending Results...');

				// append them to the existing rows.
				inputResults.each(function () {
					// remove it from where it resides now
					$(this).remove();
					// add it to the output
					outputResultsDiv.append(this);
				});

				// launch subsequent search here..
				if (numRows !== rows) {
					// got less than we requested, so we're done
					_debug.debug('BondiTouch.Search: No More Results');
				}
				else {

					// probably more.
					var newStart = start + rows;
					var newRows = rows * 2; // double it

					// make sure we don't go past the max
					if (newStart == _maxRecords) {
						_debug.debug('BondiTouch.Search: Hit Max - No More Results');
					}
					else {

						if (_maxRecords < (newStart + newRows)) {

							_debug.debug('BondiTouch.Search: Reached Max.. just getting partial');
							newRows = _maxRecords - newStart;
						}

						_debug.info('BondiTouch.Search: Retrieving next batch...');

						_searchImpl(newStart, newRows);
						stillGettingResults = true;
					}
				}
			}

			// run client-side post-search methods once all is done
			if (!stillGettingResults) {
				_postProcessSearchResults(stillGettingResults);
			}
		},

		/*______________________________________________________________________________________________________________________________________________________________________________________*/
		sizeToFit: function () {
			$("#searchResultsDiv")
				.width($("#searchContents").width() - $("#searchResultsOptions").width() - 60 /*margins */);
			
			var headerHeight = $("#searchHeader").outerHeight(true);
			$("#searchBox").css({top: headerHeight});
		},

		/*______________________________________________________________________________________________________________________________________________________________________________________*/
		activateScroll: _activateScroll,

		/*______________________________________________________________________________________________________________________________________________________________________________________*/
		handleResize: function () {
			if (!this.shown())
				return;
				
			this.sizeToFit();
			
			if (_resultScroll) 
				_resultScroll.refresh();
		},

		/*______________________________________________________________________________________________________________________________________________________________________________________*/
		resetOptions: function () {
			/// <summary>
			/// Resets all the options to default values
			/// </summary>
			self.issueKey = null;
			self.isCurrentIssueSelected = false;
			self.searchPhrase = '';
			self.sort = '';
			self.asGrid = false;
			self.filters = {};
		},

		/*______________________________________________________________________________________________________________________________________________________________________________________*/
		Config: (function () {
			/// <summary>Creates a hash to hold settings</summary>
			var configHash = {
				'DECADE_KEY': 'Decade',
				'SELECTED_ISSUE_KEY': 'Issue'
			};

			return {
				get: function (name) { return configHash[name]; }
			}
		})(),

		/*______________________________________________________________________________________________________________________________________________________________________________________*/
		performSearch: (otherSelf.performSearch) ? otherSelf.performSearch : function (phrase) {

			// cancel any pending highlight searches 
			if (_highlightsRequest != null) {
				_highlightsRequest.abort();
				_highlightsRequest = null;
			}



			// remove highlights..
			_highlights = null;

			// stop autocomplete process
			//_autocompleteControl.hide();

			if (phrase && phrase.length > 0) {

				// search for the phrase.. resetting the options first
				self.resetOptions();

				self.issueKey = BondiTouch.Core.navState.issueKey;
				self.isCurrentIssueSelected = false; // start off as issue NOT selected
				self.searchPhrase = phrase;

				_executeSearch();

				this.toggleMenu(true);
			}
			else {
				debug.warn('phrase was null or empty, not searching...');
			}
		},

		/*______________________________________________________________________________________________________________________________________________________________________________________*/
		toggleMenu: (otherSelf.toggleMenu) ? otherSelf.toggleMenu : function (onOff) {

			BondiTouch.Tasks.hideOverlays();
			if (!self.shown() || onOff /* override */) {
				_menu.focus().show();
				BondiTouch.Tasks.hideFooter();
			} else {
				BondiTouch.Tasks.showFooter();
			}

		},

		/*______________________________________________________________________________________________________________________________________________________________________________________*/
		shown: (otherSelf.shown) ? otherSelf.shown : function () {
			return _view.is(":hidden") === false;
		},

		/*______________________________________________________________________________________________________________________________________________________________________________________*/
		onReady: (otherSelf.onReady) ? otherSelf.onReady : function () {
			if (_readied == true) {
				return;
			}

			// NOTE: These are all 'live' methods because we're dynamically adding things to the DOM

			// wire up CurrentIssue radio buttons
			$(':radio[name=CurrentIssue]').live('change', function () {
				// read the value of the selected radio
				var value = $(this).val();

				// using the new value, rerequest the Search...
				if (value != self.isCurrentIssueSelected) {

					self.isCurrentIssueSelected = (value.toLowerCase() === 'true');
					_executeSearch();
				}
			});

			// wire up sort options
			$('a.sortOption').live('click', function (e) {
				var sortOption = this.attr('rel');

				if (sortOption !== undefined) {
					if (sortOption !== self.sort) {

						self.sort = sortOption;
						_executeSearch();
					}
				}

				e.preventDefault();
			});

			// wire up Facet fields.
			$('a.facetLink').live('click', function (e) {
				var name = $(this).data('facetname');
				var val = $(this).data('facetvalue');

				self.addFilter(name, val);
				_executeSearch();

				e.preventDefault();
			});

			// wire up Breadcrumb / facet choice removal
			$('a.removeFacetLink').live('click', function (e) {
				var name = $(this).data('facetname');
				var val = $(this).data('facetvalue');

				if (name === 'Issue') {
					self.isCurrentIssueSelected = false;
				}
				else {
					self.removeFilter(name, val);
				}
				_executeSearch();

				e.preventDefault();
			});

			// wire up sorting functionality
			$('a.searchSortLink').live('click', function (e) {
				var sort = $(this).data('sortoption');

				if (sort !== self.sort) {
					self.sort = sort;
					_executeSearch();
				}
				e.preventDefault();
			});

			// wire up list/grid view options
			$('div.searchViewOption').live('click', function () {
				var grid = $(this).data('asgrid');

				if (grid !== self.asGrid) {
					self.asGrid = grid;
					_executeSearch();
				}
			});

			// sure up thumbnail image hover
			//            $('img.searchResultThumbnail').live('hover', function (e) {
			//                // 
			//                //_debug.debug("Hovering!");
			//            });

			if (_view == undefined) {

				var msg = '"#searchContents" needs to be defined';
				debug.error(msg);

				throw new {
					name: 'Search Setup Error',
					message: msg
				};
			}

			// create hidden form 
			_hiddenForm = $('<form></form>')
														.attr({ 'method': 'POST' });
			var formDiv = $('<div></div>')
														.attr({ 'id': 'bondiTouchSearchDiv' })
														.append(_hiddenForm);

			$(document).append(formDiv);

			// bind the search form submission
			$('#searchForm').live('submit', function (e) {
				e.preventDefault();
				var form = $(this);
				var phrase = form.find('#searchText').val();
				self.performSearch(phrase);
			});

			// wire up article clicks.
			$('div.searchImageWrap, div.searchVerbiage').live('click', function (e) {
				e.preventDefault();
				var link = $(this).data('link');
				if (link) {
					var artKey = $(this).data('articlekey');
					var terms = $(this).data('terms');

					// set the parms
					_hiddenForm
												.attr({ 'action': link })
												.append(
														$('<input type="text" name="articleKey"/>').val(artKey))
												.append(
														$('<input type="text" name="terms"/>').val(terms));

					// go to the link.
					_hiddenForm.submit();

					// close the menu
					self.toggleMenu(false);
				}
			});

			// wire up the autocomplete for the search text.
			//            _autocompleteControl = $('#searchText')
			//                .autocomplete({
			//                    minChars: 3,
			//                    serviceUrl: self.getAutocompleteTermsUrl,
			//                    deferRequestBy: 250,
			//                    delimiter: ' ',
			//                    fnFormatResult: function (value, data, currentValue) {
			//                        var reEscape = new RegExp('(\\' + ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'].join('|\\') + ')', 'g');
			//                        var pattern = '(' + currentValue.replace(reEscape, '\\$1') + ')';
			//                        return value.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>') + '\t (' + data + ')';
			//                    }
			//                });
			//                .keyup($.debounce(250, function () {
			//                    var val = $(this).val();
			//                    if (val.length > 3) {
			//                        _getAutocompleteTerms(val);
			//                    }
			//                }))
			//

			_readied = true;

		},

		/*______________________________________________________________________________________________________________________________________________________________________________________*/
		setHighlightInfo: (otherSelf.setHighlightInfo) ? otherSelf.setHighlightInfo : function (articleKey, searchTerms) {

			if (_highlightsRequest != null) {
				debug.log('highlights request already in progress, aborting.');
				_highlightsRequest.abort();
				_highlightsRequest = null;
			}

			// retrieve them here..
			debug.log('retrieving highlights...');
			_highlightsRequest = $.ajax({
				type: "POST",
				//                dataType: 'json',
				url: self.getHighlightsUrl,
				data: {
					'issueKey': BondiTouch.Core.navState.issueKey,
					'articleKey': articleKey,
					'terms': searchTerms
				},
				success: function (retval) {
					debug.debug('highlights callback, success = ' + retval.success);

					_highlightsRequest = null;

					if (retval.success == true) {
						_highlights = retval.highlights;
						if (retval.highlights == undefined) {
							debug.debug("NO highlights returned");
						}
					}
					else {
						// TODO: display error...?
						debug.error('error retrieving highlights: ' + retval.message);
					}
				},
				error: function (req, status, error) {
					_highlightsRequest = null;
					// TODO: display error...?
					if (status !== 'abort' && error != undefined) {  // put this here because jQuery 1.4.4 has a bug w/ aborting that calls error
						//TODO: ???
						var msg = 'ERROR Retrieving Highlights Results [' + status + ']';
						if (error) {
							msg += ': ' + error;
						}
						debug.error(msg);
						//alert(msg);
					}
					else {
						debug.error('... aborted?');
					}
				}
			});
		},

		/*______________________________________________________________________________________________________________________________________________________________________________________*/
		displayHighlightTerms: (otherSelf.displayHighlightTerms) ? otherSelf.displayHighlightTerms : function () {
			// make sure they've been retrieved
			if (_highlightsRequest != null) {
				// we're still waiting... queue up the display for when it returns?
				return;
			}
			if (_highlights != null) {
				// display now.
				// check the page name to see if it matches what we have
				// if so, iterate through and draw the boxes.

				var sprd = BondiTouch.Core.pageListing.getCurrentSpread();
				if (!sprd) {
					return;
				};
				_.each(sprd.pages, function (pageObj) {

					// NOTE: The math here differs from the JumpLinks because the HitHighlight coords are 
					// on a scale of 0.0 to 1.0 in proportion to page size
					var pgHeight = pageObj.height;
					var pgWidth = pageObj.width;

					var $img = $("#" + pageObj.imgID);

					for (hlPage in _highlights) {
						if (hlPage === pageObj.pageNames) {

							var vals = _highlights[hlPage];

							for (var i = 0; i < vals.length; ++i) {
								var val = vals[i];

								var calcLeft = 0;
								var calcTop = 0;
								var calcWidth = 0;
								var calcHeight = 0;

								calcLeft = (val.X * pgWidth);
								calcWidth = (val.Width * pgWidth);
								// NOTE: YES, this *IS* multiplying Y coordinates by the Width, which seems wrong, but
								// since the BoundingBox class is also doing it wrong, 2 wrongs make a right... sortof
								calcTop = (val.Y * pgWidth) + pageObj.topMargin;
								calcHeight = (val.Height * pgWidth);

								//calcLeft += leftOffset;
								var $em = $("<em />")
																		.addClass("searchHighlight")
									.css({
										"width": calcWidth,
										"height": calcHeight,
										"left": calcLeft,
										"top": calcTop
									})
									.appendTo($img)
									.fadeTo(1500, 0.4)
									.fadeTo(1500, 0.2);
							}

							delete _highlights[hlPage]; // we've added them, don't do it twice.
						};
					}

				});
			}
		}
	};

	return self;

})(BondiTouch.Search || {});                                                                           // pass in existing BondiTouch.Search, or empty object - makes this a singleton, even if the .js file is included more than once.


