/*
 *
 *  Ajax Autofomplete for jQuery, version 1.0
 *  (c) 2008 Tomas Kirda
 *
 *  Ajax Autofomplete for jQuery is freely distributable under the terms of an MIT-style license.
 *  For details, see the web site: http://www.devbridge.com/projects/autocomplete/
 *
 */
 
 var Autocomplete = function(el, options){
  this.el = jQuery('#' + el);
  if(this.el.length == 0){ return; }
  this.id = this.el.attr('id');
  this.el.attr('autocomplete','off');
  this.suggestions = [];
  this.data = [];
  this.badQueries = [];
  this.selectedIndex = -1;
  this.currentValue = this.el.val();
  this.intervalId = 0;
  this.cachedResponse = [];
  this.instanceId = null;
  this.onChangeInterval = null;
  this.ignoreValueChange = false;
  this.serviceUrl = options.serviceUrl;
  this.options = {
    autoSubmit:false,
    minChars:1,
    maxHeight:300,
    deferRequestBy:0,
    width:0
  };
  if(options){ jQuery.extend(this.options, options); }
  if(Autocomplete.isDomLoaded){
    this.initialize();
  }else{
    var me = this;
    jQuery(document).ready(function(){ me.initialize(); })
  }
};

Autocomplete.instances = [];
Autocomplete.isDomLoaded = false;
Autocomplete.inProgress = false;
Autocomplete.isArray = function(obj){ return obj && obj.constructor == Array; }


Autocomplete.getInstance = function(id){
  var instances = Autocomplete.instances;
  var i = instances.length;
  while(i--){ if(instances[i].id === id){ return instances[i]; }}
};

Autocomplete.highlight = function(value, re){
  return value.replace(re, function(match){ return '<strong>' + match + '<\/strong>' });
};

