/*
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_demo.c  -- demo recording

#include "client.h"

static void CL_PauseDemo( qboolean paused );

/*
====================
CL_WriteDemoMessage

Dumps the current net message, prefixed by the length
====================
*/
void CL_WriteDemoMessage( msg_t *msg )
{
	int len, swlen;

	if( cls.demofile <= 0 )
	{
		cls.demorecording = qfalse;
		return;
	}

	// the first eight bytes are just packet sequencing stuff
	len = msg->cursize-8;
	swlen = LittleLong( len );

	// skip bad packets
	if( swlen )
	{
		FS_Write( &swlen, 4, cls.demofile );
		FS_Write( msg->data+8, len, cls.demofile );
	}
}

/*
====================
CL_Stop_f

stop recording a demo
====================
*/
void CL_Stop_f( void )
{
	int len, arg;
	qboolean silent, cancel;

	// look through all the args
	silent = qfalse;
	cancel = qfalse;
	for( arg = 1; arg < Cmd_Argc(); arg++ )
	{
		if( !Q_stricmp( Cmd_Argv( arg ), "silent" ) )
			silent = qtrue;
		else if( !Q_stricmp( Cmd_Argv( arg ), "cancel" ) )
			cancel = qtrue;
	}

	if( !cls.demorecording )
	{
		if( !silent )
			Com_Printf( "Not recording a demo.\n" );
		return;
	}

	// finish up
	len = -1;
	FS_Write( &len, 4, cls.demofile );
	FS_FCloseFile( cls.demofile );

	// cancel the demos
	if( cancel )
	{
		// remove the file that correspond to cls.demofile
		if( !silent )
			Com_Printf( "Canceling demo: %s\n", cls.demofilename );
		if( !FS_RemoveFile( cls.demofilename ) && !silent )
			Com_Printf( "Error canceling demo." );
	}

	if( !silent )
		Com_Printf( "Stopped demo: %s\n", cls.demofilename );

	cls.demofile = 0; // file id
	Mem_ZoneFree( cls.demofilename );
	cls.demofilename = NULL;
	cls.demorecording = qfalse;

}

/*
====================
CL_Record_f

record <demoname>

Begins recording a demo from the current position
====================
*/
#define DEMO_SAFEWRITE(buf,force) \
	if( force || buf.cursize > buf.maxsize / 2 ) \
	{ \
		CL_WriteDemoMessage( &buf ); \
		buf.cursize = 8; \
	}

