/*
 * OpenSearch ajax suggestion engine for MediaWiki
 *
 * uses core MediaWiki open search support to fetch suggestions
 * and show them below search boxes and other inputs
 *
 * by Robert Stojnic (April 2008)
 */

// search_box_id -> Results object
var os_map = {};
// cached data, url -> json_text
var os_cache = {};
// global variables for suggest_keypress
var os_cur_keypressed = 0;
var os_keypressed_count = 0;
// type: Timer
var os_timer = null;
// tie mousedown/up events
var os_mouse_pressed = false;
var os_mouse_num = -1;
// if true, the last change was made by mouse (and not keyboard)
var os_mouse_moved = false;
// delay between keypress and suggestion (in ms)
var os_search_timeout = 250;
// these pairs of inputs/forms will be autoloaded at startup
var os_autoload_inputs = new Array('searchInput', 'searchInput2', 'powerSearchText', 'searchText');
var os_autoload_forms = new Array('searchform', 'searchform2', 'powersearch', 'search' );
// if we stopped the service
var os_is_stopped = false;
// max lines to show in suggest table
var os_max_lines_per_suggest = 7;
// number of steps to animate expansion/contraction of container width
var os_animation_steps = 6;
// num of pixels of smallest step
var os_animation_min_step = 2;
// delay between steps (in ms)
var os_animation_delay = 30;
// max width of container in percent of normal size (1 == 100%)
var os_container_max_width = 2;
// currently active animation timer
var os_animation_timer = null;

/** Timeout timer class that will fetch the results */
function os_Timer(id,r,query) {
	this.id = id;
	this.r = r;
	this.query = query;
}

/** Timer user to animate expansion/contraction of container width */
function os_AnimationTimer(r, target) {
	this.r = r;
	var current = _proxy_jslib_handle(document, 'getElementById')(r.container).offsetWidth;
	this.inc = Math.round((target-current) / os_animation_steps);
	if(this.inc < os_animation_min_step && this.inc >=0)
		this.inc = os_animation_min_step; // minimal animation step
	if(this.inc > -os_animation_min_step && this.inc <0)
		this.inc = -os_animation_min_step;
	this.target = target;
}

/** Property class for single search box */
function os_Results(name, formname) {
	this.searchform = formname; // id of the searchform
	this.searchbox = name; // id of the searchbox
	this.container = name+"Suggest"; // div that holds results
	this.resultTable = name+"Result"; // id base for the result table (+num = table row)
	this.resultText = name+"ResultText"; // id base for the spans within result tables (+num)
	this.toggle = name+"Toggle"; // div that has the toggle (enable/disable) link
	this.query = null; // last processed query
	this.results = null;  // parsed titles
	this.resultCount = 0; // number of results
	this.original = null; // query that user entered
	this.selected = -1; // which result is selected
	this.containerCount = 0; // number of results visible in container
	this.containerRow = 0; // height of result field in the container
	this.containerTotal = 0; // total height of the container will all results
	this.visible = false; // if container is visible
	this.stayHidden = false; // don't try to show if lost focus
}

/** Hide results div */
function os_hideResults(r) {
	var c = _proxy_jslib_handle(document, 'getElementById')(r.container);
	if(c != null)
		c.style.visibility = "hidden";
	r.visible = false;
	r.selected = -1;
}

/** Show results div */
function os_showResults(r) {
	if(os_is_stopped)
		return;
	if(r.stayHidden)
		return
	os_fitContainer(r);
	var c = _proxy_jslib_handle(document, 'getElementById')(r.container);
	r.selected = -1;
	if(c != null){
		c.scrollTop = 0;
		c.style.visibility = "visible";
		r.visible = true;
	}
}

function os_operaWidthFix(x) {
	// For browsers that don't understand overflow-x, estimate scrollbar width
	if(typeof document.body.style.overflowX != "string"){
		return 30;
	}
	return 0;
}