Autocomplete.prototype = {

  killerFn: null,
  
  initialize: function(){

    var me = this;

    this.killerFn = function(e){
      if(jQuery(e.target).parents('.autocomplete').length > 0){
        me.killSuggestions();
        me.disableKillerFn();
      }
    };

    if(!this.options.width){ this.options.width = this.el.width(); }
    this.mainContainerId = 'AutocompleteContainter_' + this.id;
    
    jQuery('<div id="' + this.mainContainerId + '" style="position:absolute;"><div class="autocomplete-w1"><div class="autocomplete-w2"><div class="autocomplete" id="Autocomplete_' + this.id + '" style="display:none; width:' + this.options.width + 'px;"></div></div></div></div>').appendTo('body');

    this.container = jQuery('#Autocomplete_' + this.id);
    this.fixPosition();
    
    this.el.keydown(function(e){ me.onKeyPress(e); });
    this.el.keyup(function(e){ me.onKeyUp(e); });
    this.el.blur(function(){ me.enableKillerFn(); });
    this.el.focus(function(){ me.fixPosition(); });

    this.container.css({ maxHeight:this.options.maxHeight + 'px' });
    this.instanceId = Autocomplete.instances.push(this) - 1;
  },
  
  fixPosition: function(){
    var offset = this.el.offset();
    jQuery('#'+this.mainContainerId).css({top: (offset.top+this.el.height())+'px', left: offset.left+'px' });
  },
  
  enableKillerFn: function(){
    var me = this;
    jQuery(document).bind('click', me.killerFn)
  },

  disableKillerFn: function(){
    var me = this;
    jQuery(document).unbind('click', me.killerFn)
  },
  
  killSuggestions: function(){
    var me = this;
    this.stopKillSuggestions();
    this.intervalId = window.setInterval(function(){ me.hide(); me.stopKillSuggestions(); }, 300);
  },
  
  stopKillSuggestions: function(){
    window.clearInterval(this.intervalId);
  },
  
  onKeyPress: function(e){
    if(!this.enabled){ return; }
    // return will exit the function
    // and event will not fire
    switch(e.keyCode){
      case 27: //Event.KEY_ESC:
        this.el.val(this.currentValue);
        this.hide();
        break;
      case 9: //Event.KEY_TAB:
      case 13: //Event.KEY_RETURN:
        if(this.selectedIndex === -1){ 
          this.hide();
          return; 
        }
        this.select(this.selectedIndex);
        if(e.keyCode === 9/* Event.KEY_TAB */){ return; }
        break;
      case 38: //Event.KEY_UP:
        this.moveUp();
        break;
      case 40: //Event.KEY_DOWN:
        this.moveDown();
        break;
      default: 
        return;
    }
    e.preventDefault();
    e.stopPropagation();
  },
  
  onKeyUp: function(e){
    switch(e.keyCode){
      case 38: //Event.KEY_UP:
      case 40: //Event.KEY_DOWN:
        return;
    }
    clearInterval(this.onChangeInterval);
    if(this.currentValue !== this.el.val()){
      if(this.deferRequestBy > 0){
        // Defer lookup in case when value changes very quickly:
        this.onChangeInterval = setInterval((function(){
          this.onValueChange();
        }).bind(this), this.deferRequestBy);
      }else{
        this.onValueChange();
      }
    }
  },
 
  onValueChange: function(){
    clearInterval(this.onChangeInterval);
    this.currentValue = this.el.val();
    this.selectedIndex = -1;
    if(this.ignoreValueChange){
      this.ignoreValueChange = false;
      return;
    }
    if(this.currentValue === '' || this.currentValue.length < this.options.minChars){ 
      this.hide();
    }else{ 
      this.getSuggestions(); 
    }
  },
  
  getSuggestions: function(){
    var cr = this.cachedResponse[this.currentValue];
    if(cr && Autocomplete.isArray(cr.suggestions)){
      this.suggestions = cr.suggestions;
      this.data = cr.data;
      this.suggest();
    }else if(!this.isBadQuery(this.currentValue)){
      var me = this;
      Autocomplete.inProgress = true;
      jQuery.get(this.serviceUrl, { query:this.currentValue }, function(json){ me.processResponse(json) }, 'json');
    }
  },
  
  isBadQuery: function(q){
    var i = this.badQueries.length;
    while(i--){
      if(q.indexOf(this.badQueries[i]) === 0){ return true; }
    }
    return false;
  },
  
  hide: function(){
    this.enabled = false;
    this.selectedIndex = -1;
    this.container.hide();
  },
  
  suggest: function(){
    if(this.suggestions.length === 0){
      this.hide();
      return;
    }
    var content = [];
    var re = new RegExp('\\b' + this.currentValue.match(/\w+/g).join('|\\b'), 'gi');
    var me = this;
    var len = this.suggestions.length;
    for(var i = 0; i < len; i++){
      content.push((me.selectedIndex === i ? '<div class="selected"' : '<div'),' title="', me.suggestions[i], '" onclick="Autocomplete.instances[', me.instanceId ,'].select(',i,');" onmouseover="Autocomplete.instances[', me.instanceId ,'].activate(',i,');">', Autocomplete.highlight(me.suggestions[i], re), '</div>');
    }
    this.enabled = true;
    this.container.html(content.join('')).show();
  },
  
  processResponse: function(json){
    Autocomplete.inProgress = false;
    var response;
    try{
      response = json;
      if(!Autocomplete.isArray(response.data)){ response.data = []; }
    }catch(err){ return; }
    this.suggestions = response.suggestions;
    this.data = response.data;
    this.cachedResponse[response.query] = response.suggestions;
    if(response.suggestions.length === 0){ this.badQueries.push(response.query); }
    if(response.query === this.currentValue){ this.suggest(); }
  },
  
  activate: function(index){
    var divs = this.container.children();
    var activeItem;
    // Clear previous selection:
    if(this.selectedIndex !== -1 && divs.length > this.selectedIndex){
      jQuery(divs.get(this.selectedIndex)).attr('class', '');
    }
    this.selectedIndex = index;
    if(this.selectedIndex !== -1 && divs.length > this.selectedIndex){
      activeItem = divs.get(this.selectedIndex);
      jQuery(activeItem).attr('class', 'selected');
    }
    return activeItem;
  },
  
  deactivate: function(div, index){
    div.className = '';
    if(this.selectedIndex === index){ this.selectedIndex = -1; }
  },
  
  select: function(i){
    var selectedValue = this.suggestions[i];
    if(selectedValue){
      this.el.val(selectedValue);
      if(this.options.autoSubmit){ 
        var f = this.el.parents('form');
        if(f.length > 0){ f.get(0).submit(); }
      }
      this.ignoreValueChange = true;
      this.hide();
      this.onSelect(i);
    }
  },
  
  moveUp: function(){
    if(this.selectedIndex === -1){ return; }
    if(this.selectedIndex === 0){ 
      this.container.children().get(0).className = '';
      this.selectedIndex = -1;
      this.el.val(this.currentValue);
      return; 
    }
    this.adjustScroll(this.selectedIndex - 1);
  },
  
  moveDown: function(){
    if(this.selectedIndex === (this.suggestions.length - 1)){ return; }
    this.adjustScroll(this.selectedIndex + 1);
  },
  
  adjustScroll: function(i){
    var activeItem = this.activate(i);
    var offsetTop = activeItem.offsetTop;
    var upperBound = this.container.scrollTop();
    var lowerBound = upperBound + this.options.maxHeight - 25;
    if(offsetTop < upperBound){
      this.container.scrollTop(offsetTop);
    }else if(offsetTop > lowerBound){
      this.container.scrollTop(offsetTop - this.options.maxHeight + 25);
    }
    this.el.val(this.suggestions[i]);
  },
  
  onSelect: function(i){
    (this.options.onSelect || function(){})(this.suggestions[i], this.data[i]);
  }

};

jQuery(document).ready(function(){ Autocomplete.isDomLoaded = true; })
