var loadingAjaxData = false;

var AutoComplete = Class.create();

AutoComplete.prototype = { // {{{
  Version: '1.3.0',
  REQUIRED_PROTOTYPE: '1.6.0',

  initialize: function (id, param) { // {{{
  	// check whether we have the appropiate javascript libraries
  	this.PROTOTYPE_CHECK();
	
    // Get the field we're watching.
    // It needs to be a valid field so throw an error if it's not valid or can't be found.
    this.fld = $(id);
    if (!this.fld)
    {
      throw("AutoComplete requires a field id to initialize");
    }
	
    // Init variables
    this.sInp 	= ""; // input value 
    this.nInpC 	= 0;	// input value length
    this.aSug 	= []; // suggestions array 
    this.iHigh 	= 0;	// level of list selection 
	
  // Parameter Handling {{{
	// Set the use specified options
	this.options = param ? param : {};
	// These are the default settings {{{
  var k, def = {
    valueSep:null,
    minchars:1,
    meth:"get",
    varname:"input",
    className:"autocomplete",
    timeout:3000,
    delay:0,
    offsety:-5,
    shownoresults: true,
    noresults: "No results were found.",
    maxheight: 250,
    cache: true,
    maxentries: 25,
    onAjaxError:null,
    setWidth: false,
    minWidth: 100,
    maxWidth: 200,
    fadeDuration: 0.3,
    useNotifier: true,
    parentId: 'suggestionHolder',
    layerId: 'suggestionLayer',
    formId: 'searchForm'
  };
  //}}}
  // Overlay any values which weren't user specified.
	for (k in def) 
	{
		if (typeof(this.options[k]) != typeof(def[k]))
			this.options[k] = def[k];
	}
  // End of Parameter Handling }}}

  // Not everyone wants to use the Notifier. Give them the option	
	if (this.options.useNotifier)
  {
    this.fld.addClassName('searchField');
  }

	// set keyup handler for field
	// and prevent AutoComplete from client
	var p = this;
	
	// NOTE: not using addEventListener because UpArrow fired twice in Safari
	this.fld.onkeypress 	= function(ev){ return p.onKeyPress(ev); };
	this.fld.onkeyup      = function(ev){ return p.onKeyUp(ev); };
  // ARN-DEBUG Chances are we want to reset the timeout when they lose focus, at least that's what I prefer
	this.fld.onblur			  = function(ev){  this.clearSuggestions.bind(this).delay(0.2); return true; }.bind(this);	
  // ARN-DEBUG Not sure what this is about!
	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("AutoComplete requires the Prototype JavaScript framework >= " +
        this.REQUIRED_PROTOTYPE);
  }, // }}}

  // set responses to keypress events in the field
  // this allows the user to use the arrow keys to scroll through the results
  // ESCAPE clears the list
  // RETURN sets the current highlighted value
  // UP/DOWN move around the list

  onKeyPress: function (e) { // {{{
  	if (!e) e = window.event;
  	var key	= e.keyCode || e.wich;
    switch(key)
    {
      case Event.KEY_RETURN:
        if($(this.setHighlightedValue()))
        {
        	Event.stop(e);
        }
        break;
      case Event.KEY_TAB:
        this.setHighlightedValue();
        //Event.stop(e);
        break;
      case Event.KEY_ESC:
        this.clearSuggestions();
        break;
    }
    return true;
  }, //}}}

  onKeyUp: function (e) { // {{{
  	if (!e) e = window.event;
  	
  	var key = e.keyCode || e.wich;

  	if (key == Event.KEY_UP || key == Event.KEY_DOWN) 
  	{
  		this.changeHighlight(key);
  		Event.stop(e);
  	}
  	else this.getSuggestions(this.fld.value);
  	
	return true;
  }, //}}}

  getSuggestions: function(val) { // {{{
  	if(this.animation) return false;
  	
  	// input the same? do nothing
  	if(val==this.sInp) return false;
  	
  	// kill the old list
//  	if($(this.acID)) $(this.acID).remove();
  	
  	var words = this.parseWords(val.toLowerCase());
  	var oldWords = this.parseWords(this.sInp.toLowerCase());
  	
  	this.sInp = val;
  	
  	// input length is less than the min required to trigger a request
  	// do nothing
  	if (val.length < this.options.minchars)
  	{
  		this.aSug 	= [];
  		this.nInpC	= val.length; 
  		this.clearSuggestions();
  		return false;
  	}

  	// Here we will detect if there is a comma and the splitted value has a value to check
  	// comma stars a new search and val is converted to the new value after the comma
  	var ol	= this.nInpC; // old length
  	this.nInpC	= val.length ? val.length : 0;

  	
  	  	
  	// if caching enabled, and we didn't receive the maxentries value
    // and user is typing (ie. length of input is increasing)
  	// filter results out of suggestions from last request
  	var l = 0;
  	for (var j=0,al = this.aSug.length; j<al; j++)
  	{
  		l += this.aSug[j].items.length;
  	}
  	
  	var newSearch = true;
  	if(!$(this.options.layerId).visible())
  	{
  		newSearch = true;
  	}
  	else if( this.options.cache && ( this.nInpC > ol ) && l && ( l < this.options.maxentries ) && words.length == oldWords.length)
  	{
  		var arr = [];
  		for (var j=0,al = this.aSug.length; j<al; j++)
  		{
  			arr[j] = {'name': this.aSug[j].name, 'items': []}
	  		for (var i=0;i<this.aSug[j].items.length;i++) 
	  		{
	  			
	  			for(var k = 0; k < words.length; k++)
	  			{
	  				var regexp = new RegExp("\\b(" + words[k] + ".*)" , "g");
	  				var matches = this.aSug[j].items[i].value.toLowerCase().match(regexp);
	  			
		  			if (matches)
		        	{
		        		newSearch = false;
		  				arr[j].items.push(this.aSug[j].items[i]);
		  				break;
		        	}
	  			}
	  		}
  		}
  		this.aSug = arr;
  		
  		// recreate the list
  		this.createList(this.aSug);
  	} 
  	
  	if(newSearch && !loadingAjaxData) {
  		// do new request
  		var p = this;
  		//var input	= this.sInp; // send the converted new value (comma)
  		
  		clearTimeout(this.ajID); // ajax id timer
  		this.ajID = setTimeout( function () {p.doAjaxRequest(p.sInp)}, this.options.delay);
  	}
    document.helper = this;	
  	return false;
  }, // }}}

  parseWords: function(value)
  {
  		value = value.replace(/,/g, ' ');
  		value = value.replace(/;/g, ' ');
  		value = value.replace(/(\s+)/g, ' ');
  		var words = value.replace(/^\s*([\S\s]*)\b\s*$/, '$1').split(' ');
  		return words;
  },
  
  getLastInput : function(str) { // {{{
  	var ret = str;
  	if (undefined != this.options.valueSep) {
  		var idx = ret.lastIndexOf(this.options.valueSep);
      ret = idx == -1 ? ret : ret.substring(idx + 1, ret.length);
  	}
  	
  	return ret;
  }, // }}}

  doAjaxRequest: function (input) { // {{{
  	// we have to check here if there is a new splitted value (, or ;)
  	// always check against the last part of the comma and then check
  	// saved input is still the value of the field
  	if (input != this.fld.value) 
  		return false;
  	
  	// Gmail like : get only the last user's input
  	this.sInp = this.getLastInput(this.sInp);
 	
  	// create ajax request
  	// do we need to call a function to recreate the url?
  	if (typeof this.options.script == 'function')
  		var url = this.options.script(encodeURIComponent(this.sInp));
  	else
  		var url = this.options.script+this.options.varname+'='+encodeURIComponent(this.sInp);
  	
  	if(!url) return false;
  	
  	var p = this;
  	var m = this.options.meth;  // get or post?
    if( this.options.useNotifier )
    {
      this.fld.removeClassName('searchField');
	    this.fld.addClassName('searchFieldBusy');
    };
  	
  	var options = {
  		method: m,
  		onSuccess: function (req) { // {{{
        if( p.options.useNotifier )
        {
          p.fld.removeClassName('searchFieldBusy');
          p.fld.addClassName('searchField');
        };
        loadingAjaxData = false
        p.setSuggestions(req,input);
  		}, // }}}

  		onFailure: (typeof p.options.onAjaxError == 'function')? function (status) { // {{{
        if (p.options.useNotifier)
        {
          p.fld.removeClassName('searchFieldBusy');
          p.fld.addClassName('searchField');
        }
        p.options.onAjaxError(status)
      } : // }}}

      function (status) { // {{{
        if (p.options.useNotifier)
        {
          p.fld.removeClassName('searchFieldBusy');
          p.fld.addClassName('searchField');
        }
        alert("AJAX error: "+status); 
      } // }}}
    }
  	// make new ajax request
  	new Ajax.Request(url, options);
  	loadingAjaxData = true;
  }, // }}}

  setSuggestions: function (req, input) { // {{{
	  // if field input no longer matches what was passed to the request
	  // don't show the suggestions
	  // here we need to check against the splitted values if any (, or ;)
//    if (input != this.fld.value)
//      return false;
	
    this.aSug = [];
	
    if(this.options.json) 
    { // response in json format?
      var jsondata = eval('(' + req.responseText + ')');

      this.aSug = jsondata.results;
      
    } else {
      // response in xml format?
      var results = req.responseXML.getElementsByTagName('results')[0].childNodes;
    
      for(var i=0;i<results.length;i++)
      {
        if(results[i].hasChildNodes())
          this.aSug.push(  { 'id':results[i].getAttribute('id'), 'value':results[i].childNodes[0].nodeValue, 'info':results[i].getAttribute('info') }  );
      }
    }
    this.acID = 'ac_'+this.fld.id;
    this.createList(this.aSug);
  }, // }}}

  createDOMElement: function ( type, attr, cont, html ) { // {{{
    var ne = document.createElement( type );
	
    if (!ne)
      return 0;
      
    for (var a in attr)
      ne[a] = attr[a];
    
    var t = typeof(cont);
    
    if (t == "string" && !html)
      ne.appendChild( document.createTextNode(cont) );
    else if (t == "string" && html)
      ne.innerHTML = cont;
    else if (t == "object")
      ne.appendChild( cont );

    return ne;
  }, // }}}
  
  
  //SHIBUMI FIX - podpora pre viacero sekcii
  createList:	function(arr) { // {{{
    // get rid of the old list if any  
//  	if($(this.acID)) $(this.acID).remove();
  	
  	// clear list removal timeout
  	this.killTimeout();
  	
  	// if no results, and showNoResults is false, do nothing
  	var itemCount = 0;
  	for (var j=0,al = arr.length; j<al; j++)
  	{
  		itemCount += arr[j].items.length;
  	}
  	if (itemCount == 0 && !this.options.shownoresults) 
  	{
		this.clearSuggestions();  		
  		return false;
  	}
  	
  	// create holding div
  	var div	= this.createDOMElement('div', {id:this.acID});
  	
    // create and populate ul
    var ul	= this.createDOMElement('ul', {id:'ac_ul'});
    var p 	= this; // pointer that we will need later on
    
    
    //create and populate section
    var itemIndex = 1;
  	for (var j=0,al = arr.length; j<al; j++)
  	{
  		var sectionName = arr[j].name;
  		var items = arr[j].items;
  		
	    // no results?
	    if (items.length == 0)
	    {
	    	if(this.options.shownoresults)
	    	{
		      	var li = this.createDOMElement('li', {className: 'section'}, sectionName );
		      	ul.appendChild(li);
		    	
		    	var li = this.createDOMElement('li', {className: 'warning'}, this.options.noresults );
		      	ul.appendChild(li);
	    	}
	      		
	    } else {
	    	
	    	
	    	
	    	var li = this.createDOMElement('li', {className: 'section'}, sectionName );
	      	ul.appendChild(li);
	      	itemIndex++;
	    	
	    	
	      // loop through arr of suggestions creating an LI element for each of them
	      for (var i=0,l = items.length; i<l; i++)
	      {
	        // format output with the input enclosed in a EM elementFromPoint
	        // (as HTML not DOM)
	        var val   = items[i].value;
	        var link  = items[i].link;
	        var image = items[i].image;
	        
	        var words = this.parseWords(this.sInp.toLowerCase());
	        var st = 0;
	        var output = val;
	        for(var k = 0; k < words.length; k++)
	        {
	        	//bude hladat vsetky zaciatky slov
	        	var regexp = new RegExp("(\\b" + words[k] + "\\w*)", "g");
	        	var matches = output.toLowerCase().match(regexp);
	        	if(matches)
	        	{
		        	for(var m = 0; m < matches.length; m++)
		        	{
		        		st = output.toLowerCase().search(matches[m]);
			        	output 	= output.substring(0,st) + '~' + output.substring(st,st+words[k].length) + '°' + output.substring(st+words[k].length);
		        	}
	        	}
	        }
	        output = output.replace(/~/g, '<em>').replace(/°/g, '</em>');
			
	        var linkOptions = link ? {href: link} : {href: '#'};	
	        var a = this.createDOMElement('a', linkOptions);
	        
	        var liOptions = {'className': 'word'}
	        if(image)	
	        {
				var img = this.createDOMElement('img', {src: image, alt: '', align: 'left', width:'44',height:'44'});	        	
				a.appendChild(img);
				liOptions = {};
	        }
	        
	        var span = this.createDOMElement('span',{},output,true); // type of, properties, output, isHTML?
	        a.appendChild(span);
	        
	        var small = this.createDOMElement('small',{}, items[i].info);
			a.appendChild(small);
	        
	        a.name = itemIndex++;
	        
	        if(!link)
	        {
		        a.onclick 		= function () { // {{{
		          p.setHighlightedValue();
		          $('searchForm').submit();
		          return false; 
		        }; // }}}
	        }
	        a.onmouseover	= function () { // {{{
	          p.setHighlight(this.name); 
	        }; // }}} 
				
	        var li = this.createDOMElement('li', liOptions, a); // add the link element to a li element
	        
	        // finally add the newly created li element to the ul element 
	        ul.appendChild(li);
	      }
	    }
  	}
    
    div.appendChild(ul); // add the newly created list to the div element
    div.onmouseover 	= function(){ p.killTimeout() };
    div.onmouseout 		= function(){ p.resetTimeout() };
    
    if($(this.acID)) $(this.acID).remove();
    $(this.options.parentId).appendChild(div);
    $(this.options.layerId).show();
    
    // highlight first item
    this.iHigh = 1;
    this.setHighlight(1);
    
    // remove list after interval
    if(this.options.timeout > 0)
  	{
	    this.toID	= setTimeout(
	      function () {
	        p.clearSuggestions() 
	      }, this.options.timeout
	    );
  	}
	
  }, // }}}

  changeHighlight:	function(key) { // {{{
  	var list = $("ac_ul");
    if (!list)
      return false;
	
    var n;

    //SHIBUMI FIX - multisekcie
    if(key == Event.KEY_DOWN || key == Event.KEY_TAB)
    {
    	n = list.childNodes[this.iHigh] && $(list.childNodes[this.iHigh]).hasClassName('section') ? this.iHigh + 2 : this.iHigh + 1;
    }
    else
    {
    	n = list.childNodes[this.iHigh-2] && $(list.childNodes[this.iHigh-2]).hasClassName('section') ? this.iHigh - 2 : this.iHigh - 1;
    }
	//n = (key == Event.KEY_DOWN || key == Event.KEY_TAB)? this.iHigh + 1 : this.iHigh - 1; // false assumed to be Event.KEY_UP
	//SHIBUMI FIX - multisekcie
    
    n = (n > list.childNodes.length)? list.childNodes.length : ((n < 1)? 1 : n);	
    
    n = $(list.childNodes[n - 1]).hasClassName('section') ? n + 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);
  	
  	if(list.childNodes[this.iHigh-1] && !$(list.childNodes[this.iHigh-1]).hasClassName('highlight'))
  	{
  		$(list.childNodes[this.iHigh-1]).addClassName('highlight');
  	}
  	
  	this.killTimeout();
  }, // }}}

  clearHighlight:	function() { // {{{
  	var list = $('ac_ul');
  	
  	if(!list) return false;
  	
  	if(this.iHigh > 0)
  	{
  		if($(list.childNodes[this.iHigh-1]).hasClassName('highlight'))
  		{
  			$(list.childNodes[this.iHigh-1]).removeClassName('highlight');
  		}
  		this.iHigh = 0;
  	}
  	
  }, // }}}

  setHighlightedValue:	function() { // {{{
  	if (this.iHigh)
  	{
  		var suggestions;
  		var suggestionIndex;
  		var itemCount = 0;
  		for(var j = 0; j < this.aSug.length; j++)
  		{
  			itemCount += this.aSug[j].items.length + 1;
  			if(this.iHigh <= itemCount)
  			{
  				suggestions = this.aSug[j].items;
  				suggestionIndex = this.iHigh - (itemCount - this.aSug[j].items.length );
  				break;
  			}
  		}
  		
  		// HERE WE NEED TO IMPLEMENT THE GMAIL LIKE SPLITTED VALUE
  		if (!suggestions[suggestionIndex - 1]) return;
  		if(suggestions[suggestionIndex - 1].link)
  		{
			window.location = suggestions[suggestionIndex - 1].link;
			return true;
  		}
  		
  		// Gmail like
      if (undefined != this.options.valueSep) {
        var str = this.getLastInput(this.fld.value);
        var idx = this.fld.value.lastIndexOf(str);
        str = suggestions[ suggestionIndex -1 ].value + this.options.valueSep;
        this.sInp = this.fld.value = idx == -1 ? str : this.fld.value.substring(0, idx) + str;
      } else {
        var str = this.getLastInput(this.fld.value);
        var idx = this.fld.value.lastIndexOf(str);
        str = suggestions[ suggestionIndex -1 ].value;
        this.sInp = this.fld.value = idx == -1 ? str : this.fld.value.substring(0, idx) + str;
      }
  		
  		// move cursor to end of input (safari)
  		this.fld.focus();
  		if(this.fld.selectionStart)
  			this.fld.setSelectionRange(this.sInp.length, this.sInp.length);
  			
  		this.clearSuggestions();
  		
  		// pass selected object to callback function, if exists
  		if (typeof this.options.callback == 'function')
  			this.options.callback(suggestions[suggestionIndex-1]); // the object has the properties we want, it will depend of
  		
  		return false;	
  	}
  	return false;
  }, // }}}

  killTimeout:	function() { // {{{
  	clearTimeout(this.toID);
  }, // }}}

  resetTimeout:	function() { // {{{
  	this.killTimeout();
  	var p = this;
  	if(p.options.timeout > 0)
  	{
	  	this.toID = setTimeout(
	      function () { 
	        p.clearSuggestions();
	      }, p.options.timeout
	    );
  	}
    // ARN-DEBUG Added p.options.timeout back :|
  }, // }}}

  
  
  clearSuggestions:	function () { // {{{
  	
  		
  	
  	
    this.killTimeout();
    if ($(this.acID))
    {
    	this.animation = true;
    	Effect.Fade(this.options.layerId, {duration: this.options.fadeDuration, afterFinish: function() { this.animation = false; this.getSuggestions(this.fld.value);}.bind(this)} );
    }
  }, // }}}

  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.options.layerId))
    {
  		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.options.layerId);
  	if (el.filters) // internet explorer
  	{
      try {
        el.filters.item("DXImageTransform.Microsoft.Alpha").opacity = ieop;
      } catch (e) { 
        // If it is not set initially, the browser will throw an error.
        // This will set it if it is not set yet.
        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();
    }

  } // }}}
 
} // }}}