function os_encodeQuery(value) {
  if (encodeURIComponent) {
    return encodeURIComponent(_proxy_jslib_handle(null, 'value', value));
  }
  if(escape) {
    return escape(_proxy_jslib_handle(null, 'value', value));
  }
  return null;
}
function os_decodeValue(value) {
  if (decodeURIComponent) {
    return decodeURIComponent(_proxy_jslib_handle(null, 'value', value));
  }
  if(unescape){
  	return unescape(_proxy_jslib_handle(null, 'value', value));
  }
  return null;
}

/** Brower-dependent functions to find window inner size, and scroll status */
function f_clientWidth() {
	return f_filterResults (
		window.innerWidth ? window.innerWidth : 0,
		document.documentElement ? document.documentElement.clientWidth : 0,
		document.body ? document.body.clientWidth : 0
	);
}
function f_clientHeight() {
	return f_filterResults (
		window.innerHeight ? window.innerHeight : 0,
		document.documentElement ? document.documentElement.clientHeight : 0,
		document.body ? document.body.clientHeight : 0
	);
}
function f_scrollLeft() {
	return f_filterResults (
		window.pageXOffset ? window.pageXOffset : 0,
		document.documentElement ? document.documentElement.scrollLeft : 0,
		document.body ? document.body.scrollLeft : 0
	);
}
function f_scrollTop() {
	return f_filterResults (
		window.pageYOffset ? window.pageYOffset : 0,
		document.documentElement ? document.documentElement.scrollTop : 0,
		document.body ? document.body.scrollTop : 0
	);
}
function f_filterResults(n_win, n_docel, n_body) {
	var n_result = n_win ? n_win : 0;
	if (n_docel && (!n_result || (n_result > n_docel)))
		n_result = n_docel;
	return n_body && (!n_result || (n_result > n_body)) ? n_body : n_result;
}

/** Get the height available for the results container */
function os_availableHeight(r) {
	var absTop = _proxy_jslib_handle(_proxy_jslib_handle(document, 'getElementById')(r.container).style, 'top');
	var px = absTop.lastIndexOf("px");
	if(px > 0)
		absTop = absTop.substring(0,px);
	return f_clientHeight() - (absTop - f_scrollTop());
}


/** Get element absolute position {left,top} */
function os_getElementPosition(elemID) {
	var offsetTrail = _proxy_jslib_handle(document, 'getElementById')(elemID);
	var offsetLeft = 0;
	var offsetTop = 0;
	while (offsetTrail){
		offsetLeft += offsetTrail.offsetLeft;
		offsetTop += offsetTrail.offsetTop;
		offsetTrail = offsetTrail.offsetParent;
	}
	if (navigator.userAgent.indexOf('Mac') != -1 && typeof document.body.leftMargin != 'undefined'){
		offsetLeft += document.body.leftMargin;
		offsetTop += document.body.topMargin;
	}
	return {left:offsetLeft,top:offsetTop};
}

/** Create the container div that will hold the suggested titles */
function os_createContainer(r) {
	var c = document.createElement("div");
	var s = _proxy_jslib_handle(document, 'getElementById')(r.searchbox);
	var pos = os_getElementPosition(r.searchbox);
	var left = pos.left;
	var top = _proxy_jslib_handle(pos, 'top') + s.offsetHeight;
	c.className = "os-suggest";
	_proxy_jslib_handle(c, 'setAttribute')("id", r.container);
	_proxy_jslib_handle(document.body, 'appendChild')(c);

	// dynamically generated style params
	// IE workaround, cannot explicitely set "style" attribute
	c = _proxy_jslib_handle(document, 'getElementById')(r.container);
	_proxy_jslib_assign('', c.style, 'top', '=', ( _proxy_jslib_handle(null, 'top', top)+"px"));
	c.style.left = left+"px";
	c.style.width = s.offsetWidth+"px";

	// mouse event handlers
	c.onmouseover = function(event) { os_eventMouseover(r.searchbox, event); };
	c.onmousemove = function(event) { os_eventMousemove(r.searchbox, event); };
	c.onmousedown = function(event) { return os_eventMousedown(r.searchbox, event); };
	c.onmouseup = function(event) { os_eventMouseup(r.searchbox, event); };
	return c;
}

