//very very basic server browser.
//this was written as an example for the api, rather than a truely usable server browser.

//WARNING: make sure you only create one server browser widget otherwise they'll fight each other

#ifndef MOD_GAMEDIR
//look up what its meant to be.
//#define MOD_GAMEDIR cvar_string("game")
#endif

#include "../menusys/mitem_menu.qc"		//subwindow

var float autocvar_sb_shownumplayers = 1;
var float autocvar_sb_showmaxplayers = 0;
var float autocvar_sb_showfraglimit = 0;
var float autocvar_sb_showtimelimit = 0;
var float autocvar_sb_showping = 1;
var float autocvar_sb_showgamedir = 0;
var float autocvar_sb_showaddress = 0;
var float autocvar_sb_showmap = 1;
var float autocvar_sb_showname = 1;

//#define COLUMN(width, sortname, title, draw)
#define COLUMN_NUMPLAYERS		COLUMN(2*8,	numplayers,	"Pl", 			ui.drawstring(pos, sprintf("%-2g", gethostcachenumber(field_numplayers, sv)), '8 8', col, 1, 0);)
#define COLUMN_MAXPLAYERS		COLUMN(2*8,	maxplayers,	"MP", 		ui.drawstring(pos, sprintf("%-2g", gethostcachenumber(field_maxplayers, sv)), '8 8', col, 1, 0);)
#define COLUMN_PING			COLUMN(4*8,	ping,			"Ping", 		ui.drawstring(pos, sprintf("%-4g", gethostcachenumber(field_ping, sv)), '8 8', col, 1, 0);)
#define COLUMN_FRAGLIMIT		COLUMN(4*8,	fraglimit,		"FL",			ui.drawstring(pos, sprintf("%-3g", gethostcachenumber(field_fraglimit, sv)), '8 8', col, 1, 0);)
#define COLUMN_TIMELIMIT		COLUMN(4*8,	timelimit,		"TL",			ui.drawstring(pos, sprintf("%-3g", gethostcachenumber(field_timelimit, sv)), '8 8', col, 1, 0);)
#define COLUMN_GAMEDIR		COLUMN(8*8,	gamedir,		"Gamedir",	ui.drawstring(pos, sprintf("%-.8s", gethostcachestring(field_gamedir, sv)), '8 8', col, 1, 0);)
#define COLUMN_ADDRESS		COLUMN(21*8,	address,		"Address",		ui.drawstring(pos, sprintf("%-.21s", gethostcachestring(field_address, sv)), '8 8', col, 1, 0);)
#define COLUMN_MAP			COLUMN(8*8,	map,			"Map",		ui.drawstring(pos, sprintf("%-.8s", gethostcachestring(field_map, sv)), '8 8', col, 1, 0);)
#define COLUMN_HOSTNAME		COLUMN(64*8,	name,		"Name",		ui.drawstring(pos, sprintf("%s", gethostcachestring(field_name, sv)), '8 8', col, 1, 0);)
//FIXME: add a little * icon before the hostname for favourites or something

#define COLUMNS 				COLUMN_NUMPLAYERS	COLUMN_MAXPLAYERS	COLUMN_PING	COLUMN_FRAGLIMIT	COLUMN_TIMELIMIT	COLUMN_GAMEDIR	COLUMN_ADDRESS	COLUMN_MAP	COLUMN_HOSTNAME

class mitem_servers : mitem
{
	float server_selected;
	mitem_vslider slider;
	float dbltime;
	float dobound;

	void() mitem_servers =
	{
		dbltime = cltime - 10;
		this.item_flags |= IF_SELECTABLE;
		server_selected = -1;

		//clear the filter
		resethostcachemasks();
#ifdef MOD_GAMEDIR
		if (MOD_GAMEDIR != "")
		{
			//constrain the list to only servers with the right gamedir.
			sethostcachemaskstring(0, gethostcacheindexforkey("gamedir"), MOD_GAMEDIR, SLIST_TEST_EQUAL);
		}
#endif
		//sort by ping by default
		sethostcachesort(gethostcacheindexforkey("ping"), FALSE);
		//(re)query the servers.
		refreshhostcache();

		resorthostcache();
	};

