/*
 *
 *  Ajax Autofomplete for Prototype, version 1.0.1
 *  (c) 2008 Tomas Kirda
 *
 *  Ajax Autofomplete for Prototype 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 = $(el);
  this.id = this.el.identify();
  this.el.setAttribute('autocomplete','off');
  this.suggestions = [];
  this.data = [];
  this.badQueries = [];
  this.selectedIndex = -1;
  this.currentValue = this.el.value;
  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:200
  };
  if(options){ Object.extend(this.options, options); }
  if(Autocomplete.isDomLoaded){
    this.initialize();
  }else{
    Event.observe(document, 'dom:loaded', this.initialize.bind(this), false);
  }
};

Autocomplete.instances = [];
Autocomplete.isDomLoaded = false;

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(){
    this.killerFn = function(e){
      if(!$(Event.element(e)).up('.autocomplete')){
        this.killSuggestions();
        this.disableKillerFn();
      }
    }.bindAsEventListener(this);
    
    if(!this.options.width){ this.options.width = this.el.getWidth(); }
    
    var div = new Element('div', { style:'position:absolute;' });
    div.update('<div class="autocomplete-w1"><div class="autocomplete-w1"><div class="autocomplete" id="Autocomplete_' + this.id + '" style="display:none; width:' + this.options.width + 'px;"></div></div></div>');
    document.body.appendChild(div);
    
    this.mainContainerId = div.identify();
    this.container = $('Autocomplete_' + this.id);
    this.fixPosition();
    
    Event.observe(this.el, 'keydown', this.onKeyPress.bind(this));
    Event.observe(this.el, 'keyup', this.onKeyUp.bind(this));
    Event.observe(this.el, 'blur', this.enableKillerFn.bind(this));
    Event.observe(this.el, 'focus', this.fixPosition.bind(this));
    this.container.setStyle({ maxHeight:this.options.maxHeight + 'px' });
    this.instanceId = Autocomplete.instances.push(this) - 1;
  },
  
  fixPosition: function(){
    var offset = this.el.cumulativeOffset();
    $(this.mainContainerId).setStyle({top: (offset.top+this.el.getHeight())+'px', left: offset.left+'px' });
  },
  
  enableKillerFn: function(){
    Event.observe(document.body, 'click', this.killerFn);
  },

  disableKillerFn: function(){
    Event.stopObserving(document.body, 'click', this.killerFn);
  },
  
  killSuggestions: function(){
    this.stopKillSuggestions();
    this.intervalId = window.setInterval(function(){ this.hide(); this.stopKillSuggestions(); }.bind(this), 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 Event.KEY_ESC:
        this.el.value = this.currentValue;
        this.hide();
        break;
      case Event.KEY_TAB:
      case Event.KEY_RETURN:
        if(this.selectedIndex === -1){ 
          this.hide();
          return; 
        }
        this.select(this.selectedIndex);
        if(e.keyCode === Event.KEY_TAB){ return; }
        break;
      case Event.KEY_UP:
        this.moveUp();
        break;
      case Event.KEY_DOWN:
        this.moveDown();
        break;
      default: 
        return;
    }
    Event.stop(e);
  },
  
  onKeyUp: function(e){
    switch(e.keyCode){
      case Event.KEY_UP:
      case Event.KEY_DOWN:
        return;
    }
    clearInterval(this.onChangeInterval);
    if(this.currentValue !== this.el.value){
      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.value;
    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 && Object.isArray(cr.suggestions)){
      this.suggestions = cr.suggestions;
      this.data = cr.data;
      this.suggest();
    }else if(!this.isBadQuery(this.currentValue)){
      new Ajax.Request(this.serviceUrl, { 
          parameters:{ query:this.currentValue },
          onComplete: this.processResponse.bind(this),
          method:'get'
        });
    }
  },
  
  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');
    this.suggestions.each(function(value, i){
        content.push((this.selectedIndex === i ? '<div class="selected"' : '<div'),' title="', value, '" onclick="Autocomplete.instances[', this.instanceId ,'].select(',i,');" onmouseover="Autocomplete.instances[', this.instanceId ,'].activate(',i,');">', Autocomplete.highlight(value, re), '</div>');
      }.bind(this));
    this.enabled = true;
    this.container.update(content.join('')).show();
  },
  
  processResponse: function(xhr){
    var response;
    try{
      response = xhr.responseText.evalJSON();
      if(!Object.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.childNodes;
    var activeItem;
    // Clear previous selection:
    if(this.selectedIndex !== -1 && divs.length > this.selectedIndex){
      divs[this.selectedIndex].className = '';
    }
    this.selectedIndex = index;
    if(this.selectedIndex !== -1 && divs.length > this.selectedIndex){
      activeItem = divs[this.selectedIndex]
      activeItem.className = '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.value = selectedValue;
      if(this.options.autoSubmit && this.el.form){ 
        this.el.form.submit(); 
      }
      this.ignoreValueChange = true;
      this.hide();
      this.onSelect(i);
    }
  },
  
  moveUp: function(){
    if(this.selectedIndex === -1){ return; }
    if(this.selectedIndex === 0){ 
      this.container.childNodes[0].className = '';
      this.selectedIndex = -1;
      this.el.value = 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 container = this.container;
    var activeItem = this.activate(i);
    var offsetTop = activeItem.offsetTop;
    var upperBound = container.scrollTop;
    var lowerBound = upperBound + this.options.maxHeight - 25;
    if(offsetTop < upperBound){
      container.scrollTop = offsetTop;
    }else if(offsetTop > lowerBound){
      container.scrollTop = offsetTop - this.options.maxHeight + 25;
    }
    this.el.value = this.suggestions[i];
  },
  
  onSelect: function(i){
    (this.options.onSelect || Prototype.emptyFunction)(this.suggestions[i], this.data[i]);
  }

};

Event.observe(document, 'dom:loaded', function(){ Autocomplete.isDomLoaded = true; }, false);