/** change container height to fit to screen */
function os_fitContainer(r) {
	var c = _proxy_jslib_handle(document, 'getElementById')(r.container);
	var h = os_availableHeight(r) - 20;
	var inc = r.containerRow;
	h = parseInt(h/inc) * inc;
	if(h < (2 * inc) && r.resultCount > 1) // min: two results
		h = 2 * inc;
	if((h/inc) > os_max_lines_per_suggest )
		h = inc * os_max_lines_per_suggest;
	if(h < r.containerTotal){
		c.style.height = h +"px";
		r.containerCount = parseInt(Math.round(h/inc));
	} else{
		c.style.height = r.containerTotal+"px";
		r.containerCount = r.resultCount;
	}
}
/** If some entries are longer than the box, replace text with "..." */
function os_trimResultText(r) {
	// find max width, first see if we could expand the container to fit it
	var maxW = 0;
	for(var i=0;i<r.resultCount;i++){
		var e = _proxy_jslib_handle(document, 'getElementById')(r.resultText+i);
		if(e.offsetWidth > maxW)
			maxW = e.offsetWidth;
	}
	var w = _proxy_jslib_handle(document, 'getElementById')(r.container).offsetWidth;
	var fix = 0;
	if(r.containerCount < r.resultCount){
		fix = 20; // give 20px for scrollbar
	} else
		fix = os_operaWidthFix(w);
	if(fix < 4)
		fix = 4; // basic padding
	maxW += fix;

	// resize container to fit more data if permitted
	var normW = _proxy_jslib_handle(document, 'getElementById')(r.searchbox).offsetWidth;
	var prop = maxW / normW;
	if(prop > os_container_max_width)
		prop = os_container_max_width;
	else if(prop < 1)
		prop = 1;
	var newW = Math.round( normW * prop );
	if( w != newW ){
		w = newW;
		if( os_animation_timer != null )
			clearInterval(os_animation_timer.id)
		os_animation_timer = new os_AnimationTimer(r,w);
		os_animation_timer.id = _proxy_jslib_handle(null, 'setInterval', setInterval)("os_animateChangeWidth()",os_animation_delay);
		w -= fix; // this much is reserved
	}

	// trim results
	if(w < 10)
		return;
	for(var i=0;i<r.resultCount;i++){
		var e = _proxy_jslib_handle(document, 'getElementById')(r.resultText+i);
		var replace = 1;
		var lastW = e.offsetWidth+1;
		var iteration = 0;
		var changedText = false;
		while(e.offsetWidth > w && (e.offsetWidth < lastW || iteration<2)){
			changedText = true;
			lastW = e.offsetWidth;
			var l = _proxy_jslib_handle(e, 'innerHTML');
			_proxy_jslib_assign('', e, 'innerHTML', '=', ( l.substring(0,l.length-_proxy_jslib_handle(null, 'replace', replace))+"..."));
			iteration++;
			replace= _proxy_jslib_assign_rval('', 'replace', '=', ( 4), replace); // how many chars to replace
		}
		if(changedText){
			// show hint for trimmed titles
			_proxy_jslib_handle(_proxy_jslib_handle(document, 'getElementById')(r.resultTable+i), 'setAttribute')("title",_proxy_jslib_handle(r.results, (i)));
		}
	}
}

/** Invoked on timer to animate change in container width */
function os_animateChangeWidth() {
	var r = os_animation_timer.r;
	var c = _proxy_jslib_handle(document, 'getElementById')(r.container);
	var w = c.offsetWidth;
	var normW = _proxy_jslib_handle(document, 'getElementById')(r.searchbox).offsetWidth;
	var normL = os_getElementPosition(r.searchbox).left;
	var inc = os_animation_timer.inc;
	var target = os_animation_timer.target;
	var nw = w + inc;
	if( (inc > 0 && nw >= target) || (inc <= 0 && nw <= target) ){
		// finished !
		c.style.width = target+"px";
		clearInterval(os_animation_timer.id)
		os_animation_timer = null;
	} else{
		// in-progress
		c.style.width = nw+"px";
		if(document.documentElement.dir == "rtl")
			c.style.left = (normL + normW + (target - nw) - os_animation_timer.target - 1)+"px";
	}
}