	virtual void(vector pos) item_draw =
	{
		local float sv, maxsv = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
		local float maxy = self.item_size_y;
		float left = pos_x;
	
		local vector omin = ui.drawrectmin, omax = ui.drawrectmax;
		local vector cmin = pos + '0 8', cmax = pos + item_size;
	
		cmin_x = max(omin_x, cmin_x);
		cmin_y = max(omin_y, cmin_y);
		cmax_x = min(omax_x, cmax_x);
		cmax_y = min(omax_y, cmax_y);

		slider.maxv = maxsv - (floor(maxy/8) - 1);

		//constrain the view the lazy way.
		if (dobound)
		{
			dobound = FALSE;
			if (slider.val < 0)
				slider.val = 0;
			while (slider.val + floor(maxy/8) - 1 <= server_selected)
				slider.val+=1;
			while (server_selected < slider.val && slider.val > 0)
				slider.val-=1;
		}

		float field_name = gethostcacheindexforkey("name");
		float field_ping = gethostcacheindexforkey("ping");
		float field_numplayers = gethostcacheindexforkey("numhumans");
		if (field_numplayers < 0)
			field_numplayers = gethostcacheindexforkey("numplayers");
		float field_maxplayers = gethostcacheindexforkey("maxplayers");
		float field_gamedir = gethostcacheindexforkey("gamedir");
		if (field_gamedir < 0)
			field_gamedir = gethostcacheindexforkey("mod");
		float field_address = gethostcacheindexforkey("cname");
		float field_map = gethostcacheindexforkey("map");
		float field_timelimit = gethostcacheindexforkey("timelimit");
		float field_fraglimit = gethostcacheindexforkey("fraglimit");

		maxy = ceil(maxy/8) - 1;
		if (maxsv  > slider.val + maxy)
			maxsv = slider.val + maxy;
	
		float sort = gethostcachevalue(SLIST_SORTFIELD);
		string colkey = __NULL__;
#define COLUMN(width, sortname, title, draw) if (field_##sortname<0) autocvar_sb_show##sortname = FALSE; if (autocvar_sb_show##sortname) {if (ui.mousepos[0] > pos_x && ui.mousepos[1] < pos_y+8) colkey = #sortname; pos_x += width+8;}
		COLUMNS
#undef COLUMN

		vector col;
		pos_x = left;
#define COLUMN(width, sortname, title, draw) if (autocvar_sb_show##sortname) {col = '1 1 1'; if (sort == field_##sortname) col_z = 0; if (colkey == #sortname) col_x = 0; ui.drawstring(pos, title, '8 8', col, 1, 0); pos_x += width+8;}
		COLUMNS
#undef COLUMN

		//make sure things get cut off if we have too many rows (or fractional start)
		ui.setcliparea(cmin[0], cmin[1], cmax[0] - cmin[0], cmax[1] - cmin[1]);

		pos_y += 8 * (1-(slider.val-floor(slider.val)));
		for (float y=pos_y,	sv = max(0, floor(slider.val)); sv < maxsv; sv+=1)
		{
			col = (sv&1)?'0.1 0.1 0.1':'0.15 0.1 0.05';
			drawfill([left, y], [item_size_x,8], col, 0.8, 0); 
			y += 8;
		}
		for (	sv = max(0, floor(slider.val)); sv < maxsv; sv+=1)
		{
			col = ((server_selected==sv)?'1 1 0':'1 1 1');
	
			//if isproxy
			//if islocal
			//if isfavorite
	
			pos_x = left;
#define COLUMN(width, sortname, title, draw) if (autocvar_sb_show##sortname) {draw   pos_x += width+8; }
			COLUMNS
#undef COLUMN
			pos_y += 8;
		}
	
		ui.setcliparea(omin_x, omin_y, omax_x - omin_x, omax_y - omin_y);
	};
	static string() getgamedircmd =
	{	//sadly the vanilla NQ network protocol gave clients no idea which gamedir the server is actually using.
		//many extended protocols still lack that info, so be sure to try to switch according to the server browser's gamedir info, because its more robust than not even trying.
		float field_gamedir = gethostcacheindexforkey("gamedir");
		if (field_gamedir < 0)
			field_gamedir = gethostcacheindexforkey("mod");
		if (field_gamedir < 0)
			return "";	//erk. dodgy engine?

		string gd = gethostcachestring(field_gamedir, server_selected);
		if (strstrofs(gd, "\"")>=0 || strstrofs(gd, "\n")>=0 || strstrofs(gd, "\r")>=0)
			return "";	//no, just no. trust the engine to reject bad paths, we're just preventing cbuf corruption here (DP doesn't support "%S").
		return sprintf("gamedir \"%s\";", gd);
	};
	virtual float(vector pos, float scan, float char, float down) item_keypress =
	{
		float displaysize;
		string addr;
		/*just sink all inputs*/
		if (!down)
			return FALSE;
		if (scan != K_MOUSE1)
			dbltime = cltime - 10;
		if (scan == K_MOUSE1)
		{
			float news = ui.mousepos[1] - (pos_y+8);
			news = floor(news / 8);
			if (news == -1)
			{
				string colkey = "";
#define COLUMN(width, sortname, title, draw) if (autocvar_sb_show##sortname) {if (ui.mousepos[0] > pos_x) colkey = #sortname; pos_x += width+8;}
				COLUMNS
#undef COLUMN
				if (colkey != "")
				{
					float fld = gethostcacheindexforkey(colkey);
					if (colkey == "numplayers")
					{
						//favour descending order
						sethostcachesort(fld, (gethostcachevalue(SLIST_SORTFIELD) != fld) || !gethostcachevalue(SLIST_SORTDESCENDING));
					}
					else
					{
						//favour ascending order
						sethostcachesort(fld, (gethostcachevalue(SLIST_SORTFIELD) == fld) && !gethostcachevalue(SLIST_SORTDESCENDING));
					}
					resorthostcache();	//tell the engine that its okay to resort everything.
					dobound = TRUE;
				}
			}
			if (news < 0 || news >= gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT))
				return FALSE;
	
			news += floor(slider.val);

			if (server_selected == news && dbltime > cltime)
			{
				//connect on double clicks. because we can.
				addr = gethostcachestring(gethostcacheindexforkey("cname"), server_selected);
				if (addr)
					localcmd(sprintf("m_pop;%sconnect \"%s\"\n", getgamedircmd(), addr));
			}
			else
				server_selected = news;
			dobound = TRUE;
	
			dbltime = cltime + 0.5;
		}
		else if (scan == K_ENTER)
		{	//connect normally
			addr = gethostcachestring(gethostcacheindexforkey("cname"), server_selected);
			if (addr)
				localcmd(sprintf("m_pop;%sconnect \"%s\"\n", getgamedircmd(), addr));
		}
		else if (scan == 's')
		{	//s = join as a spectator
			addr = gethostcachestring(gethostcacheindexforkey("cname"), server_selected);
			if (addr)
				localcmd(sprintf("m_pop;%sobserve \"%s\"\n", getgamedircmd(), addr));
		}
		else if (scan == 'j')
		{	//s = join as a spectator
			addr = gethostcachestring(gethostcacheindexforkey("cname"), server_selected);
			if (addr)
				localcmd(sprintf("m_pop;%sjoin \"%s\"\n", getgamedircmd(), addr));
		}
		else if (scan == K_UPARROW || scan == K_MWHEELUP)
		{
			this.server_selected -= 1;
			if (this.server_selected < 0)
			{
				this.server_selected = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
				if (this.server_selected)
					this.server_selected -= 1;
			}
			dobound = TRUE;
		}
		else if (scan == K_DOWNARROW || scan == K_MWHEELDOWN)
		{
			this.server_selected += 1;
			if (this.server_selected >= gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT))
				this.server_selected = 0;
			dobound = TRUE;
		}
		else if (scan == K_PGUP)
		{
			displaysize = (item_size[1]-8)/(8);	//this many rows
			displaysize = floor(displaysize*0.5);
			if (displaysize < 1)
				displaysize = 1;
			this.server_selected -= displaysize ;
			if (this.server_selected < 0)
				this.server_selected = 0;
			dobound = TRUE;
		}
		else if (scan == K_PGDN)
		{
			displaysize = (item_size[1]-8)/(8);	//this many rows
			displaysize = floor(displaysize*0.5);
			if (displaysize < 1)
				displaysize = 1;
			this.server_selected += displaysize;
			if (this.server_selected >= gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT))
				this.server_selected = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)-1;
			dobound = TRUE;
		}
		else if (scan == K_HOME)
		{
			this.server_selected = 0;
			dobound = TRUE;
		}
		else if (scan == K_END)
		{
			this.server_selected = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)-1;
			if (this.server_selected < 0)
				this.server_selected = 0;
			dobound = TRUE;
		}
		else if (char == 'r' || char == 'R' || scan == K_F5)
		{
			refreshhostcache();
			resorthostcache();
		}
		else
			return FALSE;
		return TRUE;
	};
};

