/*
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_parse.c  -- parse a message received from the server

#include "client.h"
#include "../qcommon/webdownload.h"

char *svc_strings[256] =
{
	"svc_bad",
	"svc_nop",
	"svc_servercmd",
	"svc_sound",
	"svc_serverdata",
	"svc_spawnbaseline",
	"svc_download",
	"svc_playerinfo",
	"svc_packetentities",
	"svc_match",
	"svc_clcack",
	"svc_servercs", //tmp jalfixme: sends reliable command as unreliable
	"svc_frame"
#ifdef BATTLEYE
	, "svc_battleye"
#endif
};

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

/*
===============
CL_CheckOrDownloadFile

Returns true if the file exists, otherwise it attempts
to start a download from the server.
===============
*/
qboolean CL_CheckOrDownloadFile( char *filename )
{
	if( !cl_downloads->integer )
		return qtrue;

	if( strstr (filename, "..") || *filename == '.'  || *filename == '/' || strchr (filename, '\"') ) {
		Com_Printf( "Not downloading, invalid filename: %s\n", filename );
		return qtrue;
	}

	// it exists, no need to download
	if( FS_FOpenFile ( filename, NULL, FS_READ ) != -1 ) {
		return qtrue;
	}

	Com_Printf( "Asking to download %s\n", filename );
	CL_AddReliableCommand( va( "download \"%s\"", filename ) );

	return qfalse;
}

/*
===============
CL_Download_f
===============
*/
void CL_Download_f( void )
{
	char filename[MAX_OSPATH];

	if( Cmd_Argc() != 2 ) {
		Com_Printf( "Usage: download <filename>\n" );
		return;
	}

	Q_snprintfz( filename, sizeof(filename), "%s", Cmd_Argv(1) );

	if( strstr (filename, "..") || *filename == '.'  || *filename == '/' || strchr (filename, '\"') ) {
		Com_Printf( "Not downloading, invalid filename.\n" );
		return;
	}

	if ( FS_FOpenFile (filename, NULL, FS_READ) != -1 ) {
		Com_Printf( "File already exists.\n" );
		return;
	}

	Q_strncpyz( cls.download.name, filename, sizeof(cls.download.name) );
	Com_Printf( "Asking to download %s\n", cls.download.name );

	CL_AddReliableCommand( va("download \"%s\"", cls.download.name) );
}

/*
=====================
CL_WebDownloadProgress
Callback function for webdownloads.
Since Web_Get only returns once it's done, we have to do various things here:
Update download percent, handle input, redraw UI and send net packets.
=====================
*/

int CL_WebDownloadProgress( double percent )
{
	cls.download.percent = percent;

	IN_Frame();
	CL_SendCommand();
	CL_UIModule_DrawConnectScreen(qtrue);
	SCR_UpdateScreen();

	// get new key events
	Sys_SendKeyEvents();
	// allow mice or other external controllers to add commands
	IN_Commands();
	// process console commands
	Cbuf_Execute();

	return cls.download.disconnect;
}

/*
=====================
CL_DownloadComplete
Checks downloaded file's checksum, renames it and adds to the filesystem.
=====================
*/
void CL_DownloadComplete( void )
{
	char		oldn[MAX_OSPATH], newn[MAX_OSPATH];
	unsigned	checksum;
	qbyte		*buffer;
	int			length;

	Q_snprintfz( oldn, sizeof(oldn), "%s/%s", FS_Gamedir(), cls.download.tempname );
	Q_snprintfz( newn, sizeof(newn), "%s/%s", FS_Gamedir(), cls.download.name );

	// verify checksum
	length = FS_LoadAbsoluteFile( oldn, (void **)&buffer, NULL, 0);
	if( !buffer ) {
		Com_Printf("Error loading the downloaded file.\n");
		return;
	}
	checksum = Com_BlockChecksum( buffer, length );
	FS_FreeFile( buffer );

	if( cls.download.checksum != checksum ) {
		Com_Printf("Downloaded file has wrong checksum. Removing.\n");
		FS_RemoveFile( oldn );
		return;
	}

	if( FS_RenameFile( oldn, newn ) ) {
		Com_Printf( "Failed to rename the downloaded file.\n" );
		return;
	}

	FS_AddPakFile( newn ); // wsw : jal : update filesys
}

