function sysAC(input, uri, re_init) {
  var ac = this;
  this.input = input;
  this.uri = uri;
  this.delay = 300;
  this.cache = {};
  
  if (re_init) {
	$(input).unbind('keydown').unbind('keyup').unbind('blur');
  }
  
  $(input)
    .keydown(function (event) { return ac.onkeydown(this, event); })
    .keyup(function (event) { ac.onkeyup(this, event); })
    .blur(function () { ac.hidePopup(); ac.cancel(); });
};

sysAC.prototype.onkeydown = function (input, e) {
  if (!e) {
    e = window.event;
  }
  switch (e.keyCode) {
    case 40: // down arrow
      this.selectDown();
      return false;
    case 38: // up arrow
      this.selectUp();
      return false;
	case 13:
	  if (this.popup)
		return false;
    default: // all other keys
      return true;
  }
};

sysAC.prototype.onkeyup = function (input, e) {
  if (!e) {
    e = window.event;
  }
  
  switch (e.keyCode) {
    case 16: // shift
    case 17: // ctrl
    case 18: // alt
    case 20: // caps lock
    case 33: // page up
    case 34: // page down
    case 35: // end
    case 36: // home
    case 37: // left arrow
    case 38: // up arrow
    case 39: // right arrow
    case 40: // down arrow
      return true;

    case 9:  // tab
    case 13: // enter
    case 27: // esc
      this.hidePopup(e.keyCode);
      return true;

    default: // all other keys

	  var min_length = 3;
	  
	  if (pre_callback = $(input).attr('autocomplete_pre_callback')) {
		eval(pre_callback+'(input);');
	  }
	  
	  var $input = $(input);
	  if ($input.hasClass('sys-ref-name')) {
		$input.parent().find('INPUT.sys-ref-id').val('');
	  }	  
	
      if (input.value.length >= min_length) {
        this.populatePopup();
      } else if (input.value.length > 0) {
		this.hidePopup();
	  } else {
		this.hidePopup(e.keyCode);
	  }
      return true;
  }
};

sysAC.prototype.select = function (node) {
  if (callback = $(this.input).attr('autocomplete_callback')) {
	eval(callback+'(this.input, node);');
  } else {
	this.input.value = node.autocompleteValue;
	var $input = $(this.input);
	if ($input.hasClass('sys-ref-name')) {
		$input.parent().find('INPUT.sys-ref-id').val(node.autocompleteID);
	}
  }
};

sysAC.prototype.selectDown = function () {
  if (this.selected && this.selected.nextSibling) {
    this.highlight(this.selected.nextSibling);
  }
  else {
    var lis = $('li', this.popup);
    if (lis.size() > 0) {
      this.highlight(lis.get(0));
    }
  }
  
};

sysAC.prototype.selectUp = function () {
  if (this.selected && this.selected.previousSibling) {
    this.highlight(this.selected.previousSibling);
  } else {
    var lis = $('li:last', this.popup);
    if (lis.size() > 0) {
      this.highlight(lis.get(0));
    }
  }
};

sysAC.prototype.highlight = function (node) {
  if (this.selected) {
    $(this.selected).removeClass('selected');
  }
  $(node).addClass('selected');
  this.selected = node;
};

sysAC.prototype.unhighlight = function (node) {
  $(node).removeClass('selected');
  this.selected = false;
};

sysAC.prototype.hidePopup = function (keycode) {
  // Select item if the right key or mousebutton was pressed
  if (this.selected && ((keycode && keycode != 46 && keycode != 8 && keycode != 27)/* || !keycode*/)) {
    if (callback = $(this.input).attr('autocomplete_callback')) {
		eval(callback+'(this.input, this.selected);');
	} else {
		this.input.value = this.selected.autocompleteValue;
		var $input = $(this.input);
		if ($input.hasClass('sys-ref-name')) {
			$input.parent().find('INPUT.sys-ref-id').val(this.selected.autocompleteID);
		}		
	}
  }
  // Hide popup
  var popup = this.popup;
  if (popup) {
    this.popup = null;
    $(popup).fadeOut('fast', function() { $(popup).remove(); });
  }
  this.selected = false;
};

