/**
 * AutoSuggest.js
 *
 * Prototype-based version of
 * http://www.brandspankingnew.net/specials/ajax_autosuggest/ajax_autosuggest_autocomplete.html
 */
var AutoSuggest = Class.create(
{
  initialize: function (id)
  {
    // get field via DOM
    this._fld = $(id);
    this._spinner = new Element('img', { className: 'autosuggest-spinner', src: 'img/ajax-loader.gif' }).hide();

    if (!this._fld)
      throw 'Field #' + id + ' doesn\'t exist';

    this._fld.insert({ before: this._spinner });

    // init variables
    this._sInp 	= '';
    this._nInpC = 0;
    this._aSug 	= $A();
    this._iHigh = 0;
    this._itemCounter = 1;

    this._opts = Object.extend(
    {
      minchars: 1,
      meth: 'get',
      varname: 'input',
      className: 'autosuggest',
      timeout: 2500,
      delay: 500,
      offsety: -5,
      shownoresults: true,
      noresults: 'No results!',
      maxheight: 250,
      cache: true,
      maxentries: 25,
      callback: Prototype.emptyFunction
    }, arguments[1] || {})

    this._fld.observe('keypress', function (evt)
    {
      if (!this.onKeyPress(evt))
        evt.stop();
    }.bind(this))
    
    this._fld.observe('keyup', function (evt)
    {
      if (!this.onKeyUp(evt))
        evt.stop();
    }.bind(this));

    this._fld.writeAttribute('autocomplete', 'off');
  },

  onKeyPress: function (evt)
  {
    var bubble = true;

    switch (evt.keyCode)
    {
      case Event.KEY_RETURN:

        if ($(this._idAs))
          bubble = !this.setHighlightedValue();
        
        break;

      case Event.KEY_ESC:
        this.clearSuggestions();
        break;
    }

    return bubble;
  },

  onKeyUp: function (evt)
  {
    var bubble = true;

    switch (evt.keyCode)
    {
      case Event.KEY_UP:
        this.changeHighlight(evt.keyCode);
        bubble = false;
        break;

      case Event.KEY_DOWN:
        this.changeHighlight(evt.keyCode);
        bubble = false;
        break;

      default:
        this.getSuggestions(this._fld.value);
    }

    return bubble;
  },

  getSuggestions: function (val)
  {
    // if input stays the same, do nothing
    if (val == this._sInp)
      return 0;

    // kill list
    if ($(this._idAs))
      $(this._idAs).remove();

    this._sInp = val;

    // input length is less than the min required to trigger a request -> do nothing
    if (val.length < this._opts.minchars)
    {
      this._aSug.clear();
      this._nInpC = val.length;
      return 0;
    }

    var ol = this._nInpC; // old length
    this._nInpC = val.length ? val.length : 0;

    // if caching enabled, and user is typing (ie. length of input is increasing)
    // filter results out of aSuggestions from last request
    var l = this._aSug.size();

    if (this._nInpC > ol && l && l < this._opts.maxentries && this._opts.cache)
    {
      var arr = $A();

      for (var i = 0; i < l; i++)
      {
        if (this._aSug[i].value.toLowerCase().include(val.toLowerCase()))
          arr.push(this._aSug[i]);
      }

      this._aSug = arr;
      this.createList(this._aSug);

      return false;
    }

    // do new request
    else
    {
      clearTimeout(this._ajID);
      this._ajID = setTimeout( function() {this.doAjaxRequest(this._sInp);}.bind(this), this._opts.delay);
    }

    return false;
  },

  doAjaxRequest: function (input)
  {
    // check that saved input is still the value of the field
    if (input != this._fld.value)
      return false;

    var url = null;

    // create ajax request
    if (typeof(this._opts.script) == 'function')
      url = this._opts.script(encodeURIComponent(this._sInp));
    else
      url = this._opts.script + this._opts.varname + "=" + encodeURIComponent(this._sInp);

    if (!url)
      return false;

    new Ajax.Request(url,
    {
      method: this._opts.meth,
      onSuccess: function (req) {this.setSuggestions(req, this._sInp);}.bind(this),
      onFailure: function (status) {alert('AJAX error: ' + status);}
    });
  },

  setSuggestions: function (req, input)
  {
    // if field input no longer matches what was passed to the request don't show the suggestions
    if (input != this._fld.value)
      return false;

    this._aSug.clear();

    var json = req.responseText.evalJSON();

    for (var i = 0; i < json.results.length; i++)
    {
      this._aSug.push(
      {
        'id': json.results[i].id,
        'value': json.results[i].value,
        'group': json.results[i].group || '',
        'info': json.results[i].info,
        'url': json.results[i].url || ''
      });
    }

    this._idAs = 'as_' + this._fld.readAttribute('id');
    this.createList(this._aSug);
  },

  createList: function (arr)
  {
    this._itemCounter = 1;

    // get rid of old list and clear the list removal timeout
    if ($(this._idAs))
      $(this._idAs).remove();

    this.killTimeout();

    // if no results, and shownoresults is false, do nothing
    if (arr.size() == 0 && !this._opts.shownoresults)
      return false;

    // create holding div
    var div = new Element('div', {id: this._idAs, className: this._opts.className});
    var groups = arr.pluck('group').uniq().without('');

    // no results
    if (arr.size() == 0 && this._opts.shownoresults)
      div.insert(new Element('p', {className: 'as_warning'}).update(this._opts.noresults));

    else
    {
      if (groups.size() > 0)
      {
        groups.each(function (group, index)
        {
          div.insert(this.createGroup(arr.findAll(function (item) {return item.group == group;}), group));
        }, this);
      }
      else
        div.insert(this.createGroup(arr, ''));
    }

    // get position of target textfield
    // position holding div below it
    // set width of holding div to width of field
    var pos = this._fld.cumulativeOffset();
    var right = $$('body').first().getWidth() - (pos.left + this._fld.getWidth());
    div.setStyle({right: right + 'px',
                   top: (pos.top + this._fld.offsetHeight + this._opts.offsety) + 'px'});

    // set mouseover functions for div
    // when mouse pointer leaves div, set a timeout to remove the list after an interval
    // when mouse enters div, kill the timeout so the list won't be removed
    div.observe('mouseover', function () {this.killTimeout();}.bind(this)).
        observe('mouseout', function () {this.resetTimeout();}.bind(this));

    $$('body').first().insert(div); // add DIV to document
    this._iHigh = 0; // currently no item is highlighted

    // remove list after an interval
    this._toID = setTimeout(function () {this.clearSuggestions()}.bind(this), this._opts.timeout);
  },

  createGroup: function (items, group)
  {
    // create and populate ul
    var el = new Element('div',{className: 'as-group-items'});
    var ul = new Element('ul', {className: 'as_ul'});

    if (!group.blank())
      el.insert(new Element('div', {className: 'as-group-title'}).update(group));

    el.insert(ul);

    // loop throught arr of suggestions creating an LI element for each suggestion
    items.each(function (item, i)
    {
      // format output with the input enclosed in a EM element
      // (as HTML, not DOM)
      var val = item.value;
      var st = val.toLowerCase().indexOf(this._sInp.toLowerCase());
      var output = val.substring(0, st) + '<em>' +
        val.substring(st, st + this._sInp.length) + '</em>' + val.substring(st + this._sInp.length);

      var span = new Element('span').update(output);

      if (item.info != '')
        span.insert(new Element('br')).insert(new Element('small').update(item.info));

      var a = new Element('a', {href: item.url.blank() ? '#' : item.url});
      a.insert(span);

      a.name = this._itemCounter;
      a.observe('click', function (evt)
      {
        if (item.url.blank())
        {
          this.setHighlightedValue();
          evt.stop();
        }
      }.bind(this));

      a.observe('mouseover', function (evt) {this.setHighlight(evt.findElement('a').readAttribute('name'));}.bind(this));
      ul.insert(new Element('li').insert(a));

      this._itemCounter++;
    }, this);

    return el;
  },

  changeHighlight: function (key)
  {
    var listItems = $$('.as_ul li');

    if (!listItems.size() === 0)
      return false;

    var n;

    if (key == Event.KEY_UP)
      n = this._iHigh - 1;
    else if (key == Event.KEY_DOWN)
      n = this._iHigh + 1;


    if (n > listItems.size())
      n = listItems.size();

    if (n < 1)
      n = 1;

    this.setHighlight(n);
  },

  setHighlight: function (n)
  {
    var listItems = $$('.as_ul li');

    if (listItems.size() === 0)
      return false;

    if (this._iHigh > 0)
      this.clearHighlight();

    this._iHigh = Number(n);

    listItems[this._iHigh-1].addClassName('as_highlight');
    this.killTimeout();
  },

  clearHighlight: function ()
  {
    var listItems = $$('.as_ul li');

    if (!listItems.size() === 0)
      return false;

    if (this._iHigh > 0)
    {
      listItems[this._iHigh-1].removeClassName('as_highlight');
      this._iHigh = 0;
    }
  },

  setHighlightedValue: function ()
  {
    if (this._iHigh)
    {
      var selected = this._aSug[this._iHigh-1];

      if (selected.url == '#')
      {
        this._sInp = selected.value;
        this._fld.setValue(value);

        // move cursor to end of input (safari)
        this._fld.focus();

        if (this._fld.selectionStart)
          this._fld.setSelectionRange(this._sInp.length, this._sInp.length);

        this.clearSuggestions();

        // pass selected object to callback function, if exists
        this._opts.callback(this._aSug[this._iHigh-1]);
      }
      else
        window.location = selected.url;

      return true;
    }
    else
      return false;
  },

  killTimeout: function ()
  {
    clearTimeout(this._toID);
  },

  resetTimeout: function ()
  {
    clearTimeout(this._toID);
    this._toID = setTimeout(function () {this.clearSuggestions()}.bind(this), 1000);
  },

  clearSuggestions: function ()
  {
    this.killTimeout();

    var ele = $(this._idAs);

    if (ele)
      ele.fade({duration: .25, after: function (evt) {if (evt.element) {evt.element.remove();}}.bind(this)})
  }
});