/*
=====================
CL_FreeDownloadList
=====================
*/
void CL_FreeDownloadList( void )
{
	download_list_t	*prev, *dl;

	dl = cls.download.list;
	while( dl != NULL ) {
		prev = dl;
		dl = prev->next;
		Mem_TempFree( prev );
	}

	cls.download.list = NULL;
}

/*
=====================
CL_InitDownload_f
Hanldles server's initdownload message, starts web or server download if possible
=====================
*/
void CL_InitDownload_f( void )
{
	char			filename[MAX_OSPATH];
	char			url[MAX_STRING_CHARS];
	int				size;
	unsigned		checksum;
	qboolean		allow_serverdownload;
	download_list_t	*new;
	download_list_t	*dl;

	// read the data
	Q_strncpyz( filename, Cmd_Argv(1), sizeof(filename) );
	size = atoi(Cmd_Argv(2));
	checksum = strtoul(Cmd_Argv(3), NULL, 10);
	allow_serverdownload = (atoi(Cmd_Argv(4)) != 0);
	Q_strncpyz(url, Cmd_Argv(5), sizeof(url));

	if( cls.download.filenum || cls.download.web ) {
		Com_Printf("Got init download message while already downloading.\n");
		return;
	}

	if( size == -1 ) { // means that download was refused
		Com_Printf( "Download request refused: %s\n", url ); // if it's refused, url field holds the reason
		CL_RequestNextDownload();
		return;
	}

	if( allow_serverdownload == qfalse && strlen(url) == 0 ) {
		Com_Printf( "Server doesn't allow downloading of %s\n", filename );
		CL_RequestNextDownload();
		return;
	}

	if( size <= 0 ) {
		Com_Printf( "Server gave invalid size. Not downloading.\n" );
		CL_RequestNextDownload();
		return;
	}

	if( checksum == 0 ) {
		Com_Printf( "Server didn't provide checksum. Not downloading.\n" );
		CL_RequestNextDownload();
		return;
	}

	if( strstr (filename, "..") || *filename == '.'  || *filename == '/'
		|| *filename == '$' ) { // deny player models by now
		Com_Printf( "Not downloading, invalid filename: %s\n", filename );
		CL_RequestNextDownload();
		return;
	}

	if( strchr (filename, '\\') || strchr (filename, '/') ) {
		Com_Printf( "Refusing to download to subdirectory: %s\n", filename );
		CL_RequestNextDownload();
		return;
	}

	if( Q_stricmp(COM_FileExtension(filename), ".pk3")) {
		Com_Printf( "Refusing to download non .pk3 file: %s\n", filename );
		CL_RequestNextDownload();
		return;
	}

	if( FS_FOpenFile (filename, NULL, FS_READ) != -1 ) { // it exists, no need to download
		Com_Printf( "Can't download %s. File already exists.\n", filename );
		CL_RequestNextDownload();
		return;
	}

	if( !cl_downloads->integer ) { // shouldn't happen
		CL_RequestNextDownload();
		return;
	}

	if( !cl_downloads_from_web->integer && !allow_serverdownload ) {
		Com_Printf( "Not downloading. Server only provided web download.\n" );
		CL_RequestNextDownload();
		return;
	}

	dl = cls.download.list;
	while( dl != NULL ) {
		if( !Q_stricmp(dl->pakname, filename) ) {
			Com_Printf( "Allready tried downloading %s. Skipping.\n", filename );
			CL_RequestNextDownload();
			return;
		}
		dl = dl->next;
	}

	Q_strncpyz( cls.download.name, filename, sizeof(cls.download.name) );
	Q_snprintfz( cls.download.tempname, sizeof(cls.download.tempname), "%s.tmp", filename );

	cls.download.size = size;
	cls.download.checksum = checksum;
	cls.download.percent = 0;

	new = Mem_TempMalloc(sizeof(download_list_t));
	Q_strncpyz( new->pakname, cls.download.name, sizeof(new->pakname) );
	new->next = NULL;
	if( cls.download.list == NULL ) {
		cls.download.list = new;
	} else {
		dl = cls.download.list;
		while( dl->next != NULL )
			dl = dl->next;
		dl->next = new;
	}

	if( cl_downloads_from_web->integer && strlen(url) > 0 )
	{
		char		fulltemp[MAX_OSPATH], referer[MAX_STRING_CHARS];
		qboolean	success;

		Com_Printf( "Web download: %s from %s\n", cls.download.tempname, url );
		Q_snprintfz( fulltemp, sizeof(fulltemp), "%s/%s", FS_Gamedir(), cls.download.tempname );
		Q_snprintfz( referer, sizeof(referer), "wsw://%s", NET_AdrToString(&cls.serveraddress) );

		cls.download.web = qtrue;
		cls.download.disconnect = qfalse;

		success = Web_Get( url, referer, fulltemp, qtrue, 30, CL_WebDownloadProgress );

		cls.download.web = qfalse;

		if( success ) {
			Com_Printf("Web download of %s was succesfull\n", cls.download.tempname);

			CL_DownloadComplete();

			cls.download.name[0] = 0;
			cls.download.tempname[0] = 0;
			cls.download.size = 0;
			cls.download.percent = 0.0;
		} else {
			Com_Printf("Web download of %s failed\n", cls.download.tempname);
		}

		// check if user pressed escape to stop the download
		if( cls.download.disconnect ) {
			cls.download.disconnect = qfalse;
			CL_Disconnect_f();
			return;
		}

		if( success ) {
			CL_RequestNextDownload();
			return;
		}
	}

	if( !allow_serverdownload ) {
		CL_RequestNextDownload();
		return;
	}

	cls.download.offset = FS_FOpenFile(cls.download.tempname, &cls.download.filenum, FS_APPEND);
	if( cls.download.offset < 0 ) {
		Com_Printf( "Can't download. Couldn't open %s for writing.\n", cls.download.tempname );
		cls.download.filenum = 0;
		cls.download.offset = 0;
		cls.download.size = 0;
		CL_RequestNextDownload();
		return;
	}

	Com_Printf( "Server download: %s\n", cls.download.tempname );

	// have to use Sys_Milliseconds because cls.realtime might be old from Web_Get
	cls.download.timeout = Sys_Milliseconds() + 2000; 
	cls.download.retries = 0;

	CL_AddReliableCommand( va("nextdl %s %i", cls.download.name, cls.download.offset) );
}