class mitem_servers_players : mitem
{
	mitem_servers listing;

	void() mitem_servers_players =
	{
//		this.item_flags |= IF_SELECTABLE;
	};
	
	virtual void(vector pos) item_draw =
	{
		float player;
		float y;
		float m;
		vector opos = pos;
		if (listing.server_selected < 0)
			return;
		for (player = 0, y = 0; player < 256; player++)
		{
			float key = gethostcacheindexforkey(sprintf("player%g", player));
			if (key < 0)
				break;	//probably DP. this isn't going to work for us.
			string playerinfo = gethostcachestring(key, listing.server_selected);
			if (!playerinfo)
				break;
			tokenize(playerinfo);
			float userid	= stof(argv(0));
			float frags		= stof(argv(1));
			float ontime	= stof(argv(2));
			float ping		= stof(argv(3));
			string name	= argv(4);
			string skin		= argv(5);
			vector top		= stov(argv(6));
			vector bot		= stov(argv(7));

			drawfill(pos,		'16 4',	top, 1, 0);
			drawfill(pos+'0 4 0',	'16 4',	bot, 1, 0);
			drawstring(pos,	sprintf("%g", frags), '8 8', '1 1 1', 1, 0);
			drawstring(pos+'20 0',	name, '8 8', '1 1 1', 1, 0);
			pos_y += 8;
			if (++y == 8)
			{
				y-= 8;
				pos_y = opos_y;
				pos_x += 16*8;
			}
		}
	
		if (y)
		{
			pos_y = opos_y;
			pos_x += 16*6;
			y = 0;
		}

		if (checkbuiltin(drawtextfield))
		{
//			drawtextfield(opos, item_size, 3, gethostcachestring(gethostcacheindexforkey("serverinfo"), listing.server_selected));
			m = tokenizebyseparator(gethostcachestring(gethostcacheindexforkey("serverinfo"), listing.server_selected), "\\");
			for(player = 1; player <= m; player += 2)
			{
				drawtextfield(pos,		'64 8', 6, argv(player));
				drawtextfield(pos+'68 0',	[32*8-40, 8], 3, argv(player+1));
		
				pos_y += 8;
				if (++y == 8)
				{
					y-= 8;
					pos_y -= 8*8;
					pos_x += 32*8;
				}
			}
		}
	};
};


