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

server_constant_t svc;			// constant server info (trully persistant since sv_init)
server_static_t	svs;				// persistant server info
server_t		sv;					// local server

/* We cache pak checksums at map start so we don't need to calculate them while game is going on */
typedef struct cached_checksum_s cached_checksum_t;

struct cached_checksum_s {
	char				pakname[MAX_OSPATH];
	unsigned			checksum;
	cached_checksum_t	*next;
};

static cached_checksum_t *cached_checksums = NULL;

/*
================
SV_FindIndex

================
*/
int SV_FindIndex( char *name, int start, int max, qboolean create )
{
	int		i;
	
	if( !name || !name[0] )
		return 0;

	for( i = 1; i < max && sv.configstrings[start+i][0]; i++ )
		if( !strncmp( sv.configstrings[start+i], name, sizeof(sv.configstrings[start+i]) ) )
			return i;

	if( !create )
		return 0;

	if( i == max )
		Com_Error( ERR_DROP, "*Index: overflow" );

	Q_strncpyz( sv.configstrings[start+i], name, sizeof(sv.configstrings[i]) );

	if( sv.state != ss_loading )
	{	// send the update to everyone
		SV_SendServerCommand( NULL, "cs %i \"%s\"", start+i, name );
	}

	return i;
}


int SV_ModelIndex( char *name )
{
	return SV_FindIndex( name, CS_MODELS, MAX_MODELS, qtrue );
}

int SV_SoundIndex( char *name )
{
	return SV_FindIndex( name, CS_SOUNDS, MAX_SOUNDS, qtrue );
}

int SV_ImageIndex( char *name )
{
	return SV_FindIndex( name, CS_IMAGES, MAX_IMAGES, qtrue );
}

int SV_SkinIndex( char *name )
{
	return SV_FindIndex( name, CS_SKINFILES, MAX_SKINFILES, qtrue );
}

/*
================
SV_CreateBaseline

Entity baselines are used to compress the update messages
to the clients -- only the fields that differ from the
baseline will be transmitted
================
*/
void SV_CreateBaseline (void)
{
	edict_t			*svent;
	int				entnum;	

	for (entnum = 1; entnum < sv.num_edicts ; entnum++)
	{
		svent = EDICT_NUM(entnum);
		if (!svent->r.inuse)
			continue;
		if (!svent->s.modelindex && !svent->s.sound && !svent->s.effects)
			continue;
		svent->s.number = entnum;

		//
		// take current state as baseline
		//
		VectorCopy (svent->s.origin, svent->s.old_origin);
		sv.baselines[entnum] = svent->s;
	}
}


/*
=================
SV_CheckForSavegame
=================
*/
void SV_CheckForSavegame (void)
{
	char		name[MAX_OSPATH];
	int			i;

	if (sv_noreload->integer)
		return;

	// wsw : Medar : disabled for now since we only have DM
	// we should most likely allow game module to decide whether to save

	return;

	Q_snprintfz (name, sizeof(name), "save/current/%s.sav", sv.name);
	if( FS_FOpenFile( name, NULL, FS_READ ) == -1 )
		return;		// no savegame

	SV_ClearWorld ();

	// get configstrings and areaportals
	SV_ReadLevelFile ();

	if (!sv.loadgame)
	{	// coming back to a level after being in a different
		// level, so run it for ten seconds

		// rlava2 was sending too many lightstyles, and overflowing the
		// reliable data. temporarily changing the server state to loading
		// prevents these from being passed down.
		server_state_t		previousState;

		previousState = sv.state;
		sv.state = ss_loading;

		Com_Printf("******RUNNING MAP FOR 10secs********\n");
		Com_Printf("******If you see this message*******\n");
		Com_Printf("******please, warn the warsow*******\n");
		Com_Printf("******team (www.warsow.net)*******\n");

		for (i=0 ; i<10000 ; i+=svc.frametime)
			ge->RunFrame ( svc.frametime );

		sv.state = previousState;
	}
}

void SV_FreeCachedChecksums (void)
{
	cached_checksum_t *cc, *next;

	cc = cached_checksums;
	while( cc != NULL ) {
		next = cc->next;
		Mem_Free( cc );
		cc = next;
	}

	cached_checksums = NULL;
}

/*
=================
SV_GetCachedPakChecksum
=================
*/
unsigned SV_GetCachedPakChecksum (const char *pakname)
{
	cached_checksum_t *cc;

	cc = cached_checksums;
	while( cc != NULL ) {
		if( !Q_stricmp( cc->pakname, pakname ) )
			return cc->checksum;
		cc = cc->next;
	}
	return 0;
}