/*
=====================
CL_StopServerDownload
=====================
*/
void CL_StopServerDownload( void )
{
	FS_FCloseFile( cls.download.filenum );
	cls.download.filenum = 0;
	cls.download.name[0] = 0;
	cls.download.tempname[0] = 0;
	cls.download.offset = 0;
	cls.download.size = 0;
	cls.download.percent = 0.0;
	cls.download.timeout = 0;
	cls.download.retries = 0;
}

/*
=====================
CL_RetryDownload
Resends download request
Also aborts download if we have retried too many times
=====================
*/
static void CL_RetryDownload( void )
{
	if( ++cls.download.retries > 5 ) {
		Com_Printf( "Download timed out: %s\n", cls.download.name );

		// let the server know we're done
		CL_AddReliableCommand( va("nextdl %s %i", cls.download.name, -2) );

		CL_StopServerDownload();

		CL_RequestNextDownload();
	} else {
		cls.download.timeout = cls.realtime + 2000;
		CL_AddReliableCommand( va("nextdl %s %i", cls.download.name, cls.download.offset) );
	}
}

/*
=====================
CL_CheckDownloadTimeout
Retry downloading if too much time has passed since last download packet was received
=====================
*/
void CL_CheckDownloadTimeout( void )
{
	if( !cls.download.filenum || !cls.download.timeout || cls.download.timeout > cls.realtime )
		return;

	CL_RetryDownload();
}

