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

 */
// sv_main.c -- server main program

#include "server.h"

// shared message buffer to be used for occasional messages
msg_t tmpMessage;
qbyte tmpMessageData[MAX_MSGLEN];



//=============================================================================
//
//Com_Printf redirection
//
//=============================================================================

char sv_outputbuf[SV_OUTPUTBUF_LENGTH];
void SV_FlushRedirect( int sv_redirected, char *outputbuf, flush_params_t *extra )
{
	if( sv_redirected == RD_PACKET )
	{
		Netchan_OutOfBandPrint( extra->socket, extra->address, "print\n%s", outputbuf );
	}
}


//=============================================================================
//
//EVENT MESSAGES
//
//=============================================================================

//===============
//SV_AddGameCommand
//===============
void SV_AddGameCommand( client_t *client, const char *cmd )
{
	int index;

	if( !client )
		return;

	client->gameCommandCurrent++;
	index = client->gameCommandCurrent & ( MAX_RELIABLE_COMMANDS - 1 );
	Q_strncpyz( client->gameCommands[index].command, cmd, sizeof( client->gameCommands[index].command ) );
	if( client->lastSentFrameNum )
		client->gameCommands[index].framenum = client->lastSentFrameNum + 1;
	else
		client->gameCommands[index].framenum = sv.framenum;
}

//======================
//SV_AddServerCommand
//
//The given command will be transmitted to the client, and is guaranteed to
//not have future snapshot_t executed before it is executed
//======================
void SV_AddServerCommand( client_t *client, const char *cmd )
{
	int index;
	unsigned int i;

	if( !client )
		return;

	if( client->edict && ( client->edict->r.svflags & SVF_FAKECLIENT ) )
		return;

	if( !cmd || !cmd[0] || !strlen( cmd ) )
		return;

	client->reliableSequence++;
	// if we would be losing an old command that hasn't been acknowledged, we must drop the connection
	// we check == instead of >= so a broadcast print added by SV_DropClient() doesn't cause a recursive drop client
	if( client->reliableSequence - client->reliableAcknowledge == MAX_RELIABLE_COMMANDS + 1 )
	{
		//Com_Printf( "===== pending server commands =====\n" );
		for( i = client->reliableAcknowledge + 1; i <= client->reliableSequence; i++ )
		{
			Com_DPrintf( "cmd %5d: %s\n", i, client->reliableCommands[i & ( MAX_RELIABLE_COMMANDS-1 )] );
		}
		Com_DPrintf( "cmd %5d: %s\n", i, cmd );
		SV_DropClient( client, DROP_TYPE_GENERAL, "Error: Server command overflow" );
		return;
	}
	index = client->reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 );
	Q_strncpyz( client->reliableCommands[index], cmd, sizeof( client->reliableCommands[index] ) );
}

//=================
//SV_SendServerCommand
//
//Sends a reliable command string to be interpreted by
//the client: "cs", "changing", "disconnect", etc
//A NULL client will broadcast to all clients
//=================
void SV_SendServerCommand( client_t *cl, const char *format, ... )
{
	va_list	argptr;
	char message[MAX_MSGLEN];
	client_t *client;
	int i;

	va_start( argptr, format );
	Q_vsnprintfz( message, sizeof( message ), format, argptr );
	va_end( argptr );

	if( cl != NULL )
	{
		if( cl->state < CS_CONNECTING )
			return;
		SV_AddServerCommand( cl, message );
		return;
	}

	// send the data to all relevant clients
	for( i = 0, client = svs.clients; i < sv_maxclients->integer; i++, client++ )
	{
		if( client->state < CS_CONNECTING )
			continue;
		SV_AddServerCommand( client, message );
	}

	// add to demo
	if( svs.demo.file )
		SV_AddServerCommand( &svs.demo.client, message );
}

//==================
//SV_AddReliableCommandsToMessage
//
//(re)send all server commands the client hasn't acknowledged yet
//==================
void SV_AddReliableCommandsToMessage( client_t *client, msg_t *msg )
{
	unsigned int i;

	if( client->edict && ( client->edict->r.svflags & SVF_FAKECLIENT ) )
		return;

	if( sv_debug_serverCmd->integer )
	{
		Com_Printf( "sv_cl->reliableAcknowledge: %i sv_cl->reliableSequence:%i\n", client->reliableAcknowledge,
		            client->reliableSequence );
	}

	// write any unacknowledged serverCommands
	for( i = client->reliableAcknowledge + 1; i <= client->reliableSequence; i++ )
	{
		if( !strlen( client->reliableCommands[i & ( MAX_RELIABLE_COMMANDS-1 )] ) )
			continue;
		MSG_WriteByte( msg, svc_servercmd );
		if( !client->reliable )
			MSG_WriteLong( msg, i );
		MSG_WriteString( msg, client->reliableCommands[i & ( MAX_RELIABLE_COMMANDS-1 )] );
		if( sv_debug_serverCmd->integer )
		{
			Com_Printf( "SV_AddServerCommandsToMessage(%i):%s\n", i,
			            client->reliableCommands[i & ( MAX_RELIABLE_COMMANDS-1 )] );
		}
	}
	client->reliableSent = client->reliableSequence;
	if( client->reliable )
		client->reliableAcknowledge = client->reliableSent;
}