/** Handles data from XMLHttpRequest, and updates the suggest results */
function os_updateResults(r, query, text, cacheKey) {
	_proxy_jslib_assign('', os_cache, (cacheKey), '=', ( text));
	r.query = query;
	r.original = query;
	if(text == ""){
		r.results = null;
		r.resultCount = 0;
		os_hideResults(r);
	} else{
		try {
			var p = eval(_proxy_jslib_proxify_js('('+text+')')); // simple json parse, could do a safer one
			if(p.length<2 || p[1].length == 0){
				r.results = null;
				r.resultCount = 0;
				os_hideResults(r);
				return;
			}
			var c = _proxy_jslib_handle(document, 'getElementById')(r.container);
			if(c == null)
				c = os_createContainer(r);
			_proxy_jslib_assign('', c, 'innerHTML', '=', ( os_createResultTable(r,p[1])));
			// init container table sizes
			var t = _proxy_jslib_handle(document, 'getElementById')(r.resultTable);
			r.containerTotal = t.offsetHeight;
			r.containerRow = t.offsetHeight / r.resultCount;
			os_fitContainer(r);
			os_trimResultText(r);
			os_showResults(r);
		} catch(e){
			// bad response from server or such
			os_hideResults(r);
			_proxy_jslib_assign('', os_cache, (cacheKey), '=', ( null));
		}
	}
}

/** Create the result table to be placed in the container div */
function os_createResultTable(r, results) {
	var c = _proxy_jslib_handle(document, 'getElementById')(r.container);
	var width = c.offsetWidth - os_operaWidthFix(c.offsetWidth);
	var html = "<table class=\"os-suggest-results\" id=\""+r.resultTable+"\" style=\"width: "+width+"px;\">";
	r.results = new Array();
	r.resultCount = results.length;
	for(i=0;i<results.length;i++){
		var title = os_decodeValue(_proxy_jslib_handle(results, (i)));
		_proxy_jslib_assign('', r.results, (i), '=', ( title));
		html += "<tr><td class=\"os-suggest-result\" id=\""+r.resultTable+i+"\"><span id=\""+r.resultText+i+"\">"+title+"</span></td></tr>";
	}
	html+="</table>"
	return html;
}

/** Fetch namespaces from checkboxes or hidden fields in the search form,
    if none defined use wgSearchNamespaces global */
function os_getNamespaces(r) {
	var namespaces = "";
	var elements = document.forms[(r.searchform)].elements;
	for(i=0; i < elements.length; i++){
		var name = _proxy_jslib_handle(elements, (i)).name;
		if(typeof name != 'undefined' && name.length > 2
		&& name[0]=='n' && name[1]=='s'
		&& ((_proxy_jslib_handle(elements, (i)).type=='checkbox' && _proxy_jslib_handle(elements, (i)).checked)
		 	|| (_proxy_jslib_handle(elements, (i)).type=='hidden' && _proxy_jslib_handle(_proxy_jslib_handle(elements, (i)), 'value')=="1")) ){
			if(namespaces!="")
				namespaces+="|";
			namespaces+=name.substring(2);
		}
	}
	if(namespaces == "")
		namespaces = wgSearchNamespaces.join("|");
	return namespaces;
}

/** Update results if user hasn't already typed something else */
function os_updateIfRelevant(r, query, text, cacheKey) {
	var t = _proxy_jslib_handle(document, 'getElementById')(r.searchbox);
	if(t != null && _proxy_jslib_handle(t, 'value') == query){ // check if response is still relevant
		os_updateResults(r, query, text, cacheKey);
	}
	r.query = query;
}

