/**
 * TComboBox is a custom select implementation to get more
 * control over item list visual styles and implements
 * incremental search on list options.
 *
 * TCombobox is a DIV container with a hidden field
 * to store and submit the selected item value, a text
 * field to write the selected item text or search into
 * options, a div element to act as a dropdown button and
 * a last div conainer to hold options.
 * Options can be any type of element, like <p>, <img>, etc,
 * where the 'id' attribute is the value part of the option and
 * the inner html is the text part (text part can be any kind of HTML code)
 *
 * Visual styles and elements must be created on design time.
 * TComboBox does not create any element and does not
 * apply any visual style at run time (beside of mouseover and click classes)
 *
 * Example:
 *
 * <div id="ComboBox>
 *   <input type="hidden" name="cb_value" />
 *   <input type"text" name="cb_text" />
 *   <div class="button">&nbsp;</div>
 *   <div class="combo_list">
 *     <p id="0">Value 0</p>
 *     <p id="1">Value 1</p>
 *     <p id="n">Value n</p>
 *   </div>
 * </div>
 *
 * @param {String | Element} cb: the id or the combo box element
 * @param {Integer} selected: (Optional) the initial selexted item
 *                  (You can also use select method after creation)
 */
TComboBox = function (cb) {
  this.count = 0;                       // Options count
  this.selected = -1;                   // Index of the selected item (-1 none selected)
  this.opened = false;                  // Dropdown opened / closed
  this.value = '';                      // Current selected item value

  this._cb = TDOM.getElement(cb);       // ComboBox object container
  this._input = null;                   // Text input element
  this._value = null;                   // Value input (Usually a hidden field)
  this._button = null;                  // DropDown button
  this._dropdown = null;                // DropDown container
  this._id = TComboBox.cbCounter++;     // Unique CB id
  if (!this._cb) return;

  this._text = '';                      // Current text in input
  this._items = [];                     // Array of option elements/values
  this._skipBackspace = false;          // Skip last char in query string if this var is true
  this._overDropDown = false;           // Prevent closing dropdown if user click in an option
  this._overButton = false;             // Same as above but in the dropdown button
  this._searching = false;              // Avoind firing change event when incremental searching or exploring list items
  this._itemtag = 'P';
  this._enabled = true;
  this._height = 0;
  this._changed = false;
  this._stopOpera = false;

  // Initialize ComboBox
  this._value = TDOM.firstChild(this._cb);
  this._input = TDOM.nextElement(this._value);
  this._button = TDOM.nextElement(this._input);
  this._dropdown = TDOM.nextElement(this._button);
  if (!this._input || !this._button || !this._dropdown || !this._value) { return }
  if (TDOM.hasClass(this._cb,'disabled')) {
    this._enabled = false;
    this._input.readOnly = true;
  }
  //this._text = this._input.value;
  this._input.setAttribute("autocomplete","off");
  // Setup combobox listeners
  TEvents.listen(this._input, 'click', this._onInputExit, this);
  TEvents.listen(this._input, 'blur', this._onInputExit, this);
  TEvents.listen(this._input, 'keyup', this._onInputKeyUp, this);
  TEvents.listen(this._input, 'keydown', this._onInputKeyDown, this);
  TEvents.listen(this._button, 'click', this._onButtonClick, this);
  TEvents.listen(this._button, 'mouseover', this._onButtonOver, this);
  TEvents.listen(this._button, 'mouseout', this._onButtonOut, this);
  TEvents.listen(this._dropdown, 'mouseover', this._onDropDownEnter, this);
  TEvents.listen(this._dropdown, 'mouseout', this._onDropDownExit, this);
  // Parse predefined options
  this._height = 0;
  var op = TDOM.firstChild(this._dropdown);
  while (op) {
    var index = this.count++;
    var id = 'cb'+this._id+'__option__'+index;
    this._items.push({value: op.id ? op.id : ""});
    TEvents.listen(op, 'click', this._onOptionClick, this);
    TEvents.listen(op, 'mouseover', this._onOptionOver, this);
    TEvents.listen(op, 'mouseout', this._onOptionOut, this);
    op.id = id;
    this._itemtag = op.nodeName;
    if (TDOM.hasClass(op,'selected')) { this.select(index); }
    op = TDOM.nextElement(op);
  }
  if (TDOM.hasClass(this._cb,'readonly')) { this.readonly(true); }
  if (this.count == 0) { TDOM.addClass(this._button, 'empty'); }
  TEvents.listen(window,'load',this.resize,this);
};
TComboBox.inherits(TEventDispatcher);
TComboBox.cbCounter = 0;

