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

 */

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


//===============================================================================
//
//OPERATOR CONSOLE ONLY COMMANDS
//
//These commands can only be entered from stdin or by a remote operator datagram
//===============================================================================

//==================
//SV_FindPlayer
// Helper for the functions below. It finds the client_t for the given name or id
//==================
static client_t *SV_FindPlayer( char *s )
{
	client_t *cl;
	client_t *player;
	int i;
	int idnum = 0;

	if( !s )
		return NULL;

	// numeric values are just slot numbers
	if( s[0] >= '0' && s[0] <= '9' )
	{
		idnum = atoi( s );
		if( idnum < 0 || idnum >= sv_maxclients->integer )
		{
			Com_Printf( "Bad client slot: %i\n", idnum );
			return NULL;
		}

		player = &svs.clients[idnum];
		goto found_player;
	}

	// check for a name match
	for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ )
	{
		if( !cl->state )
			continue;
		if( !Q_stricmp( cl->name, s ) )
		{
			player = cl;
			goto found_player;
		}
	}

	Com_Printf( "Userid %s is not on the server\n", s );
	return NULL;

found_player:
	if( !player->state || !player->edict )
	{
		Com_Printf( "Client %s is not active\n", s );
		return NULL;
	}

	return player;
}

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

#ifdef SERVER_DOWNLOAD_COMMAND

//=====================
//SV_WebDownloadProgress
//Callback function for webdownloads.
//=====================
static int SV_WebDownloadProgress( double percent )
{
	Com_Printf( "Download progress: %02.02f\n", 100.0f*percent );
	return 0;
}

//==================
//SV_Download_f
//Download command issued from server
//==================
static void SV_Download_f( void )
{
	qboolean success;
	int alloc_size;
	char *url, *name, *fullname, *tempname;

	if( Cmd_Argc() != 2 )
	{
		Com_Printf( "Usage: download <url>\n" );
		Com_Printf( "Downloads .pk3 from URL to gamedir and adds it to the server\n" );
		Com_Printf( "Note, server will not function properly while downloading\n" );
		return;
	}

	if( !strrchr( Cmd_Argv( 1 ), '/' ) )
	{
		Com_Printf( "Invalid URL\n" );
		return;
	}

	name = strrchr( Cmd_Argv( 1 ), '/' ) + 1;

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

	if( !COM_FileExtension( name ) || Q_stricmp( COM_FileExtension( name ), ".pk3" ) )
	{
		Com_Printf( "Only downloading of .pk3 files is allowed\n" );
		return;
	}

	url = TempCopyString( Cmd_Argv( 1 ) );
	alloc_size = sizeof( char ) * ( strlen( FS_WriteDirectory() ) + 1 + strlen( FS_GameDirectory() ) + 1 + strlen( name ) + strlen( ".tmp" ) + 1 );
	tempname = Mem_TempMalloc( alloc_size );
	Q_snprintfz( tempname, alloc_size, "%s/%s/%s.tmp", FS_WriteDirectory(), FS_GameDirectory(), name );

	Com_Printf( "Server web download: %s from %s\n", tempname, url );

	success = Web_Get( url, NULL, tempname, qtrue, 60 * 15, 60, SV_WebDownloadProgress );

	if( !success )
	{
		Com_Printf( "Server web download wasn't succesful\n" );
		Com_Printf( "Removing temporary file: %s\n", tempname );
		FS_RemoveAbsoluteFile( tempname );
		Mem_TempFree( url );
		Mem_TempFree( tempname );
		return;
	}

	Com_Printf( "Server web download was succesful\n" );

	// final name for downloaded file
	alloc_size = sizeof( char ) * ( strlen( FS_WriteDirectory() ) + 1 + strlen( FS_GameDirectory() ) + 1 + strlen( name ) + 1 );
	fullname = Mem_TempMalloc( alloc_size );
	Q_snprintfz( fullname, alloc_size, "%s/%s/%s.tmp", FS_WriteDirectory(), FS_GameDirectory(), name );

	// rename the downloaded file
	if( FS_MoveFile( tempname, fullname ) )
	{
		Com_Printf( "Failed to rename the downloaded file.\n" );
		Mem_TempFree( url );
		Mem_TempFree( tempname );
		Mem_TempFree( fullname );
		return;
	}

	Mem_TempFree( url );
	Mem_TempFree( tempname );
	Mem_TempFree( fullname );

	// we now need to update the file system problem the old file is still in the FS structure
	// so we cannot just add the new pak to the FS, so we restart the server
	SV_Shutdown( "Adding downloaded file\n" );
	SV_Init();
}
#endif