/*
=================
SV_CachePakChecksum
=================
*/
static void SV_CachePakChecksum (const char *filename)
{
	const char			*pakname;
	cached_checksum_t	*new;
	int					length;
	qbyte				*buffer;
	unsigned			checksum;

	if( strstr (filename, "..") || *filename == '.'  || *filename == '/' || strchr (filename, '\"') )
		return; // invalid filename for uploading

	pakname = FS_PakNameForFile(filename);
	if( !pakname )
		return;

	if( SV_GetCachedPakChecksum(pakname) )
		return; // already cached

	Com_DPrintf("Caching checksum for pak: %s (%s)\n", pakname, filename);

	length = FS_LoadAbsoluteFile( pakname, (void **)&buffer, NULL, 0 );
	if( !buffer ) {
		Com_Printf( "SV_CachePakChecksum: Error loading %s\n", pakname );
		checksum = 0;
	} else {
		checksum = Com_BlockChecksum( buffer, length );
		FS_FreeFile( buffer );
	}

	new = Mem_Alloc( sv_mempool, sizeof(cached_checksum_t) );
	Q_strncpyz( new->pakname, pakname, MAX_OSPATH );
	new->checksum = checksum;
	new->next = NULL;

	if( cached_checksums == NULL ) {
		cached_checksums = new;
	} else {
		cached_checksum_t *cc = cached_checksums;
		while( cc->next != NULL )
			cc = cc->next;
		cc->next = new;
	}
}

/*
=================
SV_CacheChecksums
=================
*/
void SV_CacheChecksums (void)
{
	int		i;
	char	tempname[MAX_QPATH];

	for( i = CS_MODELS; i < CS_IMAGES+MAX_IMAGES; i++ ) {
		if( strlen(sv.configstrings[i]) == 0 )
			continue;

		if( i == CS_MODELS || i == CS_SOUNDS || i == CS_IMAGES ) // empty fields
			continue;

		if( i == CS_MODELS+1 ) // map
		{
			SV_CachePakChecksum( sv.configstrings[i] );
			continue;
		}
		else if( i < CS_MODELS+MAX_MODELS )
		{
			if (sv.configstrings[i][0] == '*' ||
				sv.configstrings[i][0] == '$' || // disable playermodel downloading for now
				sv.configstrings[i][0] == '#') {
				continue;
			}
			SV_CachePakChecksum( sv.configstrings[i] );
			continue;
		}
		else if( i < CS_SOUNDS+MAX_SOUNDS )
		{
			if( sv.configstrings[i][0] == '*' ) // sexed sounds
				continue;
			Q_strncpyz( tempname, sv.configstrings[i], sizeof(tempname) );
			COM_DefaultExtension( tempname, ".wav", sizeof(tempname) );
			SV_CachePakChecksum( tempname );
			continue;
		}
		else if( i < CS_IMAGES+MAX_IMAGES )
		{
			// disabled for now
			continue;
		}
	}
}

/*
================
SV_SpawnServer

Change the server to a new map, taking all connected
clients along with it.

================
*/
void SV_SpawnServer( char *server, char *spawnpoint, server_state_t serverstate, qboolean loadgame, qboolean devmap )
{
	unsigned	checksum;

	if( devmap )
		Cvar_ForceSet( "sv_cheats", "1" );
	Cvar_FixCheatVars();

	Com_Printf( "------- Server Initialization -------\n" );
	Com_DPrintf( "SpawnServer: %s\n",server );

	svs.spawncount++;		// any partially connected client will be
							// restarted
	sv.state = ss_dead;
	Com_SetServerState( sv.state );

	// wipe the entire per-level structure
	memset( &sv, 0, sizeof(sv) );
	SV_ResetClientFrameCounters();
	svs.realtime = 0;
	sv.loadgame = loadgame;

	Q_strncpyz( sv.name, server, sizeof(sv.name) );

	sv.time = 1000;
	
	if( serverstate != ss_game )
		CM_LoadMap( "", qfalse, &checksum );	// no real map
	else
	{
		Q_snprintfz( sv.configstrings[CS_MODELS+1],sizeof(sv.configstrings[CS_MODELS+1]),
			"maps/%s.bsp", server );
		CM_LoadMap( sv.configstrings[CS_MODELS+1], qfalse, &checksum );

		//
		// clear physics interaction links
		//
		SV_ClearWorld();
	}

	Q_snprintfz( sv.configstrings[CS_MAPCHECKSUM], sizeof(sv.configstrings[CS_MAPCHECKSUM]), "%i", checksum );

	//
	// spawn the rest of the entities on the map
	//	

	// precache and static commands can be issued during
	// map initialization
	sv.state = ss_loading;
	Com_SetServerState( sv.state );

	// load and spawn all other entities
	ge->SpawnEntities( sv.name, CM_EntityString(), CM_EntityStringLen(), spawnpoint );

	// run two frames to allow everything to settle
	ge->RunFrame( svc.frametime );
	ge->RunFrame( svc.frametime );

	// all precaches are complete
	sv.state = serverstate;
	Com_SetServerState( sv.state );
	
	// create a baseline for more efficient communications
	SV_CreateBaseline();

	// check for a savegame
	SV_CheckForSavegame();

	// cache checksums
	SV_CacheChecksums();

	// set serverinfo variable
	Cvar_FullSet( "mapname", sv.name, CVAR_SERVERINFO | CVAR_NOSET, qtrue );

	Com_Printf( "-------------------------------------\n" );
}