/** Fetch results after some timeout */
function os_delayedFetch() {
	if(os_timer == null)
		return;
	var r = os_timer.r;
	var query = os_timer.query;
	os_timer = null;
	var path = _proxy_jslib_handle(_proxy_jslib_handle(_proxy_jslib_handle(wgMWSuggestTemplate, 'replace')("{namespaces}",os_getNamespaces(r))
							  	  , 'replace')("{dbname}",wgDBname)
							  	  , 'replace')("{searchTerms}",os_encodeQuery(query));

	// try to get from cache, if not fetch using ajax
	var cached = _proxy_jslib_handle(os_cache, (path));
	if(cached != null){
		os_updateIfRelevant(r, query, cached, path);
	} else{
		var xmlhttp = sajax_init_object();
		if(xmlhttp){
			try {
				_proxy_jslib_handle(xmlhttp, 'open')("GET", path, true);
				xmlhttp.onreadystatechange=function() {
		        	if (xmlhttp.readyState==4 && typeof os_updateIfRelevant == 'function') {
		        		os_updateIfRelevant(r, query, xmlhttp.responseText, path);
	        		}
	      		};
	     		xmlhttp.send(null);
	     	} catch (e) {
				if (_proxy_jslib_handle(_proxy_jslib_handle(window, 'location'), 'hostname') == "localhost") {
					alert("Your browser blocks XMLHttpRequest to 'localhost', try using a real hostname for development/testing.");
				}
				throw e;
			}
		}
	}
}

/** Init timed update via os_delayedUpdate() */
function os_fetchResults(r, query, timeout) {
	if(query == ""){
		r.query = "";
		os_hideResults(r);
		return;
	} else if(query == r.query)
		return; // no change

	os_is_stopped = false; // make sure we're running

	/* var cacheKey = wgDBname+":"+query;
	var cached = os_cache[cacheKey];
	if(cached != null){
		os_updateResults(r,wgDBname,query,cached);
		return;
	} */

	// cancel any pending fetches
	if(os_timer != null && os_timer.id != null)
		clearTimeout(os_timer.id);
	// schedule delayed fetching of results
	if(timeout != 0){
		os_timer = new os_Timer(_proxy_jslib_handle(null, 'setTimeout', setTimeout)("os_delayedFetch()",timeout),r,query);
	} else{
		os_timer = new os_Timer(null,r,query);
		os_delayedFetch(); // do it now!
	}

}
/** Change the highlighted row (i.e. suggestion), from position cur to next */
function os_changeHighlight(r, cur, next, updateSearchBox) {
	if (next >= r.resultCount)
		next = r.resultCount-1;
	if (next < -1)
		next = -1;
	r.selected = next;
   	if (cur == next)
    	return; // nothing to do.

    if(cur >= 0){
    	var curRow = _proxy_jslib_handle(document, 'getElementById')(r.resultTable + cur);
    	if(curRow != null)
    		curRow.className = "os-suggest-result";
    }
    var newText;
    if(next >= 0){
    	var nextRow = _proxy_jslib_handle(document, 'getElementById')(r.resultTable + next);
    	if(nextRow != null)
    		nextRow.className = os_HighlightClass();
    	newText = _proxy_jslib_handle(r.results, (next));
    } else
    	newText = r.original;

    // adjust the scrollbar if any
    if(r.containerCount < r.resultCount){
    	var c = _proxy_jslib_handle(document, 'getElementById')(r.container);
    	var vStart = c.scrollTop / r.containerRow;
    	var vEnd = vStart + r.containerCount;
    	if(next < vStart)
    		c.scrollTop = next * r.containerRow;
    	else if(next >= vEnd)
    		c.scrollTop = (next - r.containerCount + 1) * r.containerRow;
    }

    // update the contents of the search box
    if(updateSearchBox){
    	os_updateSearchQuery(r,newText);
    }
}

function os_HighlightClass() {
	var match = navigator.userAgent.match(/AppleWebKit\/(\d+)/);
	if (match) {
		var webKitVersion = parseInt(match[1]);
		if (webKitVersion < 523) {
			// CSS system highlight colors broken on old Safari
			// https://bugs.webkit.org/show_bug.cgi?id=6129
			// Safari 3.0.4, 3.1 known ok
			return "os-suggest-result-hl-webkit";
		}
	}
	return "os-suggest-result-hl";
}

