/*
--------------------------------------------------------
jssuggest.js - Input Suggest

depends on prototype.js(http://www.prototypejs.org/)

--------------------------------------------------------
*/

if (!JSSuggest) {
  var JSSuggest = {};
}

/*-- KeyCodes -----------------------------------------*/
JSSuggest.Key = {
  RETURN: 13,
  ESC:    27,
  UP:     38,
  DOWN:   40
};

/*-- Utils --------------------------------------------*/
JSSuggest.copyProperties = function(dest, src) {
  for (var property in src) {
    dest[property] = src[property];
  }
  return dest;
};

/*-- JSSuggest.Local ------------------------------------*/
JSSuggest.Local = function() {
  this.initialize.apply(this, arguments);
};

JSSuggest.Local.prototype = {
  initialize: function(
    input,
    suggestArea,
    ifPanel,
    selectedByKey,
    selectedByMouse
  ) {
    this.input = this._getElement(input);
    this.suggestArea = this._getElement(suggestArea);
    this.candidateList = [];
    this.oldText = this.input.value;
    this.mousemoveFlag = false;
    this.selectedByKey = selectedByKey ? selectedByKey : this._dummyFunction;
    this.selectedByMouse = selectedByMouse ? selectedByMouse : this._dummyFunction;

    // for IE6
    this.isIE6 = navigator.userAgent.indexOf("MSIE 6") > -1;
    if (this.isIE6) {
      this.ifPanel = this._getElement(ifPanel);
    }

    if (arguments[5]) this.setOptions(arguments[5]);

    // reg event
    this._addEvent(this.input, 'focus', this._bind(this.checkLoop));
    this._addEvent(this.input, 'blur', this._bind(this.inputBlur));

    var keyevent = 'keydown';
    if (window.opera || (navigator.userAgent.indexOf('Gecko') >= 0 && navigator.userAgent.indexOf('KHTML') == -1)) {
      keyevent = 'keypress';
    }
    this._addEvent(this.input, keyevent, this._bindEvent(this.keyEvent));

    // init
    this.clearSuggestArea();
  },

  // options
  interval: 500,
  dispMax: 10,
  listTagName: 'div',
  prefix: false,
  highlight: false,
  dispAllKey: false,
  classMouseOver: 'over',
  classSelect: 'select',

  setOptions: function(options) {
    JSSuggest.copyProperties(this, options);
  },

  inputBlur: function() {
    this.changeUnactive();
    this.oldText = this.input.value;

    if (this.timerId) clearTimeout(this.timerId);
    this.timerId = null;

//    setTimeout(this._bind(this.clearSuggestArea), 500);
    setTimeout(this._bind(this.hideSuggestArea), 500);
  },

  checkLoop: function() {
    var text = this.input.value;
    if (text != this.oldText) {
      this.oldText = text;
      this.clearSuggestArea();

      if (this.input.value.match(/[@\s]$/) == null && this.input.value != '') {
        var params = {};
        params['inputquery'] = encodeURIComponent(this.input.value);

        var URL = '/suggest/SuggestCandidateServlet';
        new Ajax.Request(URL, {
          method: 'post',
          parameters: params,
          onSuccess: this.success.bind(this),
          onFailure: this.failure.bind(this)
        });
      }
    }

    if (this.timerId) clearTimeout(this.timerId);
    this.timerId = setTimeout(this._bind(this.checkLoop), this.interval);
  },

  success: function(req) {
    if (this.input.value != req.responseXML.getElementsByTagName('inputquery')[0].childNodes[0].nodeValue) {
      return;
    }

    var scObj = req.responseXML.getElementsByTagName('suggestcandidate');
    var resultList = [];
    this.candidateList = [];
    this.suggestIndexList = [];
    for (var i = 0; i < scObj.length; i++) {
      var tmp = scObj[i].childNodes[0].nodeValue;
      if (tmp != null && tmp != '') {
        resultList.push(tmp.escapeHTML().truncateTailInWidth(this.input.clientWidth, 'ruler'));
        this.candidateList.push(tmp);
        this.suggestIndexList.push(i);

        if (this.dispMax != 0 && this.candidateList.length >= this.dispMax) break;
      }
    }

    if (resultList != 0) this.createSuggestArea(resultList);
  },

  failure: function(req) {
//    alert("error.");
  },

  clearSuggestArea: function() {
    this.suggestArea.innerHTML = '';
    this.suggestArea.style.display = 'none';
    this.suggestList = null;
    this.suggestIndexList = null;
    this.activePosition = null;

    if (this.isIE6 && this.ifPanel) {
      this.ifPanel.style.display = 'none';
    }
  },

  hideSuggestArea: function() {
    this.suggestArea.style.display = 'none';

    if (this.isIE6 && this.ifPanel) {
      this.ifPanel.style.display = 'none';
    }
  },

  createSuggestArea: function(resultList) {
    this.suggestArea.style.top = cumulativeOffset(this.input)[1] + this.input.offsetHeight - 1 + 'px';
    this.suggestArea.style.left = cumulativeOffset(this.input)[0] + 'px';
    this.suggestArea.style.width = this.input.offsetWidth + 'px';

    this.suggestList = [];
    this.inputValueBackup = this.input.value;

    for (var i = 0, length = resultList.length; i < length; i++) {
      var element = document.createElement(this.listTagName);
      element.innerHTML = resultList[i] + " ";
      this.suggestArea.appendChild(element);

      this._addEvent(element, 'click', this._bindEvent(this.listClick, i));
//      this._addEvent(element, 'mouseover', this._bindEvent(this.listMouseOver, i));
      this._addEvent(element, 'mousemove', this._bindEvent(this.listMouseMove, i));
      this._addEvent(element, 'mouseout', this._bindEvent(this.listMouseOut, i));

      this.suggestList.push(element);
    }

    this.suggestArea.style.display = '';

    if (this.isIE6 && this.ifPanel) {
      this.ifPanel.style.top = this.suggestArea.style.top;
      this.ifPanel.style.left = this.suggestArea.style.left;
      this.ifPanel.style.width = this.suggestArea.style.width;
      this.ifPanel.style.height = this.suggestArea.offsetHeight + "px";
      this.ifPanel.style.display = 'block';
    }
  },

  showSuggestArea: function() {
    this.suggestArea.style.top = cumulativeOffset(this.input)[1] + this.input.offsetHeight - 1 + 'px';
    this.suggestArea.style.left = cumulativeOffset(this.input)[0] +'px';
    this.suggestArea.style.width = this.input.offsetWidth + 'px';

    this.suggestArea.style.display = '';

    if (this.isIE6 && this.ifPanel) {
      this.ifPanel.style.top = this.suggestArea.style.top;
      this.ifPanel.style.left = this.suggestArea.style.left;
      this.ifPanel.style.width = this.suggestArea.style.width;
      this.ifPanel.style.height = this.suggestArea.offsetHeight + "px";
      this.ifPanel.style.display = 'block';
    }
  },

  // key event
  keyEvent: function(event) {
    if (!this.timerId) {
      this.timerId = setTimeout(this._bind(this.checkLoop), this.interval);
    }

    if (event.keyCode == JSSuggest.Key.UP ||
               event.keyCode == JSSuggest.Key.DOWN) {
      // key move
      if (this.suggestList && this.suggestList.length != 0) {
        this._stopEvent(event);
        if (this.suggestArea.style.display == 'none') {
          this.showSuggestArea();
          if (this.activePosition != null) {
            this.setStyleActive(this.suggestList[this.activePosition]);
          }
        } else {
          this.keyEventMove(event.keyCode);
        }
      }
    } else if (event.keyCode == JSSuggest.Key.RETURN) {
      // fix
      if (this.suggestList && this.suggestList.length != 0) {
        this._stopEvent(event);
        this.keyEventReturn();
      }
    } else if (event.keyCode == JSSuggest.Key.ESC) {
      // cancel
      if (this.suggestList && this.suggestList.length != 0) {
        this._stopEvent(event);
        this.keyEventEsc();
      }
    } else {
      this.keyEventOther(event);
    }
  },

  keyEventMove: function(keyCode) {
    this.changeUnactive();

    if (keyCode == JSSuggest.Key.UP) {
      // up
      if (this.activePosition == null) {
        this.activePosition = this.suggestList.length - 1;
      }else{
        this.activePosition--;
        if (this.activePosition < 0) {
          this.activePosition = null;
          this.input.value = this.inputValueBackup;
          this.oldText = this.input.value;
          return;
        }
      }
    }else{
      // down
      if (this.activePosition == null) {
        this.activePosition = 0;
      }else{
        this.activePosition++;
      }

      if (this.activePosition >= this.suggestList.length) {
        this.activePosition = null;
        this.input.value = this.inputValueBackup;
        this.oldText = this.input.value;
        return;
      }
    }

    this.changeActive(this.activePosition);
  },

  keyEventReturn: function() {
    this.clearSuggestArea();
    this.moveEnd();

    this.selectedByKey();
  },

  keyEventEsc: function() {
//    this.clearSuggestArea();
    this.hideSuggestArea();
    this.input.value = this.inputValueBackup;
    this.oldText = this.input.value;

    if (window.opera) setTimeout(this._bind(this.moveEnd), 5);
  },

  keyEventOther: function(event) {},

  changeActive: function(index) {
    this.setStyleActive(this.suggestList[index]);

    this.input.value = this.candidateList[this.suggestIndexList[index]];

    this.oldText = this.input.value;
    this.input.focus();
  },

  changeUnactive: function() {
    if (this.suggestList != null
        && this.suggestList.length > 0
        && this.activePosition != null) {
      this.setStyleUnactive(this.suggestList[this.activePosition]);
    }
  },

  listClick: function(event, index) {
    this.changeUnactive();
    this.activePosition = index;
    this.changeActive(index);

    this.moveEnd();
    this.selectedByMouse();
  },

//  listMouseOver: function(event, index) {
//    this.changeUnactive();
//    this.activePosition = index;
//    this.setStyleMouseOver(this._getEventElement(event));
//  },
//
  listMouseMove: function(event, index) {
    if (this.activePosition == index) {
      return;
    }

    if (this.mousemoveFlag == false) {
      this.mousemoveFlag = true;
      return;
    }

    this.changeUnactive();
    this.activePosition = index;
    this.setStyleMouseOver(this._getEventElement(event));
    this.mousemoveFlag = false;
  },

  listMouseOut: function(event, index) {
    if (!this.suggestList) return;

    var element = this._getEventElement(event);

    if (index == this.activePosition) {
      this.setStyleActive(element);
    }else{
      this.setStyleUnactive(element);
    }
  },

  setStyleActive: function(element) {
    element.className = this.classSelect;
  },

  setStyleUnactive: function(element) {
    element.className = '';
  },

  setStyleMouseOver: function(element) {
//    element.className = this.classMouseOver;
    element.className = this.classSelect;
  },

  moveEnd: function() {
    if (this.input.createTextRange) {
      this.input.focus(); // Opera
      var range = this.input.createTextRange();
      range.move('character', this.input.value.length);
      range.select();
    } else if (this.input.setSelectionRange) {
      this.input.setSelectionRange(this.input.value.length, this.input.value.length);
    }
  },

  // Utils
  _getElement: function(element) {
    return (typeof element == 'string') ? document.getElementById(element) : element;
  },

  _addEvent: (window.addEventListener ?
    function(element, type, func) {
      element.addEventListener(type, func, false);
    } :
    function(element, type, func) {
      element.attachEvent('on' + type, func);
    }),

  _stopEvent: function(event) {
    if (event.preventDefault) {
      event.preventDefault();
      event.stopPropagation();
    } else {
      event.returnValue = false;
      event.cancelBubble = true;
    }
  },

  _getEventElement: function(event) {
    return event.target || event.srcElement;
  },

  _bind: function(func) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    return function(){ func.apply(self, args); };
  },

  _bindEvent: function(func) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    return function(event){ event = event || window.event; func.apply(self, [event].concat(args)); };
  },

  _escapeHTML: function(value) {
    return value.replace(/\&/g, '&amp;').replace( /</g, '&lt;').replace(/>/g, '&gt;')
             .replace(/\"/g, '&quot;').replace(/\'/g, '&#39;');
  },

  _dummyFunction: function() {}

};

/*-- Utils --------------------------------------------*/
String.prototype.getExtent = function(ruler) {
  var e = document.getElementById(ruler);
  var c;
  while (c = e.lastChild) e.removeChild(c);
  var text = e.appendChild(document.createTextNode(this));
  var width = e.offsetWidth;
  e.removeChild(text);
  return width;
}

String.prototype.truncateTailInWidth = function(maxWidth, ruler) {
  if (this.length == 0) return '';
  if (this.getExtent(ruler) <= maxWidth) return this;
  for (var i = this.length - 1; i >= 1; --i) {
    var s = this.slice(0, i) + '...';
    if (s.getExtent(ruler) <= maxWidth) return s;
  }
  return '';
}

function cumulativeOffset(element) {
  var valueT = 0, valueL = 0;
  do {
    valueT += element.offsetTop  || 0;
    valueL += element.offsetLeft || 0;
    element = element.offsetParent;
  } while (element);
  return [valueL, valueT];
}