/**
 * Shrink input 19 pixels to prevent text to be written above the button
 */
TComboBox.prototype.resize = function() {
  for (var i=0; i < this._items.length; i++) {
    var rec = TDOM.getSize('cb'+this._id+'__option__'+i);
    this._height += rec.height;
    if (this._height > 250) { break; }
  }
  var rec1 = TDOM.getBounds(this._cb);
  this._input.style.width = (rec1.width - 23) + 'px';
  this._dropdown.style.height = this._height > 250 ? '250px' : this._height+'px';
  this._dropdown.style.overflowY = this._height > 250 ? 'scroll' : 'hidden';
  this._dropdown.style.width = rec1.width-2 + 'px';
  TEvents.unlisten(window,'load',this.resize);
}

/**
 * Set an item in the list te be selected
 *
 * @param {Integer} id: option id
 */
TComboBox.prototype.select = function(id) {
  if (id == this.selected) { return; }
  var op = this._items[id];
  var el = TDOM.getElement('cb'+this._id+'__option__'+id);
  if (this.selected > -1 && this._items[this.selected]) {
    TDOM.removeClass('cb'+this._id+'__option__'+this.selected, 'selected');
  }
  if (!op || !el) {
    this._text = '';
    this.value = '';
    this._value.value = "";
    this._input.value = "";
    this.selected = -1;
  } else {
    var str = el.innerHTML;
    this.selected = id;
    this.value = op.value;
    this._value.value = op.value;
    this._input.value = el.firstChild.data;
    this._text = this._input.value;
    this.makeVisible(id);
    TDOM.addClass(el, 'selected');
  }
  this._changed = true;
  if (!this._searching) {
    this._changed = false;
    this.dispatch("change");
  }
}

/**
 * Move scrollbar up or down in order to make
 * selected item visible
 *
 * @param {Integer} id: option id
 */
TComboBox.prototype.makeVisible = function(id) {
  if (!this._items[id] || !this.opened) { return }
  var rec1 = TDOM.getBounds('cb'+this._id+'__option__'+id);
  var rec2 = TDOM.getBounds(this._dropdown);
  var top  = rec1.top - rec2.top;
  // Element is after bottom item -> scroll down
  if (top + rec1.height > rec2.height) {
    this._dropdown.scrollTop = rec1.top - rec2.top + this._dropdown.scrollTop - rec2.height + rec1.height;
  }
  // Element is before top item -> scroll up
  if (top < 0) {
    this._dropdown.scrollTop = rec1.top - rec2.top + this._dropdown.scrollTop;
  }
}

TComboBox.prototype.readonly = function(value) {
  this._input.readOnly = value;
}

TComboBox.prototype.enable = function() {
  if (this._enabled) { return; }
  TDOM.removeClass(this._cb, 'disabled');
  this._input.readOnly = false;
  this._enabled = true;
}

TComboBox.prototype.disable = function() {
  if (!this._enabled) { return; }
  TDOM.addClass(this._cb, 'disabled');
  this._input.readOnly = true;
  this._enabled = false;
}

TComboBox.prototype.readOnly = function(value) {
  this._input.readOnly = value;
}

/**
 * Open dropdown list and scroll to the selected item
 */
TComboBox.prototype.open = function() {
  if (!this._enabled) { return }
  this.opened = true;
  if (this.showmenutop) {
    this.showmenutop = false;
    this._dropdown.style.top = (this._height) * -1 + 'px';
  }
  TDOM.addClass(this._button, 'open');
  this._cb.style.zIndex = 99999;
  this._dropdown.style.display = 'block';
  this.makeVisible(this.selected);
  this.dispatch("open");
}

/**
 * Close dropdown list
 */
