/*
 * Copyright (C) 1997-2001 Id Software, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
/* cl_scrn.c -- master for refresh, status bar, console, chat, notify, etc */

/*
 * full screen console put up loading plaque blanked background with loading
 * plaque blanked background with menu cinematics full screen image for quit
 * and victory
 *
 * end of unit intermissions
 *
 */

#include "client.h"

float		scr_con_current;/* aproaches scr_conlines at scr_conspeed */
float		scr_conlines;	/* 0.0 to 1.0 lines of console to display */

qboolean	scr_initialized;/* ready to draw */

int		scr_draw_loading;

vrect_t		scr_vrect;	/* position of render window on screen */


cvar_t         *scr_viewsize;
cvar_t         *scr_conspeed;
cvar_t         *scr_centertime;
cvar_t         *scr_showturtle;
cvar_t         *scr_showpause;
cvar_t         *scr_printspeed;

cvar_t         *scr_netgraph;
cvar_t         *scr_netgraph_trans;
cvar_t         *scr_netgraph_image;
cvar_t         *scr_netgraph_size;
cvar_t         *scr_netgraph_pos;
cvar_t         *scr_netgraph_pos_x;
cvar_t         *scr_netgraph_pos_y;
cvar_t         *scr_timegraph;
cvar_t         *scr_debuggraph;
cvar_t         *scr_graphheight;
cvar_t         *scr_graphscale;
cvar_t         *scr_graphshift;
cvar_t         *scr_drawall;
cvar_t         *con_height;
cvar_t         *cl_drawclock;
cvar_t         *cl_drawtimestamps;
cvar_t         *cl_drawping;
cvar_t         *cl_draw_x;
cvar_t         *cl_draw_y;
cvar_t         *cl_drawrate;
cvar_t         *cl_drawmaptime;
cvar_t         *cl_drawdate;
cvar_t         *cl_drawhud;
cvar_t         *cl_drawchathud;

#define		MAX_SCRN_PINGS		16
int		ScrnPings[MAX_SCRN_PINGS];

typedef struct {
	int	x1, y1, x2, y2;
} dirty_t;

dirty_t		scr_dirty, scr_old_dirty[2];

char		crosshair_pic[MAX_QPATH];
int		crosshair_width, crosshair_height;

extern cvar_t  *cl_drawfps;	/* FPS hack */

void		SCR_TimeRefresh_f(void);
void		SCR_Loading_f(void);

extern NiceAss_Chat_t ChatMessages;

/*
 *
 * ============================================================================
 * ===
 *
 * BAR GRAPHS
 *
 * ==========
 * ====================================================================
 */

/*
 * ============== CL_AddNetgraph
 *
 * A new packet was just parsed ==============
 */
int		currentping;
void
CL_AddNetgraph(void)
{
	int		i;
	int		in;
	int		ping;
	static int	ScrnPingsIndex = 0;

	in = cls.netchan.incoming_acknowledged & (CMD_BACKUP - 1);
	ping = cls.realtime - cl.cmd_time[in];
	currentping = cls.realtime - cl.cmd_time[in];

	/* if using the debuggraph for something else, don't add the net lines */
	if (scr_debuggraph->value || scr_timegraph->value)
		return;

	for (i = 0; i < cls.netchan.dropped; i++)
		SCR_DebugGraph(30, 0x40);

	for (i = 0; i < cl.surpressCount; i++)
		SCR_DebugGraph(30, 0xdf);

	/* see what the latency was on this packet */
	/* in = cls.netchan.incoming_acknowledged & (CMD_BACKUP-1); */
	/* ping = cls.realtime - cl.cmd_time[in]; */
	/* NiceAss: */
	ScrnPings[ScrnPingsIndex % MAX_SCRN_PINGS] = ping;
	ScrnPingsIndex++;

	/* ping /= 30; */
	ping = currentping / 30;
	if (ping > 30)
		ping = 30;
	SCR_DebugGraph(ping, 0xd0);
}


typedef struct {
	float		value;
	int		color;
} graphsamp_t;

static int	current;
static graphsamp_t values[1024];

/*
 * ============== SCR_DebugGraph ==============
 */
void
SCR_DebugGraph(float value, int color)
{
	values[current & 1023].value = value;
	values[current & 1023].color = color;
	current++;
}

/*
 * ============== SCR_DrawDebugGraph ==============
 * Plus transparency
 */
void 
SCR_DrawDebugGraph_Original (void)
{
	int		a, x, y, w, i, h;
	float		v;
	int		color;

	/* draw the graph */
	w = scr_vrect.width;
	x = scr_vrect.x;
	y = scr_vrect.y+scr_vrect.height;
	
	if (!scr_netgraph_trans->value) {
		re.DrawFill (x, y-scr_graphheight->value, w, scr_graphheight->value, 8);
	}
	
	for (a=0 ; a<w ; a++)
	{
		i = (current-1-a+1024) & 1023;
		v = values[i].value;
		color = values[i].color;
		v = v*scr_graphscale->value + scr_graphshift->value;
		
		if (v < 0)
			v += scr_graphheight->value * (1+(int)(-v/scr_graphheight->value));
		h = (int)v % (int)scr_graphheight->value;
		re.DrawFill (x+w-1-a, y - h, 1,	h, color);
	}
}

