/*
--------------------------------------------------------
suggest.js - Input Suggest
Version 2.1 (Update 2008/04/02)

Copyright (c) 2006-2008 onozaty (http://www.enjoyxstudy.com)

Released under an MIT-style license.

For details, see the web site:
 http://www.enjoyxstudy.com/javascript/suggest/

--------------------------------------------------------
*/

if (!Suggest) {
  var Suggest = {};
}
/*-- KeyCodes -----------------------------------------*/
Suggest.Key = {
  TAB:	   9,
  RETURN: 13,
  ESC:	  27,
  UP:	  38,
  DOWN:   40
};

/*-- Utils --------------------------------------------*/
Suggest.copyProperties = function(dest, src) {
  for (var property in src) {
	dest[property] = src[property];
  }
  return dest;
};

/*-- Suggest.Local ------------------------------------*/
Suggest.Local = function() {
  this.initialize.apply(this, arguments);
};
Suggest.Local.prototype = {
  initialize: function(input, suggestArea, candidateList) {

	this.input = this._getElement(input);
	this.suggestArea = this._getElement(suggestArea);
	this.candidateList = candidateList;
	this.oldText = this.getInputText();

	if (arguments[3]) this.setOptions(arguments[3]);

	// 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: 20,
  listTagName: 'div',
  prefix: false,
  ignoreCase: true,
  highlight: false,
  dispAllKey: false,
  classMouseOver: 'over',
  classSelect: 'select',
  candidateValueList: null, // add
  eventHandler: function() {}, // add
  hookBeforeSearch: function(){},

  setOptions: function(options) {
	Suggest.copyProperties(this, options);
  },

  inputBlur: function() {

	this.changeUnactive();
	this.oldText = this.getInputText();

	if (this.timerId) clearTimeout(this.timerId);
	this.timerId = null;

	setTimeout(this._bind(this.clearSuggestArea), 500);
  },

  checkLoop: function() {
	var text = this.getInputText();
	if (text != this.oldText) {
	  this.oldText = text;
	  this.search();
	}
	if (this.timerId) clearTimeout(this.timerId);
	this.timerId = setTimeout(this._bind(this.checkLoop), this.interval);
  },

  search: function() {

	// init
	this.clearSuggestArea();

	var text = this.getInputText();

	if (text == '' || text == null) return;

	this.hookBeforeSearch(text);
	var resultList = this._search(text);
	if (resultList != 0) this.createSuggestArea(resultList);
  },

  _search: function(text) {

	var resultList = [];
	var temp;
	this.suggestIndexList = [];

	for (var i = 0, length = this.candidateList.length; i < length; i++) {
	  if ((temp = this.isMatch(this.candidateList[i], text)) != null) {
		if (!this.candidateValueList) {
		  resultList.push(temp);
		  this.suggestIndexList.push(i);
		} else {
		  resultList.push(this.candidateValueList[i]); // add
		  this.suggestIndexList.push(i); // add
		}

		if (this.dispMax != 0 && resultList.length >= this.dispMax) break;
	  }
	}
	return resultList;
  },

  isMatch: function(value, pattern) {

	if (value == null) return null;

	var pos = (this.ignoreCase) ?
	  value.toLowerCase().indexOf(pattern.toLowerCase())
	  : value.indexOf(pattern);

	if ((pos == -1) || (this.prefix && pos != 0)) return null;

	if (this.highlight) {
	  return (this._escapeHTML(value.substr(0, pos)) + '<strong>'
			 + this._escapeHTML(value.substr(pos, pattern.length))
			   + '</strong>' + this._escapeHTML(value.substr(pos + pattern.length)));
	} else {
	  return this._escapeHTML(value);
	}
  },

  clearSuggestArea: function() {
	var tmp = (this.suggestArea.innerHTML != ''); // add

	this.suggestArea.innerHTML = '';
	this.suggestArea.style.display = 'none';
	this.suggestList = null;
	this.suggestIndexList = null;
	this.activePosition = null;

	if (tmp) this.eventHandler('hide', [this.input.id]); // add
  },

  createSuggestArea: function(resultList) {

	this.suggestList = [];
	this.inputValueBackup = this.input.value;

	var tmp = (this.suggestList.length == 0); // add

	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, 'mouseout', this._bindEvent(this.listMouseOut, i));

	  this.suggestList.push(element);
	}

	this.suggestArea.style.display = '';

	if (tmp) this.eventHandler('show', [this.input.id]); // add
  },

  getInputText: function() {
	return this.input.value;
  },

  setInputText: function(text) {
	this.input.value = text;
  },

  // key event
  keyEvent: function(event) {

	if (!this.timerId) {
	  this.timerId = setTimeout(this._bind(this.checkLoop), this.interval);
	}

	if (this.dispAllKey && event.ctrlKey
		&& this.getInputText() == ''
		&& !this.suggestList
		&& event.keyCode == Suggest.Key.DOWN) {
	  // dispAll
	  this._stopEvent(event);
	  this.keyEventDispAll();
	} else if (event.keyCode == Suggest.Key.UP ||
			   event.keyCode == Suggest.Key.DOWN) {
	  // key move
	  if (this.suggestList && this.suggestList.length != 0) {
		this._stopEvent(event);
		this.keyEventMove(event.keyCode);
	  }
	} else if (event.keyCode == Suggest.Key.RETURN) {
	  // fix
	  if (this.suggestList && this.suggestList.length != 0) {
		this._stopEvent(event);
		this.keyEventReturn();
	  }
	} else if (event.keyCode == Suggest.Key.ESC) {
	  // cancel
	  if (this.suggestList && this.suggestList.length != 0) {
		this._stopEvent(event);
		this.keyEventEsc();
	  }
	} else {
	  this.keyEventOther(event);
	}
  },

  keyEventDispAll: function() {

	// init
	this.clearSuggestArea();

	this.oldText = this.getInputText();

	this.suggestIndexList = [];
	for (var i = 0, length = this.candidateList.length; i < length; i++) {
	  this.suggestIndexList.push(i);
	}

	this.createSuggestArea(this.candidateList);
  },

  keyEventMove: function(keyCode) {

	this.changeUnactive();

	if (keyCode == Suggest.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;
		  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;
		return;
	  }
	}

	this.changeActive(this.activePosition);
  },

  keyEventReturn: function() {

	this.clearSuggestArea();
	this.moveEnd();
  },

  keyEventEsc: function() {

	this.clearSuggestArea();
	this.input.value = this.inputValueBackup;
	this.oldText = this.getInputText();

	if (window.opera) setTimeout(this._bind(this.moveEnd), 5);
  },

  keyEventOther: function(event) {},

  changeActive: function(index) {

	this.setStyleActive(this.suggestList[index]);

	if (!this.candidateValueList) {
	  this.setInputText(this.candidateList[this.suggestIndexList[index]]);
	} else {
	  this.setInputText(this.candidateValueList[this.suggestIndexList[index]]); // add
	}

	this.oldText = this.getInputText();
	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();
  },

  listMouseOver: function(event, index) {
	this.setStyleMouseOver(this._getEventElement(event));
  },

  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;
  },

  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;');
  }
};