nonstatic void(mitem_desktop desktop) M_Servers =
{
	mitem_menu o;

	if (checkcommand2("menu_servers", FALSE) && argv(1) != "force")
	{
		localcmd("menu_servers\n");
		return;
	}
	
#ifdef CSQC
	if not (checkextension("FTE_CSQC_SERVERBROWSER"))
	{
		print(_("Sorry, your client does not support FTE_CSQC_SERVERBROWSER\n"));
		return;
	}
#endif

	o = (mitem_menu)desktop.findchildtext(_("Servers List"));
	if (o)
		o.totop();
	else
	{
#if 1
		mitem_exmenu m;
		m = spawn(mitem_exmenu, item_text:_("Options"), item_flags:IF_SELECTABLE, item_command:"m_main");
		desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0');
		desktop.item_focuschange(m, IF_KFOCUSED);
		m.totop();
#else
		mitem_menu m;
		m = menu_spawn(desktop, _("Servers List"), '320 200');
		m.item_command = "m_main";
		m.item_flags |= IF_RESIZABLE;
		desktop.item_focuschange(m, IF_KFOCUSED);
		m.totop();
#endif

		mitem_vslider sl = spawn(mitem_vslider, stride:4, item_flags:IF_SELECTABLE);
		mitem_servers ls = spawn(mitem_servers, slider:sl);
	
		m.add(ls, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '-16 -68');
		m.add(spawn(mitem_servers_players, listing:ls),	RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, [8*20, -8*8], [0, -8*0]);
		m.add(sl,	RS_X_MIN_PARENT_MAX|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, [-16, 8], [0, -68]);

		//only add checkboxes for field names accepted by this engine.
		if (gethostcacheindexforkey("ping") >= 0)
			m.add(menuitemcheck_spawn(_("Ping"),			"sb_showping", 			[8*8, 8]),	RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*8], [8*20, -8*7]);
		if (gethostcacheindexforkey("cname") >= 0)
			m.add(menuitemcheck_spawn(_("Address"),		"sb_showaddress", 		[8*8, 8]),	RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*7], [8*20, -8*6]);
		if (gethostcacheindexforkey("map") >= 0)
			m.add(menuitemcheck_spawn(_("Map"),			"sb_showmap", 			[8*8, 8]),	RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*6], [8*20, -8*5]);
		if (gethostcacheindexforkey("gamedir") >= 0 || gethostcacheindexforkey("mod") >= 0)
			m.add(menuitemcheck_spawn(_("Gamedir"),		"sb_showgamedir", 		[8*8, 8]),	RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*5], [8*20, -8*4]);
		if (gethostcacheindexforkey("numplayers") >= 0)
			m.add(menuitemcheck_spawn(_("NumPlayers"),		"sb_shownumplayers",	[8*8, 8]),	RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*4], [8*20, -8*3]);
		if (gethostcacheindexforkey("maxplayers") >= 0)
			m.add(menuitemcheck_spawn(_("MaxPlayers"),		"sb_showmaxplayers",	[8*8, 8]),	RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*3], [8*20, -8*2]);
		if (gethostcacheindexforkey("fraglimit") >= 0)
			m.add(menuitemcheck_spawn(_("Fraglimit"),		"sb_showfraglimit", 		[8*8, 8]),	RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*2], [8*20, -8*1]);
		if (gethostcacheindexforkey("timelimit") >= 0)
			m.add(menuitemcheck_spawn(_("Timelimit"),		"sb_showtimelimit", 		[8*8, 8]),	RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MAX | RS_X_MAX_PARENT_MIN|RS_Y_MAX_PARENT_MAX, [0, -8*1], [8*20, -8*0]);
	}
};