//=============================================================================
//
//EVENT MESSAGES
//
//=============================================================================

//=================
//SV_BroadcastCommand
//
//Sends a command to all connected clients. Ignores client->state < CS_SPAWNED check
//=================
void SV_BroadcastCommand( const char *format, ... )
{
	client_t *client;
	int i;
	va_list	argptr;
	char string[1024];

	if( !sv.state )
		return;

	va_start( argptr, format );
	Q_vsnprintfz( string, sizeof( string ), format, argptr );
	va_end( argptr );

	for( i = 0, client = svs.clients; i < sv_maxclients->integer; i++, client++ )
	{
		if( client->state < CS_CONNECTING )
			continue;
		SV_SendServerCommand( client, string );
	}
}

//==================
//SV_StartSound
//
//Each entity can have eight independant sound sources, like voice,
//weapon, feet, etc.
//
//If cahnnel & 8, the sound will be sent to everyone, not just
//things in the PHS.
//
//FIXME: if entity isn't in PHS, they must be forced to be sent or
//have the origin explicitly sent.
//
//Channel 0 is an auto-allocate channel, the others override anything
//already running on that entity/channel pair.
//
//An attenuation of 0 will play full volume everywhere in the level.
//Larger attenuations will drop off.  (max 4 attenuation)
//
//Timeofs can range from 0.0 to 0.1 to cause sounds to be started
//later in the frame than they normally would.
//
//If origin is NULL, the origin is determined from the entity origin
//or the midpoint of the entity box for bmodels.
//==================
void SV_StartSound( vec3_t origin, edict_t *ent, int channel, int sound_num, float volume, float attenuation )
{
	int i;
	sound_t	*sound;

	if( sv.numframesounds >= MAX_FRAME_SOUNDS )
	{
		Com_DPrintf( "sv.numframesounds >= MAX_FRAME_SOUNDS, dropping a sound\n" );
		return;
	}
	sv.numframesounds++;

	sound = &sv.framesounds[sv.numframesounds - 1];
	sound->entnum = NUM_FOR_EDICT( ent );
	sound->num = sound_num;
	sound->channel = channel;
	sound->volume = volume;
	sound->attenuation = attenuation;

	// use the entity origin unless it is a bmodel or explicitly specified
	if( !origin )
	{
		// the client doesn't know that bmodels have weird origins
		// the origin can also be explicitly set
		if( ent->r.solid == SOLID_BSP )
		{
			for( i = 0; i < 3; i++ )
				sound->pos[i] = ent->s.origin[i]+0.5 * ( ent->r.mins[i]+ent->r.maxs[i] );
		}
		else
		{
			VectorCopy( ent->s.origin, sound->pos );
		}
	}
	else
	{
		VectorCopy( origin, sound->pos );
	}
}


//===============================================================================
//
//FRAME UPDATES
//
//===============================================================================

//=======================
//SV_SendClientsFragments
//=======================
qboolean SV_SendClientsFragments( void )
{
	client_t *client;
	int i;
	qboolean remaining = qfalse;

	// send a message to each connected client
	for( i = 0, client = svs.clients; i < sv_maxclients->integer; i++, client++ )
	{
		if( client->state == CS_FREE || client->state == CS_ZOMBIE )
			continue;
		if( client->edict && ( client->edict->r.svflags & SVF_FAKECLIENT ) )
			continue;
		if( !client->netchan.unsentFragments )
			continue;

		if( !Netchan_TransmitNextFragment( &client->netchan ) )
		{
			Com_Printf( "Error sending fragment to %s: %s\n", NET_AddressToString( &client->netchan.remoteAddress ),
			           NET_ErrorString() );
			if( client->reliable )
				SV_DropClient( client, DROP_TYPE_GENERAL, "Error sending fragment: %s\n", NET_ErrorString() );
			continue;
		}

		if( client->netchan.unsentFragments )
			remaining = qtrue;
	}

	return remaining;
}

