// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
//           (c) 2005 Jon Tirsen (http://www.tirsen.com)
// Contributors:
//  Richard Livsey
//  Rahul Bhargava
//  Rob Wills
// 
// See scriptaculous.js for full license.

// Autocompleter.Base handles all the autocompletion functionality 
// that's independent of the data source for autocompletion. This
// includes drawing the autocompletion menu, observing keyboard
// and mouse events, and similar.
//
// Specific autocompleters need to provide, at the very least, 
// a getUpdatedChoices function that will be invoked every time
// the text inside the monitored textbox changes. This method 
// should get the text for which to provide autocompletion by
// invoking this.getToken(), NOT by directly accessing
// this.element.value. This is to allow incremental tokenized
// autocompletion. Specific auto-completion logic (AJAX, etc)
// belongs in getUpdatedChoices.
//
// Tokenized incremental autocompletion is enabled automatically
// when an autocompleter is instantiated with the 'tokens' option
// in the options parameter, e.g.:
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
// will incrementally autocomplete with a comma as the token.
// Additionally, ',' in the above example can be replaced with
// a token array, e.g. { tokens: [',', '\n'] } which
// enables autocompletion on multiple tokens. This is most 
// useful when one of the tokens is \n (a newline), as it 
// allows smart autocompletion after linebreaks.

if(typeof Effect == 'undefined')
  throw("controls.js requires including script.aculo.us' effects.js library");