/*
=====================
CL_ParseDownload
Handles download message from the server.
Writes data to the file and requests next download block.
=====================
*/
void CL_ParseDownload( msg_t *msg )
{
	int		size, offset;
	char	*svFilename;

	// read the data
	svFilename = MSG_ReadString( msg );
	offset = MSG_ReadLong( msg );
	size = MSG_ReadLong( msg );

	if( size <= 0 ) {
		Com_Printf( "invalid size on download message\n" );
		CL_RetryDownload();
		return;
	}

	if( !cls.download.filenum ) {
		Com_Printf( "download message while not dowloading\n" );
		msg->readcount += size;
		return;
	}

	if( Q_stricmp(cls.download.name, svFilename) ) {
		Com_Printf( "download message for wrong file\n" );
		msg->readcount += size;
		return;
	}

	if( offset < 0 || offset+size > cls.download.size ) {
		Com_Printf( "invalid download message\n" );
		msg->readcount += size;
		CL_RetryDownload();
		return;
	}

	if( cls.download.offset != offset ) {
		Com_Printf( "download message for wrong position\n" );
		msg->readcount += size;
		CL_RetryDownload();
		return;
	}

	FS_Write( msg->data + msg->readcount, size, cls.download.filenum );
	msg->readcount += size;
	cls.download.offset += size;
	cls.download.percent = cls.download.offset*1.0 / cls.download.size;

	if( cls.download.offset < cls.download.size )
	{
		cls.download.timeout = cls.realtime+2000;
		cls.download.retries = 0;

		CL_AddReliableCommand( va("nextdl %s %i", cls.download.name, cls.download.offset) );
	}
	else
	{
		Com_Printf( "Download complete: %s\n", cls.download.name );

		FS_FCloseFile( cls.download.filenum );

		CL_DownloadComplete();

		// let the server know we're done
		CL_AddReliableCommand( va("nextdl %s %i", cls.download.name, -1) );

		cls.download.filenum = 0;
		cls.download.name[0] = 0;
		cls.download.tempname[0] = 0;
		cls.download.offset = 0;
		cls.download.size = 0;
		cls.download.percent = 0.0;
		cls.download.timeout = 0;
		cls.download.retries = 0;

		CL_RequestNextDownload();
	}
}

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

  SERVER CONNECTING MESSAGES

=====================================================================
*/
void CL_ParseConfigstringCommand( void );
/*
==================
CL_ParseServerData
==================
*/
void CL_ParseServerData( msg_t *msg )
{
	extern cvar_t	*fs_gamedirvar;
	char	*str;
	int		i, start, sv_bitflags;

	Com_DPrintf( "Serverdata packet received.\n" );
	
	// wipe the client_state_t struct
	
	CL_ClearState();
	CL_SetClientState( CA_CONNECTED );

	// parse protocol version number
	i = MSG_ReadLong( msg );

	if( i != PROTOCOL_VERSION )
		Com_Error( ERR_DROP, "Server returned version %i, not %i", i, PROTOCOL_VERSION );

	cl.servercount = MSG_ReadLong( msg );

	// game directory
	str = MSG_ReadString( msg );
	Q_strncpyz( cl.gamedir, str, sizeof(cl.gamedir) );

	// set gamedir
	if( ( *str && ( !fs_gamedirvar->string || !*fs_gamedirvar->string 
		|| strcmp(fs_gamedirvar->string, str) ) ) 
		|| ( !*str && (fs_gamedirvar->string && *fs_gamedirvar->string) ) ) {
		Cvar_Set( "fs_game", str );
	}

	// parse player entity number
	cl.playernum = MSG_ReadShort( msg );

	// get the full level name
	Q_strncpyz( cl.servermessage, MSG_ReadString( msg ), sizeof(cl.servermessage) );

	sv_bitflags = MSG_ReadByte( msg );

#if 1
	// get the configstrings request
	start = MSG_ReadShort( msg );
	if( start >= 0 )
		CL_AddReliableCommand( va("configstrings %i %i", cl.servercount, start) );
#else
	// read the configstrings we could get inside this packet
	{
		int csNum;
		char	*s;

		start = MSG_ReadShort( msg );
		for( i = 0; i < start; i++ ) {
			csNum = MSG_ReadShort( msg );
			s = MSG_ReadString( msg );
			//add the configstring 
			Cmd_TokenizeString( va("cs %i %s", i, s ), qfalse );
			CL_ParseConfigstringCommand();
		}

		start = MSG_ReadShort( msg );
		if( start >= 0 ) {
			if( start < MAX_CONFIGSTRINGS )
				CL_AddReliableCommand( va("configstrings %i %i", cl.servercount, start) );
			else
				CL_AddReliableCommand( va("baselines %i 0", cl.servercount, start) );
		}
	}
#endif
	CL_RestartMedia();

#ifdef BATTLEYE
	if( sv_bitflags & 128 ) {
		// don't start BE again if it's already running (e.g. when changing map)
		if( cl_battleye->integer && !cls.runBattlEye )
			CL_BE_Start();
	}
#endif

	// separate the printfs so the server message can have a color
	Com_Printf( "\n%s\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", S_COLOR_RED );
	Com_Printf( "%s%s\n\n", S_COLOR_WHITE, cl.servermessage );
}

