//-----------------------------------------------------------------------------
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
//

#include "server.h"
#ifndef DEDICATED_ONLY
#include "console3d.h"
#endif

server_static_t	svs;				// persistant server info
server_t		sv;					// local server

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

================
*/
static int SV_FindIndex (const 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 (!Q_stricmp(sv.configstrings[start+i], name))
			return i;

	if ( !create )
		return 0;

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

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

	if (sv.state != ss_loading)
	{	// send the update to everyone
		SZ_Clear (&sv.multicast);
		MSG_WriteChar (&sv.multicast, svc_configstring);
		MSG_WriteShort (&sv.multicast, start+i);
		MSG_WriteString (&sv.multicast, name);
		SV_Multicast (vec3_origin, MULTICAST_ALL_R);
	}

	return i;
}

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

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

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

int SV_ItemIndex(const char *name)
{
	return SV_FindIndex (name, CS_ITEMS, MAX_ITEMS, true);
}

/*
================
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
================
*/
static void SV_CreateBaseline (void)
{
	edict_t			*svent;
	int				entnum;

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

		//
		// take current state as baseline
		//
		if ( !(svent->s.effects & EF_BEAMEFFECT ) && !(svent->s.renderfx & RF_BEAM ) )
			VectorCopy (svent->s.origin, svent->s.old_origin);
		sv.baselines[entnum] = svent->s;
	}
}

static void SV_CreateConsoleBaseline (void)
{
	netconsole_t  *svcon;
	int i;

	for (i=1;i<=ge->num_consoles;i++)
	{
		svcon = CONSOLE_NUM(i);

		if (!svcon->inuse)
			continue;

		// take current state as baseline
		sv.console_baselines[i] = svcon->s;
	}
}

static void SV_CreateSurfaceBaseline (void)
{
	netsurface_t  *svsurf;
	int i;

	for (i=1;i<=ge->num_surfaces;i++)
	{
		svsurf = SURFACE_NUM(i);

		if (!svsurf->inuse)
			continue;

		// take current state as baseline
		sv.surface_baselines[i] = svsurf->s;
	}
}


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

	if (sv_noreload->value)
		return;

	if (Cvar_VariableValue ("deathmatch"))
		return;

	Com_sprintf (name, sizeof(name), "%s/save/current/%s.sav", FS_Playerdir(), COM_SkipPath( sv.name ) );
	f = fopen (name, "rb");
	if (!f)
		return;		// no savegame

	fclose (f);

	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
		server_state_t		previousState;		// PGM

		previousState = sv.state;				// PGM
		sv.state = ss_loading;					// PGM
		for (i=0 ; i<100 ; i++)
			ge->RunFrame ();

		sv.state = previousState;				// PGM
	}
}


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

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