TComboBox.prototype.close = function() {
  this._dropdown.style.display = 'none';
  TDOM.removeClass(this._button, 'open');
  this._cb.style.zIndex = 0;
  this._overDropDown = false;
  this.opened = false;
  this.dispatch("close");
}

/**
 * Clear combobox options
 */
TComboBox.prototype.clear = function() {
  this.select(-1);
  this._dropdown.innerHTML = '';
  this._items = [];
  this._value.value = '';
  this._input.value = '';
  TDOM.addClass(this._button, 'empty');
  this.count = 0;
  this._height = 0;
  this._dropdown.style.height = 0;
  this._dropdown.style.overflowY = 'hidden';
}

/**
 * Add a new option at the bottom of the combobox
 */
TComboBox.prototype.add = function(text, value) {
  var id = this.count++;
  var el = document.createElement('p');
  el.id = id; el.innerHTML = text;
  this._dropdown.appendChild(el);
  this._items.push({element:el, value:value});
  TEvents.listen(el, 'click', this._onOptionClick, this);
  TEvents.listen(el, 'mouseover', this._onOptionOver, this);
  TEvents.listen(el, 'mouseout', this._onOptionOut, this);
  TDOM.removeClass(this._button, 'empty')
  if (this._height < 250) {
    var rec = TDOM.getSize(el);
    this._height += rec.height;
    this._dropdown.style.height = this._height > 250 ? '250px' : this._height+'px';
    this._dropdown.style.overflowY = this._height > 250 ? 'scroll' : 'hidden';
  }
}

/**
 * Select an element by passing his value
 * If element does not exist value y set anyway
 */
TComboBox.prototype.setValue = function(value) {
  for (i=0; i < this._items.length; i++) {
    if (this._items[i].value == value) {
      this.select(i);
      return;
    }
  }
  this.value = value;
  this._input.value = value;
}

/**
 * Button click event handler
 * Open dropdown list and set focus back to text input element
 *
 * @param {TDOMEvent} e: DOM event object
 */
TComboBox.prototype._onButtonClick = function(e) {
  if (this.count == 0) { return; }
  this.opened ? this.close() : this.open();
  this._input.focus();
}

/**
 * Button mouse over event handler
 * Add highlighted button class and
 * set mouse over button flag to true to prevent
 * onInputExit event to be fired when mouse click over
 * the dropdown button and the input loose the focus
 *
 * @param {TDOMEvent} e: DOM event object
 */
TComboBox.prototype._onButtonOver = function(e) {
  TDOM.addClass(e.currentTarget, 'over');
  this._overButton = true;
}

/**
 * Button mouse out event handler
 * Remove the button highlight class and
 * set the mouse over button flag to false
 *
 * @param {TDOMEvent} e: DOM event object
 */
TComboBox.prototype._onButtonOut = function(e) {
  TDOM.removeClass(e.currentTarget, 'over');
  this._overButton = false;
}

/**
 * Option click event handler
 * Select the clicked item and set focus back to
 * text input element. Also close the dropdown list.
 *
 * @param {TDOMEvent} e: DOM event object
 */
TComboBox.prototype._onOptionClick = function(e) {
  var id = e.currentTarget.id;
  id = parseInt(id.replace('cb'+this._id+'__option__',''));
  if (!isNaN(id)) {
    this.select(id);
    this._input.focus();
    this.close();
  }
}

/**
 * Option mouse over event handler
 * Add highlighted item class when moving mouse
 * over dropdown list items
 *
 * @param {TDOMEvent} e: DOM event object
 */
TComboBox.prototype._onOptionOver = function(e) {
  TDOM.addClass(e.currentTarget, 'over');
}

/**
 * Option mouse out event handler
 * Remove highlighted item class when moving mouse
 * out of a dropdown list item
 *
 * @param {TDOMEvent} e: DOM event object
 */
TComboBox.prototype._onOptionOut = function(e) {
  TDOM.removeClass(e.currentTarget, 'over');
}

/**
 * Set the mouse over dropdown list flag to true
 * to prevent onInputExit event to be fired when mouse
 * is clicked over an item and the input loose his focus
 *
 * @param {TDOMEvent} e: DOM event object
 */