/*
==================
CL_ParseBaseline
==================
*/
void CL_ParseBaseline( msg_t *msg )
{
	entity_state_t	*es;
	unsigned		bits;
	int				newnum;
	entity_state_t	nullstate;

	memset (&nullstate, 0, sizeof(nullstate));
	newnum = CL_ParseEntityBits( msg, &bits );
	es = &cl_baselines[newnum];
	CL_ParseDelta( msg, &nullstate, es, newnum, bits );
}


//========= StringCommands================

void CL_Reconnect_f( void );
void CL_Changing_f( void );
void CL_Precache_f( void );
void CL_ForwardToServer_f( void );
void CL_ServerDisconnect_f( void );

/*
==================
CL_ValidateConfigstring
==================
*/
qboolean CL_ValidateConfigstring( char *string )
{
	char *p;
	qboolean	opened = qfalse;
	int			parity = 0;

	if( !string || !string[0] )
		return qfalse;

	p = string;
	while( *p ) {
		if( *p == '\"' ) {
			if( opened ) {
				parity--;
				opened = qfalse;
			} else {
				parity++;
				opened = qtrue;
			}
		}
		p++;
	}

	if( parity != 0 )
		return qfalse;

	return qtrue;
}

/*
==================
CL_ParseConfigstringCommand
==================
*/
void CL_ParseConfigstringCommand( void )
{
	int		i;
	char	*s;

	if( Cmd_Argc() < 2 )
		return;

	i = atoi( Cmd_Argv(1) );
	s = Cmd_Argv(2);

	if( !s || !s[0] )
		return;

	if( cl_debug_serverCmd->integer && (cls.state >= CA_ACTIVE || cls.demoplaying) )
		Com_Printf( "CL_ParseConfigstringCommand(%i): \"%s\"\n", i, s );
	
	if( i < 0 || i >= MAX_CONFIGSTRINGS )
		Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" );
	
	// wsw : jal : warn if configstring overflow
	if( strlen(s) >= MAX_CONFIGSTRING_CHARS ) {
		Com_Printf( "%sWARNING:%s Configstring %i overflowed\n", S_COLOR_YELLOW, S_COLOR_WHITE, i ); 
		Com_Printf( "%s%s\n", S_COLOR_WHITE, s );
	}

	if( !CL_ValidateConfigstring(s) ) {
		Com_Printf( "%sWARNING:%s Invalid Configstring (%i): %s\n", S_COLOR_YELLOW, S_COLOR_WHITE, i, s );
		return;
	}
	
	Q_strncpyz( cl.configstrings[i], s, sizeof(cl.configstrings[i]) );
	
	// allow cgame to update it too
	CL_GameModule_ServerCommand();
}


void CL_CmdStuffText_f( void ) {
	char *s = Cmd_Argv(1);
	Com_DPrintf( "stufftext: %s\n", s );
	Cbuf_AddText( s );
}

typedef struct {
	char	*name;
	void	(*func) (void);
} svcmd_t;