void
SCR_DrawDebugGraph_Box(void)
{
	int		a, x, y, w, i, h, min, max;
	float		v, avgs = 0, avgr = 0;
	int		color, time_net;
	static float	lasttime = 0;
	static int	fps, ping;
	char           *netgraph_image;
	struct tm      *now;
	time_t		tnow;
	
	char		timebuf[32], 
			temp_map[32];
	int		time_clock, 
			map_time = 0,
			time_map, 
			hour, 
			mins, 
			secs;

	time_net = Sys_Milliseconds();
	tnow = time((time_t *) 0);
	now = localtime(&tnow);

	h = w = scr_netgraph_size->value;

	if (scr_netgraph_pos->value == 1) {		/* bottom right */
		x = scr_vrect.width - (w + 2) - 1;
		y = scr_vrect.height - (h + 2) - 1;
	} else if (scr_netgraph_pos->value == 2) {	/* bottom left */
		x = 0;
		y = scr_vrect.height - (h + 2) - 1;
	} else if (scr_netgraph_pos->value == 3) {	/* top right */
		x = scr_vrect.width - (w + 2) - 1;
		y = 0;
	} else if (scr_netgraph_pos->value == 4) {  	/* top left */
		x = 0;
		y = 0;
	}
	else  {  					/* custom */
		x = scr_netgraph_pos_x->value;
		y = scr_netgraph_pos_y->value;
	}

	netgraph_image = scr_netgraph_image->string;

	re.DrawStretchPic(x, y, w + 2, h + 2, netgraph_image, scr_netgraph_trans->value);

	for (a = 0; a < w; a++) {
		i = (current - 1 - a + 1024) & 1023;
		v = values[i].value;
		color = values[i].color;
		v = v * scr_graphscale->value;

		if (v < 1)
			v += h * (1 + (int)(-v / h));

		max = (int)v % h + 1;
		min = y + h - max - scr_graphshift->value;

		/* bind to box! */
		if (min < y + 1)
			min = y + 1;
		if (min > y + h)
			min = y + h;
		if (min + max > y + h)
			max = y + h - max;

		re.DrawFill(x + w - a, min, 1, max, color);
	}

	if (cls.realtime - lasttime > 50) {
		lasttime = cls.realtime;
		fps = (cls.frametime) ? 1 / cls.frametime : 0;
		ping = currentping;
	}
	for (i = 0; i < MAX_NET_HISTORY; i++)
		avgs += Net_History.SendsSize[i];

	if (Net_History.SendsStartTime)
		avgs /= (time_net - Net_History.SendsStartTime);
	avgs *= 1000.0;
	avgs /= 1024.0;

	for (i = 0; i < MAX_NET_HISTORY; i++)
		avgr += Net_History.RecsSize[i];

	if (Net_History.RecsStartTime)
		avgr /= (time_net - Net_History.RecsStartTime);
	avgr *= 1000.0;
	avgr /= 1024.0;
	
	if (scr_netgraph->value == 3 ) {
#if 0	
		if (cl_drawclock->value == 1) {
			time_clock = strftime( timebuf, 32, " Time: %H:%M", now );
		} else {
			time_clock = strftime( timebuf, 32, " Time: %I:%M %p", now );	
		}
#else
		time_clock = strftime( timebuf, 32, " Time: %H:%M", now );	
#endif
		time_map = cl.time / 1000;
		hour = time_map/3600;
		mins = (time_map%3600) /60;
		secs = time_map%60;
		
		if (hour > 0) {
			Com_sprintf(temp_map, sizeof(temp_map), " Map: %i:%02i:%02i", hour, mins, secs);
		}
		else {
			Com_sprintf(temp_map, sizeof(temp_map), " Map: %i:%02i", mins, secs);
		}
		
		DrawString(x, y + 5 * 0, va(" FPS: %3i", fps));
		DrawString(x, y + 5 * 1.7, va(" Ping: %3i", ping));
		DrawString(x, y + 5 * 3.4, va(" DL: %2.1f", avgr));
		DrawString(x, y + 5 * 5.1, va(" UL: %2.1f", avgs));
		DrawString(x, y + 5 * 6.8, va(timebuf, time_clock));
		DrawString(x, y + 5 * 8.5, va(temp_map, map_time));
	}
#if 0	
	/* border... */
	re.DrawFill(x, y, (w + 2), 1, 0);
	re.DrawFill(x, y + (h + 2), (w + 2), 1, 0);
	re.DrawFill(x, y, 1, (h + 2), 0);
	re.DrawFill(x + (w + 2), y, 1, (h + 2), 0);
#endif
}

void
SCR_DrawDebugGraph(void)
{
	if (scr_netgraph->value == 1)
		SCR_DrawDebugGraph_Original();
	else if (scr_netgraph->value == 2 || scr_netgraph->value == 3)
		SCR_DrawDebugGraph_Box();
	else
		return;	
}

/*
 *
 * ============================================================================
 * ===
 *
 * CENTER PRINTING
 *
 * ===============
 * ===============================================================
 */

char		scr_centerstring[1024];
float		scr_centertime_start;	/* for slow victory printing */
float		scr_centertime_off;
float		scr_centertime_end;
int		scr_center_lines;
int		scr_erase_center;

/*
 * ============== SCR_CenterPrint
 *
 * Called for important messages that should stay in the center of the screen
 * for a few moments ==============
 */
void
SCR_CenterPrint(char *str)
{
	char           *s;
	char		line[64];
	int		i, j, l;

	Q_strncpyz(scr_centerstring, str, sizeof(scr_centerstring));
	scr_centertime_off = scr_centertime->value;
	scr_centertime_end = scr_centertime_off;
	scr_centertime_start = cl.time;

	/* count the number of lines for centering */
	scr_center_lines = 1;
	s = str;
	while (*s) {
		if (*s == '\n')
			scr_center_lines++;
		s++;
	}

	/* echo it to the console */
	Com_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n");

	s = str;
	do {
		/* scan the width of the line */
		for (l = 0; l < 40; l++)
			if (s[l] == '\n' || !s[l])
				break;
		for (i = 0; i < (40 - l) / 2; i++)
			line[i] = ' ';

		for (j = 0; j < l; j++) {
			line[i++] = s[j];
		}

		line[i] = '\n';
		line[i + 1] = 0;

		Com_Printf("%s", line);

		while (*s && *s != '\n')
			s++;

		if (!*s)
			break;
		s++;		/* skip the \n */
	} while (1);
	Com_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n");
	Con_ClearNotify();
}


