/**
 * jQuery autosuggest plugin
 * Displays autosuggest results in the given text input element
 * @author Matthew Mawdsley <matthew@hallnet.co.uk>
 * $Id: jquery.autosuggest.js 13882 2012-01-06 19:35:17Z mmawdsley $
 */

(function ($)
 {
   /**
    * Plugin methods
    * @var object
    */
   var methods =
     {
       /**
        * Plugin constructor
        * @param object options plugin options
        */
       init : function (options) {
         return this.each (function () {
             var $this = $(this);                   // Search input
             var data = $this.data ('autosuggest'); // Plugin attributes

             if (!data)
             {
               data = {};
               data.enabled = true;
               data.settings = { uri : null,
                                 closeImageUri : null,
                                 closeImageWidth : null,
                                 closeImageHeight : null,
                                 hideDelay : null,
                                 maxResults : 20,
                                 minChars : 2,
                                 delay : 2000,
                                 arguments : null,
                                 backgroundColor : '#f4f4f4',
                                 borderColor: '#cccccc',
                                 setSuggestionHook : null };
               data.updateTimer = null;
               data.hideTimer = null;
               data.suggestionDialog = null;
               data.suggestionOffset = null;
               data.currentSuggestion = null;
               data.previousKeyword = null;
               data.suggestions = [];
               data.keyCodeEscape = 27;
               data.keyCodeEnter = 13;
               data.keyCodeUp = 38;
               data.keyCodeDown = 40;
               data.browserIsIe6Or7 = false;

               $this.data ('autosuggest', data);

             }

             if (options)
             {
               $.extend ($this.data ('autosuggest').settings, options);
             }

             $this.attr ('autocomplete', 'off');

             methods._createSuggestionDialog.apply ($this);
             methods._setupEvents.apply ($this);

           });

       },

       /**
        * Updates the results
        */
       update : function () {
         return this.each (function () {
             var $this = $(this); // Search input
             var data = {};       // Data to submit

             if ($this.val ().length < $this.data ('autosuggest').settings.minChars)
             {
               return;
             }

             data.maxResults = $this.data ('autosuggest').settings.maxResults;
             data.keywords = $this.val ();

             if ($this.data ('autosuggest').settings.arguments)
             {
               $.each ($this.data ('autosuggest').settings.arguments, function (index, value) {
                   data[index] = value;
                 });
             }

             $.ajax ({ url: $this.data ('autosuggest').settings.uri,
                       data : data,
                       dataType : 'json',
                       success : function (data) { methods._update.apply ($this, [data]); }});

           });

       },

       /**
        * Highlights the next result
        */
       nextResult : function ()
       {
         return this.each (function () {
             var $this = $(this);   // Search input
             var suggestion = null; // Current suggestion element

             $('.autosuggest-suggestion', $this.data ('autosuggest').suggestionDialog).removeClass ('autosuggest-suggestion-current');

             if (!$this.data ('autosuggest').suggestionOffset)
             {
               $this.data ('autosuggest').suggestionOffset = 0;
             }

             ++$this.data ('autosuggest').suggestionOffset;

             if ($this.data ('autosuggest').suggestionOffset > $('.autosuggest-suggestion', $this.data ('autosuggest').suggestionDialog).length)
             {
               $this.data ('autosuggest').suggestionOffset = $('.autosuggest-suggestion', $this.data ('autosuggest').suggestionDialog).length;
             }

             suggestion = $('.autosuggest-suggestion', $this.data ('autosuggest').suggestionDialog).get ($this.data ('autosuggest').suggestionOffset - 1);
             $(suggestion).addClass ('autosuggest-suggestion-current');

             $this.data ('autosuggest').currentSuggestion = $(suggestion).data ('suggestion');

           });

       },

       /**
        * Highlights the previous result
        */
       previousResult : function ()
       {
         return this.each (function () {
             var $this = $(this);   // Search input
             var suggestion = null; // Current suggestion element

             $('.autosuggest-suggestion', $this.data ('autosuggest').suggestionDialog)
               .removeClass ('autosuggest-suggestion-current');

             if (!$this.data ('autosuggest').suggestionOffset)
             {
               $this.data ('autosuggest').suggestionOffset = $('.autosuggest-suggestion', $this.data ('autosuggest').suggestionDialog).length + 1;
             }

             --$this.data ('autosuggest').suggestionOffset;

             if ($this.data ('autosuggest').suggestionOffset < 1)
             {
               $this.data ('autosuggest').suggestionOffset = 1;
             }

             suggestion = $('.autosuggest-suggestion', $this.data ('autosuggest').suggestionDialog).get ($this.data ('autosuggest').suggestionOffset - 1);
             $(suggestion).addClass ('autosuggest-suggestion-current');

             $this.data ('autosuggest').currentSuggestion = $(suggestion).data ('suggestion');

           });

       },

       /**
        * Clears the suggestions
        */
       clear : function ()
       {
         return this.each (function () {
             var $this = $(this); // Search input

             if ($this.data ('autosuggest').suggestionDialog)
             {
               $('div', $this.data ('autosuggest').suggestionDialog).empty ();
               $this.data ('autosuggest').suggestionDialog.hide ();

             }

             $this.data ('autosuggest').currentSuggestion = null;
             $this.data ('autosuggest').suggestionOffset = null;

           });

       },

       /*
        * Enables the suggestions
        */
       enable : function ()
       {
         return this.each (function () {
             var $this = $(this); // Search input

             $this.data ('autosuggest').enabled = true;

           });

       },

       /*
        * Disables the suggestions
        */
       disable : function ()
       {
         return this.each (function () {
             var $this = $(this);

             $this.data ('autosuggest').enabled = false;

           });

       },

       /**
        * Returns true if the plugin is enabled
        * @return boolean
        */
       enabled : function ()
       {
         var $this = $(this);

         return $this.data ('autosuggest').enabled;

       },

       /**
        * Creates the events
        */
       _setupEvents : function ()
       {
         return this.each (function () {
             var $this = $(this);

             $this.blur (function () {
                 var $this = $(this);

                 if (!$this.data ('autosuggest').enabled)
                 {
                   return;
                 }

                 if ($this.data ('autosuggest').hideTimer)
                 {
                   clearTimeout ($this.data ('autosuggest').hideTimer);
                 }

                 $this.data ('autosuggest').hideTimer = setTimeout (function () { methods.clear.apply ($this); }, 1000);

               });

             if ($.browser.msie
                 && (parseInt ($.browser.version) == 6
                     || parseInt ($.browser.version) == 7))
             {
               $this.data ('autosuggest').browserIsIe6Or7 = true;
             }

             $this.bind ('keypress', function (event) {

                 var $this = $(this);

                 if (!$this.data ('autosuggest').enabled)
                 {
                   return;
                 }

                 if ($this.data ('autosuggest').browserIsIe6Or7
                     && event.keyCode == $this.data ('autosuggest').keyCodeEscape)
                 {
                   methods.clear.apply ($this);
                   return false;

                 }

                 if (event.keyCode == $this.data ('autosuggest').keyCodeEnter)
                 {
                   if (methods._setCurrentSuggestion.apply ($this))
                   {
                     event.stopPropagation ();
                     event.preventDefault ();

                     return false;

                   }

                   return true;

                 }

               });

             $this.keyup (function (event) {
                 var $this = $(this); // Search input

                 if (!$this.data ('autosuggest').enabled)
                 {
                   return;
                 }

                 if ($this.data ('autosuggest').previousKeyword
                     && $this.data ('autosuggest').previousKeyword == $this.val ())
                 {
                   return;
                 }

                 $this.data ('autosuggest').previousKeyword = $this.val ();
                 methods.clear.apply ($this);

                 if ($this.data ('autosuggest').updateTimer)
                 {
                   clearTimeout ($this.data ('autosuggest').updateTimer);
                 }

				 $this.data("autosuggest").suggestionDialog.css ('top', ($this.position ().top + $this.outerHeight ()) + 'px');
				 $this.data("autosuggest").suggestionDialog.css ('left', $this.position ().left + 'px');
				 
                 $this.data ('autosuggest').updateTimer = setTimeout (function () { methods.update.apply ($this); },
                                                                      $this.data ('autosuggest').settings.delay);

                 return true;

               });

             $this.keydown (function (event) {
                 var $this = $(this); // Search input

                 if (!$this.data ('autosuggest').enabled)
                 {
                   return true;
                 }

                 if (event.keyCode == $this.data ('autosuggest').keyCodeDown)
                 {
                   methods.nextResult.apply ($this);
                   return true;

                 }

                 if (event.keyCode == $this.data ('autosuggest').keyCodeUp)
                 {
                   methods.previousResult.apply ($this);
                   return true;

                 }

                 if (!$this.data ('autosuggest').browserIsIe6Or7
                     && event.keyCode == $this.data ('autosuggest').keyCodeEscape)
                 {
                   methods.clear.apply ($this);
                   return false;

                 }

               });

           });

       },

       /**
        * Destroys the events
        */
       _removeEvents : function ()
       {
         return this.each (function () {
             var $this = $(this);

             $this.unbind ('blur')
               .unbind ('keypress')
               .unbind ('keyup')
               .unbind ('keydown');

           });

       },

       /**
        * Creates the suggestion container
        */
       _createSuggestionDialog : function ()
       {
         var $this = $(this); // Search input
         var link = null;     // Link element

         $this.data ('autosuggest').suggestionDialog = $('<div/>')
           .css ('width', $this.innerWidth () + 'px')
           .css ('background-color', '#f4f4f4')
           .css ('border-radius', '4px')
           .css ('border', '1px solid #ccc')
           .css ('position', 'absolute')
           .css ('top', ($this.position ().top + $this.outerHeight ()) + 'px')
           .css ('left', $this.position ().left + 'px')
           .addClass ('autosuggest')
           .hover (function () {
               $('.autosuggest-suggestion', $this.data ('autosuggest').suggestionDialog)
               .removeClass ('autosuggest-suggestion-current');

             });

         $this.data ('autosuggest').suggestionDialog.insertAfter ($this);

         if ($this.data ('autosuggest').settings.closeImageUri
             && $this.data ('autosuggest').settings.closeImageWidth
             && $this.data ('autosuggest').settings.closeImageHeight)
         {
           link = $('<a/>')
             .css ('position', 'absolute')
             .css ('margin-left', ($this.innerWidth () - $this.data ('autosuggest').settings.closeImageWidth - 2) + 'px')
             .css ('margin-top', '2px')
             .css ('width', $this.data ('autosuggest').settings.closeImageWidth + 'px')
             .css ('height', $this.data ('autosuggest').settings.closeImageHeight + 'px')
             .css ('background', 'url(' + $this.data ('autosuggest').settings.closeImageUri + ') top left no-repeat')
             .addClass ('autosuggest-link')
             .click (function () { methods.clear.apply ($this) });

           $this.data ('autosuggest').suggestionDialog.append (link);

         }

         $this.data ('autosuggest').suggestionDialog.append ($('<div/>')).hide ();

       },

       /**
        * Receives the new suggestions
        * @param array suggestions list of suggestions
        */
       _update : function (suggestions)
       {
         var $this = $(this);   // Search input
         var sectionCount = 0;  // Number of sections

         $this.data ('autosuggest').suggestions = suggestions;

         methods.clear.apply ($this);

         $.each ($this.data ('autosuggest').suggestions, function (index, suggestions) {
             ++sectionCount;
           });

         $.each ($this.data ('autosuggest').suggestions, function (index, suggestions) {
             if (sectionCount > 1)
             {
               methods._addSuggestionHeading.apply ($this, [index]);
             }

             $.each (suggestions, function (index, value) {
                 methods._addSuggestion.apply ($this, [value]);
               });

           });

         $this.data ('autosuggest').suggestionDialog.show ();

         if ($this.data ('autosuggest').settings.hideDelay)
         {
           if ($this.data ('autosuggest').hideTimer)
           {
             clearTimeout ($this.data ('autosuggest').hideTimer);
           }

           $this.data ('autosuggest').hideTimer = setTimeout (function () { methods.clear.apply ($this); },
                                                              $this.data ('autosuggest').settings.hideDelay);

         }

       },

       /**
        * Creates a suggestion heading
        * @param string content
        */
       _addSuggestionHeading : function (content)
       {
         var $this = $(this); // Search input
         var container = null;
         var heading = null;

         container = $('<h2/>').addClass ('autosuggest-heading')
           .append (content)
           .addClass ('autosuggest-suggestion-suggestion');

         $('div', $this.data ('autosuggest').suggestionDialog).first ()
           .append (container);

       },

       /**
        * Creates a suggestion from the data and adds it to the list
        * @param object suggestion suggestion data
        * @return boolean
        */
       _addSuggestion : function (suggestion)
       {
         var $this = $(this);  // Search input
         var container = null; // Suggestion container
         var heading = null;   // Suggestion heading

         if (suggestion.type == 'heading')
         {
           container = $('<div/>').addClass ('autosuggest-suggestion');

           heading = $('<p/>').append (suggestion.heading)
             .addClass ('autosuggest-suggestion-heading');

           container.append (heading)
             .addClass ('autosuggest-suggestion-suggestion');

           $('div', $this.data ('autosuggest').suggestionDialog).first ()
             .append (container);

           return true;

         }
         else if (suggestion.type == 'suggestion')
         {
           container = $('<div/>').addClass ('autosuggest-suggestion')
           .data ('suggestion', suggestion)
           .click (function () {
               $this.data ('autosuggest').currentSuggestion = $(this).data ('suggestion');
               methods._setCurrentSuggestion.apply ($this);
             });

           heading = $('<p/>').append ('Did you mean "' + suggestion.value + '"?')
             .addClass ('autosuggest-suggestion-heading');

           container.append (heading)
             .addClass ('autosuggest-suggestion-suggestion');

           $('div', $this.data ('autosuggest').suggestionDialog).first ()
             .append (container);

           container.hover (function () {
               $(this).addClass ('autosuggest-suggestion-current');
             }, function () {
               $(this).removeClass ('autosuggest-suggestion-current');
             } );

           return true;

         }
         else if (suggestion.type == 'result')
         {
           container = $('<div/>').addClass ('autosuggest-suggestion')
             .data ('suggestion', suggestion)
             .click (function () {
                 $this.data ('autosuggest').currentSuggestion = $(this).data ('suggestion');
                 methods._setCurrentSuggestion.apply ($this);
               });

           heading = $('<p/>').append (suggestion.heading)
             .addClass ('autosuggest-suggestion-heading');

           container.append (heading);

           if (suggestion.subheading)
           {
             heading = $('<p/>').append (suggestion.subheading)
               .addClass ('autosuggest-suggestion-subheading');

             container.append (heading);

           }

           $('div', $this.data ('autosuggest').suggestionDialog).first ()
             .append (container);

           container.hover (function () {
               $(this).addClass ('autosuggest-suggestion-current');
             }, function () {
               $(this).removeClass ('autosuggest-suggestion-current');
             } );

           return true;

         }

         return false;

       },

       /**
        * Sets the current suggestion
        * @param string value new value
        */
       _setSuggestion : function (value)
       {
         var $this = $(this);  // Search input

         if ($this.data ('autosuggest').hideTimer)
         {
           clearTimeout ($this.data ('autosuggest').hideTimer);
         }

         $this.val (value);
         $this.data ('autosuggest').previousKeyword = value;
         $this.data ('autosuggest').currentSuggestion = null;
         $this.data ('autosuggest').suggestionOffset = null;
         $this.data ('autosuggest').suggestionDialog.hide ();

       },

       /**
        * Sets the currently highlighted suggestion
        * @return boolean
        */
       _setCurrentSuggestion : function ()
       {
         var $this = $(this);          // Search input
         var suggestionId = null;      // Current suggestion ID
         var suggestionHeading = null; // Current suggestion heading

         if (!$this.data ('autosuggest').currentSuggestion)
         {
           return false;
         }

         suggestion = $this.data ('autosuggest').currentSuggestion;

         methods._setSuggestion.apply (this, [suggestion.value]);

         if ($this.data ('autosuggest').settings.setSuggestionHook)
         {
           $this.data ('autosuggest').settings.setSuggestionHook (suggestion);
         }

         return true;

       }

     }

   $.fn.autosuggest = function (method) {

     if (methods[method] && method[0] != '_')
     {
       return methods[method].apply (this, Array.prototype.slice.call (arguments, 1));
     }
     else if (typeof method === 'object' || !method)
     {
       return methods.init.apply (this, arguments);
     }
     else
     {
       $.error ('Method ' +  method + ' does not exist on jQuery.autosuggest');
     }

   };

 })(jQuery);