var Autocompleter = {}
Autocompleter.Base = function() {};
Autocompleter.Base.prototype = {
  baseInitialize: function(element, update, options) {
    this.element     = $(element); 
    this.update      = $(update);  
    this.hasFocus    = false; 
    this.changed     = false; 
    this.active      = false; 
    this.index       = 0;     
    this.entryCount  = 0;

    if (this.setOptions)
      this.setOptions(options);
    else
      this.options = options || {};

    this.options.paramName    = this.options.paramName || this.element.name;
    this.options.tokens       = this.options.tokens || [];
    this.options.frequency    = this.options.frequency || 0.4;
    this.options.minChars     = this.options.minChars || 1;
    this.options.onShow       = this.options.onShow || 
    function(element, update){ 
      if(!update.style.position || update.style.position=='absolute') {
        update.style.position = 'absolute';
        Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
      }
      Effect.Appear(update,{duration:0.15});
    };
    this.options.onHide = this.options.onHide || 
    function(element, update){ new Effect.Fade(update,{duration:0.15}) };

    if (typeof(this.options.tokens) == 'string') 
      this.options.tokens = new Array(this.options.tokens);

    this.observer = null;
    
    // added this
//    this.resizeInterval = null;
//    this.observerBlur = null;
    //
        
    this.element.setAttribute('autocomplete','off');

    Element.hide(this.update);
    
    // added this
//    Event.observe(this.update, "focus", this.onUpdateFocus.bindAsEventListener(this));
//    Event.observe(this.update,  "blur", this.onBlur.bindAsEventListener(this));
    Event.observe(this.element, "click", this.onKeyPress.bindAsEventListener(this));
    //    

    Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
    Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
  },
  
  // added this
//  onUpdateFocus: function() {
//    if(this.observerBlur)
//    clearTimeout(this.observerBlur);
//  },

//  onUpdateMouseOver: function() {
//    if(this.observerBlur)
//    clearTimeout(this.observerBlur);
//  },
  //

  show: function() {
    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
    if(!this.iefix && 
      (navigator.appVersion.indexOf('MSIE')>0) &&
      (navigator.userAgent.indexOf('Opera')<0) &&
      (Element.getStyle(this.update, 'position')=='absolute')) {
      new Insertion.After(this.update, 
       '<iframe id="' + this.update.id + '_iefix" '+
       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
      this.iefix = $(this.update.id+'_iefix');
    }
    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
  },
  
  fixIEOverlapping: function() {
    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
    this.iefix.style.zIndex = 1;
    this.update.style.zIndex = 2;
    Element.show(this.iefix);
  },
  
  // added this
//  scrollIntoView: function() 
//  {
//    var item=this.getEntry(this.index);
//    while(item.offsetTop<item.parentNode.scrollTop)
//    {
//        item.parentNode.scrollTop = item.offsetTop;
//    }
//    var i=17;
//    while(item.offsetTop+item.offsetHeight>item.parentNode.scrollTop+item.parentNode.offsetHeight)
//    {
//        item.parentNode.scrollTop = item.offsetTop+item.offsetHeight-item.parentNode.offsetHeight+(i++);
//        //item.scrollIntoView(true);
//    }
//  },
  //

  hide: function() {
    this.stopIndicator();
    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
    if(this.iefix) Element.hide(this.iefix);
  },

  startIndicator: function() {
    if(this.options.indicator)
        for(var i = 0; i < this.options.indicator.length; i++)
            Element.show(this.options.indicator[i]);
  },
  
  stopIndicator: function() {
      if(this.options.indicator)
        for(var i = 0; i < this.options.indicator.length; i++)
            Element.hide(this.options.indicator[i]);
  },

  onKeyPress: function(event) {
    if(this.active)
      switch(event.keyCode) {
       case Event.KEY_TAB:
       case Event.KEY_RETURN:
         this.selectEntry();
         Event.stop(event);
       case Event.KEY_ESC:
         this.hide();
         this.active = false;
         Event.stop(event);
         return;
       case Event.KEY_LEFT:
       case Event.KEY_RIGHT:
         return;
       
       // added this
//       case Event.KEY_PAGEUP:
//         this.markPreviousPage();
//         this.render();
//         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
//         this.scrollIntoView();
//         return;
//       case Event.KEY_PAGEDOWN:
//         this.markNextPage();
//         this.render();
//         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
//         this.scrollIntoView();
//         return;
//       case Event.KEY_HOME:
//         this.index=0;
//         this.render();
//         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
//         this.scrollIntoView();
//         return;
//       case Event.KEY_END:
//         this.index=this.entryCount-1;
//         this.render();
//         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
//         this.scrollIntoView();
//         return;
       //
      
       case Event.KEY_UP:
         this.markPrevious();
         this.render();
         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
         // added this
//         this.scrollIntoView();
         //
         return;
       case Event.KEY_DOWN:
         this.markNext();
         this.render();
         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
         // added this
//         this.scrollIntoView();
         //
         return;
      }
     else 
       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
         (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;

    this.changed = true;
    this.hasFocus = true;

    if(this.observer) clearTimeout(this.observer);
    
    // changed this
      this.observer = setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
    // to this
//    if (event.button!=0)
//        this.observer = setTimeout(this.onObserverEvent.bind(this), 100);
//    else //wait for the next keychord
//        this.observer = setTimeout(this.onObserverEvent.bind(this), this.options.frequency*900);
    //
  },

  activate: function() {
    this.changed = false;
    this.hasFocus = true;
    this.getUpdatedChoices();
  },

  onHover: function(event) {
    var element = Event.findElement(event, 'LI');
    if(this.index != element.autocompleteIndex) 
    {
        this.index = element.autocompleteIndex;
        this.render();
    }
    Event.stop(event);
  },
  
  onClick: function(event) {
    var element = Event.findElement(event, 'LI');
    this.index = element.autocompleteIndex;
    this.selectEntry();
    this.hide();
  },
  
  onBlur: function(event) {
    // needed to make click events working
    // changed this
    setTimeout(this.hide.bind(this), 250);
    // to this
//    if(this.observerBlur) 
//        clearTimeout(this.observerBlur);
//    this.observerBlur=setTimeout(this.hide.bind(this), 250);
    //
    this.hasFocus = false;
    this.active = false;     
  }, 
  
  render: function() {
    if(this.entryCount > 0) {
      for (var i = 0; i < this.entryCount; i++)
        this.index==i ? 
          Element.addClassName(this.getEntry(i),"selected") : 
          Element.removeClassName(this.getEntry(i),"selected");
          
      // added this to fix the update element height when not needed in scrolling
//      var item=this.getEntry(this.index);
//       
//      //assume a default size
//      var size=22*this.entryCount;
//      var scrollSize=parseInt(this.element.getAttribute("ScrollSize"));
//      item.parentNode.style.height = (size>scrollSize?scrollSize:size)+"px";
      //
        
      if(this.hasFocus) { 
        this.show();
        this.active = true;
      }
      
      // added this
//      this.onItemResize();
      //
      
    } else {
      this.active = false;
      this.hide();
    }
  },
  
  // added this
//  onItemResizeCaller: function() { this.onItemResize(); },
//  
//  onItemResize: function() {

//      var item=this.getEntry(this.index);
//      
//      if (item.offsetHeight==0)
//      {
//         setTimeout(this.onItemResizeCaller.bind(this), 10);
//      }
//      else
//      {
//          
//        var parentNode=item.parentNode;
//        var scrollSize=this.element.getAttribute("ScrollSize");
//        var itemsSize=this.entryCount*item.offsetHeight;
//        
//        if (scrollSize>itemsSize)
//        {
//            parentNode.style.height=itemsSize;
//        }
//        else
//        {
//            parentNode.style.height=scrollSize;
//        }
//        this.update.visibility = 'visible';
//      }
//  },
  //
  
  // changed this
  markPrevious: function() {
    if(this.index > 0) this.index--
      else this.index = this.entryCount-1;
    this.getEntry(this.index).scrollIntoView(true);
  },
  
  markNext: function() {
    if(this.index < this.entryCount-1) this.index++
      else this.index = 0;
    this.getEntry(this.index).scrollIntoView(false);
  },
  // to this
//  markPrevious: function() {
//    if(this.index > 0) 
//        this.index--;
//    else 
//        this.index = this.index = this.entryCount-1;
//  },
//  
//  markNext: function() {
//    if(this.index < this.entryCount-1) 
//        this.index++;
//    else 
//        this.index = 0;
//  },
  //
  
  // added this
//  markPreviousPage: function() {
//    if(this.index > 0) 
//    {
//        var item=this.getEntry(this.index);
//        var itemsPerPage=Math.floor(item.parentNode.offsetHeight/item.offsetHeight);
//        this.index-=itemsPerPage;
//        if (this.index<0)
//            this.index=0;
//    }
//  },
//  
//  markNextPage: function() {
//    if(this.index < this.entryCount-1) 
//    {
//        var item=this.getEntry(this.index);
//        var itemsPerPage=Math.floor(item.parentNode.offsetHeight/item.offsetHeight);
//        this.index += itemsPerPage;
//        if (this.index > this.entryCount-1)
//            this.index = this.entryCount-1;
//    }
//  },
  //
  
  getEntry: function(index) {
    return this.update.firstChild.childNodes[index];
  },
  
  getCurrentEntry: function() {
    return this.getEntry(this.index);
  },
  
  selectEntry: function() {
    this.active = false;
    this.updateElement(this.getCurrentEntry());
  },

  updateElement: function(selectedElement) {
    if (this.options.updateElement) {
      this.options.updateElement(selectedElement);
      return;
    }
    var value = '';
    if (this.options.select) {
      var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
    } else
      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
    
    // changed this
//    var lastTokenPos = this.findLastToken();
//    if (lastTokenPos != -1) {
//      var newValue = this.element.value.substr(0, lastTokenPos + 1);
//      var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
//      if (whitespace)
//        newValue += whitespace[0];
//      this.element.value = newValue + value;
//    } else {
//      this.element.value = value;
//    }
    // to this for AutocompleteAsHint feature
    if (this.element.getAttribute("AutocompleteAsHint") == '1')
    {
        this.element.focus();
        this.element.value+=value;
        this.element.selectionEnd=this.element.selectionStart=this.element.value.length;
    }
    else
    {
        var lastTokenPos = this.findLastToken();
        if (lastTokenPos != -1) {
          var newValue = this.element.value.substr(0, lastTokenPos + 1);
          var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
          if (whitespace)
            newValue += whitespace[0];
          this.element.value = newValue + value;
        } else {
          this.element.value = value;
        }
    }
    //    
    
    this.element.focus();
    
    if (this.options.afterUpdateElement)
      this.options.afterUpdateElement(this.element, selectedElement);
      
    // added this
    if (this.element.getAttribute("AutocompleteAsHint") == '1')
    {
        this.changed = false;
        this.hasFocus = true;
        //fix the autocomplete issue when dispayed as hint to get the next autocomplete
        setTimeout(this.onAutoCompleteAsHint.bind(this), 550);
    }
    //      
  },
  
  // added this
  onAutoCompleteAsHint: function()
  {
      if (navigator.appVersion.indexOf('MSIE')>0)
      {
        this.element.click();
      }
      else
      {
        //simulate the click in Firefox
        var evt = document.createEvent("MouseEvents");
        evt.initMouseEvent("click", true, true, window,
            0, 0, 0, 0, 0, false, false, false, false, 0, null);
        var canceled = !this.element.dispatchEvent(evt);
    }   
  },
  //

  updateChoices: function(choices) {
    if(!this.changed && this.hasFocus) {
    // changed this
    //  this.update.innerHTML = choices;
    // to this to make it work with ASP.NET built-in callback mechanism
      this.update.innerHTML = choices.substring(choices.indexOf("<ul"));
    
      Element.cleanWhitespace(this.update);
      Element.cleanWhitespace(this.update.firstChild);

      if(this.update.firstChild && this.update.firstChild.childNodes) {
        this.entryCount = 
          this.update.firstChild.childNodes.length;
        for (var i = 0; i < this.entryCount; i++) {
          var entry = this.getEntry(i);
          entry.autocompleteIndex = i;
          this.addObservers(entry);
        }
      } else { 
        this.entryCount = 0;
      }

      this.stopIndicator();

      this.index = 0;
      this.render();
    }
  },

  addObservers: function(element) {
    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
  },

  onObserverEvent: function() {
    this.changed = false;   
    if(this.getToken().length>=this.options.minChars) {
      this.startIndicator();
      this.getUpdatedChoices();
    } else {
      this.active = false;
      this.hide();
    }
  },

  getToken: function() {
    var tokenPos = this.findLastToken();
    if (tokenPos != -1)
      var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
    else
      var ret = this.element.value;

    return /\n/.test(ret) ? '' : ret;
  },

  findLastToken: function() {
    var lastTokenPos = -1;

    for (var i=0; i<this.options.tokens.length; i++) {
      var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
      if (thisTokenPos > lastTokenPos)
        lastTokenPos = thisTokenPos;
    }
    return lastTokenPos;
  }
}

Ajax.Autocompleter = Class.create();
Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
  initialize: function(element, update, url, options) {
    this.baseInitialize(element, update, options);
    this.options.asynchronous  = true;
    this.options.onComplete    = this.onComplete.bind(this);
    this.options.defaultParams = this.options.parameters || null;
    this.url                   = url;
  },

  getUpdatedChoices: function() {
    // changed this
//    entry = encodeURIComponent(this.options.paramName) + '=' + 
//      encodeURIComponent(this.getToken());
    // to this
    InitScriptaculousCallback();
    
    entry = __scriptaculousFormPostData +
            "__CALLBACKID=" + 
            encodeURIComponent(this.options.paramName) +
            "&__CALLBACKPARAM=" + 
            encodeURIComponent(this.getToken());
            
    if (scriptaculousForm["__EVENTVALIDATION"]) {
        entry += "&__EVENTVALIDATION=" + encodeURIComponent(scriptaculousForm["__EVENTVALIDATION"].value);
    }
    //
    
    this.options.parameters = this.options.callback ?
      this.options.callback(this.element, entry) : entry;

    if(this.options.defaultParams) 
      this.options.parameters += '&' + this.options.defaultParams;
      
    new Ajax.Request(this.url, this.options);
  },

  onComplete: function(request) {
    this.updateChoices(request.responseText);
  }

});