void
SCR_DrawCenterString(void)
{
	char           *start;
	int		l;
	int		j;
	int		x, y;
	int		remaining;
	int		alpha = 254 * ( 1 - (((cl.time-scr_centertime_start)/1000.0)/(scr_centertime_end)));

	/* the finale prints the characters one at a time */
	remaining = 9999;

	scr_erase_center = 0;
	start = scr_centerstring;

	if (scr_center_lines <= 4)
		y = viddef.height * 0.35;
	else
		y = 48;

	do {
		/* scan the width of the line */
		for (l = 0; l < 40; l++)
			if (start[l] == '\n' || !start[l])
				break;
		x = (viddef.width - l * 8) / 2;
		SCR_AddDirtyPoint(x, y);
		for (j = 0; j < l; j++, x += 8) {
			re.DrawChar(x, y, start[j], alpha);
			if (!remaining--)
				return;
		}
		SCR_AddDirtyPoint(x, y + 8);

		y += 8;

		while (*start && *start != '\n')
			start++;

		if (!*start)
			break;
		start++;	/* skip the \n */
	} while (1);
}

void
SCR_CheckDrawCenterString(void)
{
	scr_centertime_off -= cls.frametime;

	if (scr_centertime_off <= 0)
		return;

	SCR_DrawCenterString();
}

/*
 * ===========================================================================
 * ==
 */

/*
 * ================= SCR_CalcVrect
 *
 * Sets scr_vrect, the coordinates of the rendered window =================
 */
static void
SCR_CalcVrect(void)
{
	int		size;

	/* bound viewsize */
	if (scr_viewsize->value < 40)
		Cvar_Set("viewsize", "40");
	if (scr_viewsize->value > 100)
		Cvar_Set("viewsize", "100");

	size = scr_viewsize->value;

	scr_vrect.width = viddef.width * size / 100;
	scr_vrect.width &= ~7;

	scr_vrect.height = viddef.height * size / 100;
	scr_vrect.height &= ~1;

	scr_vrect.x = (viddef.width - scr_vrect.width) / 2;
	scr_vrect.y = (viddef.height - scr_vrect.height) / 2;
}


/*
 * ================= SCR_SizeUp_f
 *
 * Keybinding command =================
 */
void
SCR_SizeUp_f(void)
{
	Cvar_SetValue("viewsize", scr_viewsize->value + 10);
}


/*
 * ================= SCR_SizeDown_f
 *
 * Keybinding command =================
 */
void
SCR_SizeDown_f(void)
{
	Cvar_SetValue("viewsize", scr_viewsize->value - 10);
}

/*
 * ================= SCR_Sky_f
 *
 * Set a specific sky and rotation speed =================
 */
void
SCR_Sky_f(void)
{
	float		rotate;
	vec3_t		axis;

	if (Cmd_Argc() < 2) {
		Com_Printf("Usage: sky <basename> <rotate> <axis x y z>\n");
		return;
	}
	if (Cmd_Argc() > 2)
		rotate = atof(Cmd_Argv(2));
	else
		rotate = 0;
	if (Cmd_Argc() == 6) {
		axis[0] = atof(Cmd_Argv(3));
		axis[1] = atof(Cmd_Argv(4));
		axis[2] = atof(Cmd_Argv(5));
	} else {
		axis[0] = 0;
		axis[1] = 0;
		axis[2] = 1;
	}

	re.SetSky(Cmd_Argv(1), rotate, axis);
}

/*
 * ===========================================================================
 * =
 */

/*
 * ================== SCR_Init ==================
 */
void
SCR_Init(void)
{
	scr_viewsize = Cvar_Get("viewsize", "100", CVAR_ARCHIVE);
	scr_conspeed = Cvar_Get("scr_conspeed", "3", 0);
	scr_showturtle = Cvar_Get("scr_showturtle", "0", 0);
	scr_showpause = Cvar_Get("scr_showpause", "1", 0);
	scr_centertime = Cvar_Get("scr_centertime", "2.5", 0);
	scr_printspeed = Cvar_Get("scr_printspeed", "8", 0);
	scr_netgraph = Cvar_Get("netgraph", "3", CVAR_ARCHIVE);
	scr_netgraph_trans = Cvar_Get("netgraph_trans", "0.3", CVAR_ARCHIVE);
	scr_netgraph_image = Cvar_Get("netgraph_image", "net", CVAR_ARCHIVE);
	scr_netgraph_size = Cvar_Get("netgraph_size", "140", CVAR_ARCHIVE);
	scr_netgraph_pos = Cvar_Get("netgraph_pos", "3", CVAR_ARCHIVE);
	scr_netgraph_pos_x = Cvar_Get("netgraph_pos_x", "0", CVAR_ARCHIVE);
	scr_netgraph_pos_y = Cvar_Get("netgraph_pos_y", "0", CVAR_ARCHIVE);
	scr_timegraph = Cvar_Get("timegraph", "0", 0);
	scr_debuggraph = Cvar_Get("debuggraph", "0", 0);
	scr_graphheight = Cvar_Get("graphheight", "32", 0);
	scr_graphscale = Cvar_Get("graphscale", "1", 0);
	scr_graphshift = Cvar_Get("graphshift", "0", 0);
	scr_drawall = Cvar_Get("scr_drawall", "0", 0);

	con_height = Cvar_Get("con_height", "0.5", CVAR_ARCHIVE);

	cl_drawping = Cvar_Get("cl_drawping", "0", CVAR_ARCHIVE);
	cl_draw_x = Cvar_Get("cl_draw_x", "0", CVAR_ARCHIVE);
	cl_draw_y = Cvar_Get("cl_draw_y", "0.8", CVAR_ARCHIVE);
	cl_drawrate = Cvar_Get("cl_drawrate", "0", CVAR_ARCHIVE);
	cl_drawdate = Cvar_Get("cl_drawdate", "0", CVAR_ARCHIVE);

	cl_drawhud = Cvar_Get("cl_drawhud", "1", 0);
	cl_drawchathud = Cvar_Get("cl_drawchathud", "1", CVAR_ARCHIVE);

	//
	/* register our commands */
	//
	Cmd_AddCommand("timerefresh", SCR_TimeRefresh_f);
	Cmd_AddCommand("loading", SCR_Loading_f);
	Cmd_AddCommand("sizeup", SCR_SizeUp_f);
	Cmd_AddCommand("sizedown", SCR_SizeDown_f);
	Cmd_AddCommand("sky", SCR_Sky_f);

	scr_initialized = true;
}