sysAC.prototype.populatePopup = function () {
  // Show popup
  if (this.popup) {
    $(this.popup).remove();
  }
  this.selected = false;
  this.popup = document.createElement('div');
  this.popup.id = 'autocomplete';
  this.popup.owner = this;
  $(this.popup).css({
    marginTop: this.input.offsetHeight +'px',
    width: (this.input.offsetWidth - 4) +'px',
    display: 'none'
  });
  $(this.input).before(this.popup);

  this.search(this.input.value);

};

sysAC.prototype.found = function (matches) {
  // If no value in the textfield, do not show the popup.
  if (!this.input.value.length) {
    return false;
  }

  // Prepare matches
  var ul = document.createElement('ul');
  var ac = this;
  
  var direction_top = ($(this.input).attr('autocomplete_direction')=='top');
  
  var $input = $(this.input);
  
  for (key in matches) {
    var li = document.createElement('li');
	
    $(li)
      .html('<div>'+ matches[key]["name"] + ($input.hasClass('sys-ref-name') ? ' ['+matches[key]["id"]+']' : '') + '</div>')
      .mousedown(function () { ac.select(this); })
      .mouseover(function () { ac.highlight(this); });
    /*.mouseout(function () { ac.unhighlight(this); }) */;

    li.autocompleteID = matches[key]["id"];
	li.autocompleteValue = matches[key]["name"];

	if (direction_top) {
		$(ul).prepend(li);
	} else {
		$(ul).append(li);
	}
  }

  // Show popup with matches, if any
  if (this.popup) {
    if (ul.childNodes.length > 0) {
	  $(this.popup).empty().append(ul);
	  
	  if (direction_top) {
		  $(this.popup).css({
				marginTop: (-14-$(this.popup).height()) +'px'
		   });
		this.selectUp();
	  } else {
		this.selectDown();
	  }
	  
	  $(this.popup).show();
    }
    else {
	  if (noresults_callback = $(this.input).attr('autocomplete_noresults')) {
		eval(noresults_callback + '($(this.popup), $(this.input));');
	  } else {
		$(this.popup).css({visibility: 'hidden'});
		this.hidePopup();
	  }
    }
  }
};

sysAC.prototype.setStatus = function (status) {
  switch (status) {
    case 'begin':
      $(this.input).addClass('throbbing');
      break;
    case 'cancel':
    case 'error':
    case 'found':
      $(this.input).removeClass('throbbing');
      break;
  }
};

sysAC.prototype.search = function (searchString) {
  var ac = this;
  this.searchString = searchString;

  // See if this key has been searched for before
  if (this.cache[searchString]) {
    return this.found(this.cache[searchString]);
  }

  // Initiate delayed search
  if (this.timer) {
    clearTimeout(this.timer);
  }
  this.timer = setTimeout(function() {
   ac.setStatus('begin');
   
    // Ajax GET request for autocompletion
    $.ajax({
      type: "GET",
      url: ac.uri + '?q=' + encodeURIComponent(searchString),
      dataType: 'json',
      success: function (matches) {
        if (typeof matches['status'] == 'undefined' || matches['status'] != 0) {
          ac.cache[searchString] = matches;
          // Verify if these are still the matches the user wants to see
          if (ac.searchString == searchString) {
            ac.found(matches);
          }
          ac.setStatus('found');
        }
      },
      error: function (xmlhttp) {
        alert('Error!');
      }
    });
  }, this.delay);
};

sysAC.prototype.cancel = function() {
  this.setStatus('cancel');
  if (this.timer) clearTimeout(this.timer);
  this.searchString = '';
};