================
*/
static void SV_SpawnServer (char *server, char *spawnpoint, server_state_t serverstate, qboolean attractloop, qboolean loadgame)
{
	int			i;
	unsigned	checksum;
	entity_state_t	*ent;
	surface_state_t *surf;
	int total,current=0;
	const char *data;
	const char *com_token;
	int entity_count=0;

#ifndef DEDICATED_ONLY
   CONSOLE_ClearFade_f();
   CONSOLE_ClearFadeExtra();
#endif

	if (attractloop)
		Cvar_Set ("paused", "0");

	Com_Printf ("------- Server Initialization -------\n");

	Com_DPrintf (DEVELOPER_MSG_SERVER, "SpawnServer: %s\n",server);
	// Cvar_SetValue ("cl_predict", 0);		// FIXME DEBUG TEMPORARY
	if (sv.demofile)
	{
		fclose(sv.demofile);
	}

	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));
	svs.realtime = 0;
	sv.loadgame = loadgame;
	sv.attractloop = attractloop;

	// Set up the default baseline values
	for (i=0; i<MAX_EDICTS ; i++)
	{
		ent = &sv.baselines[i];
		ent->scale = 1.0;
		ent->alpha = 1.0;
		ent->renderfx = RF_FRAMELERP;
	}
 	for (i=0; i<MAX_SURFACES ; i++)
	{
		surf = &sv.surface_baselines[i];
		surf->magnitude = 1.0f;
	}

	// save name for levels that don't set message
	strcpy (sv.configstrings[CS_NAME], server);

	// initialize the soundtrack
	strcpy (sv.configstrings[CS_SOUNDTRACK], "none");

	SZ_Init (&sv.multicast, sv.multicast_buf, sizeof(sv.multicast_buf));

	strcpy (sv.name, server);

	// leave slots at start for clients only
	for (i=0 ; i<maxclients->value ; i++)
	{
		// needs to reconnect
		if (svs.clients[i].state > cs_connected)
			svs.clients[i].state = cs_connected;
		svs.clients[i].lastframe = -1;
	}

	sv.time = 1000;

	strcpy (sv.name, server);
	strcpy (sv.configstrings[CS_NAME], server);

	Com_UpdateStatusString( "Starting Up Server" );
	total = 43;

	if (serverstate != ss_game)
	{
		sv.models[1] = CM_LoadMap ("", false, &checksum);	// no real map
	}
	else
	{
		Com_sprintf (sv.configstrings[CS_MODELS+1],sizeof(sv.configstrings[CS_MODELS+1]),
			"maps/%s.bsp", server);
		sv.models[1] = CM_LoadMap (sv.configstrings[CS_MODELS+1], false, &checksum);
	}
	Com_sprintf (sv.configstrings[CS_MAPCHECKSUM],sizeof(sv.configstrings[CS_MAPCHECKSUM]),
		"%i", checksum);

	// Count up inline models
	total += CM_NumInlineModels();

	// Count up entities
	data = CM_EntityString();
	while (1)
	{	
		com_token = COM_Parse( &data );

		if ( !com_token[0] )
			break;

		if ( com_token[ 0 ] == '}' )
		{
			total++;
			entity_count++;
		}
	}   
	total *= 2;

	if ( serverstate == ss_cinematic )
	{
		total = 1;
	}

	// clear physics interaction links
	SV_ClearWorld ();

	Com_UpdateStatus( current++, total, "Loading Inline Models" );

	for (i=1 ; i< CM_NumInlineModels() ; i++)
	{
		Com_sprintf (sv.configstrings[CS_MODELS+1+i], sizeof(sv.configstrings[CS_MODELS+1+i]),
			"*%i", i);
		sv.models[i+1] = CM_InlineModel (sv.configstrings[CS_MODELS+1+i]);
		// Update every 20 models
		if ( !( i % 20 ) )
			Com_UpdateStatus( current++, total, va( "Loading Model %s", &sv.configstrings[CS_MODELS+1+i][1] ) );
		else
			current++;
	}

	//
	// 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
	Com_UpdateStatus( current++, total, "Spawning Entities" );

	ge->SpawnEntities ( sv.name, CM_EntityString(), spawnpoint );

	current+= entity_count;
	Com_UpdateStatus( current++, total, "Creating Surfaces" );
	ge->CreateSurfaces (CM_Surfinfos(), CM_NumSurfinfos());

	// run two frames to allow everything to settle
	Com_UpdateStatus( current, total, "Initializing Game 1" );
	current+= 20;
	ge->RunFrame ();
	Com_UpdateStatus( current, total, "Initializing Game 2" );
	current += 20;
	ge->RunFrame ();

	// all precaches are complete
	sv.state = serverstate;
	Com_SetServerState (sv.state);

	// create a baseline for more efficient communications
	Com_UpdateStatus( current++, total, "Creating Baselines" );
	SV_CreateBaseline ();

	SV_CreateConsoleBaseline();
	SV_CreateSurfaceBaseline();

	// check for a savegame
	SV_CheckForSavegame ();

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

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

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

A brand new game has been started
==============
*/
extern cvar_t *public_server;

