/**
 * Autocompleter klasse
 *
 * Deze klasse is onafhankelijk van elke library
 *
Voorbeeld gebruik:
var myinputautocomplete = new AutoCompleter('inputid', {
	onChange: function(value){
		//Hier kan natuurlijk ook Ajax gebeuren
		myinputautocomplete.setListFromArray([value+'1', value+'2', value+'3', value+'4']);
	}
});
Ajax: 
 Code:
 new AutoCompleter('inputid', {ajaxUrl: 'http://www.domein.nl/ajax/daarzo?zoekterm=*&param=value'});
 
 De plaats van het sterretje wordt verwisseld door de ingevulde zoekterm
 Bij deze Ajax moet je altijd html teruggeven met een root en verschillende children als opties, bijvoordbeeld:
  <ul>
   <li>Eerste optie</li>
   <li>Tweede optie</li>
  </ul>
  
  Of:
  <table>
   <tr>
    <td>Optie 1</td><td>11 resultaten</td>
   </tr>
   <tr>
    <td>Optie 2</td><td>12 resultaten</td>
   </tr>
  </table>
 
Voorbeeld Style: 

div.suggest {
	border: 1px solid #716F64;
	font-family: Arial, Verdana, sans-serif;
	font-size: 11pt;
	width: 200px;
	background-color: white;
}

div.suggest * {
	padding: 0;
	margin: 0;
}

div.suggest li {
	list-style-type: none;
	cursor: hand;
}

div.suggest li.selected {
	background-color: #316AC5;
	color: white;
}

 */