/*
 * ============== SCR_DrawNet ==============
 */
void
SCR_DrawNet(void)
{
	if (cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged
	    < CMD_BACKUP - 1)
		return;

	re.DrawPic(scr_vrect.x + 64, scr_vrect.y, "net", cl_hudalpha->value);

}

/*
 * ============== SCR_DrawPause ==============
 */
void
SCR_DrawPause(void)
{
	int		w, h;

	if (!scr_showpause->value)	/* turn off for screenshots */
		return;

	if (!cl_paused->value)
		return;

	if (cls.key_dest == key_menu)
		return;

	re.DrawGetPicSize(&w, &h, "pause");
	re.DrawPic((viddef.width - w) / 2, viddef.height / 2 + 8, "pause", 1.0);

}

/*
 * ============== SCR_DrawLoading ==============
 */
void
SCR_DrawLoading(void)
{
	int		w, h;

	if (!scr_draw_loading)
		return;

	scr_draw_loading = false;
	re.DrawGetPicSize(&w, &h, "loading");
	re.DrawPic((viddef.width - w) / 2, (viddef.height - h) / 2, "loading", 1.0);
}

/*
 * ===========================================================================
 * ==
 */

/*
 * ================== SCR_RunConsole
 *
 * Scroll it up or down ==================
 */
void
SCR_RunConsole(void)
{
	/* decide on the height of the console */
	if (cls.key_dest == key_console) 
	{
		if (con_height->value < 0.1)
			Cvar_SetValue("con_height", 0.1);
		scr_conlines = con_height->value;	/* user controllable */
	} else
		scr_conlines = 0;	/* none visible */

	if (scr_conlines < scr_con_current) {
		scr_con_current -= scr_conspeed->value * cls.frametime;
		if (scr_conlines > scr_con_current)
			scr_con_current = scr_conlines;

	} else if (scr_conlines > scr_con_current) {
		scr_con_current += scr_conspeed->value * cls.frametime;
		if (scr_conlines < scr_con_current)
			scr_con_current = scr_conlines;
	}
}

/*
 * ================== SCR_DrawConsole ==================
 */
void
SCR_DrawConsole(void)
{
	Con_CheckResize();

	if (cls.state == ca_disconnected || cls.state == ca_connecting) {	/* forced full screen console */
		Con_DrawConsole(1.0, false);
		return;
	}
	if (cls.state != ca_active || !cl.refresh_prepped) {	/* connected, but can't render */
		if (con_height->value < 0.1)
			Cvar_SetValue("con_height", 0.1);
		else if (con_height->value > 1)
			Cvar_SetValue("con_height", 1);

		Con_DrawConsole(0.5, false);
		re.DrawFill(0, viddef.height / 2, viddef.width, viddef.height / 2, 0);
		return;
	}
	if (scr_con_current) 
	{
		Con_DrawConsole(scr_con_current, true);
	} else {
		if (cls.key_dest == key_game || cls.key_dest == key_message)
			Con_DrawNotify();	/* only draw notify in game */
	}
}

/*
 * ===========================================================================
 * ==
 */

/*
 * ================ SCR_BeginLoadingPlaque ================
 */
void
SCR_BeginLoadingPlaque(void)
{
	S_StopAllSounds();
	cl.sound_prepped = false;	/* don't play ambients */
	CDAudio_Stop();
	if (cls.disable_screen)
		return;
	if (developer->value)
		return;
		
	if (cls.state == ca_disconnected)
		return;		/* if at console, don't bring up the plaque */
	if (cls.key_dest == key_console)
		return;
	if (cl.cinematictime > 0)
		scr_draw_loading = 2;	/* clear to balack first */
	else
		scr_draw_loading = 1;
	SCR_UpdateScreen();
	cls.disable_screen = Sys_Milliseconds();
	cls.disable_servercount = cl.servercount;
}

/*
 * ================ SCR_EndLoadingPlaque ================
 */
void
SCR_EndLoadingPlaque(void)
{
	cls.disable_screen = 0;
	Con_ClearNotify();
}

/*
 * ================ SCR_Loading_f ================
 */
void
SCR_Loading_f(void)
{
	SCR_BeginLoadingPlaque();
}

/*
 * ================ SCR_TimeRefresh_f ================
 */
int
entitycmpfnc(const entity_t * a, const entity_t * b)
{

	/*
	 * * all other models are sorted by model then skin
	 */
	if (a->model == b->model) {
		return ((INT) a->skin - (INT) b->skin);
	} else {
		return ((INT) a->model - (INT) b->model);
	}
}

void
SCR_TimeRefresh_f(void)
{
	int		i;
	int		start, stop;
	float		time;

	if (cls.state != ca_active)
		return;

	start = Sys_Milliseconds();

	if (Cmd_Argc() == 2) {	/* run without page flipping */
		re.BeginFrame(0);
		for (i = 0; i < 128; i++) {
			cl.refdef.viewangles[1] = i / 128.0 * 360.0;
			re.RenderFrame(&cl.refdef);
		}
		re.EndFrame();
	} else {
		for (i = 0; i < 128; i++) {
			cl.refdef.viewangles[1] = i / 128.0 * 360.0;

			re.BeginFrame(0);
			re.RenderFrame(&cl.refdef);
			re.EndFrame();
		}
	}

	stop = Sys_Milliseconds();
	time = (stop - start) / 1000.0;
	Com_Printf("%f seconds (%f fps)\n", time, 128 / time);
}

/*
 * ================= SCR_AddDirtyPoint =================
 */
void
SCR_AddDirtyPoint(int x, int y)
{
	if (x < scr_dirty.x1)
		scr_dirty.x1 = x;
	if (x > scr_dirty.x2)
		scr_dirty.x2 = x;
	if (y < scr_dirty.y1)
		scr_dirty.y1 = y;
	if (y > scr_dirty.y2)
		scr_dirty.y2 = y;
}