function os_updateSearchQuery(r,newText) {
	_proxy_jslib_assign('', _proxy_jslib_handle(document, 'getElementById')(r.searchbox), 'value', '=', ( newText));
    r.query = newText;
}

/** Find event target */
function os_getTarget(e) {
	if (!e) e = window.event;
	if (e.target) return e.target;
	else if (e.srcElement) return e.srcElement;
	else return null;
}



/********************
 *  Keyboard events
 ********************/

/** Event handler that will fetch results on keyup */
function os_eventKeyup(e) {
	var targ = os_getTarget(e);
	var r = _proxy_jslib_handle(os_map, (targ.id));
	if(r == null)
		return; // not our event

	// some browsers won't generate keypressed for arrow keys, catch it
	if(os_keypressed_count == 0){
		os_processKey(r,os_cur_keypressed,targ);
	}
	var query = _proxy_jslib_handle(targ, 'value');
	os_fetchResults(r,query,os_search_timeout);
}

/** catch arrows up/down and escape to hide the suggestions */
function os_processKey(r,keypressed,targ) {
	if (keypressed == 40){ // Arrow Down
    	if (r.visible) {
      		os_changeHighlight(r, r.selected, r.selected+1, true);
    	} else if(os_timer == null){
    		// user wants to get suggestions now
    		r.query = "";
			os_fetchResults(r,_proxy_jslib_handle(targ, 'value'),0);
    	}
  	} else if (keypressed == 38){ // Arrow Up
  		if (r.visible){
  			os_changeHighlight(r, r.selected, r.selected-1, true);
  		}
  	} else if(keypressed == 27){ // Escape
  		_proxy_jslib_assign('', _proxy_jslib_handle(document, 'getElementById')(r.searchbox), 'value', '=', ( r.original));
  		r.query = r.original;
  		os_hideResults(r);
  	} else if(r.query != _proxy_jslib_handle(_proxy_jslib_handle(document, 'getElementById')(r.searchbox), 'value')){
  		// os_hideResults(r); // don't show old suggestions
  	}
}

/** When keys is held down use a timer to output regular events */
function os_eventKeypress(e) {
	var targ = os_getTarget(e);
	var r = _proxy_jslib_handle(os_map, (targ.id));
	if(r == null)
		return; // not our event

	var keypressed = os_cur_keypressed;

	os_keypressed_count++;
	os_processKey(r,keypressed,targ);
}

/** Catch the key code (Firefox bug)  */
function os_eventKeydown(e) {
	if (!e) e = window.event;
	var targ = os_getTarget(e);
	var r = _proxy_jslib_handle(os_map, (targ.id));
	if(r == null)
		return; // not our event

	os_mouse_moved = false;

	os_cur_keypressed = (e.keyCode == undefined) ? e.which : e.keyCode;
	os_keypressed_count = 0;
}

/** Event: loss of focus of input box */
function os_eventBlur(e) {
	var targ = os_getTarget(e);
	var r = _proxy_jslib_handle(os_map, (targ.id));
	if(r == null)
		return; // not our event
	if(!os_mouse_pressed){
		os_hideResults(r);
		// force canvas to stay hidden
		r.stayHidden = true
		// cancel any pending fetches
		if(os_timer != null && os_timer.id != null)
			clearTimeout(os_timer.id);
		os_timer = null
	}
}

/** Event: focus (catch only when stopped) */
function os_eventFocus(e) {
	var targ = os_getTarget(e);
	var r = _proxy_jslib_handle(os_map, (targ.id));
	if(r == null)
		return; // not our event
	r.stayHidden = false
}



/********************
 *  Mouse events
 ********************/

/** Mouse over the container */
function os_eventMouseover(srcId, e) {
	var targ = os_getTarget(e);
	var r = _proxy_jslib_handle(os_map, (srcId));
	if(r == null || !os_mouse_moved)
		return; // not our event
	var num = os_getNumberSuffix(targ.id);
	if(num >= 0)
		os_changeHighlight(r,r.selected,num,false);

}

