if (!Array.prototype.filter)
{
  Array.prototype.filter = function(fun /*, thisp*/)
  {
    var len = this.length >>> 0;
    if (typeof fun != "function")
      throw new TypeError();

    var res = new Array();
    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
      {
        var val = this[i]; // in case fun mutates this
        if (fun.call(thisp, val, i, this))
          res.push(val);
      }
    }

    return res;
  };
}


(function($){ 
	$.fn.quickSearch = function(opts) {
		
		function alignResultsDiv(){
				// position results div
				var offset = qInput.offset();
				qResults.css({'left':offset.left,'top':offset.top+25});
		}		
		
		var defaults = {
			'limit':5,
			'dataSrc':'',
			'srcType':'ajax',
			'parameters':'',
			'queryParam':'q',
			'cssPrefix':'_qSearch',
			'limitParam':'limit',
			'instructions':'Begin typing to start the search',
			'speed':400,
			'showNoResults':true,
			'onComplete':
				function(elm){
					qInput.val(elm.text());
				},
			'customResultTextValue':'',
			'customAttr':[],
			'inputValue':'',
			'multiSearch':false,
			'msCloseBubble':
				function(elm){
					elm.fadeOut(400,function(){$(this).remove();alignResultsDiv()});
				},
			'onFocus':
				function(){
					$(this).stopTime("noFocus");
					if (qResults.css('display') != 'block'){
						getResults(qInput.val());
						qResults.slideDown();	
					}
				},
			'onBlur':
				function(){
					$(this).oneTime(500, "noFocus", 
							function() {
								qResults.slideUp();
							}
					);							
				},
			'variablePrefix':'qSearch',
			'header':'',
			'highlightFirst':true,
			'searchButton':false,
			'searchButtonText':'Search',
			'searchButtonClick':function(){
									qSearch.onComplete();
								}
		};

		// Overwrite default settings with any passed settings
		var qSearch = $.extend(defaults,opts);

		// Add classname to container div this is bound to
		$(this).addClass(qSearch.cssPrefix);

		// create the container for the bubbles of a multisearch
		if (qSearch.multiSearch){
			var qListHolder = jQuery('<ul class="'+qSearch.cssPrefix+'_MultiContainer"></ul>');
			qListHolder.bind("click",function(){$("."+qSearch.cssPrefix+"_Input").focus();});
		}

		// set pointer to input box
		var qInput = jQuery('<input type="text" value="'+qSearch.inputValue+'" class="'+qSearch.cssPrefix+'_Input"/>');

		// bind the onBlur function to the blur event
		qInput.bind("blur",
				function(){
					qSearch.onBlur();
				}
		);
		
		// bind the onFocus function to the focus event
		qInput.bind("focus",
				function(){
					qSearch.onFocus();
				}
		);		
		
		
		if (qSearch.searchButton){
			var qSearchButton = jQuery('<input type="submit" class="'+qSearch.cssPrefix+'_SearchButton" value="'+qSearch.searchButtonText+'"/>');
			
			qSearchButton.bind("click",
					function(){
						qSearch.searchButtonClick();
					}
			);				
		} else {
			var qSearchButton = jQuery('<span style="display:none;"></span>');	
		}

		// create div with instruction text and set pointer
		var div = "<div class='"+qSearch.cssPrefix+"_ResultContainer'></div>";
		var qResults = jQuery(div);
		
		qResults.html('<div class="'+qSearch.cssPrefix+'_Result">'+qSearch.instructions+'</div>');
		
		// default global variables
		var qLastQuery = null;
		var results = null;
		
		// if we're searching based on an array
		// we need an array to show what we filter out
		if (qSearch.srcType == 'array'){
			var shownArray = null;
		}
		
		// Appending elements to container
		$(this).append('<div class="'+qSearch.cssPrefix+'_Container"></div>');
		
		// append the input box to either the container
		// div or the bubble containing div
		if (!qSearch.multiSearch){
			qInput.appendTo("."+qSearch.cssPrefix+"_Container");
			qSearchButton.appendTo("."+qSearch.cssPrefix+"_Container");
		} else {
			qListHolder.appendTo("."+qSearch.cssPrefix+"_Container");
			jQuery("<li class='"+qSearch.cssPrefix+"_BubbleHolder'></li>").appendTo(qListHolder);
			qInput.appendTo(qListHolder);
			qSearchButton.appendTo(qListHolder);
		}
		
		qResults.appendTo("."+qSearch.cssPrefix+"_Container")

		alignResultsDiv();

		// handles all key presses while the input has focus
		qInput.bind("keydown",
				function(e){
					if (e.keyCode){
						keycode = e.keyCode;
					} else {
						keycode = e.which;
					}
					
					
					// If they didn't press up, down or enter, get results
					if (keycode != 40 && keycode != 38 && keycode != 13){
						if (qInput.val() != qLastQuery){
							qLastQuery = qInput.val();
							$(this).stopTime("query");
							$(this).oneTime(qSearch.speed, "query", 
								function() {
									getResults(qInput.val());
								});
						}
					}
					
					if (keycode == 40){// Down
						moveHighlight(1);
					}
					
					if (keycode == 38){// Up
						moveHighlight(-1);						
					}
					
					if (keycode == 13){ // Enter
						selectResult($('.'+qSearch.cssPrefix+'_Highlight'));
					}
					
					if (qSearch.multiSearch){
						if (keycode == 8){ // backspace
							if (qInput.val() == ''){
								qSearch.msCloseBubble($("."+qSearch.cssPrefix+"_BubbleHolder li:last-child"));
							}
						}
					}
				}
		);
		

		function midwordHighlight(value, term) {
			return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), '<span class="'+qSearch.cssPrefix+'_HighlightText">$1</span>');
		}
		
		function selectResult(elmPoint){
			var elm = elmPoint;
			
			if (qSearch.multiSearch){
				var bubbles = $('.'+qSearch.cssPrefix+'_Bubble');
				var exists = false;
				
				$.each(bubbles, 
						 function(i,bubble){
							 if ($(bubble).text() == elm.text()){
								exists = true 
							 }
						 }
				);
				if (!exists){
					var bubble = jQuery("<li class='"+qSearch.cssPrefix+"_Bubble'></li>").appendTo('.'+qSearch.cssPrefix+'_BubbleHolder');
					bubble.html("<span>"+elm.text()+"</span>");

					var btnBubbleDelete = jQuery("<img title='Remove' src='/images/delete.gif'>").appendTo(bubble);
					btnBubbleDelete.bind("click",function(){qSearch.msCloseBubble($(this).parent());});

					jQuery('<input type="hidden" name="'+qSearch.variablePrefix+'_name[]" value="'+elm.text()+'">').appendTo(bubble);

					if(qSearch.customAttr.length > 0){
						for(x=0;x<qSearch.customAttr.length;x++){
							var attr = qSearch.customAttr[x];
							var attr_value = elm.attr("_"+qSearch.customAttr[x]);
							bubble.attr('_'+attr,attr_value);
							
							jQuery('<input type="hidden" name="'+qSearch.variablePrefix+'_'+attr+'[]" value="'+attr_value+'">').appendTo(bubble);
						}
					}

					alignResultsDiv();
					var temp = ' ';
					qInput.val('');

					qInput.focus();
					qSearch.onComplete(elm);	
				}
			} else {
				qInput.focus();
				qSearch.onComplete(elm);	
			}
			
			
		}
		
		function moveHighlight(increment){
			if (increment == 1){
				newHighlight = $('.'+qSearch.cssPrefix+'_Highlight').next();
			} else {
				newHighlight = $('.'+qSearch.cssPrefix+'_Highlight').prev();
			}
			if (newHighlight.hasClass(qSearch.cssPrefix+'_Result')){
				$('.'+qSearch.cssPrefix+'_Highlight').removeClass(qSearch.cssPrefix+'_Highlight');
				newHighlight.addClass(qSearch.cssPrefix+'_Highlight');
				
				if (qSearch.customResultTextValue == ''){				
					qInput.val(newHighlight.text());
				} else {
					if (newHighlight.attr("_"+qSearch.customResultTextValue) > ''){
						qInput.val(newHighlight.attr("_"+qSearch.customResultTextValue));	
					} else {
						qInput.val(newHighlight.text());	
					}
				}
			}
		}
		
		function changeHighlight(elmPoint){
			var elm = elmPoint;
			var container = $('.'+qSearch.cssPrefix+'_ResultContainer');
			
			$('.'+qSearch.cssPrefix+'_Result').removeClass(qSearch.cssPrefix+"_Highlight");
			
			$.each(container.children(), function(i,result){
				if ($(result).text() == elm.text()){
					$(result).addClass(qSearch.cssPrefix+'_Highlight');	
				}
			});
		}
		
		function getResults(query){
			if (qSearch.dataSrc > ''){
				if (query > ''){

					if (qSearch.srcType == 'ajax'){
						$.getJSON(qSearch.dataSrc+"?"+qSearch.queryParam+"="+escape(query)+"&"+qSearch.limitParam+"="+qSearch.limit+"&"+qSearch.parameters,
							function(data){
								showResults(data);
							});
					} else if (qSearch.srcType == 'array') {
						shownArray = eval(qSearch.dataSrc);
						
						shownArray =  shownArray.filter(function(n){
							var regexp = RegExp(query,"gi");
							if (n.name.search(regexp) != -1) {
								return n;
							}
						},query);
						
						var tempArray = new Array();
						
						if (shownArray.length > qSearch.limit){
							var limit = qSearch.limit;
						} else {
							var limit = shownArray.length;
						}
						
						for(x=0; x<limit; x++){
							tempArray.push(shownArray[x]);
						}
						
						showResults(tempArray);
					}
				} else {
					qResults.html('');
					var div = jQuery("<div class='"+qSearch.cssPrefix+"_Result'>"+qSearch.instructions+"</div>").appendTo(qResults);	
				}
			} else {
				alert('No "dataSrc" defined.');	
			}
		}

		function showResults(data){
			var counter = 0;

			if (data.length > 0){
				qResults.html('');
				
				if (qResults.css('display') != 'block'){
					qResults.slideDown();
				}				
				
				if (qSearch.header > ''){
					var headerDiv = jQuery('<div class="'+qSearch.cssPrefix+'_Header">'+qSearch.header+'</div>').appendTo(qResults);
				}
				
				if (!qSearch.highlightFirst){
					var startingHilight = jQuery("<div class='"+qSearch.cssPrefix+"_Result "+qSearch.cssPrefix+"_Highlight' style='display:none;'>"+qInput.val()+"</div>").appendTo(qResults);
				}
				
				$.each(data, function(i,result){
					counter++;
					
					var tempName = midwordHighlight(result.name,qInput.val());
					
					if (counter == 1 && qSearch.highlightFirst){
						var div = jQuery("<div class='"+qSearch.cssPrefix+"_Result "+qSearch.cssPrefix+"_Highlight'>"+tempName+"</div>").appendTo(qResults);
					} else {
						var div = jQuery("<div class='"+qSearch.cssPrefix+"_Result'>"+tempName+"</div>").appendTo(qResults);	
					}
					div.bind("mouseover",function(){changeHighlight($(this))});
					div.bind("click",function(){selectResult($(this))});

					if(qSearch.customAttr.length > 0){
						for(x=0;x<qSearch.customAttr.length;x++){
							var attr = 'result.'+qSearch.customAttr[x];
							var attr_value = eval(attr);
							div.attr('_'+qSearch.customAttr[x],attr_value);
						}
					}
					
				});
			} else {
				if (qSearch.showNoResults){
					qResults.html('');
					var div = jQuery("<div class='"+qSearch.cssPrefix+"_Result'>No results</div>").appendTo(qResults);
				} else {
					qResults.html('');
					qResults.slideUp();
				}
			}	
			
		}		
	}
})(jQuery);

