/*
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;

cvar_t		*scr_conspeed;
cvar_t		*scr_netgraph;
cvar_t		*scr_timegraph;
cvar_t		*scr_debuggraph;
cvar_t		*scr_graphheight;
cvar_t		*scr_graphscale;
cvar_t		*scr_graphshift;

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

MUFONT STRINGS

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


//
//	Variable width (proportional) fonts
//

//===============================================================================
//FONT LOADING
//===============================================================================

mempool_t *fonts_mempool;

#define Font_Alloc(size) Mem_Alloc( fonts_mempool, size )

typedef struct {
	qbyte	x, y, width, height;
	float	s1, t1, s2, t2;
} muchar_t;
typedef struct mufont_s {
	char		name[MAX_QPATH];
	muchar_t	chars[256];
	int			fontheight;
	float		imagewidth, imageheight;
	struct shader_s	*shader;
	struct mufont_s	*next;
} mufont_t;

static mufont_t *SCR_LoadMUFont( char *name ) {

	char		filename[MAX_QPATH];
	qbyte		*buf;
	char		*ptr, *token;
	int			filenum;
	int			length;
	mufont_t	*font;
	struct shader_s *shader;
	int			numchar;

	// load the shader
	Q_snprintfz( filename, sizeof(filename), "fonts/%s.tga", name );
	shader = R_RegisterPic( filename );
	if( !shader )
		return NULL;

	// load the font description
	Q_snprintfz( filename, sizeof(filename), "fonts/%s.wfd", name );

	// load the file
	length = FS_FOpenFile( filename, &filenum, FS_READ );
	if( length == -1 ) {
		return NULL;
	}

	buf = Mem_TempMalloc( length + 1 );
	length = FS_Read( buf, length, filenum );
	FS_FCloseFile( filenum );
	if( !length ) {
		Mem_TempFree( buf );
		return NULL;
	}

	// seems to be valid. Allocate it
	font = (mufont_t *)Font_Alloc( sizeof(mufont_t) );
	font->shader = shader;
	Q_strncpyz( font->name, name, sizeof(font->name) );

	//proceed
	ptr = ( char * )buf;

	// get texture width and height
	token = COM_Parse( &ptr );
	if( !token[0] ) {
		Mem_TempFree( buf );
		return NULL;
	}
	font->imagewidth = atoi( token );
	token = COM_Parse( &ptr );
	if( !token[0] ) {
		Mem_TempFree( buf );
		return NULL;
	}
	font->imageheight = atoi( token );

	//get the chars
	while( ptr )
	{
		// "<char>" "<x>" "<y>" "<width>" "<height>"
		token = COM_Parse( &ptr );
		if( !token[0] ) break;
		numchar = atoi(token);
		if( numchar < 32 || numchar >= 256 ) 
			break;

		font->chars[numchar].x = atoi( COM_Parse( &ptr ) );
		font->chars[numchar].y = atoi( COM_Parse( &ptr ) );
		font->chars[numchar].width = atoi( COM_Parse( &ptr ) );
		font->chars[numchar].height = atoi( COM_Parse( &ptr ) );
		// create the texture coordinates
		font->chars[numchar].s1 = ((float)font->chars[numchar].x)/(float)font->imagewidth;
		font->chars[numchar].s2 = ((float)(font->chars[numchar].x + font->chars[numchar].width))/(float)font->imagewidth;
		font->chars[numchar].t1 = ((float)font->chars[numchar].y)/(float)font->imageheight;
		font->chars[numchar].t2 = ((float)(font->chars[numchar].y + font->chars[numchar].height))/(float)font->imageheight;
	}

	//mudFont is not always giving a proper size to the space character
	font->chars[' '].width = font->chars['a'].height / 2;

	// height is the same for every character
	font->fontheight = font->chars['a'].height;

	Mem_TempFree( buf );
	return font;
}
mufont_t *gs_muFonts;

//==================
// SCR_RegisterFont
//==================
struct mufont_s *SCR_RegisterFont( char *name ) {
	mufont_t	*font;

	COM_StripExtension( name );
	for( font = gs_muFonts; font; font = font->next ) {
		if( !Q_stricmp( font->name, name ) )
			return font;
	}

	font = SCR_LoadMUFont( name );
	if( !font ) {
		return NULL;
	}

	font->next = gs_muFonts;
	gs_muFonts = font;

	return font;
}

void SCR_InitFonts( void ) {
	cvar_t	*con_fontSystemSmall = Cvar_Get( "con_fontSystemSmall", DEFAULT_FONT_SMALL, CVAR_ARCHIVE );
	cvar_t	*con_fontSystemMedium = Cvar_Get( "con_fontSystemMedium", DEFAULT_FONT_MEDIUM, CVAR_ARCHIVE );
	cvar_t	*con_fontSystemBig = Cvar_Get( "con_fontSystemBig", DEFAULT_FONT_BIG, CVAR_ARCHIVE );

	fonts_mempool = Mem_AllocPool( NULL, "Fonts" );

	// register system fonts
	cls.fontSystemSmall = SCR_RegisterFont( con_fontSystemSmall->string );
	if( !cls.fontSystemSmall ) {
		cls.fontSystemSmall = SCR_RegisterFont( DEFAULT_FONT_SMALL );
		if( !cls.fontSystemSmall )
			Com_Error( ERR_FATAL, "Couldn't load default font \"%s\"", DEFAULT_FONT_SMALL );
	}

	cls.fontSystemMedium = SCR_RegisterFont( con_fontSystemMedium->string );
	if( !cls.fontSystemMedium )
		cls.fontSystemMedium = SCR_RegisterFont( DEFAULT_FONT_MEDIUM );

	cls.fontSystemBig = SCR_RegisterFont( con_fontSystemBig->string );
	if( !cls.fontSystemBig )
		cls.fontSystemBig = SCR_RegisterFont( DEFAULT_FONT_BIG );
}

void SCR_ShutdownFonts( void ) {
	Mem_FreePool( &fonts_mempool );
	gs_muFonts = NULL;
	cls.fontSystemSmall = NULL;
	cls.fontSystemMedium = NULL;
}

//===============================================================================
//STRINGS HELPERS
//===============================================================================


int SCR_HorizontalAlignForString( const int x, int align, int width )
{
	int nx = x;

	if( align % 3 == 0 ) // left
		nx = x;
	if( align % 3 == 1 ) // center
		nx = x - width / 2;
	if( align % 3 == 2 ) // right
		nx = x - width;

	return nx;
}

int SCR_VerticalAlignForString( const int y, int align, int height )
{
	int ny = y;

	if( align / 3 == 0 ) // top
		ny = y;
	else if( align / 3 == 1 ) // middle
		ny = y - height / 2;
	else if( align / 3 == 2 ) // bottom
		ny = y - height;

	return ny;
}
/*
//==================
// SCR_Strlen
// doesn't count invisible characters
//==================
static size_t SCR_Strlen( const char *str ) {
	const char *s = str;
	size_t count = 0;

	if( s ) {
		while( *s ) {
			if( ((*s)&255) < 32 ) {
				s++;
			}
			else if( Q_IsColorString( s ) ) {
				s+=2;
			} 
			else {
				count++;
				s++;
			}
		}
	}

	return count;
}
*/
//==================
// SCR_strHeight
// it's font height in fact, but for preserving simetry I call it str
//==================
size_t SCR_strHeight( struct mufont_s *font ) {
	if( !font )
		font = cls.fontSystemSmall;

	return font->fontheight;
}

