var AutoComplete = Class.create();

AutoComplete.prototype = {
  REQUIRED_PROTOTYPE: '1.6.0',

  initialize: function (id, param) {
  	// проверяем prototype
  	this.PROTOTYPE_CHECK();
	
    // проверяем id элемента
    this.fld = $(id);
    if (!this.fld) {
        throw("parameter id is required");
    }
	
    this.fld.focused = true;
    
    // инициализация
    this.sInp 	= ""; // значение для поиска 
    this.nInpC 	= 0;  // длина значения
    this.aSug 	= []; // массив элементов списка 
    this.iHigh 	= 0;  // количество элементов 

    // Параметры
    // установка специфических настроек
    this.options = param ? param : {};
    // настройки по умолчанию
    var k, def = {
        minchars:1,
        meth:"get",
        varname:"input",
        className:"autocomplete",
        timeout:200,
        delay:100,
        offsety:-5,
        shownoresults: true,
        noresults: "По запросу ничего не найдено.",
        maxheight: 250,
        onAjaxError:null,
        setWidth: false,
        minWidth: 100,
        maxWidth: 200,
        useNotifier: true,
        smallBefore: true,
        allowNew: true,
        allowEnterReload: true
    };

    // сливаем параметры
    for (k in def) {
        if (typeof(this.options[k]) != typeof(def[k]))
            this.options[k] = def[k];
    }

    // Признак использования спинера
    if (this.options.useNotifier) {
        this.fld.addClassName('ac_field');
    }

	// установка обработчиков событий
	var p = this;

	this.fld.onkeypress 	  = function(ev){ return p.onKeyPress(ev); };
	this.fld.onkeyup          = function(ev){ return p.onKeyUp(ev); };
	this.fld.onblur			  = function(ev){ p.fld.focused = false; p.resetTimeout(); return true; };
	this.fld.onfocus       	  = function(ev){ p.fld.focused = true;  return true; };
	// убираем автозаполнение браузером
	this.fld.setAttribute("AutoComplete", "off");

  }, 

  convertVersionString: function (versionString){
      var r = versionString.split('.');
      return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
  },

  PROTOTYPE_CHECK: function() {
    if((typeof Prototype=='undefined') || 
       (typeof Element == 'undefined') || 
       (typeof Element.Methods=='undefined') ||
       (this.convertVersionString(Prototype.Version) < 
        this.convertVersionString(this.REQUIRED_PROTOTYPE)))
        throw("script requires the Prototype JavaScript framework >= " +
        this.REQUIRED_PROTOTYPE);
  },

  onKeyPress: function (e) {
  	if (!e) e = window.event;
  	var key	= e.keyCode || e.wich;
  	
    switch (key) {
      case Event.KEY_RETURN:
        this.setValue();
        if($(this.acID) || this.options.allowEnterReload) Event.stop(e);
        break;
        
      case Event.KEY_TAB:
        this.setValue();
        break;
        
      case Event.KEY_ESC:
        this.clearList();
        break;
        
      case Event.KEY_BACKSPACE:
        break;
        
      default:
  	    if (typeof this.options.onchange == 'function') {
            this.options.onchange();
      	}
    }
  	
    return true;
  }, 

  onKeyUp: function (e) {
  	if (!e) e = window.event;
  	var key = e.keyCode || e.wich;
  	
  	if (this.fld.value != this.sInp) {
  	    if (typeof this.options.onchange == 'function') {
            this.options.onchange();
      	}
  	}  	
  	
  	if (key == Event.KEY_UP || key == Event.KEY_DOWN) {
  		this.changeHighlight(key);
  		Event.stop(e);
  	} else if (key == Event.KEY_RETURN) {
  	    Event.stop(e);
  	    return false;
  	} else {
  	    this.getList(this.fld.value);
  	}
  	
	return true;
  }, 

  getList: function(val) {
    // если значение не изменилось, то ничего не делаем
  	if(val == this.sInp) return false;
  	// удаляем старый список
  	if($(this.acID)) $(this.acID).remove();
  	
  	this.sInp = val;
  	
  	// проверяем длинну введенного текста с минмиальной длинной для срабатывания
  	// очищаем список и выходим из функции
  	if (val.length < this.options.minchars) {
  		this.aSug 	= [];
  		this.nInpC	= val.length; 
  		return false;
  	}
  	this.nInpC	= val.length ? val.length : 0;
  	
  	// новый запрос
	var p = this;
	clearTimeout(this.ajID); // ajax id timer
	this.ajID = setTimeout( function () {p.doAjaxRequest(p.sInp)}, this.options.delay);
	
    document.helper = this;	
  	return false;
  },

  doAjaxRequest: function (input) {
  	if (input != this.fld.value) 
  		return false;
  	
  	// создаём url запроса
  	var url = this.options.script+this.options.varname+'='+encodeURIComponent(this.sInp);
  	
  	if(!url) return false;
  	
  	var p = this;
  	var m = this.options.meth;  // метод
    if (this.options.useNotifier) {
        this.fld.removeClassName('ac_field');
	    this.fld.addClassName('ac_field_busy');
    };
  	
    var options = {
        method: m,
        onSuccess: function (req) {
            if (p.options.useNotifier) {
                p.fld.removeClassName('ac_field_busy');
                p.fld.addClassName('ac_field');
            };
            p.setList(req, input);
        },

        onFailure: (typeof p.options.onAjaxError == 'function') ? function (status) {
                        if (p.options.useNotifier) {
                            p.fld.removeClassName('ac_field_busy');
                            p.fld.addClassName('ac_field');
                        }
                        p.options.onAjaxError(status)
                    } : function (status) {
                        if (p.options.useNotifier) {
                            p.fld.removeClassName('ac_field_busy');
                            p.fld.addClassName('ac_field');
                        }
                        alert("AJAX error: "+status);
                    }
    }
  	// делаем запрос
  	new Ajax.Request(url, options);
  },

  setList: function (req, input) {
    if (input != this.fld.value)
        return false;
    
    this.aSug = [];
    
    var jsondata = eval('(' + req.responseText + ')');
    this.aSug = jsondata.results;
    
    this.acID = 'ac_' + this.fld.id;
    this.createList(this.aSug);
  },

  createList: function(arr) {
      // удаляем старый список
      if($(this.acID)) $(this.acID).remove();

      // очищаем таймаут очистки списка
      this.killTimeout();

      // если нет результатов, то выходим
      if (arr.length == 0 && !this.options.shownoresults) return false;

      // создаём контейнер div
      var div = new Element('div', {'id': this.acID, 'class': this.options.className});
      div.addClassName(this.options.className);
      
      // создаём список ul
      var ul	= new Element('ul', {'id': 'ac_ul'});
      var p 	= this; // pointer that we will need later on
      
      if (arr.length == 0 && this.options.shownoresults) {
          var li = new Element('li', {'class': 'ac_warning'}).update(this.options.noresults);
          ul.appendChild(li);
      } else {
          // создаём элпменты списка
          for (var i=0,l = arr.length; i<l; i++) {
              var val    = arr[i].value;
              var st     = val.toLowerCase().indexOf(this.sInp.toLowerCase()); 
              var output = val.substring(0,st) + '<em>' + val.substring(st,st+this.sInp.length) + '</em>' + val.substring(st+this.sInp.length);

              var span	= new Element('span'); 

              var small = new Element('small').update(arr[i].info);

              if(this.options.smallBefore) {
                  span.insert(small);
                  span.insert(output);
              } else {
                  span.insert(output);
                  span.insert(small);
              }

              var a 	= new Element('a', {'href': '#'});

              a.insert(span); // добавляем текст
              a.name = i+1;

              a.onclick = function () {
                  p.setValue();
                  return false;
              };
              a.onmouseover = function () {
                  p.setHighlight(this.name);
              };

              var li = new Element('li').update(a); // добавляем ссылку в элемент

              // добавляем элемент в список
              ul.insert(li);
          }
      }

      div.insert(ul); 


      var pos         = this.fld.cumulativeOffset();
      div.style.left 	= pos[0] + "px";
      div.style.top 	= pos[1] + this.fld.offsetHeight + "px";

      var w = (this.options.setWidth && this.fld.offsetWidth < this.options.minWidth) ? 
               this.options.minWidth : 
                (this.options.setWidth && this.fld.offsetWidth > this.options.maxWidth) ?
                 this.options.maxWidth : 
                 this.fld.offsetWidth;

    div.style.width 	= w + "px";
    
    // устанавливаем обработчик мыши для контейнера
    // когда мышка выходит за контейнер, то устанавливаем таймаум для очистки списка
    // когда мышь попадает в контейнер, то убиваем таймаут
    div.onmouseover 	= function(){ p.killTimeout() };
    //div.onmouseout 		= function(){ p.resetTimeout() };
    
    // добавляем контейнер в DOM
    document.getElementsByTagName("body")[0].appendChild(div);
    
    // получаем позицию скролинга
    var ScrollTop = document.body.scrollTop;
    
    if (ScrollTop == 0) {
        if (window.pageYOffset)
            ScrollTop = window.pageYOffset;
        else
            ScrollTop = (document.body.parentElement) ? document.body.parentElement.scrollTop : 0;
    }
    
    // получаем размеры области, в которой отображается страница
    var winW = 630, winH = 460;
    
    if (parseInt(navigator.appVersion) > 3) {
        if (navigator.appName=="Netscape") {
            winW = window.innerWidth;
            winH = window.innerHeight;
        }
        if (navigator.appName.indexOf("Microsoft") != -1) {
            winW = document.body.offsetWidth;
            winH = document.body.offsetHeight;
        }
    }
    
    // если список выходит за нижнюю границу документа, то отображаем его сверху
    if ((winH + ScrollTop) < (div.getHeight() + div.positionedOffset()[1])) {
        div.style.top = (pos[1] - div.getHeight()) + "px";
    }
    
    // подсвечиваем 1 элемент
    if (this.options.allowNew) {
        this.iHigh = 0;
    } else {
        this.iHight = 1;
        this.setHighlight(1);
    }
    
    if (!this.fld.focused) {
        p.resetTimeout();
    }
  },

  changeHighlight: function(key) {
  	var list = $("ac_ul");
    if (!list)
      return false;
	
    var n;

    n = (key == Event.KEY_DOWN || key == Event.KEY_TAB)? this.iHigh + 1 : this.iHigh - 1; // false означает Event.KEY_UP
    
    n = (n > list.childNodes.length)? list.childNodes.length : ((n < 1)? 1 : n);	
    
    this.setHighlight(n);
  },

  setHighlight: function(n) {
  	var list = $('ac_ul');
  	if (!list) return false;
  	
  	if (this.iHigh > 0) this.clearHighlight();
  	
  	this.iHigh = Number(n);  	
  	list.childNodes[this.iHigh-1].className = 'ac_highlight';
  	
  	this.killTimeout();
  },

  clearHighlight:	function() {
  	var list = $('ac_ul');
  	if(!list) return false;
  	
  	if (this.iHigh > 0) {
  	    if (list.hasChildNodes() && list.childNodes) {
  		    list.childNodes[this.iHigh-1].className = '';
  	    }
  		this.iHigh = 0;
  	}
  },

  setValue:	function() {
    this.killTimeout();
    
    if (this.iHigh) {
        if (!this.aSug[this.iHigh - 1]) return;
        
        str = this.aSug[ this.iHigh -1 ].value;
        this.sInp = this.fld.value = str;
        
        this.fld.focus();
        if (this.fld.selectionStart)
            this.fld.setSelectionRange(this.sInp.length, this.sInp.length);
        
        // если задана callback функция, то передаём в неё выбранный объект
        if (typeof this.options.callback == 'function')
            this.options.callback(this.aSug[this.iHigh-1]); 
    
        this.clearList();
    } else if (this.options.allowNew) {
        this.fld.focus();
        if (this.fld.selectionStart)
            this.fld.setSelectionRange(this.sInp.length, this.sInp.length);
        
        // если задана callback функция, то передаём и неё null
        if (typeof this.options.callback == 'function')
            this.options.callback(null);
            
        this.clearList();
    }
  },

  killTimeout:	function() {
  	clearTimeout(this.toID);
  },

  resetTimeout:	function() {
    this.killTimeout();
  	
  	var p = this;
  	this.toID = setTimeout(
      function () { 
        p.clearList();
      }, p.options.timeout
    );
  },

  clearList:	function () {
	this.killTimeout();
    if (typeof this.options.onclear == 'function')
      	this.options.onclear();
    if ($(this.acID))
    {
      this.fadeOut(300,function () {
        $(this.acID).remove();
      } );
    }
  },

  fadeOut:	function (milliseconds, callback) {
  	this._fadeFrom 	= 1;
  	this._fadeTo	= 0;
  	this._afterUpdateInternal = callback;
  	
  	this._fadeDuration	= milliseconds;
  	this._fadeInterval = 50;
  	this._fadeTime = 0;
  	var p = this;
  	this._fadeIntervalID = setInterval(
      function() {
        p._changeOpacity()
      }, this._fadeInterval
    );
  
  },

  _changeOpacity: function() {
 
    if (!$(this.acID))
    {
  		this._fadeIntervalID=clearInterval(this._fadeIntervalID);
  		return;
  	} 
  	this._fadeTime += this._fadeInterval;
  	
  	var ieop = Math.round( (this._fadeFrom + ((this._fadeTo - this._fadeFrom) * (this._fadeTime/this._fadeDuration))) * 100)
  	var op = ieop / 100;
 
  	var el = $(this.acID);
  	if (el.filters) // internet explorer
  	{
      try {
        el.filters.item("DXImageTransform.Microsoft.Alpha").opacity = ieop;
      } catch (e) {
        el.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(opacity='+ieop+')';
      }
    } else	{
      el.style.opacity = op;
    }
	
    if (this._fadeTime >= this._fadeDuration)
    {
      clearInterval( this._fadeIntervalID );
      if (typeof this._afterUpdateInternal == 'function')
        this._afterUpdateInternal();
    }

  }
 
}