jQuery.fn.extend({
	everyTime: function(interval, label, fn, times, belay) {
		return this.each(function() {
			jQuery.timer.add(this, interval, label, fn, times, belay);
		});
	},
	oneTime: function(interval, label, fn) {
		return this.each(function() {
			jQuery.timer.add(this, interval, label, fn, 1);
		});
	},
	stopTime: function(label, fn) {
		return this.each(function() {
			jQuery.timer.remove(this, label, fn);
		});
	}
});

jQuery.event.special

jQuery.extend({
	timer: {
		global: [],
		guid: 1,
		dataKey: "jQuery.timer",
		regex: /^([0-9]+(?:\.[0-9]*)?)\s*(.*s)?$/,
		powers: {
			// Yeah this is major overkill...
			'ms': 1,
			'cs': 10,
			'ds': 100,
			's': 1000,
			'das': 10000,
			'hs': 100000,
			'ks': 1000000
		},
		timeParse: function(value) {
			if (value == undefined || value == null)
				return null;
			var result = this.regex.exec(jQuery.trim(value.toString()));
			if (result[2]) {
				var num = parseFloat(result[1]);
				var mult = this.powers[result[2]] || 1;
				return num * mult;
			} else {
				return value;
			}
		},
		add: function(element, interval, label, fn, times, belay) {
			var counter = 0;
			
			if (jQuery.isFunction(label)) {
				if (!times) 
					times = fn;
				fn = label;
				label = interval;
			}
			
			interval = jQuery.timer.timeParse(interval);

			if (typeof interval != 'number' || isNaN(interval) || interval <= 0)
				return;

			if (times && times.constructor != Number) {
				belay = !!times;
				times = 0;
			}
			
			times = times || 0;
			belay = belay || false;
			
			var timers = jQuery.data(element, this.dataKey) || jQuery.data(element, this.dataKey, {});
			
			if (!timers[label])
				timers[label] = {};
			
			fn.timerID = fn.timerID || this.guid++;
			
			var handler = function() {
				if (belay && this.inProgress) 
					return;
				this.inProgress = true;
				if ((++counter > times && times !== 0) || fn.call(element, counter) === false)
					jQuery.timer.remove(element, label, fn);
				this.inProgress = false;
			};
			
			handler.timerID = fn.timerID;
			
			if (!timers[label][fn.timerID])
				timers[label][fn.timerID] = window.setInterval(handler,interval);
			
			this.global.push( element );
			
		},
		remove: function(element, label, fn) {
			var timers = jQuery.data(element, this.dataKey), ret;
			
			if ( timers ) {
				
				if (!label) {
					for ( label in timers )
						this.remove(element, label, fn);
				} else if ( timers[label] ) {
					if ( fn ) {
						if ( fn.timerID ) {
							window.clearInterval(timers[label][fn.timerID]);
							delete timers[label][fn.timerID];
						}
					} else {
						for ( var fn in timers[label] ) {
							window.clearInterval(timers[label][fn]);
							delete timers[label][fn];
						}
					}
					
					for ( ret in timers[label] ) break;
					if ( !ret ) {
						ret = null;
						delete timers[label];
					}
				}
				
				for ( ret in timers ) break;
				if ( !ret ) 
					jQuery.removeData(element, this.dataKey);
			}
		}
	}
});

jQuery(window).bind("unload", function() {
	jQuery.each(jQuery.timer.global, function(index, item) {
		jQuery.timer.remove(item);
	});
});