//==================
// SCR_strWidth
// doesn't count invisible characters. Counts up to given length, if any.
//==================
size_t SCR_strWidth( const char *str, struct mufont_s *font, int maxlen ) {
	const char *s = str;
	size_t	width = 0;
	int		num;

	if( str )
	{
		if( !font )
			font = cls.fontSystemSmall;

		while( *s && *s != '\n' ) {
			if( maxlen && (s - str) >= maxlen ) // stop counting at desired len
				return width;

			num = (*s)&255;
			if( num < 32 ) {
				s++;
			}
			else if( Q_IsColorString( s ) ) {
				s+=2;
			} 
			else {
				width += font->chars[num].width;
				s++;
			}
		}
	}

	return width;
}

//==================
// SCR_StrlenForWidth
// returns the len allowed for the string to fit inside a given width when using a given font.
//==================
size_t SCR_StrlenForWidth( const char *str, struct mufont_s *font, size_t maxwidth ) {
	const char *s = str;
	size_t	width = 0;
	int		num;

	if( !str )
		return 0;

	if( !font )
		font = cls.fontSystemSmall;

	while( *s && *s != '\n' ) {
		if( width >= maxwidth ) {
			return (unsigned)(s - str);
		}
		num = (*s)&255;
		if( num < 32 ) {
			s++;
		}
		else if( Q_IsColorString( s ) ) {
			s+=2;
		} 
		else {
			width += font->chars[num].width;
			s++;
		}
	}

	return (unsigned)(s - str);
}


//===============================================================================
//STRINGS DRAWING
//===============================================================================

#define MAX_DRAWSTRINGLEN_CHARS 2048
static char stringLenBuffer[ MAX_DRAWSTRINGLEN_CHARS ];