void CL_Record_f( void )
{
	char *name;
	int i, name_size;
	char buf_data[MAX_MSGLEN];
	msg_t buf;
	int length;
	qboolean silent;
	purelist_t *purefile;
	entity_state_t *ent;
	entity_state_t nullstate;

	if( cls.state != CA_ACTIVE )
	{
		Com_Printf( "You must be in a level to record.\n" );
		return;
	}

	if( Cmd_Argc() < 2 )
	{
		Com_Printf( "record <demoname>\n" );
		return;
	}

	if( Cmd_Argc() > 2 && !Q_stricmp( Cmd_Argv( 2 ), "silent" ) )
		silent = qtrue;
	else
		silent = qfalse;

	if( cls.demoplaying )
	{
		if( !silent )
			Com_Printf( "You can't record from another demo.\n" );
		return;
	}

	if( cls.demorecording )
	{
		if( !silent )
			Com_Printf( "Already recording.\n" );
		return;
	}

	//
	// open the demo file
	//
	name_size = sizeof( char ) * ( strlen( "demos/" ) + strlen( Cmd_Argv( 1 ) ) + strlen( APP_DEMO_EXTENSION_STR ) + 1 );
	name = Mem_ZoneMalloc( name_size );

	Q_snprintfz( name, name_size, "demos/%s", Cmd_Argv( 1 ) );
	COM_SanitizeFilePath( name );
	COM_DefaultExtension( name, APP_DEMO_EXTENSION_STR, name_size );

	if( !COM_ValidateRelativeFilename( name ) )
	{
		if( !silent )
			Com_Printf( "Invalid filename.\n" );
		Mem_ZoneFree( name );
		return;
	}

	length = FS_FOpenFile( name, &cls.demofile, FS_WRITE );
	if( length == -1 )
	{
		Com_Printf( "Error: Couldn't create the demo file.\n" );
		Mem_ZoneFree( name );
		return;
	}

	if( !silent )
		Com_Printf( "Recording demo: %s\n", name );

	// store the name in case we need it later
	cls.demofilename = name;
	cls.demorecording = qtrue;

	// don't start saving messages until a non-delta compressed message is received
	CL_AddReliableCommand( "nodelta" ); // request non delta compressed frame from server
	cls.demowaiting = qtrue;

	//
	// write out messages to hold the startup information
	//
	MSG_Init( &buf, (qbyte *)buf_data, sizeof( buf_data ) );

	// add dummy packet sequencing stuff
	MSG_WriteLong( &buf, 0 );
	MSG_WriteLong( &buf, 0 );

	// send the serverdata
	MSG_WriteByte( &buf, svc_serverdata );
	MSG_WriteLong( &buf, APP_PROTOCOL_VERSION );
	MSG_WriteLong( &buf, 0x10000 + cl.servercount );
	MSG_WriteShort( &buf, (unsigned short)cl.snapFrameTime );
	MSG_WriteLong( &buf, cl.baseServerTime );
	MSG_WriteString( &buf, FS_BaseGameDirectory() );
	MSG_WriteString( &buf, FS_GameDirectory() );
	MSG_WriteShort( &buf, cl.playernum );
	MSG_WriteString( &buf, cl.servermessage );
	if( cls.reliable )
		MSG_WriteByte( &buf, SV_BITFLAGS_RELIABLE );
	else
		MSG_WriteByte( &buf, 0 );

	// pure files
	i = 0;
	purefile = cls.purelist;
	while( purefile )
	{
		i++;
		purefile = purefile->next;
	}
	assert( i <= (short)0x7fff );

	MSG_WriteShort( &buf, i );
	purefile = cls.purelist;
	while( purefile )
	{
		MSG_WriteString( &buf, purefile->filename );
		MSG_WriteLong( &buf, purefile->checksum );
		purefile = purefile->next;

		DEMO_SAFEWRITE( buf, qfalse );
	}

	// configstrings
	for( i = 0; i < MAX_CONFIGSTRINGS; i++ )
	{
		if( cl.configstrings[i][0] )
		{
			MSG_WriteByte( &buf, svc_servercs );
			//MSG_WriteByte( &buf, svc_servercmd );
			MSG_WriteString( &buf, va( "cs %i \"%s\"", i, cl.configstrings[i] ) );

			DEMO_SAFEWRITE( buf, qfalse );
		}
	}

	// baselines
	memset( &nullstate, 0, sizeof( nullstate ) );
	for( i = 0; i < MAX_EDICTS; i++ )
	{
		ent = &cl_baselines[i];
		if( !ent->modelindex && !ent->sound && !ent->effects )
			continue;

		MSG_WriteByte( &buf, svc_spawnbaseline );
		MSG_WriteDeltaEntity( &nullstate, &cl_baselines[i], &buf, qtrue, qtrue );

		DEMO_SAFEWRITE( buf, qfalse );
	}

	MSG_WriteByte( &buf, svc_servercs );
	//MSG_WriteByte( &buf, svc_servercmd );
	MSG_WriteString( &buf, "precache" );

	DEMO_SAFEWRITE( buf, qtrue );

	// the rest of the demo file will be individual frames
}


//================================================================
//
//	WARSOW : CLIENT SIDE DEMO PLAYBACK
//
//================================================================


// demo file
static int demofilehandle;
static int demofilelen;