TComboBox.prototype._onDropDownEnter = function(e) {
  this._overDropDown = true;
}

/**
 * Set the above flag to false when mouse leave
 * the dorpdown list
 *
 * @param {TDOMEvent} e: DOM event object
 */
TComboBox.prototype._onDropDownExit = function(e) {
  this._overDropDown = false;
}

/**
 * Input exit event handler
 * Close the dropdown list if opened and mouse
 * is not over the dropdown list himself or the
 * dropdown button.
 *
 * @param {TDOMEvent} e: DOM event object
 */
TComboBox.prototype._onInputExit = function(e) {
  if (!this._overDropDown && !this._overButton) {
    this.close();
  }
  if (this._changed) {
    this._changed = false;
    this.dispatch("change");
  }
}

/**
 * Input key up event handler
 * Autocomplete input with the text of the first item that
 * begin with the typed string, select it and make it visible.
 * If no item is found, clear selection.
 * Start looking at the last found item
 *
 * @param {TDOMEvent} e: DOM event object
 */
TComboBox.prototype._onInputKeyUp = function(e) {
  if (!this._enabled) { return; }
  var el = e.currentTarget;
  var key = e.keyCode;
  var query = el.value.toLowerCase();

  // Nothing changed, probably a control key was pressed
  if (this._text === el.value) { return; }
  this._text = el.value;
  // Up or down key pressed
  if (key == 38 || key == 40) { return; }
  // Backspace should skip the last character in query
  if (key == 8 && !this._skipBackspace) {
    query = query.substr(0,query.length-1);
    el.value = query;
  }
  this.value = this._value.value = el.value;
  this._skipBackspace = false;
  // Clear selected item if any
  if (this._items[this.selected]) {
    TDOM.removeClass('cb'+this._id+'__option__'+this.selected, 'selected');
    this.selected = -1;
    this._changed = true;
  }
  // Delete key pressed or nothing to search
  if (key == 46 || query == '') { this.close(); return; }
  // Loop though each item
  var found = false;
  for(var i = 0; i < this._items.length; i++) {
    var text = TDOM.getElement('cb'+this._id+'__option__'+i).firstChild.data.toLowerCase();
    var pos = text.indexOf(query);
    // Match found
    if (pos === 0) {
      found = true;
      this._searching = true;
      this.select(i);
      this._searching = false;
      TSelection.set(el, query.length, el.value.length);
      break;
    }
  }
  i < this._items.length ? this.open() : this.close();
}

TComboBox.prototype._onInputKeyPress = function(e) {
  if (TUSerAgent.OPERA && this._stopOpera) {
    e.stopPropagation();
    e.preventDefault();
    this._stopOpera = true;
  }
}

/**
 * Input key down event handler
 * @param {TDOMEvent} e: DOM event object
 */
TComboBox.prototype._onInputKeyDown = function(e) {
  if (!this._enabled) { return; }
  switch (e.keyCode) {
    // Tab
    case 9:  if (this.opened) { this.close(); e.currentTarget.focus(); }
             break;
    // Backspace
    case 8:  var sel = TSelection.get(e.currentTarget);
             this._skipBackspace = sel.length == 0;
             break;
    // Enter
    case 13: if (this.opened) {
               this._changed = true;
               TSelection.set(this._input,this._input.value.length,this._input.value.length);
               this.close();
             }
             if (this._changed) {
               this._changed = false;
               this.dispatch("change");
               e.stopPropagation();
               e.preventDefault();
               this._stopOpera = true;
             }
             break;
    // Escape
    case 27: this.close()
             break;
    // Up arrow
    case 38: this._searching = true;
             this.select(this.selected - 1);
             this._searching = false;
             if (this.selected < 0) this.close();
             e.stopPropagation();
             e.preventDefault();
             this._stopOpera = true;
             return false;
    // Down Arrow
    case 40: if (this.opened && this.selected + 1 < this._items.length) {
               this._searching = true;
               this.select(this.selected + 1);
               this._searching = false;
             }
             if (this.count > 0) {
               this.open();
             }
             e.stopPropagation();
             e.preventDefault();
             this._stopOpera = true;
             return false;
  }
}
