$.fn.typeahead = function(config)
{
  var typeahead_config = $.extend({
      maxrow : 10
    }, config);
  // Available Event Delegation - data_changed
  this
  .bind('select_data', function(e, dir)
  {
    var $this = $(this);
    var cont = $this.next('.ta_box');
    var idx = $this.data("selected_index");

    if (typeof idx != 'number')
      idx = 0;
    else
      idx += dir;

    var size = cont.children('.data-item').css('background', 'none').removeClass("selected").size();
    if (idx >= size)
      idx = 0;
    else if (idx < 0)
      idx = size - 1;

    cont.children(".data-item:eq(" + idx + ")").css('background', 'white').addClass('selected');

    $this.data("selected_index", idx);
  })
  .bind("commit_data", function(e, keystroke)
  {
    var $this = $(this);
    var cont = $this.next('.ta_box');
    if (keystroke == 13)
    {
      var selected = cont.find(".selected");
      var value = selected.attr("id");
      var text = selected.text();
      $this.val(text).attr("key_value", value);
      $this.trigger('data_changed',[value, text])
    }
    cont.remove();
    $this.data("showing", false);
  })
  .bind("show_data", function(e)
  {
    var $this = $(this);
    var ds = $this.data("typeahead_data");
    if (!ds)
      return;
    var data = ds.list;
    if (typeof data == 'undefined')
    {
      $this.data("showing", false);
      $this.trigger("load_data");
      return;
    }

    var list = "";
    for (var a = 0,b = 0 ;a < data.length && b < typeahead_config.maxrow; a++)
    {
      var re = new RegExp("(" + $this.val() + ")", "i");
      if (data[a].name.match(re))
      {
        list += "<div class='data-item' style='padding:3px;margin:1px;' id='" + data[a].key + "'><span>" + data[a].name.replace(re, '<strong>$1</strong>') + "</span></div>";
        b++;
      }
    }

    $this.next('.ta_box').remove();
    $this.after('<div class="ta_box" style="display:none;position:absolute;background:#99f"></div>');
    var offset = $this.offset();
    $this.next(".ta_box").html(list).css('top', offset.top + offset.height)
      .show();
    $this.data("selected_index", null);
    
    var threshold = typeahead_config.maxrow - 5;
    if (threshold < 4)
      threshold = 4;

    if (b < threshold)
    {
      $this.queue(function()
      {
        $this.trigger("load_data").dequeue();
      })
    }

    $this.data("showing", true);
  })
  .bind("accept_data", function(e, data)
  {
    var $this = $(this);
    var curdata = $this.data('typeahead_data');
    
    if (!curdata)
      curdata = {index:{},list:[],history:{}}

    for (var a = 0; a < data.length; a ++)
    {
      if (typeof curdata.index[data[a].key] == 'undefined')
      {
        curdata.index[data[a].key] = true;
        curdata.list[curdata.list.length] = data[a];
      }
    }

    curdata.list.sort(function (a, b)
    {
      if (a.name < b.name)
        return -1;
      else if (a.name > b.name)
        return 1;
      else
        return 0;
    })

    $this.data('typeahead_data', curdata);
    $this.data('loading', 1);
  })
  .bind("load_data", function(e)
  {
    var $this = $(this);
    if ($this.data("loading") == 2)
      return;
    
    $this.data("loading", 2);
    var history = $this.data("history");
    if (!history) history = {};
    if (typeof history[$this.val()] == 'undefined')
    {
      history[$this.val()] = true;
      $.post(site_url($this.attr('ajax_url')), {typed:$this.val()}, function(data, status)
      {
        if (status == 'success')
          $this.trigger('accept_data', [data]);
      }, "json");
    }
    else
      $this.data("loading", 1);
    $this.data("history", history);
  })
  .bind('focus', function(e)
  {
    var $this = $(this);
    if (!$this.data("loading"))
      $this.data("loading", 1);
    else if ($this.data('loading') == 1)
      return;

    $this.trigger("load_data");
  })
  .bind('keyup', function(e)
  {
    if (String(e.which).match(/^(13|16|17|18|20)$/))
      return;

    var $this = $(this);
    if (e.which == 38 || e.which == 40)
      $this.trigger("select_data", [e.which == 40 ? 1 : -1]);
    else
      $this.trigger("show_data");

  })
  .bind('keydown', function(e)
  {
    var $this = $(this);
    $this.data('last_string', $(this).val())
    if (e.which == 9 && $this.data('showing'))
      $this.trigger("commit_data", [e.which]);
  })
  .bind('keypress', function(e)
  {
    if (e.which == 13 && $(this).data("showing"))
    {
      $(this).trigger('commit_data', [e.which]);
      return false;
    }
  })

  return this;
}