//==================
//SV_Map_f
//
//User command to change the map
//map: restart game, and start map
//devmap: restart game, enable cheats, and start map
//gamemap: just start the map
//==================
static void SV_Map_f( void )
{
	char *map;
	char mapname[MAX_CONFIGSTRING_CHARS];
	char filename[MAX_CONFIGSTRING_CHARS];
	qboolean found = qfalse;

	if( Cmd_Argc() < 2 )
	{
		Com_Printf( "Usage: %s <map>\n", Cmd_Argv( 0 ) );
		return;
	}

	// if map "<map>" is used Cmd_Args() will return the "" as well.
	if( Cmd_Argc() == 2 )
		map = Cmd_Argv( 1 );
	else
		map = Cmd_Args();

	Com_DPrintf( "SV_GameMap(%s)\n", map );

	// applies to fullnames and filenames (whereas + strlen( "maps/" ) wouldnt)
	if( strlen( map ) >= MAX_CONFIGSTRING_CHARS )
	{
		Com_Printf( "Map name too long.\n" );
		return;
	}

	Q_strncpyz( mapname, map, sizeof( mapname ) );
	if( ML_ValidateFilename( mapname ) )
	{
		COM_StripExtension( mapname );
		Q_snprintfz( filename, sizeof( filename ), "maps/%s.bsp", mapname );
		if( FS_FOpenFile( filename, NULL, FS_READ ) != -1 )
			found = qtrue;
	}

	if( !found )
	{
		if( ML_ValidateFullname( mapname ) )
		{
			Q_strncpyz( mapname, ML_GetFilename( mapname ), sizeof( mapname ) );
			if( *mapname )
				found = qtrue;
		}

		if( !found )
		{
			Com_Printf( "Couldn't find file: %s\n", filename );
			Com_Printf( "Couldn't find map: %s\n", map );
			return;
		}
	}


	if( !Q_stricmp( Cmd_Argv( 0 ), "map" ) || !Q_stricmp( Cmd_Argv( 0 ), "devmap" ) )
		sv.state = ss_dead; // don't save current level when changing

	// start up the next map
	SV_Map( mapname, !Q_stricmp( Cmd_Argv( 0 ), "devmap" ) );

	// archive server state
	Q_strncpyz( svs.mapcmd, mapname, sizeof( svs.mapcmd ) );
}

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

//================
//SV_Status_f
//================
void SV_Status_f( void )
{
	int i, j, l;
	client_t *cl;
	const char *s;
	int ping;
	if( !svs.clients )
	{
		Com_Printf( "No server running.\n" );
		return;
	}
	Com_Printf( "map              : %s\n", sv.name );

	Com_Printf( "num score ping name            lastmsg address               port   rate  \n" );
	Com_Printf( "--- ----- ---- --------------- ------- --------------------- ------ ------\n" );
	for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ )
	{
		if( !cl->state )
			continue;
		Com_Printf( "%3i ", i );
		Com_Printf( "%5i ", cl->edict->r.client->r.frags );

		if( cl->state == CS_CONNECTED )
			Com_Printf( "CNCT " );
		else if( cl->state == CS_ZOMBIE )
			Com_Printf( "ZMBI " );
		else if( cl->state == CS_CONNECTING )
			Com_Printf( "AWAI " );
		else
		{
			ping = cl->ping < 9999 ? cl->ping : 9999;
			Com_Printf( "%4i ", ping );
		}

		s = COM_RemoveColorTokens( cl->name );
		Com_Printf( "%s", s );
		l = 16 - (int)strlen( s );
		for( j = 0; j < l; j++ )
			Com_Printf( " " );

		Com_Printf( "%7i ", svs.realtime - cl->lastPacketReceivedTime );

		s = NET_AddressToString( &cl->netchan.remoteAddress );
		Com_Printf( "%s", s );
		l = 22 - (int)strlen( s );
		for( j = 0; j < l; j++ )
			Com_Printf( " " );

		Com_Printf( "%5i", cl->netchan.game_port );
#ifndef RATEKILLED
		// wsw : jal : print real rate in use
		Com_Printf( "  " );
		if( cl->edict && ( cl->edict->r.svflags & SVF_FAKECLIENT ) )
			Com_Printf( "BOT" );
		else if( cl->rate == 99999 )
			Com_Printf( "LAN" );
		else
			Com_Printf( "%5i", cl->rate );
#endif
		Com_Printf( " " );
		if( cl->mv )
			Com_Printf( "MV" );
		Com_Printf( "\n" );
	}
	Com_Printf( "\n" );
}