svcmd_t svcmds[] =
{
	{"reconnect", CL_Reconnect_f},
	{"changing", CL_Changing_f},
	{"precache", CL_Precache_f},
	{"cmd", CL_ForwardToServer_f},
	{"stufftext", CL_CmdStuffText_f},
	{"cs", CL_ParseConfigstringCommand},
	{"disconnect", CL_ServerDisconnect_f},
	{"initdownload", CL_InitDownload_f},
	
	{NULL, NULL}
};


/*
==================
CL_ParseServerCommand
==================
*/
void CL_ParseServerCommand( msg_t *msg )
{
	char *s, *text;
	svcmd_t *cmd;

	text = MSG_ReadString( msg );
	Cmd_TokenizeString( text, qfalse );
	s = Cmd_Argv(0);

	if( cl_debug_serverCmd->integer && (cls.state < CA_ACTIVE || cls.demoplaying) )
		Com_Printf( "CL_ParseServerCommand: \"%s\"\n", text );
	
	// filter out these server commands to be called from the client
	for( cmd = svcmds; cmd->name; cmd++ ) {
		if( !strcmp(s, cmd->name) ) {
			cmd->func();
			return;
		}
	}

	CL_GameModule_ServerCommand();
}

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

ACTION MESSAGES

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

/*
==================
CL_ParseStartSoundPacket
==================
*/
void CL_ParseStartSoundPacket( msg_t *msg )
{
    vec3_t  pos;
    int 	channel, ent;
    int 	sound_num;
    float 	volume;
    float 	attenuation;  
	int		flags;

	flags = MSG_ReadByte( msg );
	sound_num = MSG_ReadByte( msg );

	// entity relative
	channel = MSG_ReadShort( msg );
	ent = channel>>3;
	if (ent > MAX_EDICTS)
		Com_Error (ERR_DROP,"CL_ParseStartSoundPacket: ent = %i", ent);
	channel &= 7;

	// positioned in space
	if ((flags & (SND_POS0_8|SND_POS0_16)) == SND_POS0_8)
		pos[0] = MSG_ReadChar( msg );
	else if ((flags & (SND_POS0_8|SND_POS0_16)) == SND_POS0_16)
		pos[0] = MSG_ReadShort( msg );
	else
		pos[0] = MSG_ReadInt3( msg );

	if ((flags & (SND_POS1_8|SND_POS1_16)) == SND_POS1_8)
		pos[1] = MSG_ReadChar( msg );
	else if ((flags & (SND_POS1_8|SND_POS1_16)) == SND_POS1_16)
		pos[1] = MSG_ReadShort( msg );
	else
		pos[1] = MSG_ReadInt3( msg );

	if ((flags & (SND_POS2_8|SND_POS2_16)) == SND_POS2_8)
		pos[2] = MSG_ReadChar( msg );
	else if ((flags & (SND_POS2_8|SND_POS2_16)) == SND_POS2_16)
		pos[2] = MSG_ReadShort( msg );
	else
		pos[2] = MSG_ReadInt3( msg );

    if (flags & SND_VOLUME)
		volume = MSG_ReadByte( msg ) / 255.0;
	else
		volume = DEFAULT_SOUND_PACKET_VOLUME;
	
    if (flags & SND_ATTENUATION)
		attenuation = MSG_ReadByte( msg ) / 64.0;
	else
		attenuation = DEFAULT_SOUND_PACKET_ATTENUATION;	

	CL_GameModule_GlobalSound( pos, ent, channel, sound_num, volume, attenuation );
}

#ifdef BATTLEYE
void CL_ParseSVCBattleye( msg_t *msg ) {
	static qbyte tempdata[BE_MAX_PACKET_SIZE];
	short len;
	unsigned int	id;

	cls.BE.acknowledgedPacket = MSG_ReadLong( msg );
	len = MSG_ReadShort( msg );
	if( len > 0 && len <= BE_MAX_PACKET_SIZE ) {
		MSG_ReadData( msg, tempdata, len );
		id = MSG_ReadLong( msg );
		if( id > cls.BE.commandReceived) {
			cls.BE.commandReceived = id;
			// pass this packet to BE Client
			if( !cls.demoplaying )
				CL_BE_NewIncomingPacket( tempdata, len );
		}
	}
	else if( len != 0 )
		Com_Error( ERR_DROP, "Invalid BattlEye packet size from server" );
}
#endif