/*
==============
SV_InitGame

A brand new game has been started
==============
*/
void SV_InitGame( void )
{
	int		i;
	edict_t	*ent;

	if( svs.initialized )
	{
		// cause any connected clients to reconnect
		SV_Shutdown ("Server restarted\n", qtrue);

		// SV_Shutdown will also call Cvar_GetLatchedVars
	}
	else
	{
		// make sure the client is down
		CL_Disconnect( NULL );
		SCR_BeginLoadingPlaque();

		// get any latched variable changes (sv_maxclients, etc)
		Cvar_GetLatchedVars( CVAR_LATCH );
	}

	svs.initialized = qtrue;

	// init clients
	if( sv_maxclients->integer <= 1 )
		Cvar_FullSet( "sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH, qtrue );
	else if( sv_maxclients->integer > MAX_CLIENTS )
		Cvar_FullSet( "sv_maxclients", va("%i", MAX_CLIENTS), CVAR_SERVERINFO | CVAR_LATCH, qtrue );

	svs.spawncount = rand();
	svs.clients = Mem_Alloc( sv_mempool, sizeof(client_t)*sv_maxclients->integer );
	svs.num_client_entities = sv_maxclients->integer * UPDATE_BACKUP * 64;
	svs.client_entities = Mem_Alloc( sv_mempool, sizeof(entity_state_t) * svs.num_client_entities );

	// init network stuff
	NET_Config( (sv_maxclients->integer > 1) );

	// init game
	SV_InitGameProgs();
	for( i=0; i < sv_maxclients->integer; i++ )
	{
		ent = EDICT_NUM(i+1);
		ent->s.number = i+1;
		svs.clients[i].edict = ent;
		memset( &svs.clients[i].lastcmd, 0, sizeof(svs.clients[i].lastcmd) );
	}
}


/*
======================
SV_Map

  the full syntax is:

  map [*]<map>$<startspot>+<nextserver>

command from the console or progs.
Map can also be a .roq, or .dqf file
Nextserver is used to allow a cinematic to play, then proceed to
another level:

	map intro.roq+q3dm0
======================
*/
void SV_Map( char *levelstring, qboolean loadgame, qboolean devmap )
{
	char	level[MAX_QPATH];
	char	*ch;
	char	spawnpoint[MAX_QPATH];
	int		i;

	sv.loadgame = loadgame;
	if( sv.state == ss_dead && !sv.loadgame )
		SV_InitGame();	// the game is just starting
	
	Q_strncpyz( level, levelstring, sizeof(level) );

	// if there is a + in the map, set nextserver to the remainder
	ch = strstr(level, "+");
	if( ch )
	{
		*ch = 0;
		Cvar_Set( "nextserver", va("gamemap \"%s\"", ch+1) );
	}
	else
		Cvar_Set( "nextserver", "" );

	// if there is a $, use the remainder as a spawnpoint
	ch = strstr( level, "$" );
	if( ch )
	{
		*ch = 0;
		Q_strncpyz( spawnpoint, ch+1, sizeof(spawnpoint) );
	}
	else
		spawnpoint[0] = 0;

	// skip the end-of-unit flag if necessary
	if( level[0] == '*' )
		Q_strncpyz( level, level+1, sizeof(level) );

	// wsw : Medar : this used to be at SV_SpawnServer, but we need to do it before sending changing
	//               so we don't send frames after sending changing command
	// leave slots at start for clients only
	for( i = 0; i < sv_maxclients->integer; i++ )
	{
		// needs to reconnect
		if( svs.clients[i].state > CS_CONNECTED )
			svs.clients[i].state = CS_CONNECTED;
		svs.clients[i].lastframe = -1;
	}

	SCR_BeginLoadingPlaque();			// for local system
	SV_BroadcastCommand( "changing\n" );
	SV_SendClientMessages();
	SV_SpawnServer( level, spawnpoint, ss_game, loadgame, devmap );
	Cbuf_CopyToDefer();

	SV_BroadcastCommand( "reconnect\n" );
}
