	
/*
 *	jquery.suggest 1.1 - 2007-08-06
 *	
 *	Uses code and techniques from following libraries:
 *	1. http://www.dyve.net/jquery/?autocomplete
 *	2. http://dev.jquery.com/browser/trunk/plugins/interface/iautocompleter.js	
 *
 *	All the new stuff written by Peter Vulgaris (www.vulgarisoip.com)	
 *	Feel free to do whatever you want with this file
 *
 */
	
(function($) {

  $.suggest = function(input, options) {
	
    var $input = $(input).attr("autocomplete", "off");
    var $results = $(document.createElement("ul"));

    var timeout = false;		// hold timeout ID for suggestion results to appear
    var input_change_prober_timeout_handler = false;
    var input_change_timer = 10;
    var prevLength = 0;			// last recorded length of $input.val()
    var prevInput = "";
    var cache = [];				// cache MRU list
    var cacheSize = 0;			// size of cache in chars (bytes?)
			
    $results.addClass(options.resultsClass).appendTo('body');
				

    resetPosition();
    $(window)
    .load(resetPosition)		// just in case user is changing size of page while loading
    .resize(resetPosition);

    $input.blur(function() {
        setTimeout(function() { $results.hide() }, 200);

        if(input_change_prober_timeout_handler) {
          clearTimeout(input_change_prober_timeout_handler);
        }
      });

    function detectInputChange() {
      // console.debug("in detectInputChange");
      // console.debug("input lenght = "+$input.val().length+", prev length = "+prevLength);

      // if (timeout) {
      //   clearTimeout(timeout);
      //   //$results.hide();
      // }
        
      // timeout = setTimeout(suggest, options.delay);
      // prevLength = $input.val().length;

      $input_content = $input.val();
      if( ($input_content.length != prevLength) ||
          ($input_content != prevInput) ){
        suggest();
        prevLength = $input.val().length;
        prevInput = $input.val();
      } 

      if(input_change_prober_timeout_handler) {
        clearTimeout(input_change_prober_timeout_handler);
      }

      input_change_prober_timeout_handler = setTimeout(detectInputChange, input_change_timer);
    }

    $input.focus( function () {
        //console.debug("focused!");
        // if(input_change_prober_timeout_handler) {
        //   clearTimeout(input_change_prober_timeout_handler);
        // }
        input_change_prober_timeout_handler = setTimeout(detectInputChange, input_change_timer);
      });
			
    // help IE users if possible
    try {
      $results.bgiframe();
    } catch(e) { }

    // I really hate browser detection, but I don't see any other way
    if ($.browser.mozilla)
      $input.keypress(processKey);	// onkeypress repeats arrow keys in Mozilla/Opera
    else
      $input.keyup(processKey);		// onkeydown repeats arrow keys in IE/Safari

    function resetPosition() {
      // requires jquery.dimension plugin
      var offset = $input.offset();
      $results.css({
        top: (offset.top + input.offsetHeight) + 'px',
            left: offset.left + 'px'
            });
    }
			
    function processKey(e) {
      //console.debug("ever here in processKey??" + e.keyCode);
      //$.log(e.keyCode);
      // handling up/down/escape requires results to be visible
      // handling enter/tab requires that AND a result to be selected
      if ((/27$|38$|40$/.test(e.keyCode) && $results.is(':visible')) ||
          (/^13$|^9$/.test(e.keyCode) && getCurrentResult())) {
        if (e.preventDefault)
          e.preventDefault();
        if (e.stopPropagation)
          e.stopPropagation();

        e.cancelBubble = true;
        e.returnValue = false;
        switch(e.keyCode) {
	
        case 38: // up							
          prevResult();
          break;
				
        case 40: // down
          nextResult();
          break;
	
        case 9:  // tab
        case 13: // return
          selectCurrentResult();
          break;
							
        case 27: //	escape
          $results.hide();
          break;
	
        }
					
      } // else if ($input.val().length != prevLength) {
      //   if (timeout) 
      //     clearTimeout(timeout);
      //   timeout = setTimeout(suggest, options.delay);
      //   prevLength = $input.val().length;
      // }			
					
				
    }
			
			
    function suggest() {
					
      var q = $.trim($input.val());
      if (q.length >= options.minchars) {
					
        cached = checkCache(q);
					
        if (cached) {
					
          displayItems(cached['items']);
						
        } else {
					
          $.get(options.source, {q: q}, function(txt) {

              $results.hide();
							
              var items = parseTxt(txt, q);
							
              displayItems(items);
              addToCache(q, items, txt.length);
							
            });
						
        }
					
      } else {
				
        $results.hide();
					
      }
					
    }
			
			
    function checkCache(q) {

      for (var i = 0; i < cache.length; i++)
        if (cache[i]['q'] == q) {
          cache.unshift(cache.splice(i, 1)[0]);
          return cache[0];
        }
				
      return false;
			
    }
			
    function addToCache(q, items, size) {

      while (cache.length && (cacheSize + size > options.maxCacheSize)) {
        var cached = cache.pop();
        cacheSize -= cached['size'];
      }
				
      cache.push({
        q: q,
            size: size,
            items: items
            });
					
      cacheSize += size;
			
    }
			
    function displayItems(items) {
				
      if (!items)
        return;
					
      if (!items.length) {
        $results.hide();
        return;
      }
				
      var html = '';
      for (var i = 0; i < items.length; i++)
        html += '<li>' + items[i] + '</li>';

      $results.html(html).show();
				
      $results
      .children('li')
      .mouseover(function() {
          $results.children('li').removeClass(options.selectClass);
          $(this).addClass(options.selectClass);
        })
      .click(function(e) {
          e.preventDefault(); 
          e.stopPropagation();
          selectCurrentResult();
        });
							
    }
			
    function parseTxt(txt, q) {
				
      var items = [];
      var tokens = txt.split(options.delimiter);
				
      // parse returned data for non-empty items
      for (var i = 0; i < tokens.length; i++) {
        var token = $.trim(tokens[i]);
        if (token) {
          token = token.replace(
                                new RegExp(q, 'ig'), 
                                function(q) { return '<span class="' + options.matchClass + '">' + q + '</span>' }
                                );
          items[items.length] = token;
        }
      }
				
      return items;
    }
			
    function getCurrentResult() {
			
      if (!$results.is(':visible'))
        return false;
			
      var $currentResult = $results.children('li.' + options.selectClass);
				
      if (!$currentResult.length)
        $currentResult = false;
					
      return $currentResult;

    }
			
    function selectCurrentResult() {
			
      $currentResult = getCurrentResult();
			
      if ($currentResult) {
        $input.val($currentResult.text());
        $results.hide();
					
        if (options.onSelect)
          options.onSelect.apply($input[0]);
						
      }
			
    }
			
    function nextResult() {
			
      $currentResult = getCurrentResult();
			
      if ($currentResult)
        $currentResult
          .removeClass(options.selectClass)
          .next()
          .addClass(options.selectClass);
      else
        $results.children('li:first-child').addClass(options.selectClass);
			
    }
			
    function prevResult() {
			
      $currentResult = getCurrentResult();
			
      if ($currentResult)
        $currentResult
          .removeClass(options.selectClass)
          .prev()
          .addClass(options.selectClass);
      else
        $results.children('li:last-child').addClass(options.selectClass);
			
    }
	
  }
		
  $.fn.suggest = function(source, options) {
		
    if (!source)
      return;
		
    options = options || {};
    options.source = source;
    //$.log(source)
    options.delay = options.delay || 100;
    options.resultsClass = options.resultsClass || 'ac_results';
    options.selectClass = options.selectClass || 'ac_over';
    options.matchClass = options.matchClass || 'ac_match';
    options.minchars = options.minchars || 1;
    options.delimiter = options.delimiter || '\n';
    options.onSelect = options.onSelect || false;
    options.maxCacheSize = options.maxCacheSize || 65536;
	
    this.each(function() {
        new $.suggest(this, options);
      });
	
    return this;
			
  };
		
})(jQuery);
	
