/*
 * OpenSearch AJAX suggestion engine
 * by Robert Stojnic (April 2008)
 *
 * Copied from "http://upload.wikimedia.org/skins/common/mwsuggest.js?194xx"
 * Altered by Dolev Birkman
 */

var clientPC = navigator.userAgent.toLowerCase(); // Get client info
var is_khtml = navigator.vendor == 'KDE' ||
	(document.childNodes && !document.all && !navigator.taintEnabled);
var is_opera = (clientPC.indexOf ('opera') != -1);

// 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_last_keypress = 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('playerSearchInput', 'friendSearchInput', 'userSearchInput');
var os_autoload_forms = new Array('playerSearchForm', 'friendSearchForm', 'friendSearchForm');
// if we stopped the service
var os_is_stopped = false;
// max lines to show in suggest table
var os_max_lines_per_suggest = 5;
// 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 = 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.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
}

/* Hide results div */
function os_hideResults (r)
{
	var c = 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;
	os_fitContainer (r);
	var c = document.getElementById (r.container);
	r.selected = -1;
	if (c != null)
	{
		c.scrollTop = 0;
		c.style.visibility = "visible";
		r.visible = true;
	}
}

function os_operaWidthFix (x)
{
	// TODO: better css2 incompatibility detection here
	if (is_opera || is_khtml || navigator.userAgent.toLowerCase().indexOf('firefox/1') != -1)
		return 30; // opera&konqueror & old firefox don't understand overflow-x, estimate scrollbar width
	return 0;
}

/* 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 = 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());
}

/* Position the container div that will hold the suggested titles */
function os_positionContainer (r)
{
	var s = document.getElementById (r.searchbox);
	var pos = getElementPosition (s);
	var left = pos.left;
	var top = pos.top + s.offsetHeight;

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

/* Create the container div that will hold the suggested titles */
function os_createContainer (r)
{
	var c = document.createElement ("div");
	c.className = "suggest";
	c.setAttribute ("id", r.container);
	document.body.appendChild (c);

	os_positionContainer (r);

	// mouse event handlers
	c = document.getElementById (r.container);
	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 = 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 = document.getElementById (r.resultText + i);
		if (e.offsetWidth > maxW)
			maxW = e.offsetWidth;
	}
	var w = 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 = 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 = 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 = 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 = e.innerHTML;
			e.innerHTML = l.substring (0, l.length - replace) + "...";
			iteration++;
			replace = 4; // how many chars to replace
		}
		if (changedText)
		{
			// show hint for trimmed titles
			document.getElementById(r.resultTable + i).setAttribute ("title", r.results[i]);
		}
	}
}

/* Invoked on timer to animate change in container width */
function os_animateChangeWidth()
{
	var r = os_animation_timer.r;
	var c = document.getElementById(r.container);
	var w = c.offsetWidth;
	var normW = document.getElementById(r.searchbox).offsetWidth;
	var normL = 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 - nw - 1) + "px";
	}
}

/* Handles data from XMLHttpRequest, and updates the suggest results */
function os_updateResults (r, query, text, cacheKey)
{
	r.query = query;
 
 	// check if response is still relevant
	if (document.getElementById(r.searchbox).value != query)
		return;

	os_cache[cacheKey] = text;
	r.original = query;
	if (text == "")
	{
		r.results = null;
		r.resultCount = 0;
		os_hideResults (r);
	}
	else
	{
		try
		{
			var p = text.split ("|");
			var c = document.getElementById (r.container);
			if (c == null)
				c = os_createContainer (r);
			else
				os_positionContainer (r);
			c.innerHTML = os_createResultTable (r, p);
			// init container table sizes
			var t = 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);
			os_cache[cacheKey] = null;
		}
	}
}

/* Create the result table to be placed in the container div */
function os_createResultTable (r, results)
{
	var c = document.getElementById(r.container);
	var width = c.offsetWidth - os_operaWidthFix (c.offsetWidth);
	var html = "<table class=\"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 = decodeURIComponent (results[i]);
		r.results[i] = title;
		html += "<tr><td class=\"suggest_result\" id=\"" + r.resultTable + i + "\"><span id=\"" + r.resultText + i + "\">" + title + "</span></td></tr>";
	}
	html += "</table>"
	return html;
}