//================
//SCR_DrawRawChar
//
//Draws one graphics character with 0 being transparent.
//It can be clipped to the top of the screen to allow the console to be
//smoothly scrolled off.
//================
void SCR_DrawRawChar( int x, int y, int num, struct mufont_s *font, vec4_t color )
{
	if( !font )
		font = cls.fontSystemSmall;

	num &= 255;
	if ( (num&127) <= 32 )
		return;		// space

	if( y <= -font->fontheight )
		return;			// totally off screen

	R_DrawStretchPic( x, y, font->chars[num].width, font->fontheight, 
		font->chars[num].s1, font->chars[num].t1, font->chars[num].s2, font->chars[num].t2,
		color, font->shader );
}

//==================
// SCR_DrawRawString : does not align nor does check off screen
//==================
void SCR_DrawRawString( int x, int y, const char *str, struct mufont_s *font, vec4_t color )
{
	int		num;
	int		xoffset = 0, yoffset = 0;
	vec4_t	scolor;

	if( !str )
		return;

	if( !font )
		font = cls.fontSystemSmall;

	Vector4Copy( color, scolor );

	while( *str ) {
		if( Q_IsColorString( str ) ) {
			VectorCopy( color_table[ColorIndex(str[1])], scolor );
			str += 2;
			continue;
		}

		num = (*str)&255;
		str++;
		if( num < 32 )
			continue;

		if( (num&127) != 32 ) {	// not a space
			R_DrawStretchPic( x+xoffset, y+yoffset, font->chars[num].width, font->chars[num].height,
				font->chars[num].s1, font->chars[num].t1, font->chars[num].s2, font->chars[num].t2,
				scolor, font->shader );
		}

		xoffset += font->chars[num].width;
	}
}

//==================
// SCR_DrawString
//==================
void SCR_DrawString( int x, int y, int align, const char *str, struct mufont_s *font, vec4_t color )
{
	int		width;

	if( !str )
		return;

	if( !font )
		font = cls.fontSystemSmall;

	width = SCR_strWidth( str, font, 0 );
	if( width ) {
		x = SCR_HorizontalAlignForString( x, align, width );
		y = SCR_VerticalAlignForString( y, align, font->fontheight );

		if( y <= -font->fontheight )
			return;			// totally off screen

		if( x <= -width )
			return;			// totally off screen

		SCR_DrawRawString( x, y, str, font, color );
	}
}

//==================
// SCR_DrawString - ignores invisible characters at counting len
//==================
void SCR_DrawStringLen( int x, int y, int align, const char *str, size_t len, struct mufont_s *font, vec4_t color )
{
	const char *s = str;
	size_t	screenlen = 0;

	if( !str )
		return;

	if( !font )
		font = cls.fontSystemSmall;

	if( len >= MAX_DRAWSTRINGLEN_CHARS )
		len = MAX_DRAWSTRINGLEN_CHARS - 1;

	while( *s && screenlen < len && *s != '\n' ) {
		if( ((*s)&255) < 32 ) {
			s++;
		}
		else if( Q_IsColorString( s ) ) {
			s+=2;
		} 
		else {
			screenlen++;
			s++;
		}
	}

	// it fits inside desired len
	if( screenlen ) {
		int reallen = s - str + 1;
		Q_strncpyz( stringLenBuffer, str, reallen );
		SCR_DrawString( x, y, align, stringLenBuffer, font, color );
	}
}


//=============
//SCR_DrawFillRect
//
//Fills a box of pixels with a single color
//=============
void SCR_DrawFillRect( int x, int y, int w, int h, vec4_t color ) {
	R_DrawStretchPic( x, y, w, h, 0, 0, 1, 1, color, cls.whiteShader );
}

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

BAR GRAPHS

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

//==============
//CL_AddNetgraph
//
//A new packet was just parsed
//==============
void CL_AddNetgraph( void )
{
	int		i;
	int		in;
	int		ping;

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

	for( i = 0; i < cls.netchan.dropped; i++ )
		SCR_DebugGraph( 30.0f, 0.655f, 0.231f, 0.169f );

	for( i = 0; i < cl.suppressCount; i++ )
		SCR_DebugGraph( 30.0f, 0.0f, 1.0f, 0.0f );

	// see what the latency was on this packet
	in = cls.netchan.incoming_acknowledged & CMD_MASK;
	ping = cls.realtime - cl.cmd_time[in];
	ping /= 30;
	if( ping > 30 )
		ping = 30;
	SCR_DebugGraph( ping, 1.0f, 0.75f, 0.06f );
}


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

static	int			current;
static	graphsamp_t	values[1024];

