/**
 * Copyright (c) White Whale Web Services (http://www.whitewhale.net/)
 * but free for any use, with or without modification
 *
 * Version 1.0 (2008-06-03)
 *
 * Usage: $('.inputselector').pagescan(settingsObject);
 * 	where .inputselector selects the container for the search input (preferred for friendliness to non-JS clients) OR the search input itself
 *	settingsObject may contain
 *  selector : the selector for the links that are to be searched (i.e. '#linkslist a' or 'a.pagescan')
 *  results : the container in which the results will be placed (default:they'll be placed in a #ps_results created immediately after the search box)
 *	onSubmit : callback function for when the user hits the enter/return key; by default, this will take them to the selected link
 *  maxResults : the maximum number of results to show at any one time
 *  tooMany : the message to show when there are more matching results than maxResults (default: 'Keep typing...')
 *  noneFound : the message to show when no results are found
 *  topMatch : a message to be prepended to the top matching element (i.e. 'Top Match: ')
 *  focus : true/false; should the search element grab focus upon initialization?
 *
 * Example: $('#quicksearch').pagescan({selector:'#offices li a', maxResults:10,noneFound:'Sorry, no matching links were found.');
 *
 * Version 1.1 (2008-06-18)
 * 
 * Changes:
 *  - renamed to PageScan (hey, it's shorter)
 *  - keywords can now be added via the title attribute of the links. thanks to donald at whitewhale for the code!
 **/

(function($){
	$.fn.extend({
		pagescan  :   function(s) {
			// initialize the settings with their defaults
			s = $.extend({
					selector			: 'a.ps',
					results				: null,
					onSubmit			: function() { if($('.ps_selected').length) window.location=$('.ps_selected').eq(0).find('a').attr('href'); },
					maxResults			: 10,
					tooMany				: 'Keep typing...',
					noneFound			: 'No matches found.',
					topMatch			: null,
					focus				: true
				},s);
			
			ps = { };

			if(this.is('input[type=text],textarea')) ps.search=this; // if the specified item is an input element, use it
			else ps.search = this.prepend('<input type="text" id="ps_search_query"/>').children().eq(0); // otherwise, add an input inside of it
			
			if(s.results) ps.results=$(s.results).eq(0); // select the specified container
			else ps.results = ps.search.after('<div id="ps_results"></div>').next(); // otherwise, create a new container
			
			ps.results.append('<ul id="ps_results_list"><li></li></ul>'); // append results list to results div
			ps.results_list = ps.results.find('ul#ps_results_list'); // results list is the ul we just added
			
			if(s.focus) ps.search.focus(); // put cursor in search box
			
			ps.matches = ps.links = $(s.selector).each(function() { $.data(this,'keywords',(this.innerHTML+" "+$(this).attr('title')).toLowerCase().replace(/[,-\/]/g,' ').replace(/[^a-zA-Z 0-9]+/g,'')); }); // grab links, attach data to match against
			
			ps.search.attr('autocomplete','off').keyup(function() { // on each keypress, filter the links
				var query = $.trim($(this).val().toLowerCase().replace(/[,\-\/\s]+/g,' ').replace(/[^a-zA-Z 0-9\.]+/g,'')),subquery; // grab query, sanitize it
				if(query==ps.lastquery) return; // do nothing if the query is unchanged
				if(!query.length) {
					ps.results_list.html('<li></li>');
					ps.results.addClass('ps_noquery');
					return;
				} else ps.results.removeClass('ps_noquery');
				if(query.indexOf(ps.lastquery)!=0) {  // if this query is NOT a subset of the last query, reinitialize the matches and search on all terms
					ps.matches = ps.links;
					subquery = query;
				} else subquery = query.substring(query.lastIndexOf(' ')+1,query.length); // otherwise, since this query IS a subset of the last query, no need to search the last query's terms
				if(query.length==0) { ps.lastquery=''; return; } // return no results if there is no query
				ps.lastquery=query;
				$.each(subquery.split(' '),function() { // filter the result for each word in the query
					var search = this;
					ps.matches = ps.matches.filter(function() { return (' ' + $.data(this,'keywords')).indexOf(' ' + search)>=0; });
				});
				ps.results_list.empty();
				if(ps.matches.length>s.maxResults) ps.results_list.html('<li>'+s.tooMany+'</li>').parent().addClass('ps_toomany'); // if there are too many matches
				else if(!ps.matches.length) ps.results_list.html('<li>'+s.noneFound+'</li>').parent().addClass('ps_nonefound'); // if there are no matches
				else { // if the matches are just right
					var query_exp = new RegExp('(\\b' + query.replace(/\s/g,'|\\b') +')','ig'); // for highlighting the query terms
					$.each(ps.matches,function() { ps.results_list.append('<li><a href="'+$(this).attr('href')+'">'+(' ' + $(this).text()).replace(query_exp,'<span class="ps_highlight">$1</span>')+'</a></li>'); }); // list the match, with highlighting
					ps.results.removeClass('ps_toomany').removeClass('ps_nonefound');
					ps.results_list.children().eq(0).attr('id','ps_topmatch').attr('class','ps_selected').prepend(s.topMatch);
				}
			}).keydown(function(event) { // capture special keys on keydown
				switch(event.keyCode) {
					case 38: // up arrow
						event.preventDefault();
						ps.results_list.find('.ps_selected').removeClass('ps_selected').each(function() {
							if($(this).prev().length) $(this).prev().addClass('ps_selected');
							else ps.results_list.find('li:last-child').addClass('ps_selected');
						});					
						break;
					case 40: // down arrow
						event.preventDefault();
						ps.results_list.find('.ps_selected').removeClass('ps_selected').each(function() {
							if($(this).next().length) $(this).next().addClass('ps_selected');
							else ps.results_list.children().eq(0).addClass('ps_selected');
						});
						break;
					case 13: // enter/return
						event.preventDefault();
						s.onSubmit();
						break;
				}
			});
			
			ps.search.keyup(); // run search in case field is pre-populated (e.g. in Firefox)
		}
	});

})(jQuery);