void SV_InitGame (void)
{
	int		i;
	edict_t	*ent;

	if (svs.initialized) {
		// cuase any connected clients to reconnect
		SV_Shutdown ("Server restarted\n", true);
	} else {
		// make sure the client is down
		CL_Drop ();
		SCR_BeginLoadingPlaque ();
	}

	// get any latched variable changes (maxclients, etc)
	Cvar_GetLatchedVars ();

	svs.initialized = true;

	if (Cvar_VariableValue ("coop") && Cvar_VariableValue ("deathmatch")) {
		Com_Printf("Deathmatch and Coop both set, disabling Coop\n");
		Cvar_FullSet ("coop", "0",  CVAR_SERVERINFO | CVAR_LATCH);
	}

	// dedicated servers are can't be single player and are usually DM
	// so unless they explicity set coop, force it to deathmatch
	if (dedicated->value) {
		// 2015 code 
		if (!Cvar_VariableValue ("coop") && !Cvar_VariableValue ("deathmatch")) {
			Cvar_FullSet ("deathmatch", "1",  CVAR_SERVERINFO | CVAR_LATCH);
		}
	}

	// init clients
	if (Cvar_VariableValue ("deathmatch")) {
		if (maxclients->value <= 1) {
			Cvar_FullSet ("maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE );
		} else if (maxclients->value > MAX_CLIENTS) {
			Cvar_FullSet ("maxclients", va("%i", MAX_CLIENTS), CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE );
		}
	} else if (Cvar_VariableValue ("coop")) {
		if (maxclients->value <= 1 || maxclients->value > 4) {
			Cvar_FullSet ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE );
		}
//#ifdef COPYPROTECT
		if (!sv.attractloop && !dedicated->value) {
			Sys_CopyProtect ();
		}
//#endif
	} else {	// non-deathmatch, non-coop is one player
		Cvar_FullSet ("maxclients", "1", CVAR_SERVERINFO | CVAR_LATCH);
//#ifdef COPYPROTECT
		if (!sv.attractloop) {
			Sys_CopyProtect ();
		}
//#endif
	}

	svs.spawncount = rand();
	svs.clients = (client_t *)Z_Malloc (sizeof(client_t)*maxclients->value);
	svs.num_client_entities = maxclients->value*UPDATE_BACKUP*64;
	svs.client_entities = (entity_state_t *)Z_Malloc (sizeof(entity_state_t)*svs.num_client_entities);

	svs.num_client_consoles = maxclients->value*UPDATE_BACKUP*32;
	svs.client_consoles = (console_state_t *)Z_Malloc (sizeof(console_state_t)*svs.num_client_consoles);
	svs.num_client_surfaces = maxclients->value*UPDATE_BACKUP*64;
	svs.client_surfaces = (surface_state_t *)Z_Malloc (sizeof(surface_state_t)*svs.num_client_surfaces);

	// init network stuff
	NET_Config ( (maxclients->value > 1) );

	svs.last_heartbeat = -99999;		// send immediately

#ifdef USE_Q2_MASTER
	if (maxclients->value > 1)
	{
		char idmaster[64];
		Com_sprintf(idmaster,sizeof(idmaster),"master.GameRanger.com:%i",PORT_MASTER);
		NET_StringToAdr(idmaster,&master_adr[0]);
	}
#endif // USE_Q2_MASTER

	// init game
	SV_InitGameProgs ();
	for (i=0 ; i<maxclients->value ; 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));
	}

	if (dedicated->value && public_server->value)
	{
		SV_InitGamespy();
	}
}


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

  the full syntax is:

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

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

	map tram.cin+jail_e3
======================
*/
#ifndef DEDICATED_ONLY
extern void CONSOLE_SetupDefaultIntermissionLayoutFile( const char *s );
#else
void CONSOLE_SetupDefaultIntermissionLayoutFile (const char *s) {}
#endif

void SV_Map (qboolean attractloop, const char *levelstring, qboolean loadgame)
{
	char	level[MAX_QPATH];
	char	*ch;
	int		l;
	char	spawnpoint[MAX_QPATH];

	strcpy (level, levelstring);

	// 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;
		strcpy (spawnpoint, ch+1);
	} else {
		spawnpoint[0] = 0;
	}
	// skip the end-of-unit flag if necessary
	l = (int)strlen(level);
	if (level[0] == '*') {
		memmove (level, level+1, l);
		--l;
	}

	// Setup default intermission screens
	CONSOLE_SetupDefaultIntermissionLayoutFile( va( "maps/%s", level ) );

	// Move this down here, so we can setup the intermission file before
	// SV_InitGame gets called.

	sv.loadgame = loadgame;
	sv.attractloop = attractloop;

	if (sv.state == ss_dead && !sv.loadgame) {
		SV_InitGame ();	// the game is just starting
	}
	if ( ( l > 4 ) && (
		!Q_stricmp (level+l-4, ".avi") ||
		!Q_stricmp (level+l-4, ".bik") ||
		!Q_stricmp (level+l-4, ".mpg") ||
		!Q_stricmp (level+l-4, ".mov")))
	{
		SCR_BeginLoadingPlaque ();			// for local system
		SV_BroadcastCommand ("changing\n");
		SV_SpawnServer (level, spawnpoint, ss_cinematic, attractloop, loadgame);
	}
	else if (l > 4 && !Q_stricmp (level+l-4, ".dm2") )
	{
		SCR_BeginLoadingPlaque ();			// for local system
		SV_BroadcastCommand ("changing\n");
		SV_SpawnServer (level, spawnpoint, ss_demo, attractloop, loadgame);
	}
	else if (l > 4 && !Q_stricmp (level+l-4, ".pcx") )
	{
		SCR_BeginLoadingPlaque ();			// for local system
		SV_BroadcastCommand ("changing\n");
		SV_SpawnServer (level, spawnpoint, ss_pic, attractloop, loadgame);
	} else {
		SCR_BeginLoadingPlaque ();			// for local system
		SV_BroadcastCommand ("changing\n");
		SV_SendClientMessages ();
		SV_SpawnServer (level, spawnpoint, ss_game, attractloop, loadgame);
		Cbuf_CopyToDefer ();
	}
	SV_BroadcastCommand ("reconnect\n");
}