/*
=================
CL_BeginDemoAviDump
=================
*/
static void CL_BeginDemoAviDump( void )
{
	cls.demoavi = qtrue;
	cls.demoavi_frame = 0;
	R_BeginAviDemo();
}

/*
=================
CL_StopDemoAviDump
=================
*/
static void CL_StopDemoAviDump( void )
{
	cls.demoavi = qfalse;
	cls.demoavi_frame = 0;
	R_StopAviDemo();
}

/*
=================
CL_DemoCompleted

Close the demo file and disable demo state. Called from disconnection proccess
=================
*/
void CL_DemoCompleted( void )
{
	if( cls.demoavi )
		CL_StopDemoAviDump();

	if( demofilehandle )
	{
		FS_FCloseFile( demofilehandle );
		demofilehandle = 0;
		demofilelen = 0;
	}

	cls.demoduration = 0;
	cls.demobasetime = 0;
	cls.demoplaying = qfalse;

	Com_SetDemoPlaying( qfalse );

	CL_PauseDemo( qfalse );

	Cvar_ForceSet( "demoname", "" ); // used by democams to load up the .cam file

	Cvar_ForceSet( "ui_demotime", "0" );
	Cvar_ForceSet( "ui_demoduration", "0" );

	Com_Printf( "Demo completed\n" );
}

/*
=================
CL_ReadDemoMessage

Read a packet from the demo file and send it to the messages parser
=================
*/
static qboolean CL_ReadDemoMessage( unsigned int *time, unsigned int *initial )
{
	static qbyte msgbuf[MAX_MSGLEN];
	static msg_t demomsg;
	int msglen;

	if( !demofilehandle )
	{
		if( !time )
			CL_Disconnect( NULL );
		return qfalse;
	}

	if( demofilelen <= 0 )
	{
		if( !time )
			CL_Disconnect( NULL );
		return qfalse;
	}

	msglen = 0;

	// get the next message
	FS_Read( &msglen, 4, demofilehandle );
	demofilelen -= 4;

	msglen = LittleLong( msglen );
	if( msglen == -1 )
	{
		if( !time )
			CL_Disconnect( NULL );
		return qfalse;
	}

	if( msglen > MAX_MSGLEN )
		Com_Error( ERR_DROP, "Error reading demo file: msglen > MAX_MSGLEN" );

	if( demofilelen <= 0 )
		Com_Error( ERR_DROP, "Error reading demo file: End of file" );

	if( FS_Read( msgbuf, msglen, demofilehandle ) != msglen )
		Com_Error( ERR_DROP, "Error reading demo file: End of file" );

	demofilelen -= msglen;

	demomsg.maxsize = sizeof( msgbuf );
	demomsg.data = msgbuf;
	demomsg.cursize = msglen;
	demomsg.readcount = 0;

	if( time )
		CL_ParseDemoMessages_ServerTime( &demomsg, time, initial );
	else
		CL_ParseServerMessage( &demomsg );

	return qtrue;
}

/*
=================
CL_ReadDemoPackets

See if it's time to read a new demo packet
=================
*/
void CL_ReadDemoPackets( void )
{
	while( cls.demoplaying && ( cl.receivedSnapNum <= 0 || !cl.frames[cl.receivedSnapNum&UPDATE_MASK].valid || cl.frames[cl.receivedSnapNum&UPDATE_MASK].serverTime < cl.serverTime ) )
		CL_ReadDemoMessage( NULL, NULL );

	Cvar_ForceSet( "ui_demotime", va( "%i", (int)floor( max(cl.frames[cl.receivedSnapNum & UPDATE_MASK].serverTime + cl.baseServerTime - cls.demobasetime,0)/1000.0f ) ) );

	if( cls.demoplay_jump )
		cls.demoplay_jump = qfalse;
}