/*-- Suggest.LocalMulti ---------------------------------*/
Suggest.LocalMulti = function() {
  this.initialize.apply(this, arguments);
};
Suggest.copyProperties(Suggest.LocalMulti.prototype, Suggest.Local.prototype);

Suggest.LocalMulti.prototype.delim = ' '; // delimiter

Suggest.LocalMulti.prototype.keyEventReturn = function() {

  this.clearSuggestArea();
  this.input.value += this.delim;
  this.moveEnd();
};

Suggest.LocalMulti.prototype.keyEventOther = function(event) {

  if (event.keyCode == Suggest.Key.TAB) {
	// fix
	if (this.suggestList && this.suggestList.length != 0) {
	  this._stopEvent(event);

	  if (!this.activePosition) {
		this.activePosition = 0;
		this.changeActive(this.activePosition);
	  }

	  this.clearSuggestArea();
	  this.input.value += this.delim;
	  if (window.opera) {
		setTimeout(this._bind(this.moveEnd), 5);
	  } else {
		this.moveEnd();
	  }
	}
  }
};

Suggest.LocalMulti.prototype.listClick = function(event, index) {

  this.changeUnactive();
  this.activePosition = index;
  this.changeActive(index);

  this.input.value += this.delim;
  this.moveEnd();
};

Suggest.LocalMulti.prototype.getInputText = function() {

  var pos = this.getLastTokenPos();

  if (pos == -1) {
	return this.input.value;
  } else {
	return this.input.value.substr(pos + 1);
  }
};

Suggest.LocalMulti.prototype.setInputText = function(text) {

  var pos = this.getLastTokenPos();

  if (pos == -1) {
	this.input.value = text;
  } else {
	this.input.value = this.input.value.substr(0 , pos + 1) + text;
  }
};

Suggest.LocalMulti.prototype.getLastTokenPos = function() {
  return this.input.value.lastIndexOf(this.delim);
};

