/**
 * jQuery plugin which converts your average text input box into a search
 * suggestion widget. When the user pauses typing, an ajax call is made to the
 * server to fetch suggestions. Suggestions are formatted in a ul/li element.
 * The user can navigate the results either using the arrow keys or they can
 * click the results individually
 *
 * @version $Revision: 278 $ / $Date: 2010-02-11 09:04:59 +0000 (Thu, 11 Feb 2010) $
 */
jQuery.fn.suggest = function(options) {

   options = jQuery.extend({
      delay:250,
      searchUrl:'/ajax/search/suggest/',
      additionalParams:{},
      resultColumns: {
         title:'title',
         linkUrl:'url'
      },
      width:480,
      invite:'type search phrase'
   },options);



   $(this).focus(function(){
      if ($(this).hasClass('hint')) {
         $(this).val('');
         $(this).removeClass('hint');
      }
   });

   $(this).blur(function(){
      if ($(this).val().length == 0) {
         $(this).val(options.invite);
         $(this).addClass('hint');
      }
   })

   // if we've called .suggest() on a text element which has no text on it by
   // default, assign the invite text to the element.
   if ($(this).val().length == 0) {
      $(this).val(options.invite);
   }


   // the timeout to show suggestions
   var __suggestTimeout = null;
   var __popupTimeout = null;

   var inputElem = $(this);
   var suggestionsElem = $('<div class="suggestions"></div>').appendTo('#page-contents');
   var css = { width:options.width,
               left:inputElem.position().left,
               top : inputElem.height()+inputElem.position().top
             };
   suggestionsElem.css(css);

   $(suggestionsElem).hover(function(){
      if (__popupTimeout) {
         clearTimeout(__popupTimeout);
      }

      // if the element is not visible, don't trigger the hover logic
      $(this).addClass('hover');
      if ($('ul li a.selected',suggestionsElem).length > 0) {
         $('ul li a.selected').removeClass('selected');
      }
   },function(){
      $(this).removeClass('hover');
      if (__popupTimeout) {
         clearTimeout(__popupTimeout);
      }
      __popupTimeout = setTimeout(function(){
         $(suggestionsElem).hide();
      },1000);

   });

   /**
    * Identify the key pressed and only proceed to the remote call if the
    * keypress was of interest. i.e., only
    *
    */
   $(this).keyup(function(event){

      var keepChecking = true;

      // letter
      if (event.which >= 65 && event.which <= 90) {
         keepChecking = false;
      }

      // number
      if (keepChecking && (event.which >= 48 && event.which <= 57 )) {
         keepChecking = false;
      }
      // #, +, backspace
      if (keepChecking && (event.which == 222 || event.which == 107 || event.which == 8)) {
         keepChecking = false;
      }

      // the key pressed wasn't a-z, 0-9 or some special punctuation... ignore
      if (keepChecking) {
         return;
      }


      var searchVal = $(this).val().replace(/^[\s]+/g,'').replace(/[\s]+$/g,'');
      if (searchVal.length == 0) {
         suggestionsElem.hide();
         return;
      }

      if (searchVal.length < 2) {
         return;
      }

      if (__suggestTimeout) {
         clearTimeout(__suggestTimeout);
      }
      __suggestTimeout = setTimeout( function() {

         inputElem.addClass('spinner');
         var params = options.additionalParams;
         params.phrase = inputElem.val();
         jQuery.post(options.searchUrl,params,
                                 function(data){
                                    inputElem.removeClass('spinner');

                                    // hide *any* suggestion windows (even
                                    // from other suggest elements)
                                    $('div.suggestions').hide();

                                    parseResponse(data, suggestionsElem);
                                    if (data.length > 0) {
                                       // this seems to be the most reliable way
                                       // of getting slideDown to work.
                                       // otherwise, suggestionsElem just appears
                                       // as if called by .show()
                                       suggestionsElem.show();
                                       suggestionsElem.children('ul').hide();
                                       suggestionsElem.children('ul').slideDown(200);
                                    }
                                 },'json');
         },options.delay);

   });



   /**
    *
    * Fired upon receipt of data from the server.
    */
   parseResponse = function(data, suggestionsElem) {

      // hide it if there are no results
      if (data.length == 0) {
         suggestionsElem.hide();
         return;
      }

      // append the results to the div
      suggestionsElem.empty().append('<ul></ul>');
      for (var i = 0; i < data.length; i++) {
         var title = data[i][options.resultColumns.title];
         var url = data[i][options.resultColumns.linkUrl];

         $(suggestionsElem).children('ul').append('<li><a href="'+url+'">'+title+'</a></li>');
      }
   }


   /**
    * Depending on how many results there are, and which is the currently
    * selected result, the up/down arrow keys provide different behaviour
    * on the results list.
    */
   navigateResults = function(event) {
      // if the mouse is hovering over any of the suggested links, then
      // ignore any of the following keypresses
      if ($(suggestionsElem).hasClass('hover')) {
         return;
      }

      switch (event.keyCode) {
         case 40:
            if ($('ul li a.selected', suggestionsElem).length == 0) {
               $('ul li:first a',suggestionsElem).addClass('selected');
               return;
            } else {
               var numberOfResults = $('ul li a',suggestionsElem).length;
               // if the last option is currently selected
               if (!$('ul li a',suggestionsElem).eq(numberOfResults-1).hasClass('selected')) {
                  $('ul li a.selected',suggestionsElem).removeClass('selected')
                                                               .parent()
                                                               .next()
                                                               .children('a')
                                                               .addClass('selected');
               }
            }
            break;
         case 38:
            if ($('ul li a.selected',suggestionsElem).length == 0) {
               $('ul li:first a',suggestionsElem).addClass('selected');
               return;
            } else {
               // if the first option is currently selected
               if (!$('ul li:first a',suggestionsElem).hasClass('selected')) {
                  $('ul li a.selected',suggestionsElem).removeClass('selected')
                                                               .parent()
                                                               .prev()
                                                               .children('a')
                                                               .addClass('selected');
               }
            }
            break;
         case 13:
            if ($('ul li a.selected',suggestionsElem).length == 0) {
               return;
            }
            // prevent the "click" to the href from propagating
            if (event.stopPropagation) event.stopPropagation();
            if (event.preventDefault) event.preventDefault();
            window.location.href = $('ul li a.selected',suggestionsElem).attr('href');
         break;
      }
   }

   // and finally, bind keypress/keydown to the navigateResults function
   if (jQuery.browser.mozilla) {
      $(this).keypress(navigateResults); // onkeypress repeats arrow keys in Mozilla/Opera
   } else {
      $(this).keydown(navigateResults);  // onkeydown repeats arrow keys in IE/Safari
   }

}