/* 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 = r.searchbox + encodeURIComponent (query);

	// try to get from cache, if not fetch using ajax
	var cached = os_cache[path];
	if (cached != null)
		os_updateResults (r, query, cached, path);
	else
		sajax_do_call ("suggest", [r.searchbox, query], function (httpRequest)
		 { os_updateResults (r, query, httpRequest.responseText, path); });
}

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

	if (query == r.query)
		return; // no change

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

	// 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 (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 = document.getElementById(r.resultTable + cur);
    	if (curRow != null)
    		curRow.className = "suggest_result";
    }
    var newText;
    if (next >= 0)
    {
    	var nextRow = document.getElementById(r.resultTable + next);
    	if (nextRow != null)
    		nextRow.className = "suggest_result_hl";
    	newText = r.results[next];
    }
    else
    	newText = r.original;

    // adjust the scrollbar if any
    if (r.containerCount < r.resultCount)
    {
    	var c = 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_updateSearchQuery (r, newText)
{
	document.getElementById(r.searchbox).value = newText;
    r.query = newText;
}

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

/* Event handler that will fetch results on keyup */
function os_eventKeyup (e)
{
	var targ = getTarget (e);
	var r = 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);

	os_fetchResults (r, targ.value, 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, 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
		  		document.getElementById(r.searchbox).value = r.original;
		  		r.query = r.original;
		  		os_hideResults (r);
		  	}
		  	else
		  		if (r.query != document.getElementById(r.searchbox).value)
		  		{
			  		// os_hideResults (r); // don't show old suggestions
  				}
}

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

	var keypressed = os_cur_keypressed;
	if (keypressed == 38 || keypressed == 40)
	{
		var d = new Date()
		var now = d.getTime();
		if (now - os_last_keypress < 120)
		{
			os_last_keypress = now;
			return;
		}
	}

	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 = getTarget (e);
	var r = 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_last_keypress = 0;
	os_keypressed_count = 0;
}

/* Event: loss of focus of input box */
function os_eventBlur (e)
{
	var targ = getTarget (e);
	var r = os_map[targ.id];
	if (r == null)
		return; // not our event
	if (!os_mouse_pressed)
		os_hideResults (r);
}

/* Event: focus (catch only when stopped) */
function os_eventFocus (e)
{
	// nothing happens here?
}

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

/* Mouse over the container */
function os_eventMouseover (srcId, e)
{
	var targ = getTarget (e);
	var r = 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 = getTarget (e);
	var r = 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
	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 = getTarget (e);
	var r = os_map[srcId];
	if (r == null)
		return; // not our event
	var num = os_getNumberSuffix (targ.id);

	if (num >= 0 && os_mouse_num == num)
	{
		document.getElementById(r.searchbox).focus();
		os_updateSearchQuery (r, r.results[num]);
		moveCursorEnd (document.getElementById (r.searchbox));
		os_hideResults (r);
//		document.getElementById(r.searchform).submit();
	}
	os_mouse_pressed = false;
	// keep the focus on the search field
	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 = 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 = os_map[os_autoload_inputs[i]];
		if (r != null)
		{
			var b = document.getElementById(r.searchform);
			if (b != null && b == targ)
			{
				// set query value so the handler won't try to fetch additional results
				r.query = document.getElementById(r.searchbox).value;
			}
			os_hideResults (r);
		}
	}
}

/* Init Result objects and event handlers */
function os_initHandlers (name, formname, element)
{
	var r = new os_Results (name, formname);
	// event handler
	hookEvent (element, "keyup", function (event) { os_eventKeyup (event); });
	hookEvent (element, "keydown", function (event) { os_eventKeydown (event); });
	hookEvent (element, "keypress", function (event) { os_eventKeypress (event); });
	hookEvent (element, "blur", function (event) { os_eventBlur (event); });
	hookEvent (element, "focus", function (event) { os_eventFocus (event); });
	element.setAttribute ("autocomplete", "off");
	// stopping handler
	hookEvent (document.getElementById(formname), "submit",
	 function (event) { os_eventOnsubmit (event); });
	os_map[name] = r;
}

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

hookEvent (window, "load", os_MWSuggestInit);