void
SCR_DirtyScreen(void)
{
	SCR_AddDirtyPoint(0, 0);
	SCR_AddDirtyPoint(viddef.width - 1, viddef.height - 1);
}

/*
 * ============== SCR_TileClear
 *
 * Clear any parts of the tiled background that were drawn on last frame
 * ==============
 */
void
SCR_TileClear(void)
{
	int		i;
	int		top       , bottom, left, right;
	dirty_t		clear;

	if (scr_drawall->value)
		SCR_DirtyScreen();	/* for power vr or broken page flippers... */

	if (scr_con_current == 1.0)
		return;		/* full screen console */
	if (scr_viewsize->value == 100)
		return;		/* full screen rendering */
	if (cl.cinematictime > 0)
		return;		/* full screen cinematic */

	/* erase rect will be the union of the past three frames */
	/* so tripple buffering works properly */
	clear = scr_dirty;
	for (i = 0; i < 2; i++) {
		if (scr_old_dirty[i].x1 < clear.x1)
			clear.x1 = scr_old_dirty[i].x1;
		if (scr_old_dirty[i].x2 > clear.x2)
			clear.x2 = scr_old_dirty[i].x2;
		if (scr_old_dirty[i].y1 < clear.y1)
			clear.y1 = scr_old_dirty[i].y1;
		if (scr_old_dirty[i].y2 > clear.y2)
			clear.y2 = scr_old_dirty[i].y2;
	}

	scr_old_dirty[1] = scr_old_dirty[0];
	scr_old_dirty[0] = scr_dirty;

	scr_dirty.x1 = 9999;
	scr_dirty.x2 = -9999;
	scr_dirty.y1 = 9999;
	scr_dirty.y2 = -9999;

	/* don't bother with anything convered by the console) */
	top = scr_con_current * viddef.height;
	if (top >= clear.y1)
		clear.y1 = top;

	if (clear.y2 <= clear.y1)
		return;		/* nothing disturbed */

	top = scr_vrect.y;
	bottom = top + scr_vrect.height - 1;
	left = scr_vrect.x;
	right = left + scr_vrect.width - 1;

	if (clear.y1 < top) {	/* clear above view screen */
		i = clear.y2 < top - 1 ? clear.y2 : top - 1;
		re.DrawTileClear(clear.x1, clear.y1,
		    clear.x2 - clear.x1 + 1, i - clear.y1 + 1, "backtile");
		clear.y1 = top;
	}
	if (clear.y2 > bottom) {/* clear below view screen */
		i = clear.y1 > bottom + 1 ? clear.y1 : bottom + 1;
		re.DrawTileClear(clear.x1, i,
		    clear.x2 - clear.x1 + 1, clear.y2 - i + 1, "backtile");
		clear.y2 = bottom;
	}
	if (clear.x1 < left) {	/* clear left of view screen */
		i = clear.x2 < left - 1 ? clear.x2 : left - 1;
		re.DrawTileClear(clear.x1, clear.y1,
		    i - clear.x1 + 1, clear.y2 - clear.y1 + 1, "backtile");
		clear.x1 = left;
	}
	if (clear.x2 > right) {	/* clear left of view screen */
		i = clear.x1 > right + 1 ? clear.x1 : right + 1;
		re.DrawTileClear(i, clear.y1,
		    clear.x2 - i + 1, clear.y2 - clear.y1 + 1, "backtile");
		clear.x2 = right;
	}
}


/* =============================================================== */


#define STAT_MINUS		10	/* num frame for '-' stats digit */
char           *sb_nums[2][11] =
{
	{"num_0", "num_1", "num_2", "num_3", "num_4", "num_5",
	"num_6", "num_7", "num_8", "num_9", "num_minus"},
	{"anum_0", "anum_1", "anum_2", "anum_3", "anum_4", "anum_5",
	"anum_6", "anum_7", "anum_8", "anum_9", "anum_minus"}
};

#define	ICON_WIDTH	24
#define	ICON_HEIGHT	24
#define	CHAR_WIDTH	16
#define	ICON_SPACE	8



/*
 * ================ SizeHUDString
 *
 * Allow embedded \n in the string ================
 */
void
SizeHUDString(char *string, int *w, int *h)
{
	int		lines, width, current;

	lines = 1;
	width = 0;

	current = 0;
	while (*string) {
		if (*string == '\n') {
			lines++;
			current = 0;
		} else {
			current++;
			if (current > width)
				width = current;
		}
		string++;
	}

	*w = width * 8;
	*h = lines * 8;
}

void
DrawHUDString(char *string, int x, int y, int centerwidth, int xor)
{
	int		margin;
	char		line[1024];
	int		width;
	int		i;

	margin = x;

	while (*string) {
		/* scan out one line of text from the string */
		width = 0;
		while (*string && *string != '\n')
			line[width++] = *string++;
		line[width] = 0;

		if (centerwidth)
			x = margin + (centerwidth - width * 8) / 2;
		else
			x = margin;
		for (i = 0; i < width; i++) {
			re.DrawChar(x, y, line[i] ^ xor, 256);
			x += 8;
		}
		if (*string) {
			string++;	/* skip the \n */
			x = margin;
			y += 8;
		}
	}
}


/*
 * ============== SCR_DrawField ==============
 */
void
SCR_DrawField(int x, int y, int color, int width, int value)
{
	char		num[16], *ptr;
	int		l;
	int		frame;

	if (width < 1)
		return;

	/* draw number string */
	if (width > 5)
		width = 5;

	SCR_AddDirtyPoint(x, y);
	SCR_AddDirtyPoint(x + width * CHAR_WIDTH + 2, y + 23);

	Com_sprintf(num, sizeof(num), "%i", value);
	l = strlen(num);
	if (l > width)
		l = width;
	x += 2 + CHAR_WIDTH * (width - l);

	ptr = num;
	while (*ptr && l) {
		if (*ptr == '-')
			frame = STAT_MINUS;
		else
			frame = *ptr - '0';

		re.DrawScaledPic(x, y, 1, cl_hudalpha->value, sb_nums[color][frame], cl_hud_red->value, cl_hud_green->value, cl_hud_blue->value, false, true);
		x += CHAR_WIDTH;
		ptr++;
		l--;
	}
}