/*
==============
SCR_DebugGraph
==============
*/
void SCR_DebugGraph( float value, float r, float g, float b )
{
	values[current].value = value;
	values[current].color[0] = r;
	values[current].color[1] = g;
	values[current].color[2] = b;
	values[current].color[3] = 1.0f;

	current++;
	current &= 1023;
}

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

	//
	// draw the graph
	//
	w = viddef.width;
	x = 0;
	y = 0+viddef.height;
	SCR_DrawFillRect (x, y-scr_graphheight->integer,
		w, scr_graphheight->integer, colorBlack);

	for (a=0 ; a<w ; a++)
	{
		i = (current-1-a+1024) & 1023;
		v = values[i].value;
		v = v*scr_graphscale->integer + scr_graphshift->integer;
		
		if (v < 0)
			v += scr_graphheight->integer * (1+(int)(-v/scr_graphheight->integer));
		h = (int)v % scr_graphheight->integer;
		SCR_DrawFillRect (x+w-1-a, y - h, 1, h, values[i].color);
	}
}

//============================================================================

/*
==================
SCR_InitScreen
==================
*/
void SCR_InitScreen( void )
{
	scr_conspeed = Cvar_Get( "scr_conspeed", "3", CVAR_ARCHIVE );
	scr_netgraph = Cvar_Get( "netgraph", "0", 0 );
	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_initialized = qtrue;
}

//=============================================================================

/*
==================
SCR_RunConsole

Scroll it up or down
==================
*/
void SCR_RunConsole( void )
{
// decide on the height of the console
	if( cls.key_dest == key_console )
		scr_conlines = 0.5;		// half screen
	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( scr_con_current ) {
		Con_DrawConsole( scr_con_current );
		return;
	}

	if( cls.state == CA_ACTIVE && (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.soundPrepped = qfalse;	// don't play ambients

	memset( cl.configstrings, 0, sizeof(cl.configstrings) );

	scr_conlines = 0;			// none visible
	scr_draw_loading = 2;		// clear to black first
	SCR_UpdateScreen();

	CL_ShutdownMedia();

#if 0
	if( cls.disable_screen )
		return;
	if( developer->integer )
		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.cin.time > 0 )
		scr_draw_loading = 2;	// clear to black first
	else
		scr_draw_loading = 1;
	SCR_UpdateScreen();
	cls.disable_screen = Sys_Milliseconds();
	cls.disable_servercount = cl.servercount;
#endif
}

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


//=======================================================

//=================
//SCR_RegisterConsoleMedia
//=================
void SCR_RegisterConsoleMedia( void )
{
	cls.whiteShader = R_RegisterPic( "gfx/ui/white" );
	cls.consoleShader = R_RegisterPic( "gfx/ui/console" );
	SCR_InitFonts();
}

//=================
//SCR_ShutDownConsoleMedia
//=================
void SCR_ShutDownConsoleMedia( void ) {
	SCR_ShutdownFonts();
}

//============================================================================

/*
==================
SCR_RenderView

==================
*/
void SCR_RenderView( float stereo_separation )
{
	if( cls.demoplaying ) {
		if( cl_timedemo->integer ) {
			if( !cl.timedemo_start )
				cl.timedemo_start = Sys_Milliseconds ();
			cl.timedemo_frames++;
		}
	}

	// frame is not valid until we load the CM data
	if( CM_ClientLoad() )
		CL_GameModule_RenderView( stereo_separation );
}

//============================================================================

/*
==================
SCR_UpdateScreen

This is called every frame, and can also be called explicitly to flush
text to the screen.
==================
*/
void SCR_UpdateScreen( void )
{
	int numframes;
	int i;
	float separation[2];

	// 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 || !cls.mediaInitialized )
		return;				// not initialized yet

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

	if( cl_stereo->integer )
	{
		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;
	}

	for( i = 0; i < numframes; i++ )
	{
		R_BeginFrame( separation[i] );

		if( scr_draw_loading == 2 )
		{	// loading plaque over black screen
			scr_draw_loading = 0;
			CL_UIModule_DrawConnectScreen( qtrue );
		}
		// if a cinematic is supposed to be running, handle menus
		// and console specially
		else if( cl.cin.time > 0 )
		{
			SCR_DrawCinematic();
		} 
		else if( cls.state == CA_DISCONNECTED )
		{
			CL_UIModule_Refresh( qtrue );
			SCR_DrawConsole();
		}
		else if( cls.state == CA_CONNECTING || cls.state == CA_CONNECTED )
		{
			CL_UIModule_DrawConnectScreen( qtrue );
		}
		else if( cls.state == CA_LOADING )
		{
			SCR_RenderView( separation[i] );
			CL_UIModule_DrawConnectScreen( qfalse );
		}
		else if( cls.state == CA_ACTIVE )
		{
			SCR_RenderView( separation[i] );

			CL_UIModule_Refresh( qfalse );

			if( scr_timegraph->integer )
				SCR_DebugGraph( cls.frametime*300, 1, 1, 1 );

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

			SCR_DrawConsole();
		}

		R_EndFrame();
	}
}
