// Google library part
var library = { };

library.createDelegate = function(object, func) {
  var obj = object;
  return function(evt) {
    func.call(obj, evt, this);
  }
}

library.bind = function(element, eventName, listener) {
  var fullEventName = "on" + eventName;
  if (element.addEventListener) element.addEventListener(eventName, listener, false);
  else if (element.attachEvent) element.attachEvent(fullEventName, listener);
  else {
    var event = element[fullEventName];
    element[fullEventName] = function() {
      var h = event.apply(this, arguments),
      i = listener.apply(this, arguments);
      return h == undefined ? i : (i == undefined ? h : i && h)
    }
  }
};

library.unbind = function(element, eventName, listener) {
  var fullEventName = "on" + eventName;
  if (element.removeEventListener) element.removeEventListener(eventName, listener, false);
  else if (element.detachEvent) element.detachEvent(fullEventName, listener);
  else delete element[fullEventName];
}

library.merge = function(obj1, obj2) {
  if (!obj1 || !obj2) return;
  for (var pName in obj2)
    obj1[pName] = obj2[pName];
}


var AutocompleteWrapper = null;
(function() {

var autocompleteCounter = 0;

function getParameterRecursive(element, parameterName) {
  var value = 0;
  while (element) {
    value += element[parameterName];
    element = element.offsetParent;
  }
  return value;
}

function addClass(element, className) {
  element.className = element.className + " " + className;
}

function removeClass(element, className) {
  if (!element.className) return;
  var classes = element.className.split(" ");
  var newClasses = [];
  for (var i = 0; i < classes.length; i++)
     if (classes[i] && classes[i] != className)
       newClasses.push(classes[i]);
  element.className = newClasses.join(" ");
}

function unbind(element, obj, vals) {
  for(var i = 0; i < vals.length; i++) {
    var v = vals[i];
    library.unbind(element, v.toLowerCase(), obj["__on" + v + "Delegate"]);
  }
}

var head = document.getElementsByTagName("head")[0];

// Autocomplete wrapper
AutocompleteWrapper = function(form, element, additionalParameters) {
  this._form = form;
  this._element = element;
  this._additionalParameters = {
    windowClass: "autocomplete-window",
    closeText: "Close",
    rowClass: "row-class",
    highlightedRowClass: "highlighted",
    closeRowClass: "close-row",
    closeButtonClass: "close-button",
    setRowContent: function() {}
  };
  this._requestQueueSize = 0;
  library.merge(this._additionalParameters, additionalParameters);

  if (this._additionalParameters.doRequest) {
    this._doRequest = this._additionalParameters.doRequest;
  }
  this._autocompleteWindow = document.createElement("table");
  this._autocompleteWindow.cellPadding = this._autocompleteWindow.cellSpacing = 0;
  this._autocompleteWindow.className = this._additionalParameters.windowClass;
  this._form.appendChild(this._autocompleteWindow);
  this._Id = autocompleteCounter++;
  this._textEnteredManually = this._lastResultString = this._lastElementValue = this._element.value;
  this._arrowKeyCounter = 0;
  this._highlightedRowIndex = -1;
  this.__onKeyUpDelegate = library.createDelegate(this, this._onKeyUp);
  this.__onKeyDownDelegate = library.createDelegate(this, this._onKeyDown);
  this.__onBlurDelegate = library.createDelegate(this, this._closeWindow);
  this.__onKeyPressDelegate = library.createDelegate(this, function(evt) {
      if (this._element && this._isWindowVisible(evt) &&
             evt.keyCode == 13 && this._highlightedRow &&
             this._highlightedRow.data &&
             this._highlightedRow.data.length > 0 &&
             this._highlightedRow.data[0] == this._element.value) {
        this._lastElementValue = this._element.value = this._highlightedRow.data[0];
        if (this._additionalParameters.onClick) {
          this._additionalParameters.onClick(this._highlightedRow.data[0]);
        }
        this._closeWindow();
        window.event.cancelBubble = true;
        window.event.returnValue = false;
        evt && evt.stopPropagation && evt.stopPropagation();
        return false;
        this._closeWindow();
      }
  });
  var $this = this;
  this.__onBeforeDeactivateDelegate = function() {
    if ($this._windowWasClicked) {
      window.event.cancelBubble = true;
      window.event.returnValue = false;
    }
    delete $this._windowWasClicked;
  };

  library.bind(this._element, "keyup", this.__onKeyUpDelegate);
  library.bind(this._element, "keydown", this.__onKeyDownDelegate);
  library.bind(this._element, "blur", this.__onBlurDelegate);
  library.bind(this._element, "keypress", this.__onKeyPressDelegate);
  library.bind(this._element, "beforedeactivate", this.__onBeforeDeactivateDelegate);

  AutocompleteWrapper.cache[this._Id] = this;

  this._closeWindow();
  this._recalcSize();

  if (this._additionalParameters.formatRequestSourceString)
    this._doRequest();
}

AutocompleteWrapper.cache = [];

AutocompleteWrapper.__serviceDeleteAllWrappers = function() {
  for(var i = 0; i < AutocompleteWrapper.cache.length; i++) {
    var v = AutocompleteWrapper.cache[i];
    v && v.dispose();
  }
  AutocompleteWrapper.cache.length = 0;
  autocompleteCounter = 0;
}

AutocompleteWrapper.prototype = {
  dispose: function() {
    if (this._requestTimer)
      window.clearTimeout(this._requestTimer);
    this._closeWindow();
    this._form.removeChild(this._autocompleteWindow);
    unbind(this._element, this, ["KeyUp", "KeyDown", "Blur", "KeyPress", "BeforeDeactivate"]);
    delete AutocompleteWrapper.cache[this._Id];
    for(var p in this)
      delete this[p];
  },
  setResult: function(res) {
    if (!res) return;
    if (this._requestQueueSize > 0) this._requestQueueSize--;
    res[0] = decodeURIComponent(res[0]);

    if (this._textEnteredManually != res[0]) return;
    if (res[0] == this._lastResultString) return;

    if (this._closeWindowTimer) {
      window.clearTimeout(this._closeWindowTimer);
      delete this._closeWindowTimer;
    }

    this._lastResultString = res[0];
    var resultsArray = res[1];
    this._resetWindow();
    var maxCells = 0;
    var $_$ = this._additionalParameters;
    var $this = this;

    var mouseDownEvent = function(evt) {
        $this._windowWasClicked = true;
        evt && evt.stopPropagation && evt.stopPropagation();
        return false;
      };

    for (var i = 0; i < resultsArray.length; i++) {
      var curr_result = resultsArray[i];
      for (var j = 0; j < curr_result.length; j++)
        curr_result[j] = decodeURIComponent(curr_result[j]);

      var row = this._autocompleteWindow.insertRow(-1);
      row.className = $_$.rowClass;
      row.data = curr_result;
      row.onmousedown = mouseDownEvent;
      row.onmouseover = function() {
        if ($this._highlightedRow)
          removeClass($this._highlightedRow, $_$.highlightedRowClass);
        addClass(this, $_$.highlightedRowClass);
        $this._highlightedRow = this;
        for (var i = 0, row; row = $this._autocompleteWindow.rows[i]; i++)
          if (row == $this._highlightedRow) $this._highlightedRowIndex = i;
      };
      row.onclick = function(evt) {
        if ($this._element && $this._isWindowVisible()) {
          $this._lastElementValue = $this._element.value = this.data[0];
          $this._closeWindow();
          if ($this._additionalParameters.onClick) {
            $this._additionalParameters.onClick(this.data[0]);
          }
        }
      };
      $_$.setRowContent(row);
      if (row.cells.length > maxCells) maxCells = row.cells.length;
    }
    var isNotEmpty = this._autocompleteWindow.rows.length;
    if (isNotEmpty) {
      var closeButtonRow = this._autocompleteWindow.insertRow(-1);
      closeButtonRow.onmousedown = mouseDownEvent;
      closeButtonRow.className = $_$.closeRowClass;
      var closeTd = document.createElement("td");
      closeTd.colSpan = maxCells;
      var closeSpan = document.createElement("span");
      closeSpan.className = $_$.closeButtonClass;
      closeSpan.appendChild(document.createTextNode($_$.closeText));
      $this = this;
      closeSpan.onclick = function() {
        $this._closeWindow();
        $this._lastResultString = "";
        window.clearTimeout($this._requestTimer);
        delete $this._requestTimer;
      }
      closeTd.appendChild(closeSpan);
      closeButtonRow = closeButtonRow.appendChild(closeTd);
    }
    this[isNotEmpty ? "_openWindow" : "_closeWindow"]();
  },
  _resetWindow: function() {
    while(this._autocompleteWindow.rows.length) {
      this._autocompleteWindow.deleteRow(-1);
    }
  },
  _openWindow: function() {
    this._autocompleteWindow.style.visibility = "visible";
  },
  _closeWindow: function() {
    if (this._closeWindowTimer) {
      window.clearTimeout(this._closeWindowTimer);
      delete this._closeWindowTimer;
    }
    if (this._autocompleteWindow)
      this._autocompleteWindow.style.visibility = "hidden";
  },
  _delayedCloseWindow: function() {
    if(!this._closeWindowTimer)
      this._closeWindowTimer = window.setTimeout(library.createDelegate(this, this._closeWindow), 500);
  },
  _isWindowVisible: function() {
    return this._autocompleteWindow.style.visibility == "visible";
  },
  _recalcSize: function() {
    if (this._autocompleteWindow) {
      this._autocompleteWindow.style.left = getParameterRecursive(this._element, "offsetLeft") + "px";
      this._autocompleteWindow.style.top = getParameterRecursive(this._element, "offsetTop") + this._element.offsetHeight - 1 + "px";
      this._autocompleteWindow.style.width = this._element.offsetWidth + "px";
    }
  },

  _doRequest: function() {
     var txt = this._textEnteredManually;
     if (this._lastRequestedText != txt && txt && (!this._additionalParameters.minLength || txt.length >= this._additionalParameters.minLength))
       this._createRequest();
     this._lastRequestedText = txt;

     var to = 100;
     for (var i = 1; i <= this._requestQueueSize / 2 - 1; i++) to *= 2;
     to += 50;

     if (!this._doRequestDelegate) this._doRequestDelegate = library.createDelegate(this, this._doRequest);
     this._requestTimer = window.setTimeout(this._doRequestDelegate, to);
  },
  _createRequest: function() {
    var $fmt = this._additionalParameters.formatRequestSourceString;
    if ($fmt) {
      this._requestQueueSize++;
      this._requestScript && head.removeChild(this._requestScript);
      this._requestScript = document.createElement("script");
      this._requestScript.src = $fmt(this._Id, this._textEnteredManually);
      head.appendChild(this._requestScript);
    }
  },
  _onKeyDown: function(evt) {
    if (!this._element) return;
    var keyCode = evt.keyCode;
    if (keyCode == 27) {
      this._closeWindow();
      this._lastElementValue = this._element.value = this._textEnteredManually;
      evt.cancelBubble = true;
      return evt.returnValue = false;
    }
    if (keyCode == 38 || keyCode == "40") {
      (++this._arrowKeyCounter) % 3 == 1 && this._processKey(keyCode);
    }
  },
  _onKeyUp: function(evt) {
    if (!this._element) return;
    if (!this._arrowKeyCounter)
      this._processKey(evt.keyCode);
    this._arrowKeyCounter = 0;
  },
  _processKey: function(keyCode) {
    if (this._lastElementValue != this._element.value)
      this._textEnteredManually = this._element.value;
    keyCode == 40 && this._selectItem(this._highlightedRowIndex + 1); // Key down
    keyCode == 38 && this._selectItem(this._highlightedRowIndex - 1); // Key up
    this._recalcSize();
    if (this._lastResultString != this._textEnteredManually && !this._closeWindowTimer) this._delayedCloseWindow();
    this._lastElementValue = this._element.value;
    !this._textEnteredManually && !this._requestTimer && this._doRequest();
  },
  _selectItem: function(pos) {
    if (!this._lastResultString && this._textEnteredManually) {
      this._lastResultString = "";
      this._doRequest();
      return;
    }

    if (this._textEnteredManually != this._lastResultString || !this._requestTimer)
      return;

    if (!this._isWindowVisible()) {
      this._openWindow();
      return;
    }

    if (this._highlightedRow) removeClass(this._highlightedRow, this._additionalParameters.highlightedRowClass);
    var lastRowIndex = this._autocompleteWindow.rows.length - 1;
    if (pos == lastRowIndex || pos == -1) {
      this._highlightedRowIndex = pos;
      this._lastElementValue = this._element.value = this._textEnteredManually;
      this._element.focus();
      return;
    }
    else if (pos > lastRowIndex) pos = 0;
    else if (pos < -1) pos = lastRowIndex - 1;
    this._highlightedRowIndex = pos;
    this._highlightedRow = this._autocompleteWindow.rows[pos];
    addClass(this._highlightedRow, this._additionalParameters.highlightedRowClass);
    this._lastElementValue = this._element.value = this._highlightedRow.data[0];
  }
}
})();