/*
=================
CL_GetDemoDuration
=================
*/
unsigned int CL_GetDemoDuration( unsigned int *initial )
{
	int pos, len;
	unsigned int serverTime = 0, initialTime =  0;

	len = demofilelen;
	pos = FS_Tell( demofilehandle );
	while( CL_ReadDemoMessage( &serverTime, (initialTime ? NULL : &initialTime) ) );
	FS_Seek( demofilehandle, pos, FS_SEEK_SET );
	demofilelen = len;

	*initial = initialTime;
	return serverTime - initialTime;
}

/*
====================
CL_StartDemo
====================
*/
static void CL_StartDemo( const char *demoname )
{
	char *name, *servername;
	int i, name_size, tempdemofilehandle = 0, tempdemofilelen = -1;

	// have to copy the argument now, since next actions will lose it
	servername = TempCopyString( demoname );
	COM_SanitizeFilePath( servername );

	name_size = sizeof( char ) * ( strlen( "demos/" ) + strlen( servername ) + strlen( APP_DEMO_EXTENSION_STR ) + 1 );
	name = Mem_TempMalloc( name_size );

	Q_snprintfz( name, name_size, "demos/%s", servername );
	COM_DefaultExtension( name, APP_DEMO_EXTENSION_STR, name_size );
	if( COM_ValidateRelativeFilename( name ) )
		tempdemofilelen = FS_FOpenFile( name, &tempdemofilehandle, FS_READ );	// open the demo file

	if( !tempdemofilehandle || tempdemofilelen < 1 )
	{
		// relative filename didn't work, try launching a demo from absolute path
		Q_snprintfz( name, name_size, "%s", servername );
		COM_DefaultExtension( name, APP_DEMO_EXTENSION_STR, name_size );
		tempdemofilelen = FS_FOpenAbsoluteFile( name, &tempdemofilehandle, FS_READ );
	}

	if( !tempdemofilehandle || tempdemofilelen < 1 )
	{
		Com_Printf( "No valid demo file found\n" );
		FS_FCloseFile( tempdemofilehandle );
		Mem_TempFree( name );
		Mem_TempFree( servername );
		return;
	}

	// make sure a local server is killed
	Cbuf_ExecuteText( EXEC_NOW, "killserver\n" );
	CL_Disconnect( NULL );
	// wsw: Medar: fix for menu getting stuck on screen when starting demo, but maybe there is better fix out there?
	CL_UIModule_ForceMenuOff();

	demofilehandle = tempdemofilehandle;
	demofilelen = tempdemofilelen;

	cls.servername = ZoneCopyString( COM_FileBase( servername ) );
	COM_StripExtension( cls.servername );

	CL_SetClientState( CA_HANDSHAKE );
	Com_SetDemoPlaying( qtrue );
	cls.demoplaying = qtrue;

	cls.demoplay_ignore_next_frametime = qfalse;
	cls.demoplay_jump = qfalse;

	// hack read-only cvars in
	Cvar_Get( "ui_demotime", "0", CVAR_READONLY );
	Cvar_Get( "ui_demoduration", "0", CVAR_READONLY );
	Cvar_Get( "ui_demopaused", "0", CVAR_READONLY );

	CL_PauseDemo( qfalse );

	Cvar_ForceSet( "demoname", name ); // used by democams to load up the .cam file

	cls.demoduration = CL_GetDemoDuration( &cls.demobasetime );
	Cvar_ForceSet( "ui_demoduration", va( "%i", (int)ceil( cls.demoduration/1000.0f ) ) );

	// set up for timedemo settings
	cl.timedemo_start = 0;
	cl.timedemo_frames = 0;
	for( i = 0; i < 100; i++ )
		cl.timedemo_counts[i] = 0;

	Mem_TempFree( name );
	Mem_TempFree( servername );
}

/*
====================
CL_PlayDemo_f

demo <demoname>
====================
*/
void CL_PlayDemo_f( void )
{
	if( Cmd_Argc() != 2 )
	{
		Com_Printf( "demo <demoname>\n" );
		return;
	}
	CL_StartDemo( Cmd_Argv( 1 ) );
}

