 /*
 * AutoSuggest
 * Copyright 2009-2010 Drew Wilson
 * www.drewwilson.com
 * code.drewwilson.com/entry/autosuggest-jquery-plugin
 *
 * Version 1.4   -   Updated: Mar. 23, 2010
 *
 * This Plug-In will auto-complete or auto-suggest completed search queries
 * for you as you type. You can add multiple selections and remove them on
 * the fly. It supports keybord navigation (UP + DOWN + RETURN), as well
 * as multiple AutoSuggest fields on the same page.
 *
 * Inspied by the Autocomplete plugin by: Jšrn Zaefferer
 * and the Facelist plugin by: Ian Tearle (iantearle.com)
 *
 * This AutoSuggest jQuery plug-in is dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *   
 * Modified by Richard Gustin for the FCNZ site.
 * 	- Custom sorting routine for multiple keyword searching
 *  - Custom highlight's for all matches
 *  - Full up/down keymappings (scrolls with the up an down arrow keys)
 */

(function($){
	$.fn.autoSuggest = function(data, options) {
		
		// Setup the default options.
		var defaults = 
		{ 
			asHtmlID: false,
			startText: 'Enter Location to Search',
			emptyText: 'No Stores Found',
			preFill: {},
			limitText: 'No More Selections Are Allowed',
			selectedItemProp: 'value', //name of object property
			selectedValuesProp: 'value', //name of object property
			searchObjProps: 'value', //comma separated list of object property names
			queryParam: 'q',
			retrieveLimit: 75, //number for 'limit' param on ajax request
			extraParams: '',
			matchCase: false,
			minChars: 1,
			keyDelay: 400,
			resultsHighlight: true,
			hideSelectBoxes: false,
			hideSelectBoxesClass: 'selectDropDown',
			neverSubmit: false,
			selectionLimit: false,
			showResultList: true,
		  	start: function(){},
		  	selectionClick: function(elem){},
		  	selectionAdded: function(elem){},
		  	selectionRemoved: function(elem){ elem.remove(); },
		  	formatList: false, //callback function
		  	beforeShow: function(results_holder){ },
		  	beforeRetrieve: function(string){ return string; },
		  	retrieveComplete: function(data){ return data; },
		  	resultClick: function(data){},
		  	resultsComplete: function(){},
            nextInput: 'input',
            strictMatching: false, // If turned on it will strictly match the text within the :search parameter when searching.
            hardSelections: false // If set to true it will place a box on the left of the input, when max selections reached the input will be hidden.
	  	};  
	 	var opts = $.extend(defaults, options);	 	
        window.autoSuggestHideTimer = {};
        
        // We use this to hide select boxes with class hideSelectBoxesClass in IE6.
        if(opts.hideSelectBoxes && !($.support.cssFloat) && ($.browser.version).charAt(0) == '6')
        {
        	opts.hideSelectBoxes=true;
        } 
        else 
        {
        	opts.hideSelectBoxes=false;
        }
        
		var d_type = "object";
		var d_count = 0;
		
		if(typeof data == "string") 
		{
			d_type = "string";
			var req_string = data;
		} 
		else 
		{
			var org_data = data;
			$.each(data, function(k){
				if (data.hasOwnProperty(k)) 
				{
					d_count++;
				}
			});
		}
		
		if((d_type == "object" && d_count > 0) || d_type == "string")
		{
			return this.each(function(x)
			{
				if(!opts.asHtmlID)
				{
					x = x + '' + Math.floor(Math.random()*100); //this ensures unique IDs if autoSuggest() is called multiple times.
					var x_id = 'as-input-' + x;
				}
				else 
				{
					x = opts.asHtmlID;
					var x_id = x;
				}
				opts.start.call(this);
				var input = $(this);
				input.attr('autocomplete','off').addClass('as-input').attr('id',x_id).val(opts.startText);
				var input_focus = false;
				
				// Setup basic elements and render them to the DOM
				input.wrap('<ul class="as-selections" id="as-selections-'+x+'"></ul>').wrap('<li class="as-original" id="as-original-'+x+'"></li>');
				var selections_holder = $('#as-selections-'+x);
				var org_li = $('#as-original-'+x);				
				var results_holder = $('<div class="as-results" id="as-results-'+x+'"></div>').hide();
                
				var results_ul =  $('<ul class="as-list"></ul>');
				
				var values_input = $('<input type="hidden" class="as-values" name="as_values_'+x+'" id="as-values-'+x+'" />');
				var prefill_value = '';
				if(typeof opts.preFill == 'string')
				{
					var vals = opts.preFill.split(',');					
					for(var i=0; i < vals.length; i++)
					{
						var v_data = {};
						v_data[opts.selectedValuesProp] = vals[i];
						
						if(vals[i] != '')
						{
							add_selected_item(v_data, '000'+i);	
						}		
					}
					prefill_value = opts.preFill;
				} 
				else
				{
					prefill_value = '';
					var prefill_count = 0;
					
					$.each(opts.preFill, function(k) 
					{
						if (opts.preFill.hasOwnProperty(k)) {
							prefill_count++;
						}
					});
					
					if(prefill_count > 0)
					{
						for(var i=0; i < prefill_count; i++)
						{
							var new_v = opts.preFill[i][opts.selectedValuesProp];
							if(new_v == undefined)
							{ 
								new_v = ''; 
							}
							
							prefill_value = prefill_value+new_v+',';
							
							if(new_v != '')
							{
								add_selected_item(opts.preFill[i], '000' + i);
							}		
						}
					}
				}
				if(prefill_value != '')
				{
					input.val('');
					var lastChar = prefill_value.substring(prefill_value.length-1);
					if(lastChar != ",")
					{ 
						prefill_value = prefill_value+","; 
					}
					
					values_input.val(','+prefill_value);
					$('li.as-selection-item', selections_holder).addClass('blur').removeClass('selected');
				}
				
				input.after(values_input);
				selections_holder.click(function(){
					input_focus = true;
                    input.focus();
				}).mousedown(function(){ 
					input_focus = false; 
				}).after(results_holder);	

				var timeout = null;
				var prev = '';
				var totalSelections = 0;
				var tab_press = false;
				
				// Handle input field events
				input.focus(function(){			
					if($(this).val() == opts.startText && values_input.val() == '')
					{
						$(this).val('');
					} 
					else if(input_focus)
					{
						$('li.as-selection-item', selections_holder).removeClass('blur');
						if($(this).val() != '')
						{
							showResults();
                            if(opts.hideSelectBoxes)
                            {
                                $('.'+opts.hideSelectBoxesClass).hide();
                            }
                        }
					}
					input_focus = true;
					return true;
				}).blur(function()
				{
					if($(this).val() == '' && values_input.val() == '' && prefill_value == '')
					{
						$(this).val(opts.startText);
					} 
					else if(input_focus) 
					{
						hideResults();
					}				
				}).keydown(function(e) 
				{
					// track last key pressed
					lastKeyPressCode = e.keyCode;
					first_focus = false;
					
					var activeIndex = $("ul.as-list li").index($("ul.as-list li.active")) + 1;
					var outerHeight = $("ul.as-list li").outerHeight();
					
					// Fully Accessable up and down key presses - RG
					switch(e.keyCode) 
					{
						case 38: // up
							if (outerHeight == null)
								return false;
							
							e.preventDefault();
							if ( $("ul.as-list").scrollTop() <= 0 && activeIndex <= 0 ) 
							{
								$("ul.as-list").scrollTop( $("ul.as-list li").length * outerHeight );
							} 
							else if ( activeIndex < ($("ul.as-list li").length - 3)) 
							{
								$("ul.as-list").scrollTop( $("ul.as-list").scrollTop() - outerHeight );
							}
							moveSelection("up");
							break;
						case 40: // down
							e.preventDefault();
							if ( activeIndex == $("ul.as-list li").length ) 
							{
								$("ul.as-list").scrollTop(0);
							} 
							else if ( activeIndex > 4 ) 
							{
								$("ul.as-list").scrollTop( $("ul.as-list").scrollTop() + outerHeight );
							}
							moveSelection("down");
							break;
						case 8:  // delete
							if(input.val() == ""){							
								var last = values_input.val().split(",");
								last = last[last.length - 2];
								selections_holder.children().not(org_li.prev()).removeClass("selected");
								
								if(org_li.prev().hasClass("selected"))
								{
									values_input.val(values_input.val().replace(","+last+",",","));
									opts.selectionRemoved.call(this, org_li.prev());
								} 
								else 
								{
									opts.selectionClick.call(this, org_li.prev());
									org_li.prev().addClass("selected");		
								}
							}
							if(input.val().length == 1)
							{
								hideResults();
								prev = "";
							}
							if($(":visible",results_holder).length > 0)
							{
								if (timeout)
								{ 
									clearTimeout(timeout); 
								}
								timeout = setTimeout(function(){ keyChange(); }, opts.keyDelay);
							}
							break;
						case 9:   // tab
							tab_press = false;
							var active = $("li.active:first", results_holder);
							if(active.length > 0)
							{
								active.click();
								hideResults();

								if(opts.neverSubmit || active.length > 0)
								{
									e.preventDefault();
								}
							} 
							else 
							{
								var i_input = input.val();
								if(i_input != "" && values_input.val().search(","+i_input+",") < 0 && i_input.length >= opts.minChars)
								{	
	                                e.preventDefault();
								    var n_data = {};
									n_data[opts.selectedItemProp] = i_input;
									n_data[opts.selectedValuesProp] = i_input;																				
									var lis = $("li", selections_holder).length;
									add_selected_item(n_data, "00"+(lis+1));
									//input.val("");
	                            }
	                            input.blur();
	                            $(opts.nextInput).focus();
							}
                            break;
						case 13: // return
							tab_press = false;
							var active = $("li.active:first", results_holder);
							if(active.length > 0)
							{
								active.click();
								hideResults();

							}
							if(opts.neverSubmit || active.length > 0)
							{
								e.preventDefault();
							}
							break;
						default:
							if(opts.showResultList)
							{
								if(opts.selectionLimit && $("li.as-selection-item", selections_holder).length >= opts.selectionLimit)
								{
									results_ul.html('<li class="as-message">'+opts.limitText+'</li>');
									showResults()
                                    if(opts.hideSelectBoxes)
                                    {
                                        $('.'+opts.hideSelectBoxesClass).hide();
                                    }
								} 
								else 
								{
									if (timeout)
									{ 
										clearTimeout(timeout); 
									}
									timeout = setTimeout(function(){ keyChange(); }, opts.keyDelay);
								}
							}
							break;
					}
				});
				
				function keyChange() 
				{
					// ignore if the following keys are pressed: [del] [shift] [capslock]
					if( lastKeyPressCode == 46 || (lastKeyPressCode > 8 && lastKeyPressCode < 32) )
					{ 
						return hideResults(); 
					}
					
					var string = input.val().replace(/[\\]+|[\/]+/g,"");
					if (string == prev) {
						return;
					}
					
					prev = string;
					if (string.length >= opts.minChars) 
					{
						selections_holder.addClass("loading");
						if(d_type == "string")
						{
							var limit = "";
							if(opts.retrieveLimit)
							{
								limit = "&limit="+encodeURIComponent(opts.retrieveLimit);
							}
							if(opts.beforeRetrieve)
							{
								string = opts.beforeRetrieve.call(this, string);
							}
							$.getJSON(req_string+"?"+opts.queryParam+"="+encodeURIComponent(string)+limit+opts.extraParams, function(data)
							{ 
								d_count = 0;
								var new_data = opts.retrieveComplete.call(this, data);
								$.each(new_data, function(k)
								{
									if (new_data.hasOwnProperty(k)) 
									{
										d_count++;	
									}
								});
								processData(new_data, string); 
							});
						} 
						else 
						{
							if(opts.beforeRetrieve)
							{
								string = opts.beforeRetrieve.call(this, string);
							}
							processData(org_data, string);
						}
					} 
					else 
					{
						selections_holder.removeClass("loading");
						hideResults();
					}
				}
				var num_count = 0;
				function processData(data, query)
				{
					if (!opts.matchCase)
					{ 
						query = query.toLowerCase(); 
					}
					
					var matchCount = 0;
					results_holder.html(results_ul.html("")).hide();
					for(var i=0;i<d_count;i++)
					{				
						var num = i;
						num_count++;
						var forward = false;
						if(opts.searchObjProps == "value") 
						{
							var str = data[num].value;
						} 
						else 
						{	
							var str = "";
							var names = opts.searchObjProps.split(",");
							for(var y=0;y<names.length;y++)
							{
								var name = $.trim(names[y]);
								str = str+data[num][name]+" ";
							}
						}
						if(str)
						{
							if (!opts.matchCase)
							{ 
								str = str.toLowerCase(); 
							}
							// Custom Sorting/Matching Routine - RG.
							
							var regexCommands = [];
							if ( opts.strictMatching != false ) 
							{
								if(str.search(query) != -1 && values_input.val().search(","+data[num][opts.selectedValuesProp]+",") == -1)
								{
									forward = true;
									
									if (!opts.matchCase)
									{ 
										regexCommands.push(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + query + ")(?![^<>]*>)(?![^&;]+;)", "gi"));
									} 
									else 
									{
										regexCommands.push(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + query + ")(?![^<>]*>)(?![^&;]+;)", "g"));
									}
								}	
							}
							else
							{
								var forward = true;
								var customQuery = $.trim(query).split(' ');
								var customKeywords = $.trim(str).split(',');
								var requiredMatches = {};
								
								$.each(customQuery, function(rm){
									requiredMatches[ customQuery[rm] ] = 'false';
								});
								
								$.each(customKeywords, function(c){
									$.each(customQuery, function(b){
										if (customKeywords[c].search(customQuery[b]) != -1 && values_input.val().search(","+data[num][opts.selectedValuesProp]+",") == -1) 
										{
											if ( typeof(requiredMatches[customQuery[b]]) != 'undefined') 
											{
												if (!opts.matchCase)
												{ 
													regexCommands.push(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + customQuery[b] + ")(?![^<>]*>)(?![^&;]+;)", "gi"));
												} 
												else 
												{
													regexCommands.push(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + customQuery[b] + ")(?![^<>]*>)(?![^&;]+;)", "g"));
												}
												requiredMatches[customQuery[b]] = 'true';
											}
										}
									});
								});
								
								$.each(requiredMatches, function(rm){
									if (requiredMatches[rm] == 'false') 
									{
										forward = false;
									}
								});
							}
						}
						if(forward){
							var formatted = $('<li class="as-result-item" id="as-result-item-'+num+'"></li>').click(function()
							{
									var raw_data = $(this).data("data");
									var number = raw_data.num;
									if($("#as-selection-"+number, selections_holder).length <= 0 && !tab_press)
									{
										var data = raw_data.attributes;
										input.val("").focus();
										prev = "";
										add_selected_item(data, number);
										opts.resultClick.call(this, raw_data);
										hideResults();
									}
									tab_press = false;
									
							}).mousedown(function()
							{ 
								input_focus = false; 
							}).mouseover(function()
							{
								$("li", results_ul).removeClass("active");
								$(this).addClass("active");
							}).data("data",{attributes: data[num], num: num_count});
							var this_data = $.extend({},data[num]);
							
							// RG - Custom Highlighting routine.
							if(opts.resultsHighlight)
							{
								$.each(regexCommands, function(rC){
									this_data[opts.selectedItemProp] = this_data[opts.selectedItemProp].replace(regexCommands[rC],"<em>$1</em>");
								});
							}
							
							if(!opts.formatList)
							{
								formatted = formatted.html(this_data[opts.selectedItemProp]);
							} 
							else 
							{
								formatted = opts.formatList.call(this, this_data, formatted);	
							}
							results_ul.append(formatted);
							delete this_data;
							matchCount++;
							if(opts.retrieveLimit && opts.retrieveLimit == matchCount )
							{ 
								break; 
							}
						}
					}
					selections_holder.removeClass("loading");
					if(matchCount <= 0)
					{
						results_ul.html('<li class="as-message">'+opts.emptyText+'</li>');
					}
					//results_ul.css("width",  selections_holder.outerWidth());
					showResults()
                    if(opts.hideSelectBoxes)
                    {
                        $('.'+opts.hideSelectBoxesClass).hide();
                    }
                    
					opts.resultsComplete.call(this);
				}
				
				function add_selected_item(data, num)
				{
					if (opts.hardSelections != false) 
					{
						values_input.val(values_input.val()+data[opts.selectedValuesProp]+",");
						var item = $('<li class="as-selection-item" id="as-selection-'+num+'"></li>').click(function()
						{
							opts.selectionClick.call(this, $(this));
							selections_holder.children().removeClass("selected");
							$(this).addClass("selected");
						}).mousedown(function(){ input_focus = false; });
						
						var close = $('<a class="as-close">&times;</a>').click(function()
						{
							values_input.val(values_input.val().replace(","+data[opts.selectedValuesProp]+",",","));
							opts.selectionRemoved.call(this, item);
							org_li.show(); // Make sure this is always shown
							input_focus = true;
							input.focus();
							return false;
						});
						org_li.before(item.html(data[opts.selectedItemProp]).prepend(close));
						
						// Hide the input box if we have reached the limit of selections.
						if(opts.selectionLimit && $("li.as-selection-item", selections_holder).length >= opts.selectionLimit)
						{
							org_li.hide();
						}
						opts.selectionAdded.call(this, org_li.prev());	
					}
					else
					{
						input.val(data.name);
						
						if (data.url)
							$('#targetStoreLink').val(data.url);
							
	                	results_holder.hide();
	                    $('input.as-input').blur();
					}
				}
				
				// Dan
                function hideResults()
                {
                	clearTimeout(window.autoSuggestHideTimer);
                	window.autoSuggestHideTimer = setTimeout("$('#"+$(results_holder).attr('id')+"').hide(); if('"+opts.hideSelectBoxes+"'=='true'){ $('."+opts.hideSelectBoxesClass+"').show(); } "  ,400);	
                }
                
                // Dan
                function showResults()
                {
                	opts.beforeShow.call(results_holder);
                	// re-instate scroll functionality on results ul
                	results_ul.scroll(function(e)
                	{
                		e.preventDefault();
                		clearTimeout(window.autoSuggestHideTimer); 
                		window.autoSuggestHideTimer=setTimeout( "$('#"+x_id+"').focus()", 400 )  
                	});
                	results_holder.show();
                }
                
				function moveSelection(direction)
				{
					if($(":visible",results_holder).length > 0)
					{
						var lis = $("li", results_holder);
						if(direction == "down")
						{
							var start = lis.eq(0);
						} 
						else 
						{
							var start = lis.filter(":last");
						}					
						var active = $("li.active:first", results_holder);
						if(active.length > 0)
						{
							if(direction == "down")
							{
								start = active.next();
							} 
							else 
							{
								start = active.prev();
							}	
						}
						lis.removeClass("active");
						start.addClass("active");
					}
				}				
			});
		}
	}
})(jQuery);