/*
 * =============== SCR_TouchPics
 *
 * Allows rendering code to cache all needed sbar graphics ===============
 */
void
SCR_TouchPics(void)
{
	int		i, j;

	for (i = 0; i < 2; i++)
		for (j = 0; j < 11; j++)
			re.RegisterPic(sb_nums[i][j]);

	if (crosshair->value) {
		if (crosshair->value > 20)
			crosshair->value = 20;

		Com_sprintf(crosshair_pic, sizeof(crosshair_pic), "ch%i", (int)(crosshair->value));
		re.DrawGetPicSize(&crosshair_width, &crosshair_height, crosshair_pic);

		if (!crosshair_width)
			crosshair_pic[0] = 0;

	}
}

/*
 * ================ SCR_ExecuteLayoutString
 *
 * ================
 */
void
SCR_ExecuteLayoutString(char *s)
{
	int		x, y;
	int		value;
	char           *token;
	int		width;
	int		index;
	clientinfo_t   *ci;

	if (cls.state != ca_active || !cl.refresh_prepped)
		return;

	if (!s[0])
		return;

	x = 0;
	y = 0;
	width = 3;

	while (s) {
		token = COM_Parse(&s);
		if (!strcmp(token, "xl")) {
			token = COM_Parse(&s);
			x = atoi(token);
			continue;
		}
		if (!strcmp(token, "xr")) {
			token = COM_Parse(&s);
			x = viddef.width + atoi(token);
			continue;
		}
		if (!strcmp(token, "xv")) {
			token = COM_Parse(&s);
			x = viddef.width / 2 - 160 + atoi(token);
			continue;
		}
		if (!strcmp(token, "yt")) {
			token = COM_Parse(&s);
			y = atoi(token);
			continue;
		}
		if (!strcmp(token, "yb")) {
			token = COM_Parse(&s);
			y = viddef.height + atoi(token);
			continue;
		}
		if (!strcmp(token, "yv")) {
			token = COM_Parse(&s);
			y = viddef.height / 2 - 120 + atoi(token);
			continue;
		}
		if (!strcmp(token, "pic")) {	/* draw a pic from a stat number */
			token = COM_Parse(&s);
			value = cl.frame.playerstate.stats[atoi(token)];
			if (value >= MAX_IMAGES)
				Com_Error(ERR_DROP, "Pic >= MAX_IMAGES");
			if (cl.configstrings[CS_IMAGES + value]) {
				SCR_AddDirtyPoint(x, y);
				SCR_AddDirtyPoint(x + 23, y + 23);
				re.DrawScaledPic(x, y, 1, cl_hudalpha->value, cl.configstrings[CS_IMAGES + value], 1, 1, 1, false, true);
			}
			continue;
		}
		if (!strcmp(token, "client")) {	/* draw a deathmatch client block */
			int		score, ping, time;

			token = COM_Parse(&s);
			x = viddef.width / 2 - 160 + atoi(token);
			token = COM_Parse(&s);
			y = viddef.height / 2 - 120 + atoi(token);
			SCR_AddDirtyPoint(x, y);
			SCR_AddDirtyPoint(x + 159, y + 31);

			token = COM_Parse(&s);
			value = atoi(token);
			if (value >= MAX_CLIENTS || value < 0)
				Com_Error(ERR_DROP, "client >= MAX_CLIENTS");
			ci = &cl.clientinfo[value];

			token = COM_Parse(&s);
			score = atoi(token);

			token = COM_Parse(&s);
			ping = atoi(token);

			token = COM_Parse(&s);
			time = atoi(token);

			DrawAltString(x + 32, y, ci->name);
			DrawString(x + 32, y + 8, "Score: ");
			DrawAltString(x + 32 + 7 * 8, y + 8, va("%i", score));
			DrawString(x + 32, y + 16, va("Ping:  %i", ping));
			DrawString(x + 32, y + 24, va("Time:  %i", time));

			if (!ci->icon)
				ci = &cl.baseclientinfo;
			re.DrawScaledPic(x, y, 1, cl_hudalpha->value, ci->iconname, cl_hud_red->value, cl_hud_green->value, cl_hud_blue->value, false, true);
			continue;
		}
		if (!strcmp(token, "ctf")) {	/* draw a ctf client block */
			int		score, ping;
			char		block[80];

			token = COM_Parse(&s);
			x = viddef.width / 2 - 160 + atoi(token);
			token = COM_Parse(&s);
			y = viddef.height / 2 - 120 + atoi(token);
			SCR_AddDirtyPoint(x, y);
			SCR_AddDirtyPoint(x + 159, y + 31);

			token = COM_Parse(&s);
			value = atoi(token);
			if (value >= MAX_CLIENTS || value < 0)
				Com_Error(ERR_DROP, "client >= MAX_CLIENTS");
			ci = &cl.clientinfo[value];

			token = COM_Parse(&s);
			score = atoi(token);

			token = COM_Parse(&s);
			ping = atoi(token);
			if (ping > 999)
				ping = 999;

			Com_sprintf(block, sizeof(block), "%3d %3d %-12.12s", score, ping, ci->name);

			if (value == cl.playernum)
				DrawAltString(x, y, block);
			else
				DrawString(x, y, block);
			continue;
		}
		if (!strcmp(token, "picn")) {	/* draw a pic from a name */
			token = COM_Parse(&s);
			SCR_AddDirtyPoint(x, y);
			SCR_AddDirtyPoint(x + 23, y + 23);
			re.DrawScaledPic(x, y, 1, cl_hudalpha->value, token, 1, 1, 1, false, true);
			continue;
		}
		if (!strcmp(token, "num")) {	/* draw a number */
			token = COM_Parse(&s);
			width = atoi(token);
			token = COM_Parse(&s);
			value = cl.frame.playerstate.stats[atoi(token)];
			SCR_DrawField(x, y, 0, width, value);
			continue;
		}
		if (!strcmp(token, "hnum")) {	/* health number */
			int		color;

			width = 3;
			value = cl.frame.playerstate.stats[STAT_HEALTH];
			if (value > 25)
				color = 0;	/* green */
			else if (value > 0)
				color = (cl.frame.serverframe >> 2) & 1;	/* flash */
			else
				color = 1;

			if (cl.frame.playerstate.stats[STAT_FLASHES] & 1)
				re.DrawScaledPic(x, y, 1, cl_hudalpha->value, "field_3", cl_hud_red->value, cl_hud_green->value, cl_hud_blue->value, false, true);

			SCR_DrawField(x, y, color, width, value);
			continue;
		}
		if (!strcmp(token, "anum")) {	/* ammo number */
			int		color;

			width = 3;
			value = cl.frame.playerstate.stats[STAT_AMMO];
			if (value > 5)
				color = 0;	/* green */
			else if (value >= 0)
				color = (cl.frame.serverframe >> 2) & 1;	/* flash */
			else
				continue;	/* negative number = don't
						 * show */

			if (cl.frame.playerstate.stats[STAT_FLASHES] & 4)
				re.DrawScaledPic(x, y, 1, cl_hudalpha->value, "field_3", cl_hud_red->value, cl_hud_green->value, cl_hud_blue->value, false, true);

			SCR_DrawField(x, y, color, width, value);
			continue;
		}
		if (!strcmp(token, "rnum")) {	/* armor number */
			int		color;

			width = 3;
			value = cl.frame.playerstate.stats[STAT_ARMOR];
			if (value < 1)
				continue;

			color = 0;	/* green */

			if (cl.frame.playerstate.stats[STAT_FLASHES] & 2)
				re.DrawScaledPic(x, y, 1, cl_hudalpha->value, "field_3", cl_hud_red->value, cl_hud_green->value, cl_hud_blue->value, false, true);

			SCR_DrawField(x, y, color, width, value);
			continue;
		}
		if (!strcmp(token, "stat_string")) {
			token = COM_Parse(&s);
			index = atoi(token);
			if (index < 0 || index >= MAX_CONFIGSTRINGS)
				Com_Error(ERR_DROP, "Bad stat_string index");
			index = cl.frame.playerstate.stats[index];
			if (index < 0 || index >= MAX_CONFIGSTRINGS)
				Com_Error(ERR_DROP, "Bad stat_string index");
			DrawString(x, y, cl.configstrings[index]);
			continue;
		}
		if (!strcmp(token, "cstring")) {
			token = COM_Parse(&s);
			DrawHUDString(token, x, y, 320, 0);
			continue;
		}
		if (!strcmp(token, "string")) {
			token = COM_Parse(&s);
			DrawString(x, y, token);
			continue;
		}
		if (!strcmp(token, "cstring2")) {
			token = COM_Parse(&s);
			DrawHUDString(token, x, y, 320, 0x80);
			continue;
		}
		if (!strcmp(token, "string2")) {
			token = COM_Parse(&s);
			DrawAltString(x, y, token);
			continue;
		}
		if (!strcmp(token, "if")) {	/* draw a number */
			token = COM_Parse(&s);
			value = cl.frame.playerstate.stats[atoi(token)];
			if (!value) {	/* skip to endif */
				while (s && strcmp(token, "endif")) {
					token = COM_Parse(&s);
				}
			}
			continue;
		}
	}
}