var AutoCompleter = function() {
	
	/**
	 * Contructor
	 *
	 * @param input Het input element waarop de autocomplete wordt toegepast,
	 *           Dit kan zowel het input element zelf zijn of de id ervan
	 * @param options Een object met opties waarmee het standaard gedrag van de autocompleter kan worden overschreven.
	 *                  onChange: Deze functie wordt aangeroepen op het moment dat de gebruiker de waarde van het input wijzigt
	 *					onSelect: Deze functie wordt aangeroepen als de gebruiker een optie selecteert of klikt
	 *					onPosition: Functie die wordt gebruikt om de div te positioneren. Default onder het input veld.
	 *					ajaxUrl: URL waarmee 
	 *
	 * @todo Een onDelayedChange die niet op elke letter reageert maar om de x seconden of x letters. Voor betere performance.
	 */
	this.constructor = function(input, options) {
		
		/* Velden */
		this.input;
		this.suggestdiv;
		this.options = {
			ac: this, //Dit is geen optie, maar een referentie aan het AutoComplete object voor binnen functies in opties
			onChange: function(){},
			onSelect: function(){},
			onPosition: this.onPosition,
			className: 'suggest'
		};
		this.visible = false;
		this.lastvalue = '';
		this.list;
		this.selected;
		
		//Pak het input inputement
		if(input.nodeType == 1) {
			this.input = input;
		} else if(typeof input == 'string') {
			this.input = document.getElementById(input);
		} else {
			return null;
		}
		
		//Sla custom options op
		for (var prop in options) this.options[prop] = options[prop];
		
		//Als de ajaxUrl optie gezet is standaard een onselect maken
		if(this.options.ajaxUrl) {
			this.options.onChange = this.ajaxSend;
		}
		
		//Verander het input element zodat hij reageert op toetsaanslagen
		this.input.setAttribute('autocomplete', 'off');
		this.input.onkeyup = this.onKeyUp;
		this.input.onkeydown = this.onKeyDown;
		this.input.onblur = this.onBlur;
		
		//Geef het input element de autocomplete mee
		//zodat het binnen de event methodes nog steeds bereikbaar is
		this.input.ac = this;
	}
	
	/**
	 * Functies die het geselecteerde item omhoog en omlaag verplaatsen (voor de pijltjestoetsen)
	 */	
	this.goUp = function() {
		this.list[this.selected].className = '';
		this.selected--;
		
		if(this.selected < 0)
			this.selected = this.list.length-1;
			
		this.list[this.selected].className = 'selected';
	}
	
	this.goDown = function() {
		this.list[this.selected].className = '';
		this.selected++;
		
		if(this.selected >= this.list.length)
			this.selected = 0;
			
		this.list[this.selected].className = 'selected';
	}
	
	/**
	 * Genereert een ul element van een JSON object
	 */
	this.setListFromJSON = function(json) {
		var ul = document.createElement('ul');
		for(var i in json) {
			var li = document.createElement('li');
			li.innerHTML = json[i];
			ul.appendChild(li);
		}
		this.setList(ul);
	}
	
	/**
	 * Genereert een ul element van een JSON object
	 */
	this.setListFromArray = function(array) {
		var ul = document.createElement('ul');
		for(var i = 0; i < array.length; i++) {
			var li = document.createElement('li');
			li.innerHTML = array[i];
			ul.appendChild(li);
		}
		this.setList(ul);
	}
	
	
	
	/**
	 * Genereert een UL element van een HTML string
	 */
	this.setListFromHTML = function(html) {
		var div = document.createElement('div');
		div.innerHTML = html;
		
		var ul = div.firstChild;
		
		if(ul.nodeType != 1) //Negeer whitespace in het begin
			ul = ul.nextSibling;

		this.setList(ul);
	}
	
	/**
	 * Bewerk een generiek element zodat alle subelementen meedoen in de autosuggest als er op gelikt wordt enzo
	 */
	this.setList = function(ul) {
		if(this.visible) {
			this.list = new Array();
			
			var number = 0;
			this.selected = 0;
			for(var i in ul.childNodes) {
				var li = ul.childNodes[i];
				if(li.nodeType == 1) {
					//Maak een lijst van de elementen die in de suggest staan
					this.list[this.list.length] = li;
					
					//Selecteer de eerste
					if(number == 0) {
						li.className = 'selected';
					}
					
					//Voeg onclick aan de li toe
					var ac = this;
					li.number = number;
					li.onclick = function(){ac.onSetItem(this)};
					li.onmouseover = function(){ac.onItemHover(this)};
					
					number++;
				}
			}
			
			this.suggestdiv.innerHTML = ''; //Verwijder oude lijstjes
			this.suggestdiv.appendChild(ul);
		}
	}
	
	/**
	 * Functie voor als er op geklikt wordt
	 */
	this.onSetItem = function(li) {
		//Zet gestripte innerHTML
		var value = li.innerHTML.replace(/^\s*|\s*$/g,"");
		this.lastvalue = value;
		this.input.value = value;
		this.options.onSelect();
	}
	
	this.ajaxSend = function() {
		if(window.XMLHttpRequest) {
			xmlhttp=new XMLHttpRequest();
  		} else if (window.ActiveXObject) {
			xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
		}
		var ac = this.ac;
		if (xmlhttp!=null)
		{
			xmlhttp.onreadystatechange=function() {
				if(xmlhttp.readyState==4 && xmlhttp.status == 200) {
					ac.setListFromHTML(xmlhttp.responseText);
				}
			}
			var url = ac.options.ajaxUrl.replace(/\*/g, ac.lastvalue);
			xmlhttp.open("GET",url,true);
			xmlhttp.send(null);
  		}
	}


	
	/**
	 * Mouseover functie voor li elementen
	 */
	this.onItemHover = function(li) {
		this.list[this.selected].className = '';
			
		this.selected = li.number;
		li.className = 'selected';
	}
	
	/**
	 * Verwijdert de div compleet uit de DOM
	 */
	this.hide = function() {
		if(this.visible) {
			document.body.removeChild(this.suggestdiv);
			this.visible = false;
		}
	}
	
	/**
	 * Maak een div aan voor de lijst & positioneer deze onder het input element	 
	 */
	this.show = function() {
		if(!this.visible) {
			this.visible = true;
			this.suggestdiv = document.createElement('div');
			this.suggestdiv.className = this.options.className;
			document.body.appendChild(this.suggestdiv);
			this.options.onPosition();
		}
	}
	
	/**
	 * Positioneer de suggestdiv onder het input element
	 */
	this.onPosition = function() {
		
		var div = this.ac.suggestdiv;
		var input = this.ac.input;
		
				var top = 0;
		var obj = input;
		if(obj.offsetParent) {
			while (obj.offsetParent) {
				top += obj.offsetTop;
				obj = obj.offsetParent;
			}
		} else if (obj.y)
			top += obj.y;
			
		var left = 0;
		var obj = input;
		if(obj.offsetParent) {
			while (obj.offsetParent) {
				left += obj.offsetLeft;
				obj = obj.offsetParent;
			}
		} else if (obj.x)
			left = obj.x;
		
		var cwidth = document.body.clientWidth;
		var dwidth = div.offsetWidth;
		if(left + dwidth > cwidth)
			left = cwidth - dwidth - 5;

		div.style.position = 'absolute';
		div.style.left = left+'px';
		div.style.top = top+input.offsetHeight+'px';
	}
	
	/***************** Afhandeling input events */
	
	/**
	 * Controleer of de waarde van het inputveld is veranderd
	 * (hoeft niet altijd ivm pijltjes toetsen)
	 * Vuur de onChange methode af als het inputveld is veranderd
	 */
	this.onKeyUp = function(e) {
		var e = e || window.event;
		var code = e.keyCode;
		
		var lastvalue = this.ac.lastvalue;
		var value = this.value
		if(value != lastvalue) {
			this.ac.lastvalue = value;
			
			if(lastvalue == '') this.ac.show();
			
			if(value == '') {
				this.ac.hide();
			} else {
				if(this.ac.visible == false) this.ac.show();
				this.ac.options.onChange(value);
			}
		}
	}
	
	/**
	 * Vang de omhoog / omlaag knopacties af en return er false op.
	 * Alle andere knoppen mogen wel ingedrukt worden
	 */
	this.onKeyDown = function(e) {
		var e = e || window.event;
		var code = e.keyCode;

		if(code == 40) {
			if(this.ac.visible && this.ac.list.length > 0) this.ac.goDown();
			return false;
		} else if(code == 38) {
			if(this.ac.visible && this.ac.list.length > 0) this.ac.goUp();
			return false;
		} else if(code == 13) {
			this.ac.onSetItem(this.ac.list[this.ac.selected]);
			this.ac.hide();
			return false;
		} else {
			return true;
		}
	}
	
	/**
	 * Verberg de autocomplete op het moment dat hij de focus verliest
	 */
	this.onBlur = function() {
		var ac = this.ac;
		setTimeout(function() {ac.hide();}, 200); //Vertraagt verbergen om li onclicks een kans te geven
	}
	
	/***************** Aanroepen contructor */
	this.constructor.apply(this, arguments);
}