//
// 2006 by bitart; Author: Alexander Graf
//
// Copyright by bitart - Furch & Graf GbR, Gartenstr. 5 56220 St. Sebastian,
// Germany. All rights reserved.
// Copyright in this software is owned by bitart, unless otherwise indicated.
//
// You are not allowed to distribute this software in any form or use it as
// part of another project without asking the owner for permission to use.
//

var suggest_default   = 'Volltextsuche'; // default text for input field
var suggest_enabled   = false; // en/disable suggestions
var suggest_single    = true;  // show single word while suggesting
var suggest_noclip    = 96;    // percentage of "not clipped" suggestion
var suggest_timeout   = 500;   // show suggestions after X ms
var suggest_chars     = 3;     // suggest only if len > X
var suggest_show      = 50;    // show only X suggestions
var suggest_typer     = null;  // input field
var suggest_popup     = null;  // suggestion popup
var suggest_timer     = null;  // timer for popup open and close
var suggest_count     = 0;     // unique request id
var suggest_cache     = {};    // cache for suggested words
var suggest_maxcache  = 20;    // maximum entries in cache
var suggest_valid     = null;  // re to find valid query words without quotes
var suggest_quote_s   = null;  // re to find quote in the rest
var suggest_quote_e   = null;  // re to find end of word without quotes
var suggest_url       = '/app/suggest.php'; // suggest server url
var suggest_xmlhttp   = null;  // http request object
var suggest_maxheight = 200;   // maximum height of suggestion popup
var suggest_cursor    = null;  // saved cursor position in input field
var suggest_submit    = null;  // submit function of form for ENTER
var suggest_openfn    = null;  // called when DOWN pressed in input
var suggest_closefn   = null;  // called when UP is pressed in input
var suggest_focusfn   = null;  // called when input field is focused
var suggest_blurfn    = null;  // called when input field is blured
var suggest_supress   = null;  // supress trigger on this count