//==================
//SV_Heartbeat_f
//==================
static void SV_Heartbeat_f( void )
{
	svc.last_heartbeat = 0;
	svc.last_mmheartbeat = 0;
}

//===========
//SV_Serverinfo_f
//
//Examine or change the serverinfo string
//===========
static void SV_Serverinfo_f( void )
{
	Com_Printf( "Server info settings:\n" );
	Info_Print( Cvar_Serverinfo() );
}

//===========
//SV_DumpUser_f
//
//Examine all a users info strings
//===========
static void SV_DumpUser_f( void )
{
	client_t *client;
	if( Cmd_Argc() != 2 )
	{
		Com_Printf( "Usage: info <userid>\n" );
		return;
	}

	client = SV_FindPlayer( Cmd_Argv( 1 ) );
	if( !client )
		return;

	Com_Printf( "userinfo\n" );
	Com_Printf( "--------\n" );
	Info_Print( client->userinfo );
}

//===============
//SV_KillServer_f
//
//Kick everyone off, possibly in preparation for a new game
//===============
static void SV_KillServer_f( void )
{
	if( !svs.initialized )
		return;

	SV_ShutdownGame( "Server was killed", qfalse );
}

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

//==================
//SV_InitOperatorCommands
//==================
void SV_InitOperatorCommands( void )
{
	Cmd_AddCommand( "heartbeat", SV_Heartbeat_f );
	Cmd_AddCommand( "status", SV_Status_f );
	Cmd_AddCommand( "serverinfo", SV_Serverinfo_f );
	Cmd_AddCommand( "dumpuser", SV_DumpUser_f );

	Cmd_AddCommand( "map", SV_Map_f );
	Cmd_AddCommand( "devmap", SV_Map_f );
	Cmd_AddCommand( "gamemap", SV_Map_f );
	Cmd_AddCommand( "killserver", SV_KillServer_f );

#ifdef SERVER_DOWNLOAD_COMMAND
	if( dedicated->integer )
		Cmd_AddCommand( "download", SV_Download_f );
#endif

	Cmd_AddCommand( "serverrecord", SV_Demo_Start_f );
	Cmd_AddCommand( "serverrecordstop", SV_Demo_Stop_f );
	Cmd_AddCommand( "serverrecordcancel", SV_Demo_Cancel_f );
	Cmd_AddCommand( "serverrecordpurge", SV_Demo_Purge_f );

	Cmd_AddCommand( "purelist", SV_PureList_f );
}

//==================
//SV_ShutdownOperatorCommands
//==================
void SV_ShutdownOperatorCommands( void )
{
	Cmd_RemoveCommand( "heartbeat" );
	Cmd_RemoveCommand( "status" );
	Cmd_RemoveCommand( "serverinfo" );
	Cmd_RemoveCommand( "dumpuser" );

	Cmd_RemoveCommand( "map" );
	Cmd_RemoveCommand( "devmap" );
	Cmd_RemoveCommand( "gamemap" );
	Cmd_RemoveCommand( "killserver" );

#ifdef SERVER_DOWNLOAD_COMMAND
	if( dedicated->integer )
		Cmd_RemoveCommand( "download" );
#endif

	Cmd_RemoveCommand( "serverrecord" );
	Cmd_RemoveCommand( "serverrecordstop" );
	Cmd_RemoveCommand( "serverrecordcancel" );
	Cmd_RemoveCommand( "serverrecordpurge" );

	Cmd_RemoveCommand( "purelist" );
}