void SHOWNET( msg_t *msg, char *s )
{
	if (cl_shownet->integer>=2)
		Com_Printf ("%3i:%s\n", msg->readcount-1, s);
}

/*
=====================
CL_ParseServerMessage
=====================
*/
void CL_ParseServerMessage( msg_t *msg )
{
	int			cmd;

	if( cl_shownet->integer == 1 ) {
		Com_Printf( "%i ", msg->cursize );
	} else if( cl_shownet->integer >= 2 ) {
		Com_Printf( "------------------\n" );
	}


// parse the message
//
	while( 1 )
	{
		if( msg->readcount > msg->cursize )
		{
			Com_Error( ERR_DROP,"CL_ParseServerMessage: Bad server message" );
			break;
		}

		cmd = MSG_ReadByte( msg );
		if( cl_debug_serverCmd->integer & 4 ) {
			if( cmd == -1 )
				Com_Printf( "%3i:CMD %i %s\n", msg->readcount-1, cmd, "EOF" );
			else
				Com_Printf( "%3i:CMD %i %s\n", msg->readcount-1, cmd, !svc_strings[cmd] ? "bad" : svc_strings[cmd] );
		}

		if( cmd == -1 )
		{
			SHOWNET( msg, "END OF MESSAGE" );
			break;
		}

		if( cl_shownet->integer>=2 )
		{
			if( !svc_strings[cmd] )
				Com_Printf( "%3i:BAD CMD %i\n", msg->readcount-1, cmd );
			else
				SHOWNET( msg, svc_strings[cmd] );
		}

	// other commands
		switch( cmd )
		{
		default:
			Com_Error( ERR_DROP,"CL_ParseServerMessage: Illegible server message" );
			break;

		case svc_nop:
//			Com_Printf( "svc_nop\n" );
			break;

		case svc_servercmd:
			{ 
				int		cmdNum;
				char	*s;
				cmdNum = MSG_ReadLong( msg );
				if( cmdNum < 0 )
					Com_Error( ERR_DISCONNECT, "CL_ParseServerMessage: Invalid cmdNum value received: %i\n", cmdNum ); 
				if( cmdNum > cls.lastExecutedServerCommand ) {
					cls.lastExecutedServerCommand = cmdNum;
					CL_ParseServerCommand( msg );
				} else {
					// read but ignore
					s = MSG_ReadString( msg );
				}
			}
			break;

		case svc_serverdata:
			{
			Cbuf_Execute();		// make sure any stuffed commands are done
			CL_ParseServerData( msg );
			}
			break;

		case svc_sound:
			CL_ParseStartSoundPacket( msg );
			break;

		case svc_spawnbaseline:
			CL_ParseBaseline( msg );
			break;

		case svc_download:
			CL_ParseDownload( msg );
			break;

		case svc_servercs: // configstrings from demo files. they don't have acknowledge
			CL_ParseServerCommand( msg );
			break;

		case svc_clcack:
			{
				cls.reliableAcknowledge = MSG_ReadLong( msg );
				if( cls.reliableAcknowledge < 0 )
					Com_Error( ERR_DISCONNECT, "CL_ParseServerMessage: Invalid cls.reliableAcknowledge value received: %i\n", cls.reliableAcknowledge ); 

				if( cl_debug_serverCmd->integer & 4 )
					Com_Printf( "svc_clcack:%i\n", cls.reliableAcknowledge );
			}
			break;

		case svc_frame:
			CL_ParseFrame( msg );
			break;
#ifdef BATTLEYE
		case svc_battleye:
			CL_ParseSVCBattleye( msg );
			break;
#endif
		case svc_playerinfo:
		case svc_packetentities:
		case svc_match:
			Com_Error( ERR_DROP, "Out of place frame data" );
			break;
		}
	}

	CL_AddNetgraph();

	//
	// if recording demos, copy the message out
	//
	//
	// we don't know if it is ok to save a demo message until
	// after we have parsed the frame
	//
	if( cls.demorecording && !cls.demowaiting )
		CL_WriteDemoMessage( msg );
}