/*
 * ================ SCR_DrawStats
 *
 * The status bar is a small layout program that is based on the stats array
 * ================
 */
void
SCR_DrawStats(void)
{
	if (!cl_drawhud->value)
		return;
	SCR_ExecuteLayoutString(cl.configstrings[CS_STATUSBAR]);
}


/*
 * ================ SCR_DrawLayout
 *
 * ================
 */
#define	STAT_LAYOUTS		13

void
SCR_DrawLayout(void)
{
	if (!cl.frame.playerstate.stats[STAT_LAYOUTS])
		return;
	SCR_ExecuteLayoutString(cl.layout);
}

/* ======================================================= */

/*
 * ================== SCR_UpdateScreen
 *
 * This is called every frame, and can also be called explicitly to flush text
 * to the screen. ==================
 */
struct tm      *now;

#define	FPS_FRAMES	128
void
SCR_UpdateScreen(void)
{
	int		numframes;
	int		i;
	float		separation[2] = {0, 0};
	static int	prevTime = 0, index = 0;
	static char	fps [32];
	time_t		tnow;
	char		timebuf[32];
	char		s[96];
	int		y = 0;
	int		avg;

	tnow = time((time_t *) 0);
	now = localtime(&tnow);

	if (!dedicated->value) {/* FIXME don't know why reason crashes
				 * dedicated servers. */
		y = viddef.height * cl_draw_y->value;
	}

	/*
	 * if the screen is disabled (loading plaque is up, or vid mode
	 * changing)
	 */
	/* do nothing at all */
	if (cls.disable_screen) {
		if (Sys_Milliseconds() - cls.disable_screen > 120000) {
			cls.disable_screen = 0;
			Com_Printf("Loading plaque timed out.\n");
		}
		return;
	}
	if (!scr_initialized || !con.initialized)
		return;		/* not initialized yet */

	/*
	 * * range check cl_camera_separation so we don't inadvertently fry
	 * someone's * brain
	 */
#ifdef REDBLUE
	if (cl_stereo_separation->value > 10.0)
		Cvar_SetValue("cl_stereo_separation", 10.0);
#else
	if (cl_stereo_separation->value > 1.0)
		Cvar_SetValue("cl_stereo_separation", 1.0);
#endif
	else if (cl_stereo_separation->value < 0)
		Cvar_SetValue("cl_stereo_separation", 0.0);

#ifdef REDBLUE
	if (!cl_stereo->value)
		Cvar_SetValue("cl_stereo", 1);

	numframes = 2;
	separation[0] = -cl_stereo_separation->value / 2.0;
	separation[1] = cl_stereo_separation->value / 2.0;
#else
	if (cl_stereo->value) {
		numframes = 2;
		separation[0] = -cl_stereo_separation->value / 2;
		separation[1] = cl_stereo_separation->value / 2;
	} else {
		separation[0] = 0;
		separation[1] = 0;
		numframes = 1;
	}
#endif
	for (i = 0; i < numframes; i++) {
		re.BeginFrame(separation[i]);

		if (scr_draw_loading == 2) {	/* loading plaque over black screen */
			int	w , h;

			re.CinematicSetPalette(NULL);
			scr_draw_loading = false;
			re.DrawGetPicSize(&w, &h, "loading");
			re.DrawPic((viddef.width - w) / 2, (viddef.height - h) / 2, "loading", 1.0);
			/* re.EndFrame(); */
			/* return; */
		}
		/* if a cinematic is supposed to be running, handle menus */
		/* and console specially */
		else if (cl.cinematictime > 0) {
			if (cls.key_dest == key_menu) {
				if (cl.cinematicpalette_active) {
					re.CinematicSetPalette(NULL);
					cl.cinematicpalette_active = false;
				}
				M_Draw();
				/* re.EndFrame(); */
				/* return; */
			} else if (cls.key_dest == key_console) {
				if (cl.cinematicpalette_active) {
					re.CinematicSetPalette(NULL);
					cl.cinematicpalette_active = false;
				}
				SCR_DrawConsole();
				/* re.EndFrame(); */
				/* return; */
			} else {
				SCR_DrawCinematic();
				/* re.EndFrame(); */
				/* return; */
			}
		} else {
			/* make sure the game palette is active */
			if (cl.cinematicpalette_active) {
				re.CinematicSetPalette(NULL);
				cl.cinematicpalette_active = false;
			}	
			
			/* do 3D refresh drawing, and then update the screen */
			SCR_CalcVrect();

			/* clear any dirty part of the background */
			SCR_TileClear();

			V_RenderView(separation[i]);

			if (scr_debuggraph->value || scr_timegraph->value || scr_netgraph->value)
				SCR_DrawDebugGraph();

			SCR_DrawStats();

			if (cl.frame.playerstate.stats[STAT_LAYOUTS] & 1)
				SCR_DrawLayout();

			if (cl.frame.playerstate.stats[STAT_LAYOUTS] & 2)
				CL_DrawInventory();

			SCR_DrawNet();

			SCR_CheckDrawCenterString();

			if (cl_drawchathud->value) {		
				DrawAltString(viddef.width * cl_draw_x->value, y, ChatMessages.chathud[0]);
				y += 8;
				DrawAltString(viddef.width * cl_draw_x->value, y, ChatMessages.chathud[1]);
				y += 8;
				DrawAltString(viddef.width * cl_draw_x->value, y, ChatMessages.chathud[2]);
				y += 8;
				DrawAltString(viddef.width * cl_draw_x->value, y, ChatMessages.chathud[3]);
				y += 10;
			}
			if (cl_drawclock->value) {

				/* 12hr, else 24hr */
				if (cl_drawclock->value == 1)
					strftime(timebuf, 32, "Time : %H:%M", now);
				else
					strftime(timebuf, 32, "Time : %I:%M %p", now);

				DrawString(viddef.width * cl_draw_x->value, y, timebuf);
				y += 9;
			}
			if (cl_drawdate->value) {
				strftime(timebuf, 32, "Date: %a, %d %b %Y", now);
				DrawString(viddef.width * cl_draw_x->value, y, timebuf);
				y += 9;
			}
			if (cl_drawfps->value) {
				index++;

				if ((index % FPS_FRAMES) == 0) {
					Com_sprintf(fps, 32, "FPS : %d", (int)(1000 / ((float)(Sys_Milliseconds() - prevTime) / FPS_FRAMES)));
					prevTime = Sys_Milliseconds();
				}
				if (index > FPS_FRAMES) {
					DrawString(viddef.width * cl_draw_x->value, y, fps);
					y += 9;
				}
			}
			if (cl_drawmaptime->value) {
				char		temp      [32];
				int		time      , hour, mins, secs;

				if (!cl_drawmaptime->value)
					return;

				if (cl_drawmaptime->value)
					time = cl.time / 1000;

				hour = time / 3600;
				mins = (time % 3600) / 60;
				secs = time % 60;

				if (hour > 0)
					Com_sprintf(temp, sizeof(temp), " Map time : %i:%02i:%02i", hour, mins, secs);
				else
					Com_sprintf(temp, sizeof(temp), "Map time : %i:%02i", mins, secs);

				DrawString(viddef.width * cl_draw_x->value, y, temp);
				y += 9;
			}
			if (cl_drawping->value) {

				avg = 0;

				for (i = 0; i < MAX_SCRN_PINGS; i++) {
					avg += ScrnPings[i];
				}

				avg /= MAX_SCRN_PINGS;

				Com_sprintf(s, sizeof(s), "Ping : %dms", avg);
				DrawString(viddef.width * cl_draw_x->value, y, s);
				y += 9;
			}
			if (cl_drawrate->value) {

				float		avgs = 0, avgr = 0;
				int		time;

				time = Sys_Milliseconds();

				for (i = 0; i < MAX_NET_HISTORY; i++)
					avgs += Net_History.SendsSize[i];

				if (Net_History.SendsStartTime)
					avgs /= (time - Net_History.SendsStartTime);
				avgs *= 1000.0;
				avgs /= 1024.0;

				for (i = 0; i < MAX_NET_HISTORY; i++)
					avgr += Net_History.RecsSize[i];

				if (Net_History.RecsStartTime)
					avgr /= (time - Net_History.RecsStartTime);
				avgr *= 1000.0;
				avgr /= 1024.0;

				Com_sprintf(s, sizeof(s), "DL:%2.1fKB/Sec UL:%2.1fKB/Sec", avgr, avgs);
				DrawString(viddef.width * cl_draw_x->value, y, s);
				y += 9;
			}
			if (scr_timegraph->value)
				SCR_DebugGraph(cls.frametime * 300, 0);

			SCR_DrawPause();

			SCR_DrawConsole();

			M_Draw();

			SCR_DrawLoading();
		}
	}
	re.EndFrame();
}