//==================
//SV_Netchan_Transmit
//==================
qboolean SV_Netchan_Transmit( netchan_t *netchan, msg_t *msg )
{
	int zerror;

	// if we got here with unsent fragments, fire them all now
	if( !Netchan_PushAllFragments( netchan ) )
		return qfalse;

	if( sv_compresspackets->integer )
	{
		zerror = Netchan_CompressMessage( msg );
		if( zerror < 0 )
		{          // it's compression error, just send uncompressed
			Com_DPrintf( "SV_Netchan_Transmit (ignoring compression): Compression error %i\n", zerror );
		}
	}

	return Netchan_Transmit( netchan, msg );
}

//=======================
//SV_InitClientMessage
//=======================
void SV_InitClientMessage( client_t *client, msg_t *msg, qbyte *data, size_t size )
{
	if( client->edict && ( client->edict->r.svflags & SVF_FAKECLIENT ) )
		return;

	if( data && size )
		MSG_Init( msg, data, size );
	MSG_Clear( msg );

	// write the last client-command we received so it's acknowledged
	if( !client->reliable )
	{
		MSG_WriteByte( msg, svc_clcack );
		MSG_WriteLong( msg, client->clientCommandExecuted );
		MSG_WriteLong( msg, client->UcmdReceived ); // acknowledge the last ucmd
	}
}

//=======================
//SV_SendMessageToClient
//=======================
qboolean SV_SendMessageToClient( client_t *client, msg_t *msg )
{
	assert( client );

	if( client->edict && ( client->edict->r.svflags & SVF_FAKECLIENT ) )
		return qtrue;

	// transmit the message data
	client->lastPacketSentTime = svs.realtime;
	return SV_Netchan_Transmit( &client->netchan, msg );
}

//=======================
//SV_ResetClientFrameCounters
// This is used for a temporary sanity check I'm doing.
//=======================
void SV_ResetClientFrameCounters( void )
{
	int i;
	client_t *client;
	for( i = 0, client = svs.clients; i < sv_maxclients->integer; i++, client++ )
	{
		if( !client->state )
			continue;
		if( client->edict && ( client->edict->r.svflags & SVF_FAKECLIENT ) )
			continue;

		client->lastSentFrameNum = 0;
	}
}

//=======================
//SV_SendClientDatagram
//=======================
static qboolean SV_SendClientDatagram( client_t *client )
{
	if( client->edict && ( client->edict->r.svflags & SVF_FAKECLIENT ) )
		return qtrue;

	SV_InitClientMessage( client, &tmpMessage, NULL, 0 );

	SV_AddReliableCommandsToMessage( client, &tmpMessage );

	// send over all the relevant entity_state_t
	// and the player_state_t
	SV_BuildClientFrameSnap( client );
	SV_WriteFrameSnapToClient( client, &tmpMessage );
	return SV_SendMessageToClient( client, &tmpMessage );
}

//=======================
//SV_SendClientMessages
//=======================
void SV_SendClientMessages( void )
{
	int i;
	client_t *client;

	// send a message to each connected client
	for( i = 0, client = svs.clients; i < sv_maxclients->integer; i++, client++ )
	{
		if( client->state == CS_FREE || client->state == CS_ZOMBIE )
			continue;

		if( client->edict && ( client->edict->r.svflags & SVF_FAKECLIENT ) )
		{
			client->lastSentFrameNum = sv.framenum;
			continue;
		}

		if( client->state == CS_SPAWNED )
		{
			if( !SV_SendClientDatagram( client ) )
			{
				Com_Printf( "Error sending message to %s: %s\n", client->name, NET_ErrorString() );
				if( client->reliable )
				{
					SV_DropClient( client, DROP_TYPE_GENERAL, "Error sending message: %s\n", NET_ErrorString() );
				}
			}
		}
		else
		{
			// send pending reliable commands, or send heartbeats for not timing out
			if( client->reliableSequence > client->reliableAcknowledge ||
			    svs.realtime - client->lastPacketSentTime > 1000 )
			{
				SV_InitClientMessage( client, &tmpMessage, NULL, 0 );
				SV_AddReliableCommandsToMessage( client, &tmpMessage );
				if( !SV_SendMessageToClient( client, &tmpMessage ) )
				{
					Com_Printf( "Error sending message to %s: %s\n", client->name, NET_ErrorString() );
					if( client->reliable )
					{
						SV_DropClient( client, DROP_TYPE_GENERAL, "Error sending message: %s\n", NET_ErrorString() );
					}
				}
			}
		}
	}
}