/*
====================
CL_PauseDemo
====================
*/
static void CL_PauseDemo( qboolean paused )
{
	cls.demopause = paused;
	Cvar_ForceSet( "ui_demopaused", va("%i", cls.demopause ) );
}

/*
====================
CL_PauseDemo_f
====================
*/
void CL_PauseDemo_f( void )
{
	if( !cls.demoplaying )
	{
		Com_Printf( "Can only demopause when playing a demo.\n" );
		return;
	}

	if( Cmd_Argc() > 1 )
	{
		if( !Q_stricmp( Cmd_Argv( 1 ), "on" ) )
			CL_PauseDemo( qtrue );
		else if( !Q_stricmp( Cmd_Argv( 1 ), "off" ) )
			CL_PauseDemo( qfalse );
		return;
	}

	CL_PauseDemo( !cls.demopause );
}

/*
====================
CL_DemoJump_f
====================
*/
void CL_DemoJump_f( void )
{
	qboolean relative;
	int time;
	char *p;

	if( !cls.demoplaying )
	{
		Com_Printf( "Can only demojump when playing a demo\n" );
		return;
	}

	if( Cmd_Argc() != 2 )
	{
		Com_Printf( "Usage: demojump <time>\n" );
		Com_Printf( "Time format is [minutes:]seconds\n" );
		Com_Printf( "Use '+' or '-' in front of the time to specify it in relation to current position\n" );
		return;
	}

	p = Cmd_Argv( 1 );

	if( Cmd_Argv( 1 )[0] == '+' || Cmd_Argv( 1 )[0] == '-' )
	{
		relative = qtrue;
		p++;
	}
	else
	{
		relative = qfalse;
	}

	if( strchr( p, ':' ) )
		time = ( atoi( p ) * 60 + atoi( strchr( p, ':' ) + 1 ) ) * 1000;
	else
		time = atoi( p ) * 1000;

	if( Cmd_Argv( 1 )[0] == '-' )
		time = -time;

	CL_SoundModule_StopAllSounds ();

	if( relative )
		cls.gametime += time;
	else
		cls.gametime = time; // gametime always starts from 0

	if( cl.serverTime < cl.frames[cl.receivedSnapNum&UPDATE_MASK].serverTime )
		cl.pendingSnapNum = 0;

	CL_AdjustServerTime( 1 );

	if( cl.serverTime < cl.frames[cl.receivedSnapNum&UPDATE_MASK].serverTime )
	{
		demofilelen += FS_Tell( demofilehandle );
		FS_Seek( demofilehandle, 0, FS_SEEK_SET );
		cl.currentSnapNum = cl.receivedSnapNum = 0;
	}

	cls.demoplay_jump = qtrue;
}

/*
====================
CL_PlayDemoToAvi_f

demoavi <demoname> (if no name suplied, toogles demoavi status)
====================
*/
void CL_PlayDemoToAvi_f( void )
{
	if( Cmd_Argc() == 1 && cls.demoplaying ) // toogle demoavi mode
	{
		if( !cls.demoavi )
			CL_BeginDemoAviDump();
		else
			CL_StopDemoAviDump();
	}
	else if( Cmd_Argc() == 2 )
	{
		char *tempname = TempCopyString( Cmd_Argv( 1 ) );

		if( cls.demoplaying )
			CL_Disconnect( NULL );

		CL_StartDemo( tempname );
		CL_BeginDemoAviDump();

		Mem_TempFree( tempname );
	}
	else
	{
		Com_Printf( "Usage: %sdemoavi <demoname>%s or %sdemoavi%s while playing a demo\n",
			S_COLOR_YELLOW, S_COLOR_WHITE, S_COLOR_YELLOW, S_COLOR_WHITE );
	}
}