function suggest_init(inputobj, submitfn, xpos, ypos, width, height)
{
	suggest_typer = inputobj;
	suggest_typer.onfocus = suggest_focus;
	suggest_typer.onblur  = suggest_blur;
	try{
		suggest_valid   = /^("?)([^\/?*"]+)("?)$/;
		suggest_quote_s = /^("?).*$/;
		suggest_quote_e = /^.*?("?)$/;
		suggest_cache._list = Array();
		suggest_submit = submitfn;
		if (typeof XMLHttpRequest != 'undefined'){
			suggest_xmlhttp = new XMLHttpRequest();
		}else if (window.ActiveXObject){
			suggest_xmlhttp = new ActiveXObject('Msxml2.XMLHTTP');
		}
		if (suggest_xmlhttp){
			suggest_popup = document.getElementById('suggest');
			suggest_popup.opened       = false;
			suggest_popup.selected     = null;
			suggest_popup.columns      = 1;
			suggest_popup.suggest      = null;
			suggest_popup.clipped      = true;
			suggest_popup.reqid        = null;
			suggest_popup.repfr        = null;
			suggest_popup.repto        = null;

			if (typeof document.selection != 'undefined'){
				suggest_typer.onkeydown  = suggest_navigate;
			}else{
				suggest_typer.onkeypress = suggest_navigate;
			}
			suggest_typer.focused      = false;
			suggest_typer.onkeyup      = suggest_trigger;
			suggest_typer.onmousedown  = suggest_clearpos;
			suggest_typer.onmouseup    = suggest_trigger;
			suggest_typer.autocomplete = 'off';

			if (typeof xpos != 'undefined') suggest_popup.style.left = xpos+'px';
			if (typeof ypos != 'undefined') suggest_popup.style.top  = ypos+'px';
			suggest_resize(width, height);
		}
	} catch(e) {};
}

function suggest_resize(width, height)
{
	if (suggest_popup){
		if (typeof width != 'undefined') suggest_popup.style.width = width;
		if (typeof height != 'undefined') suggest_maxheight = height;
		suggest_columnize();
	}
}

function suggest_enable()
{
	if (suggest_popup){
		suggest_enabled = true;
		suggest_dofocus();
		suggest_trigger(null);
	}
}

function suggest_disable()
{
	if (suggest_popup){
		suggest_close(true);
		suggest_enabled = false;
	}
}

function suggest_getinput()
{
	var res = suggest_typer.value.replace(/\s+/g,' ').replace(/^\s+/,'').replace(/\s+$/,'');
	return (res == suggest_default)?'':res;
}

function suggest_navigate(e)
{
	var res = true;
	if (typeof e == 'number'){ // select entry
		suggest_navdesel();
		suggest_popup.selected = Math.min(suggest_popup.childNodes.length/2, Math.max(0, e));
		suggest_navsel(2);
	}else if (e === false){ // stop navigation
		suggest_navdesel();
		suggest_popup.selected = null;
	}else{ // key pressed or mouse event
		var isie = typeof e == 'undefined';
		e = (typeof e == 'undefined')?window.event:e;
		if (e.type == 'mouseover'){ // mouse navigation
			suggest_navdesel();
			suggest_popup.selected = this.num;
			suggest_navsel();
		}else if (e.type == 'mouseout'){ // mouse navigation
			suggest_navdesel();
			suggest_popup.selected = null;
		}else if (suggest_popup.opened && suggest_popup.selected !== null){ // navigating
			res = false;
			var stop = false;
			var align = 0;
			var last = suggest_popup.lastChild.previousSibling;
			var end = suggest_popup.childNodes.length/2 - ((last.id == 'showmore' && last.tagName.toLowerCase() == 'span')?1:0);
			var pos = suggest_popup.selected;
			switch (e.keyCode){
				case 27: // escape = close
					suggest_close(true);
					if (!isie){
						// prevent firefox from clearing input
						suggest_typer.blur();
						window.old_onkeyup = window.onkeyup;
						window.onkeyup = suggest_refocus;
					}
					break;
				case 9: // tab = close & next
					suggest_close(true);
					res = true;
					break;
				case 13: // enter = replace
					var node = suggest_popup.childNodes[pos*2];
					if (node.id == 'showmore'){
						suggest_open(pos);
						return false; // stop now!
					}else{
						suggest_navdesel();
						suggest_click(null, node);
					}
					stop = true;
					break;
				case 33: // pic up = navigate
					if (pos < suggest_popup.columns){
						suggest_navdesel();
						stop = true;
					}else{
						if (suggest_popup.childNodes[pos*2].id == 'showmore'){
							var back = (end-1)%suggest_popup.columns;
							if (back) pos -= back-suggest_popup.columns;
						}
						align = 1;
						var vt = suggest_popup.scrollTop+1; // !! 1 padding !!
						while (pos-suggest_popup.columns>=0){
							pos -= suggest_popup.columns;
							if (suggest_popup.childNodes[pos*2].offsetTop < vt){
								align = 3;
								break;
							}
						}
					}
					break;
				case 34: // pic down = navigate
					align = 3;
					var vb = suggest_popup.scrollTop+1 + suggest_popup.offsetHeight-4; // !! 1 padding, 2*2 padding !!
					var node = null;
					while (pos+suggest_popup.columns<end){
						pos += suggest_popup.columns;
						node = suggest_popup.childNodes[pos*2];
						if (node.offsetTop+node.offsetHeight > vb){
							align = 1;
							break;
						}
					}
					if (node && (pos%suggest_popup.columns) && node.id == 'showmore'){
						pos -= suggest_popup.columns;
					}
					if (align == 3 && !(pos%suggest_popup.columns) && last.id == 'showmore' && last.tagName.toLowerCase() == 'a'){
						pos = end-1;
					}
					break;
				case 35: // end = navigate
					align = 3;
					pos = end-1;
					break;
				case 36: // pos1 = navigate
					align = 1;
					pos = 0;
					break;
				case 37: // left = navigate
					align = 3;
					if (pos>0){
						pos -= 1;
					}else{
						suggest_navdesel();
						stop = true;
					}
					break;
				case 38: // up = navigate
					align = 3;
					if (suggest_popup.childNodes[pos*2].id == 'showmore'){
						var back = (end-1)%suggest_popup.columns;
						pos -= back?back:suggest_popup.columns;
					}else if (pos-suggest_popup.columns>=0){
						pos -= suggest_popup.columns;
					}else{
						suggest_navdesel();
						stop = true;
					}
					break;
				case 39: // right = navigate
					align = 1;
					if (pos+1<end){
						pos += 1;
					}
					break;
				case 40: // down = navigate
					align = 1;
					if (pos+suggest_popup.columns<end){
						pos += suggest_popup.columns;
					}else{
						if (last.id == 'showmore' && last.tagName.toLowerCase() == 'a'){
							pos = end-1;
						}
					}
					break;
				default:
					res = true;
					break;
			}
			if (stop){ // stop navigating
				suggest_popup.selected = null;
			}else if (suggest_popup.selected != pos){ // moved navigation
				suggest_navdesel();
				suggest_popup.selected = pos;
				suggest_navsel(align);
			}
		}else{ // in input field
			switch (e.keyCode){
				case 27: // escape = close | clear
					if (suggest_popup.opened){ // close suggestions
						suggest_close(true);
						if (!isie){
							// prevent firefox from clearing input
							suggest_typer.blur();
							window.old_onkeyup = window.onkeyup;
							window.onkeyup = suggest_refocus;
						}
					}else{ // clear input
						suggest_typer.value = '';
						suggest_typer.blur()
					}
					res = false;
					break;
				case 9: // tab = close & next
					suggest_close(true);
					break;
				case 13: // enter = search
					suggest_submit();
					res = false;
					break;
				case 33: // page up
				case 38: // up = setmode
					if (suggest_closefn) suggest_closefn();
					res = false;
					break;
				case 34: // page down
				case 40: // down = setmode | start navigation
					if (suggest_popup.opened){
						suggest_popup.selected = 0;
						suggest_navsel(1);
					}else{
						if (suggest_openfn) suggest_openfn();
					}
					res = false;
					break;
			}
		}
	}
	if (!suggest_typer.focused || (suggest_popup.opened && suggest_popup.selected !== null)){
		if (suggest_typer.className.indexOf(' suggest') != -1){
			suggest_typer.className = suggest_typer.className.replace(/ suggest/g, '');
		}
	}else{
		if (suggest_typer.className.indexOf(' suggest') == -1){
			suggest_typer.className += ' suggest';
		}
	}
	return res;
}

function suggest_navsel(align)
{
	if (suggest_popup.selected !== null){
		var node = suggest_popup.childNodes[suggest_popup.selected*2];
		node.className = 'selected';
		if (align){
			var vt = suggest_popup.scrollTop+1; // !! 1 padding !!
			var vb = vt + suggest_popup.offsetHeight-4; // !! 2*2 padding !!
			var nt = node.offsetTop;
			var nb = node.offsetTop+node.offsetHeight;
			if (nt < vt || nb > vb){ // only scroll if out of view
				switch (align){
					case 1: // top aligned
						suggest_popup.scrollTop = nt;
						break;
					case 2: // middle aligned
						suggest_popup.scrollTop = Math.floor(Math.max(0, nt+(nb-nt)/2-(vb-vt)/2));
						break;
					case 3: // bottom aligned
						suggest_popup.scrollTop = Math.max(0, nb-vb+vt);
						break;
				}
			}
		}
	}
}
function suggest_navdesel()
{
	if (suggest_popup.selected !== null){
		try{
			suggest_popup.childNodes[suggest_popup.selected*2].className = '';
		}catch(e){}
	}
}

function suggest_trigger(e)
{
	if (!suggest_enabled) return true;
	if (suggest_timer){
		suggest_timer = window.clearTimeout(suggest_timer);
	}
	if (e !== null){ // key or mouse event
		e = (typeof e == 'undefined')?window.event:e;
		if (e.type == 'keyup' && e.keyCode==27){
			// dont trigger after esc-press in ie
			return false;
		}
		if (suggest_popup.opened && suggest_popup.selected !== null){
			// stop navigation on mouseclick
			if (e.type == 'mouseup'){
				suggest_navigate(false);
			}
			// dont change suggest count while navigating in suggestions
			if (e.type == 'keyup' && e.keyCode>=33 && e.keyCode<=40){
				return false;
			}
		}
	}
	suggest_count = (suggest_count+1)%100000;
	if (suggest_count == suggest_supress){
		suggest_supress = null;
	}else{
		var pos = null;
		try{
			if (typeof document.selection != 'undefined'){
				var range = document.selection.createRange();
				suggest_cursor = range.getBookmark();
				if (!range.text.length){
					pos = suggest_cursor.charCodeAt(2)-2;
				}
			}else if (suggest_typer.selectionStart == suggest_typer.selectionEnd){
				pos = suggest_typer.selectionStart;
			}
		} catch(e) {};
		if (pos !== null){
			suggest_timer = window.setTimeout('suggest_start('+suggest_count+','+pos+')', suggest_timeout);
		}
	}
	return true;
}

function suggest_clearpos()
{
	suggest_cursor = null;
	return true;
}

function suggest_dofocus()
{
	if (suggest_typer.value != suggest_default){
		suggest_typer.focus();
	}
}

function suggest_refocus(e)
{
	window.onkeyup = window.old_onkeyup;
	suggest_typer.focus();
	if (window.onkeyup) return window.onkeyup(e);
	return true;
}

function suggest_focus()
{
	try{
		suggest_typer.focused = true;
		if (suggest_typer.value == suggest_default){
			suggest_typer.value = '';
			suggest_cursor = null;
		}
		if (suggest_typer.className.indexOf(' suggest') == -1){
			suggest_typer.className += ' suggest';
		}
		suggest_restore();
		if (suggest_focusfn !== null) suggest_focusfn();
	}catch(e){}
	return true;
}

function suggest_blur()
{
	try{
		suggest_typer.focused = false;
		if (!suggest_typer.value.replace(/\s+/g,'')){
			suggest_typer.value = suggest_default;
			suggest_cursor = null;
		}
		if (suggest_typer.className.indexOf(' suggest') != -1){
			suggest_typer.className = suggest_typer.className.replace(/ suggest/g, '');
		}
		if (suggest_blurfn !== null) suggest_blurfn();
	}catch(e){}
	return true;
}

function suggest_restore(timed)
{
	if (suggest_popup){
		if (timed){
			if (suggest_cursor !== null && typeof document.selection != 'undefined'){
				var range = document.selection.createRange();
				range.moveToBookmark(suggest_cursor);
				range.select();
			}
		}else{
			if (suggest_cursor !== null && typeof document.selection != 'undefined' && suggest_typer.value.replace(/\s+/g,'')){
				window.setTimeout('suggest_restore(true)',0);
			}
		}
	}
}

function suggest_start(reqid, pos)
{
	suggest_timer = null;
	if (reqid == suggest_count && suggest_typer.value != suggest_default){
		var typed = suggest_typer.value.replace(/\s/g,' ');
		var repfr = typed.substring(0,pos).lastIndexOf(' ')+1;
		var word  = typed.substring(repfr, pos);
		var match = suggest_valid.exec(word);
		if (match && match[2].length >= suggest_chars){
			if (match[1]) repfr += match[1].length;
			var repto = typed.indexOf(' ',pos);
			if (repto<0) repto = typed.length;
			var quote = suggest_quote_e.exec(typed.substring(repfr, repto));
			if (quote) repto -= quote[1].length;
			var query = match[2];
			// check js cache
			for (var len=suggest_chars; len<=query.length; len++){
				var cached = suggest_cache[query.substr(0,len)];
				if (cached){
					if (len==query.length){
						return suggest_open(true, query, reqid, repfr, repto, cached.words, cached.complete);
					}else if (cached.complete){
						var extracted = Array();
						for (var cnt=0, len=query.length; cnt<cached.words.length; cnt++)
							if (cached.words[cnt].substr(0,len) == query)
								extracted[extracted.length] = cached.words[cnt];
						suggest_addcache(query, extracted, true);
						return suggest_open(true, query, reqid, repfr, repto, extracted, true);
					}
				}
			}
			// not cached or incomplete
			suggest_request(reqid, repfr, repto, query);
		}else{
			suggest_close();
		}
	}
}

function suggest_addcache(word, words, complete)
{
	suggest_cache._list.push(word);
	suggest_cache[word] = {
		'complete': complete,
		'words'   : words
	}
	if (suggest_cache._list.length > suggest_maxcache){
		delete(suggest_cache[suggest_cache._list.shift()]);
	}
}

function suggest_open(clip, query, reqid, repfr, repto, words, compl)
{
	var navi = false;
	if (clip !== true){
		if (typeof clip == 'number') navi = clip;
		query = suggest_popup.suggest;
		reqid = suggest_popup.reqid;
		repfr = suggest_popup.repfr;
		repto = suggest_popup.repto;
		words = suggest_cache[query].words;
		compl = suggest_cache[query].complete;
		clip  = false;
	}
	if (reqid != suggest_count) return false; // reqid changed - do nothing
	if (words.length && (words.length>1 || (words.length == 1 && (suggest_single || words[0] != query)))){
		suggest_popup.reqid = reqid;
		suggest_popup.repfr = repfr;
		suggest_popup.repto = repto;
		if (query != suggest_popup.suggest || (!clip && suggest_popup.clipped)){

			// (re)fill suggest popup
			var old = suggest_popup.childNodes;
			var end = clip?suggest_show:words.length;
			var widths = Array();
			for (var cnt=0; cnt<end; cnt++){
				if (words[cnt]){
					var word = suggest_create(words[cnt], cnt);
					if (old[cnt*2]){
						suggest_popup.replaceChild(word, old[cnt*2]);
					}else{
						suggest_popup.appendChild(word);
						suggest_popup.appendChild(document.createTextNode(' '));
					}
					word.wordwidth = word.offsetWidth;
					widths.push(word.offsetWidth);
				}else{
					break;
				}
			}

			widths.sort(numsort);
			suggest_popup.clipwidth = widths[Math.ceil((widths.length-1)*suggest_noclip/100)];
			suggest_popup.entries   = widths.length;

			var num = cnt;
			cnt = cnt*2;
			rem = old.length;
			while (cnt<rem){
				suggest_popup.removeChild(suggest_popup.lastChild);
				cnt++;
			}

			if (clip && words.length>end){
				var more = document.createElement('a');
				more.href = './';
				more.id = 'showmore';
				more.num = num;
				more.title = 'Mehr Vorschläge anzeigen...';
				more.onclick = suggest_open;
				more.onmouseover = suggest_navigate;
				more.onmouseout  = suggest_navigate;
				more.appendChild(document.createTextNode('[mehr]'));
				suggest_popup.appendChild(more);
				more.wordwidth = more.offsetWidth;
				suggest_popup.appendChild(document.createTextNode(' '));
			}else if (!compl){
				var more = document.createElement('span');
				more.id = 'showmore';
				more.title = 'Zu viele Vorschläge vorhanden.';
				more.appendChild(document.createTextNode('...'));
				suggest_popup.appendChild(more);
				suggest_popup.appendChild(document.createTextNode(' '));
			}

			suggest_popup.suggest = query;
			suggest_popup.clipped = clip;

			suggest_columnize();
			suggest_navigate(navi);
		}else if (!suggest_popup.opened){
			suggest_columnize();
			suggest_navigate(navi);
		}
		if (!suggest_popup.opened){
			suggest_popup.opened = true;
			suggest_popup.style.visibility = 'visible';
			if (navi===false) suggest_popup.scrollTop = 0;
		}
	}else{
		suggest_close();
	}
	// more clicked: focus input again
	if (!clip){
		suggest_typer.focus();
	}
	return false;
}

function suggest_columnize()
{
	suggest_popup.style.height = null; // delete fixed height

	if (suggest_popup.clipwidth){
		var have = suggest_popup.offsetWidth - 6 - 20; // !! 2*3 padding, 20 for scrollbar !!
		var cols = Math.min(suggest_popup.entries, Math.floor(have/suggest_popup.clipwidth));
		suggest_popup.columns = cols;
		var colwidth = Math.floor(have/cols)-5; // !! 5 is padding !!
		var nodes = suggest_popup.childNodes;
		for (var cnt=0, end=nodes.length; cnt<end; cnt++){
			var node = nodes[cnt];
			if (node.wordwidth){
				node.style.overflow = 'hidden';
				node.style.width = colwidth+'px';
				if (node.id != 'showmore'){
					node.title = (node.wordwidth>colwidth)?node.word:'';
				}
			}
		}
	}
	if (suggest_popup.offsetHeight > suggest_maxheight){
		suggest_popup.style.height = suggest_maxheight+'px';
	}
}

function suggest_create(word, cnt)
{
	var res = document.createElement('a');
	res.href = './';
	res.num = cnt;
	res.word = word;
	res.onclick = suggest_click;
	res.onmouseover = suggest_navigate;
	res.onmouseout  = suggest_navigate;
	res.appendChild(document.createTextNode(word));
	return res;
}

function suggest_click(e, obj)
{
	// prepare new query text
	var word  = obj?obj.word:this.word;
	var typed = suggest_typer.value.replace(/ +$/,'');
	var rest  = typed.substr(suggest_popup.repto);
	var quote = suggest_quote_s.exec(rest);
	if (quote){
		word += quote[1];
		rest = rest.substr(quote[1].length);
	}
	if (!rest.length || rest.charAt(0)!=' ') word += ' ';

	// replace text with clicked suggestion
	suggest_typer.value = typed.substr(0, suggest_popup.repfr) + word + rest;

	// set new cursor position
	var pos = suggest_popup.repfr + word.length;
	suggest_typer.focus();
	if (typeof document.selection != 'undefined'){
		var range = document.selection.createRange();
		range.move('character', -suggest_typer.value.length);
		range.move('character', pos);
		range.select();
		suggest_cursor = null;
	}else{
		suggest_typer.selectionStart = pos;
		suggest_typer.selectionEnd   = pos;
	}

	// close suggestion popup
	suggest_close();

	return false;
}

function suggest_close(nofocus)
{
	if (suggest_timer) suggest_timer = window.clearTimeout(suggest_timer);
	if (suggest_popup && suggest_popup.opened){
		suggest_popup.style.visibility = 'hidden';
		suggest_popup.opened = false;
		suggest_navigate(false);
		if (!nofocus) suggest_typer.focus();
	}
}

function suggest_request(reqid, repfr, repto, query)
{
	if (typeof XMLHttpRequest != 'undefined'){
		suggest_xmlhttp = new XMLHttpRequest();
	}else if (window.ActiveXObject){
		suggest_xmlhttp = new ActiveXObject('Msxml2.XMLHTTP');
	}
	suggest_xmlhttp.onreadystatechange = suggest_response;
	suggest_xmlhttp.open('POST', suggest_url+'?query='+escape(query)+'&from='+repfr+'&to='+repto+'&id='+reqid, true);
	suggest_xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
	suggest_xmlhttp.send('');
}

function suggest_response()
{
	if ((suggest_xmlhttp.readyState == 4) && (suggest_xmlhttp.status == 200) && (suggest_xmlhttp.responseText.length)){
		var words = suggest_xmlhttp.responseText.split(' ');
		if (words.length > 4){
			var reqid  = parseInt(words.shift());
			var repfr  = parseInt(words.shift());
			var repto  = parseInt(words.shift());
			var query  = words.shift();
			var incomp = parseInt(words.shift());
			suggest_addcache(query, words, !incomp);
			suggest_open(true, query, reqid, repfr, repto, words, !incomp);
		}
	}
}

function numsort(a, b)
{
	return a - b;
}