/* Get row where the event occured (from its id) */
function os_getNumberSuffix(id) {
	var num = id.substring(id.length-2);
	if( ! (num.charAt(0) >= '0' && num.charAt(0) <= '9') )
		num = num.substring(1);
	if(os_isNumber(num))
		return parseInt(num);
	else
		return -1;
}

/** Save mouse move as last action */
function os_eventMousemove(srcId, e) {
	os_mouse_moved = true;
}

/** Mouse button held down, register possible click  */
function os_eventMousedown(srcId, e) {
	var targ = os_getTarget(e);
	var r = _proxy_jslib_handle(os_map, (srcId));
	if(r == null)
		return; // not our event
	var num = os_getNumberSuffix(targ.id);

	os_mouse_pressed = true;
	if(num >= 0){
		os_mouse_num = num;
		// os_updateSearchQuery(r,r.results[num]);
	}
	// keep the focus on the search field
	_proxy_jslib_handle(document, 'getElementById')(r.searchbox).focus();

	return false; // prevents selection
}

/** Mouse button released, check for click on some row */
function os_eventMouseup(srcId, e) {
	var targ = os_getTarget(e);
	var r = _proxy_jslib_handle(os_map, (srcId));
	if(r == null)
		return; // not our event
	var num = os_getNumberSuffix(targ.id);

	if(num >= 0 && os_mouse_num == num){
		os_updateSearchQuery(r,_proxy_jslib_handle(r.results, (num)));
		os_hideResults(r);
		_proxy_jslib_handle(document, 'getElementById')(r.searchform).submit();
	}
	os_mouse_pressed = false;
	// keep the focus on the search field
	_proxy_jslib_handle(document, 'getElementById')(r.searchbox).focus();
}

/** Check if x is a valid integer */
function os_isNumber(x) {
	if(x == "" || isNaN(x))
		return false;
	for(var i=0;i<x.length;i++){
		var c = x.charAt(i);
		if( ! (c >= '0' && c <= '9') )
			return false;
	}
	return true;
}


/** When the form is submitted hide everything, cancel updates... */
function os_eventOnsubmit(e) {
	var targ = os_getTarget(e);

	os_is_stopped = true;
	// kill timed requests
	if(os_timer != null && os_timer.id != null){
		clearTimeout(os_timer.id);
		os_timer = null;
	}
	// Hide all suggestions
	for(i=0;i<os_autoload_inputs.length;i++){
		var r = _proxy_jslib_handle(os_map, (_proxy_jslib_handle(os_autoload_inputs, (i))));
		if(r != null){
			var b = _proxy_jslib_handle(document, 'getElementById')(r.searchform);
			if(b != null && b == targ){
				// set query value so the handler won't try to fetch additional results
				r.query = _proxy_jslib_handle(_proxy_jslib_handle(document, 'getElementById')(r.searchbox), 'value');
			}
			os_hideResults(r);
		}
	}
	return true;
}

function os_hookEvent(element, hookName, hookFunct) {
	if (element.addEventListener) {
		element.addEventListener(hookName, hookFunct, false);
	} else if (window.attachEvent) {
		element.attachEvent("on" + hookName, hookFunct);
	}
}

