/**
 * Please note that this is not the standard jQuery auto.complete plugin.
 * This is a version modified to allow multiple entries in a text box, and
 * further modified in-house to allow regular expressions.
 */

$.autocomplete = function(input, options) {
  // Create a link to self
  var me = this;

  // Create jQuery object for input element
  var $input = $(input).attr("autocomplete", "off");;

  // Apply inputClass if necessary
  if (options.inputClass) $input.addClass(options.inputClass);

  // Create results
  var results = document.createElement("div");
  // Create jQuery object for results
  var $results = $(results);
  $results.hide();

  // Add to body element
  $("body").append($results[0]);

  input.autocompleter = me;
  input.lastSelected = $input.val();

  var timeout = null;
  var prev = "";
  var active = -1;
  var cache = {};
  var keyb = false;
  $input
  .keydown(function(e) {
    if($results) {
    switch(e.keyCode) {
      case 38: // up
        e.preventDefault();
        moveSelect(-1);
        break;
      case 40: // down
        e.preventDefault();
        moveSelect(1);
        break;
      //case 188: //comma
      case 9:  // tab
        // if nothing is selected, don't attempt to
        // insert anything into the text field
        if ($("li.highlight").length == 0)
          break;
        if(selectCurrent())
          e.preventDefault();
        break;
      default:
        active = -1;
        if (timeout) clearTimeout(timeout);
        timeout = setTimeout(onChange, options.delay);
        break;
      }
    }
  })
  .keypress(function(e) {
    if($results && e.keyCode == 13) {
      hideResults();
      e.preventDefault();
      selectCurrent();
    }
  })
  .blur(function() {
    hideResults();
  });

  hideResultsNow();

  function setResultsPosition() {
    var pos = findPos(input);
    $results.addClass(options.resultsClass).css({
      position: "absolute",
      top: (pos.y + input.offsetHeight) + "px",
      left: pos.x + "px"
    });
  }

  function onChange() {
    var v = $input.val();
    //console.log('onchange:',v);
    if (v == prev) return;
    prev = v;
    if (v.length >= options.minChars) {
      $input.addClass(options.loadingClass);
      requestData(v);
    } else {
      $input.removeClass(options.loadingClass);
      $results.hide();
      $results.children().each(function(){
    $(this).remove() });
    }
  };

  function moveSelect(step) {

    var lis = $("li", results);
    if (lis.length == 0) return;

    active += step;

    if (active < 0) {
      active = 0;
    } else if (active >= lis.size()) {
      active = lis.size() - 1;
    }

    lis.removeClass("highlight");

    $(lis[active]).addClass("highlight");

    // Weird behaviour in IE
    // if (lis[active] && lis[active].scrollIntoView) {
    //   lis[active].scrollIntoView(false);
    // }

  };

  function selectCurrent() {
    var li = $("li.highlight", results)[0];
    if (!li) {
      var $li = $("li", results);
      if (options.selectOnly) {
        if ($li.length == 1) li = $li[0];
      } else if (options.selectFirst) {
        li = $li[0];
      }
    }
    if (li) {
      selectItem(li);
      return true;
    } else {
      return false;
    }
  };

  function selectItem(li) {
    if (!li) {
      li = document.createElement("li");
      li.extra = [];
      li.selectValue = "";
    }
    var v = $.trim(li.selectValue ? li.selectValue : li.innerHTML);
    input.lastSelected = v;
    prev = v;
    $results.html("");

    if (options.mode == "custom") {
      var old_value = $input.val();
      new_value = options.replaceLastItem(old_value, v);
    } else if(options.mode == "multiple") {
      old_value = $input.val();
      var old_pieces = old_value.split(options.multipleSeparator);
      old_pieces.splice(old_pieces.length - 1, 1);
      value = old_pieces.join(' ');
      if (value.length > 0)
        value += ' ';
      new_value = value + v + ' ';
    } else {
      new_value = v;
    }
    $input.val(new_value);    
    
    hideResultsNow();
    if (options.onItemSelect) setTimeout(function() { options.onItemSelect(li) }, 1);
  };

  function hideResults() {
    if (timeout) clearTimeout(timeout);
    timeout = setTimeout(hideResultsNow, 200);
  };

  function hideResultsNow() {
    if (timeout) clearTimeout(timeout);
    $input.removeClass(options.loadingClass);
    if ($results.is(":visible")) {
      $results.hide();
    }
    if (options.mustMatch) {
      var v = $input.val();
      if (v != input.lastSelected) {
        selectItem(null);
      }
    }
  };

  function receiveData(q, data) {
    if (data) {
      $input.removeClass(options.loadingClass);
      results.innerHTML = "";
      if ($.browser.msie) {
        // we put a styled iframe behind the calendar so HTML SELECT elements don't show through
        $results.append(document.createElement('iframe'));
      }
      results.appendChild(dataToDom(data));
      // Set the results box to appear in the correct place
      setResultsPosition();
      $results.children('iframe').css({
        width: $results.children('ul').css('width'),
        height: $results.children('ul').css('height')
      });
      $results.show();
    } else {
      hideResultsNow();
    }
  };

  function parseData(data) {
    if (!data || data == ' ') return null;
    var parsed = [];
    var rows = data.split(options.lineSeparator);
    for (var i=0; i < rows.length; i++) {
      var row = $.trim(rows[i]);
      if (row) {
        parsed[parsed.length] = row.split(options.cellSeparator);
      }
    }
    return parsed;
  };

  function dataToDom(data) {
    var ul = document.createElement("ul");
    var num = data.length;
    for (var i=0; i < num; i++) {
      var row = data[i];
      if (!row) continue;
      var li = document.createElement("li");
      if (options.formatItem) {
        li.innerHTML = options.formatItem(row, i, num);
        li.selectValue = row[0];
      } else {
        li.innerHTML = row[0];
      }
      var extra = null;
      if (row.length > 1) {
        extra = [];
        for (var j=1; j < row.length; j++) {
          extra[extra.length] = row[j];
        }
      }
      li.extra = extra;
      ul.appendChild(li);
      $(li).hover(
        function() { $("li", ul).removeClass("over"); $(this).addClass("over"); },
        function() { $(this).removeClass("over"); }
      ).click(function(e) { e.preventDefault(); e.stopPropagation(); selectItem(this) });
    }
    return ul;
  };

  function requestData(q) {
    if (!options.matchCase) q = q.toLowerCase();
    var data = options.cacheLength ? loadFromCache(q) : null;
    if (data) {
      receiveData(q, data);
    } else {
      var url = makeUrl(q);
      if (url) {
        $.get(makeUrl(q), function(data) {
          data = parseData(data)
          addToCache(q, data);
          receiveData(q, data);
        });
      } else {
        addToCache(q, '');
        receiveData(q, '');
      }
    }
  };

  function makeUrl(q) {
    if(options.mode == "custom") {
      q = options.getLastItem(q);
    } else  if(options.mode == "multiple") {
      q = q.split(options.multipleSeparator).reverse()[0];
    }
    if (q) {
      var url = options.url + "?q=" + q;
      for (var i in options.extraParams) {
        url += "&" + i + "=" + options.extraParams[i];
      }
      return url;
    } else {
      return '';
    }
  };

  function loadFromCache(q) {
    return null;
    if (!q) return null;
    if (cache[q]) return cache[q];
    if (options.matchSubset) {
      for (var i = q.length - 1; i >= options.minChars; i--) {
        var qs = q.substr(0, i);
        var c = cache[qs];
        if (c) {
          var csub = [];
          for (var j = 0; j < c.length; j++) {
            var x = c[j];
            var x0 = x[0];
            if (matchSubset(x0, q)) {
              csub[csub.length] = x;
            }
          }
          return csub;
        }
      }
    }
    return null;
  };

  function matchSubset(s, sub) {
    if (!options.matchCase) s = s.toLowerCase();
    var i = s.indexOf(sub);
    if (i == -1) return false;
    return i == 0 || options.matchContains;
  };

  this.flushCache = function() {
    cache = {};
  };

  this.setExtraParams = function(p) {
    options.extraParams = p;
  };

  function addToCache(q, data) {
    if (!data || !q || !options.cacheLength) return;
    if (!cache.length || cache.length > options.cacheLength) {
      cache = {};
      cache.length = 1; // we know we're adding something
    } else if (!cache[q]) {
      cache.length++;
    }
    cache[q] = data;
  };

  function findPos(obj) {
    var curleft = obj.offsetLeft || 0;
    var curtop = obj.offsetTop || 0;
    while (obj = obj.offsetParent) {
      curleft += obj.offsetLeft
      curtop += obj.offsetTop
    }
    return {x:curleft,y:curtop};
  }
}

$.fn.autocomplete = function(url, options) {
  // Make sure options exists
  options = options || {};
  // Set url as option
  options.url = url;
  // Set default values for required options
  options.inputClass = options.inputClass || "ac_input";
  options.resultsClass = options.resultsClass || "ac_results";
  options.lineSeparator = options.lineSeparator || "\n";
  options.cellSeparator = options.cellSeparator || "|";
  options.minChars = options.minChars || 1;
  options.delay = options.delay || 400;
  options.matchCase = options.matchCase || 0;
  options.matchSubset = options.matchSubset || 1;
  options.matchContains = options.matchContains || 0;
  options.cacheLength = options.cacheLength || 1;
  options.mustMatch = options.mustMatch || 0;
  options.extraParams = options.extraParams || {};
  options.loadingClass = options.loadingClass || "ac_loading";
  options.selectFirst = options.selectFirst || false;
  options.selectOnly = options.selectOnly || false;
  options.mode = options.mode || "single";
  options.multipleSeparator = options.multipleSeparator || ",";

  this.each(function() {
    var input = this;
    new $.autocomplete(input, options);
  });

  // Don't break the chain
  return this;
}