/** Init Result objects and event handlers */
function os_initHandlers(name, formname, element) {
	var r = new os_Results(name, formname);
	// event handler
	os_hookEvent(element, "keyup", function(event) { os_eventKeyup(event); });
	os_hookEvent(element, "keydown", function(event) { os_eventKeydown(event); });
	os_hookEvent(element, "keypress", function(event) { os_eventKeypress(event); });
	os_hookEvent(element, "blur", function(event) { os_eventBlur(event); });
	os_hookEvent(element, "focus", function(event) { os_eventFocus(event); });
	_proxy_jslib_handle(element, 'setAttribute')("autocomplete","off");
	// stopping handler
	os_hookEvent(_proxy_jslib_handle(document, 'getElementById')(formname), "submit", function(event) { return os_eventOnsubmit(event); });
	_proxy_jslib_assign('', os_map, (name), '=', ( r));
	// toggle link
	if(_proxy_jslib_handle(document, 'getElementById')(r.toggle) == null){
		// TODO: disable this while we figure out a way for this to work in all browsers
		/* if(name=='searchInput'){
			// special case: place above the main search box
			var t = os_createToggle(r,"os-suggest-toggle");
			var searchBody = document.getElementById('searchBody');
			var first = searchBody.parentNode.firstChild.nextSibling.appendChild(t);
		} else{
			// default: place below search box to the right
			var t = os_createToggle(r,"os-suggest-toggle-def");
			var top = element.offsetTop + element.offsetHeight;
			var left = element.offsetLeft + element.offsetWidth;
			t.style.position = "absolute";
			t.style.top = top + "px";
			t.style.left = left + "px";
			element.parentNode.appendChild(t);
			// only now width gets calculated, shift right
			left -= t.offsetWidth;
			t.style.left = left + "px";
			t.style.visibility = "visible";
		} */
	}

}

/** Return the span element that contains the toggle link */
function os_createToggle(r,className) {
	var t = document.createElement("span");
	t.className = className;
	_proxy_jslib_handle(t, 'setAttribute')("id", r.toggle);
	var link = document.createElement("a");
	_proxy_jslib_handle(link, 'setAttribute')("href","javascript:void(0);");
	link.onclick = function() { os_toggle(r.searchbox,r.searchform) };
	var msg = document.createTextNode(wgMWSuggestMessages[0]);
	_proxy_jslib_handle(link, 'appendChild')(msg);
	_proxy_jslib_handle(t, 'appendChild')(link);
	return t;
}

/** Call when user clicks on some of the toggle links */
function os_toggle(inputId,formName) {
	r = _proxy_jslib_handle(os_map, (inputId));
	var msg = '';
	if(r == null){
		os_enableSuggestionsOn(inputId,formName);
		r = _proxy_jslib_handle(os_map, (inputId));
		msg = wgMWSuggestMessages[0];
	} else{
		os_disableSuggestionsOn(inputId,formName);
		msg = wgMWSuggestMessages[1];
	}
	// change message
	var link = _proxy_jslib_handle(document, 'getElementById')(r.toggle).firstChild;
	_proxy_jslib_handle(link, 'replaceChild')(document.createTextNode(msg),link.firstChild);
}

/** Call this to enable suggestions on input (id=inputId), on a form (name=formName) */
function os_enableSuggestionsOn(inputId, formName) {
	os_initHandlers( inputId, formName, _proxy_jslib_handle(document, 'getElementById')(inputId) );
}

/** Call this to disable suggestios on input box (id=inputId) */
function os_disableSuggestionsOn(inputId) {
	r = _proxy_jslib_handle(os_map, (inputId));
	if(r != null){
		// cancel/hide results
		os_timer = null;
		os_hideResults(r);
		// turn autocomplete on !
		_proxy_jslib_handle(_proxy_jslib_handle(document, 'getElementById')(inputId), 'setAttribute')("autocomplete","on");
		// remove descriptor
		_proxy_jslib_assign('', os_map, (inputId), '=', ( null));
	}
	
	// Remove the element from the os_autoload_* arrays
	var index = os_autoload_inputs.indexOf(inputId);
	if ( index >= 0 )
		_proxy_jslib_assign('', os_autoload_inputs, (index), '=', ( _proxy_jslib_assign('', os_autoload_forms, (index), '=', ( ''))));
}

/** Initialization, call upon page onload */
function os_MWSuggestInit() {
	for(i=0;i<os_autoload_inputs.length;i++){
		var id = _proxy_jslib_handle(os_autoload_inputs, (i));
		var form = _proxy_jslib_handle(os_autoload_forms, (i));
		element = _proxy_jslib_handle(document, 'getElementById')( id );
		if(element != null)
			os_initHandlers(id,form,element);
	}
}

hookEvent("load", os_MWSuggestInit);
 ;
_proxy_jslib_flush_write_buffers() ;