/*
   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 "tv_local.h"

#include "tv_relay_snap.h"

#include "tv_relay.h"
#include "tv_server.h"

//=============
//TV_Relay_EmitPacketEntities
//
//Writes a delta update of an entity_state_t list to the message.
//=============
static void TV_Relay_EmitPacketEntities( relay_t *relay, client_frame_t *from, client_frame_t *to, msg_t *msg )
{
	entity_state_t *oldent, *newent;
	int oldindex, newindex;
	int oldnum, newnum;
	int from_num_entities;
	int bits;

	MSG_WriteByte( msg, svc_packetentities );

	if( !from )
		from_num_entities = 0;
	else
		from_num_entities = from->num_entities;

	newindex = 0;
	oldindex = 0;
	while( newindex < to->num_entities || oldindex < from_num_entities )
	{
		if( newindex >= to->num_entities )
		{
			newent = NULL;
			newnum = 9999;
		}
		else
		{
			newent = &relay->client_entities[( to->first_entity + newindex ) % relay->num_client_entities];
			newnum = newent->number;
		}

		if( oldindex >= from_num_entities )
		{
			oldent = NULL;
			oldnum = 9999;
		}
		else
		{
			oldent = &relay->client_entities[( from->first_entity + oldindex ) % relay->num_client_entities];
			oldnum = oldent->number;
		}

		if( newnum == oldnum ) // delta update from old position
		{
			MSG_WriteDeltaEntity( oldent, newent, msg, qfalse,
			                     ( ( EDICT_NUM( relay, newent->number ) )->r.svflags & SVF_TRANSMITORIGIN2 ) );
			oldindex++;
			newindex++;
			continue;
		}

		if( newnum < oldnum ) // this is a new entity, send it from the baseline
		{
			MSG_WriteDeltaEntity( &relay->baselines[newnum], newent, msg, qtrue,
			                     !( ( EDICT_NUM( relay, newent->number ) )->r.svflags & SVF_NOORIGIN2 ) );
			newindex++;
			continue;
		}

		if( newnum > oldnum ) // the old entity isn't present in the new message
		{
			bits = U_REMOVE;
			if( oldnum >= 256 )
				bits |= ( U_NUMBER16 | U_MOREBITS1 );

			MSG_WriteByte( msg, bits&255 );
			if( bits & 0x0000ff00 )
				MSG_WriteByte( msg, ( bits>>8 )&255 );

			if( bits & U_NUMBER16 )
				MSG_WriteShort( msg, oldnum );
			else
				MSG_WriteByte( msg, oldnum );

			oldindex++;
			continue;
		}
	}

	MSG_WriteShort( msg, 0 ); // end of packetentities
}

//==================
//TV_Relay_WriteFrameSnapToClient
//==================
void TV_Relay_WriteFrameSnapToClient( relay_t *relay, client_t *client, msg_t *msg )
{
	client_frame_t *frame, *oldframe;
	int flags, i, index, pos, length;

	// this is the frame we are creating
	frame = &client->frames[relay->framenum & UPDATE_MASK];

	// for non-reliable clients we need to send nodelta frame until the client responds
	if( client->nodelta && !client->reliable )
	{
		if( !client->nodelta_frame )
			client->nodelta_frame = relay->framenum;
		else if( client->lastframe >= client->nodelta_frame )
			client->nodelta = qfalse;
	}

	if( client->lastframe <= 0 || (unsigned)client->lastframe > relay->framenum || client->nodelta )
	{ // client is asking for a not compressed retransmit
		oldframe = NULL;
	}
	//else if( sv.framenum >= client->lastframe + (UPDATE_BACKUP - 3) )
	else if( relay->framenum >= (unsigned)client->lastframe + UPDATE_MASK )
	{ // client hasn't gotten a good message through in a long time
		oldframe = NULL;
	}
	else // we have a valid message to delta from
	{
		oldframe = &client->frames[client->lastframe & UPDATE_MASK];
		if( oldframe->multipov != frame->multipov )
			oldframe = NULL;	// don't delta compress a frame of different POV type
	}

	if( client->nodelta && client->reliable )
		client->nodelta = qfalse;

	MSG_WriteByte( msg, svc_frame );

	pos = msg->cursize;
	MSG_WriteShort( msg, 0 );

	MSG_WriteLong( msg, relay->curFrame->serverTime );
	MSG_WriteLong( msg, relay->framenum );
	MSG_WriteLong( msg, client->lastframe );
	MSG_WriteLong( msg, frame->UcmdExecuted );

	flags = 0;
	if( oldframe != NULL )
		flags |= FRAMESNAP_FLAG_DELTA;
	if( frame->allentities )
		flags |= FRAMESNAP_FLAG_ALLENTITIES;
	if( frame->multipov )
		flags |= FRAMESNAP_FLAG_MULTIPOV;
	MSG_WriteByte( msg, flags );

	MSG_WriteByte( msg, 0 ); // rate

	// add game comands
	MSG_WriteByte( msg, svc_gamecommands );
	if( frame->multipov )
	{
		int numcmds = 0;
		gcommand_t *commands = NULL, *gcmd;
		char *commandsData = NULL;

		if( relay->curFrame && relay->curFrame->valid )
		{
			numcmds = relay->curFrame->numgamecommands;
			commands = relay->curFrame->gamecommands;
			commandsData = relay->curFrame->gamecommandsData;
		}

		for( index = 0, gcmd = commands; index < numcmds; index++, gcmd++ )
		{
			MSG_WriteShort( msg, 0 );
			MSG_WriteString( msg, commandsData + gcmd->commandOffset );

			if( gcmd->all )
			{
				MSG_WriteByte( msg, 0 );
			}
			else
			{
				int numtargets = 0;

				for( i = 0; i < MAX_CLIENTS; i++ )
					if( gcmd->targets[i] )
						numtargets++;

				MSG_WriteByte( msg, numtargets );
				for( i = 0; i < MAX_CLIENTS; i++ )
					if( gcmd->targets[i] )
						MSG_WriteByte( msg, i );
			}
		}
	}
	else
	{
		for( i = client->gameCommandCurrent - MAX_RELIABLE_COMMANDS + 1; i <= client->gameCommandCurrent; i++ )
		{
			index = i & ( MAX_RELIABLE_COMMANDS - 1 );

			// check that it is valid command and that has not already been sent
			// we can only allow commands from certain amount of old frames, so the short won't overflow
			if( !client->gameCommands[index].command[0] || client->gameCommands[index].framenum + 256 < relay->framenum ||
			   client->gameCommands[index].framenum > relay->framenum ||
			   ( client->lastframe >= 0 && client->gameCommands[index].framenum <= (unsigned)client->lastframe ) )
				continue;

			// do not allow the message buffer to overflow (can happen on flood updates)
			if( msg->cursize + strlen( client->gameCommands[index].command ) + 512 > msg->maxsize )
				continue;

			// send it
			MSG_WriteShort( msg, relay->framenum - client->gameCommands[index].framenum );
			MSG_WriteString( msg, client->gameCommands[index].command );
		}
	}
	MSG_WriteShort( msg, -1 );

	// send over the areabits
	MSG_WriteByte( msg, frame->areabytes );
	MSG_WriteData( msg, frame->areabits, frame->areabytes );

	SV_WriteDeltaMatchStateToClient( oldframe, frame, msg );

	// delta encode the playerstate
	for( i = 0; i < frame->numplayers; i++ )
	{
		if( oldframe && oldframe->numplayers >= i )
			SV_WritePlayerstateToClient( &oldframe->ps[i], &frame->ps[i], msg );
		else
			SV_WritePlayerstateToClient( NULL, &frame->ps[i], msg );
	}
	MSG_WriteByte( msg, 0 );

	// delta encode the entities
	TV_Relay_EmitPacketEntities( relay, oldframe, frame, msg );

	// add the sound commands generated this frame
	for( i = 0; i < frame->numsounds; i++ )
		SV_WriteSoundToClient( &frame->sounds[i], msg );
	MSG_WriteByte( msg, 0 );

	length = msg->cursize - pos - 2;
	msg->cursize = pos;
	MSG_WriteShort( msg, length );
	msg->cursize += length;

	client->lastSentFrameNum = relay->framenum;
}

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

//============
//TV_Relay_FatPVS
//
//The client will interpolate the view position,
//so we can't use a single PVS point
//===========
void TV_Relay_FatPVS( relay_t *relay, vec3_t org )
{
	memset( relay->fatpvs, 0, CM_ClusterSize( relay->cms ) );
	CM_MergePVS( relay->cms, org, relay->fatpvs );
}

//============
//TV_Relay_FatPHS
//============
void TV_Relay_FatPHS( relay_t *relay, int cluster )
{
	memset( relay->fatphs, 0, CM_ClusterSize( relay->cms ) );
	CM_MergePHS( relay->cms, cluster, relay->fatphs );
}

//============
//TV_Relay_BitsCullEntity
//===========
qboolean TV_Relay_BitsCullEntity( cmodel_state_t *cms, edict_t *ent, qbyte *bits, int maxclusters )
{
	int i, l;

	// too many leafs for individual check, go by headnode
	if( ent->r.num_clusters == -1 )
	{
		if( !CM_HeadnodeVisible( cms, ent->r.headnode, bits ) )
			return qtrue;
		return qfalse;
	}

	// check individual leafs
	for( i = 0; i < maxclusters; i++ )
	{
		l = ent->r.clusternums[i];
		if( bits[l >> 3] & ( 1 << ( l&7 ) ) )
			return qfalse;
	}

	return qtrue; // not visible/audible
}

#define TV_Relay_PVSCullEntity(relay,ent) TV_Relay_BitsCullEntity(relay->cms,ent,relay->fatpvs,ent->r.num_clusters)
#define TV_Relay_PHSCullEntity(relay,ent) TV_Relay_BitsCullEntity(relay->cms,ent,relay->fatphs,1)

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

#define	MAX_SNAPSHOT_ENTITIES	1024
typedef struct
{
	int numSnapshotEntities;
	int snapshotEntities[MAX_SNAPSHOT_ENTITIES];
	int entityAddedToSnapList[MAX_EDICTS];
} snapshotEntityNumbers_t;

//==================
//TV_AddEntNumToSnapList
//==================
static void TV_AddEntNumToSnapList( int entNum, snapshotEntityNumbers_t *entsList )
{
	if( entsList->numSnapshotEntities >= MAX_SNAPSHOT_ENTITIES )
		return;

	// don't double add entities
	if( entsList->entityAddedToSnapList[entNum] )
		return;

	entsList->snapshotEntities[entsList->numSnapshotEntities] = entNum;
	entsList->numSnapshotEntities++;
	entsList->entityAddedToSnapList[entNum] = qtrue;
}

//============
//TV_Relay_SnapCullEntity
//===========
qboolean TV_Relay_SnapCullEntity( relay_t *relay, edict_t *ent, edict_t *clent, client_frame_t *frame,
                                  vec3_t vieworg, int clientarea )
{
	// filters: this entity has been disabled for communication
	if( ent->r.svflags & SVF_NOCLIENT )
		return qtrue;

	// send all entities
	if( frame->allentities )
		return qfalse;

	// filters: transmit only to clients in the same team as this entity
	// wsw: imp: moved if because broadcasting is less important than team specifics
	if( ( ent->r.svflags & SVF_ONLYTEAM ) && ( clent && ent->s.team != clent->s.team ) )
		return qtrue;

	if( ent->r.svflags & SVF_BROADCAST )  // send to everyone
		return qfalse;

	// if the entity is only a sound
	if( !ent->s.modelindex && !ent->s.events[0] && !ent->s.light && !ent->s.effects && ent->s.sound )
	{
		if( DistanceFast( ent->s.origin, vieworg ) > 400 )
			return qtrue;	// it would be attenuated
	}

	// this is the same as CM_AreasConnected but portal's visibility included
	if( !( frame->areabits[ent->r.areanum>>3] & ( 1<<( ent->r.areanum&7 ) ) ) )
	{
		// doors can legally straddle two areas, so we may need to check another one
		if( !ent->r.areanum2 || !( frame->areabits[ent->r.areanum2>>3] & ( 1<<( ent->r.areanum2&7 ) ) ) )
			return qtrue; // blocked by a door
	}

	if( (ent->r.svflags & SVF_TRANSMITORIGIN2) && !ent->s.linearProjectile )
		return TV_Relay_PHSCullEntity( relay, ent );	// cull by PHS

	return TV_Relay_PVSCullEntity( relay, ent );	// cull by PVS
}

//==================
//TV_Relay_BuildSnapEntitiesList
//==================
static void TV_Relay_BuildSnapEntitiesList( relay_t *relay, edict_t *clent, vec3_t vieworg,
                                            client_frame_t *frame, snapshotEntityNumbers_t *entsList )
{
	int leafnum = 0, clusternum = 0, clientarea = 0;
	int entNum;
	edict_t	*ent;

	// find the client's PVS
	if( frame->allentities )
	{
		frame->areabytes = CM_WriteAreaBits( relay->cms, frame->areabits, -1 );
	}
	else
	{
		leafnum = CM_PointLeafnum( relay->cms, vieworg );
		clusternum = CM_LeafCluster( relay->cms, leafnum );
#if 0
		clientarea = CM_LeafArea( relay->cms, leafnum );
		frame->areabytes = CM_WriteAreaBits( relay->cms, frame->areabits, clientarea );
#else
		// we can't set proper areabits as we don't have any clue about door entities
		frame->areabytes = CM_WriteAreaBits( relay->cms, frame->areabits, -1 );
#endif
	}

	if( clent )
	{
		TV_Relay_FatPVS( relay, vieworg );
		TV_Relay_FatPHS( relay, clusternum );

		// if the client is outside of the world, don't send him any entity (excepting himself)
		if( !frame->allentities && clusternum == -1 )
		{
			//TV_AddEntNumToSnapList( relay->playernum + 1, entsList );
			return;
		}
	}

	// no need of merging when we are sending the whole level
	if( !frame->allentities )
	{
		// make a pass checking for sky portal and portal entities and merge PVS in case of finding any
		if( relay->configstrings[CS_SKYBOX][0] != '\0' )
		{
			vec3_t origin;
			if( sscanf( relay->configstrings[CS_SKYBOX], "%f %f %f", &origin[0], &origin[1], &origin[2] ) >= 3 )
				CM_MergeVisSets( relay->cms, origin, relay->fatpvs, relay->fatphs, frame->areabits );
		}

		for( entNum = 1; entNum < relay->num_edicts; entNum++ )
		{
			ent = EDICT_NUM( relay, entNum );
			if( ent->r.svflags & SVF_PORTAL )
			{
				// merge PV sets if portal
				if( TV_Relay_SnapCullEntity( relay, ent, clent, frame, vieworg, clientarea ) )
					continue;

				if( !VectorCompare( ent->s.origin, ent->s.origin2 ) )
					CM_MergeVisSets( relay->cms, ent->s.origin2, relay->fatpvs, relay->fatphs, frame->areabits );
			}
		}
	}

	// add the entities to the list
	for( entNum = 1; entNum < relay->num_edicts; entNum++ )
	{
		ent = EDICT_NUM( relay, entNum );

		// fix number if broken
		if( ent->s.number != entNum )
		{
			Com_Printf( "Fixing entnum: %i -> %i\n", ent->s.number, entNum );
			ent->s.number = entNum;
		}

		if( (!clent || entNum != relay->playernum+1) && TV_Relay_SnapCullEntity( relay, ent, clent, frame, vieworg, clientarea ) )
			continue;

		// add it
		TV_AddEntNumToSnapList( entNum, entsList );
	}
}

//=============
//TV_Relay_SnapCullSound
//=============
static qboolean TV_Relay_SnapCullSound( relay_t *relay, sound_t *sound, edict_t *clent, client_frame_t *frame,
                                        int clientcluster, int clientarea )
{
	qbyte *mask;
	int leafnum, cluster;
	int area;

	if( frame->allentities )
		return qfalse;

	if( ( sound->channel & CHAN_NO_PHS_ADD ) || sound->attenuation == ATTN_NONE )
		return qfalse;

	leafnum = CM_PointLeafnum( relay->cms, sound->pos );
	area = CM_LeafArea( relay->cms, leafnum );
	cluster = CM_LeafCluster( relay->cms, leafnum );
	mask = CM_ClusterPHS( relay->cms, cluster );

	if( mask )
	{
		if( !( mask[clientcluster>>3] & ( 1<<( clientcluster&7 ) ) ) )
			return qtrue;
		if( !CM_AreasConnected( relay->cms, area, clientarea ) )
			return qtrue;
	}

	return qfalse;
}


//=============
//TV_Relay_BuildClientFrameSoundList
//=============
static void TV_Relay_BuildClientFrameSoundList( relay_t *relay, edict_t *clent, client_frame_t *frame )
{
	int i, leafnum, cluster, area;

	if( frame->allentities )
	{
		cluster = 0;
		area = 0;
	}
	else
	{
		leafnum = CM_PointLeafnum( relay->cms, clent->s.origin );
		cluster = CM_LeafCluster( relay->cms, leafnum );
		area = CM_LeafArea( relay->cms, leafnum );
	}

	frame->numsounds = 0;
	for( i = 0; i < relay->numframesounds && frame->numsounds < MAX_PARSE_SOUNDS; i++ )
	{
		if( TV_Relay_SnapCullSound( relay, &relay->framesounds[i], clent, frame, cluster, area ) )
			continue;

		frame->numsounds++;
		frame->sounds[frame->numsounds - 1] = relay->framesounds[i];
	}
}

//=============
//TV_Relay_BuildClientFrameSnap
//
//Decides which entities are going to be visible to the client, and
//copies off the playerstate and areabits.
//=============
void TV_Relay_BuildClientFrameSnap( relay_t *relay, client_t *client )
{
	int e, i;
	vec3_t org;
	edict_t	*clent, *ent;
	client_frame_t *frame;
	entity_state_t *state;
	snapshotEntityNumbers_t entsList;

	assert( relay );
	assert( client );
	assert( client->relay == relay );

	clent = client->edict;
	if( clent && !clent->r.client )
		return; // not in game yet

	if( clent )
	{
		VectorSet( org, clent->s.origin[0], clent->s.origin[1], clent->s.origin[2] + clent->r.client->ps.viewheight );
	}
	else
	{
		assert( client->mv );
		VectorClear( org );
	}

	// this is the frame we are creating
	frame = &client->frames[relay->framenum & UPDATE_MASK];
	frame->sentTimeStamp = tvs.realtime;
	frame->UcmdExecuted = client->UcmdExecuted;

	if( client->mv )
	{
		frame->multipov = qtrue;
		frame->allentities = qtrue;
	}
	else
	{
		frame->multipov = qfalse;
		frame->allentities = qfalse;
	}

	// grab the current player_state_t
	if( frame->multipov )
	{
		frame->numplayers = 0;
		for( i = 0; i < relay->max_clients && i < relay->num_edicts; i++ )
		{
			if( i == relay->playernum )
			{
				if( clent )
					frame->numplayers++;
				continue;
			}
			ent = EDICT_NUM( relay, i+1 );
			if( ent->r.inuse && ent->r.client && !( ent->r.svflags & SVF_NOCLIENT ) )
				frame->numplayers++;
		}
	}
	else
	{
		frame->numplayers = 1;
	}

	if( ( !frame->ps && frame->numplayers ) || frame->ps_size < frame->numplayers )
	{
		if( !frame->numplayers )
		{
			Mem_Free( frame->ps );
			frame->ps = NULL;
			frame->ps_size = 0;
		}
		else if( !frame->ps )
		{
			frame->ps = Mem_Alloc( tv_mempool, sizeof( player_state_t ) * frame->numplayers );
			frame->ps_size = frame->numplayers;
		}
		else
		{
			frame->ps = Mem_Realloc( frame->ps, sizeof( player_state_t ) * frame->numplayers );
			frame->ps_size = frame->numplayers;
		}
	}

	if( frame->multipov )
	{
		int numplayers = 0;

		for( i = 0; i < relay->max_clients && i < relay->num_edicts; i++ )
		{
			if( i == relay->playernum )
			{
				if( clent )
				{
					frame->ps[numplayers] = clent->r.client->ps;
					frame->ps[numplayers++].POVnum = relay->playernum + 1;
				}
				continue;
			}

			ent = EDICT_NUM( relay, i+1 );
			if( ent->r.inuse && ent->r.client && !( ent->r.svflags & SVF_NOCLIENT ) )
				frame->ps[numplayers++] = ent->r.client->ps;
		}
	}
	else
	{
		frame->ps[0] = clent->r.client->ps;
	}

	// grab current match state information
	relay->module_export->GetMatchState( relay->module, &frame->matchstate );

	// build up the list of visible entities
	//=============================
	entsList.numSnapshotEntities = 0;
	memset( entsList.entityAddedToSnapList, 0, sizeof( entsList.entityAddedToSnapList ) );
	TV_Relay_BuildSnapEntitiesList( relay, clent, org, frame, &entsList );
	//=============================

	// dump the entities list
	frame->num_entities = 0;
	frame->first_entity = relay->next_client_entities;

	for( e = 0; e < entsList.numSnapshotEntities; e++ )
	{
		// add it to the circular client_entities array
		state = &relay->client_entities[relay->next_client_entities % relay->num_client_entities];
		if( entsList.snapshotEntities[e] == relay->playernum + 1 )
		{
			*state = clent->s;
			state->number = relay->playernum + 1;
			state->svflags = clent->r.svflags;
		}
		else
		{
			*state = EDICT_NUM( relay, entsList.snapshotEntities[e] )->s;
			state->svflags = EDICT_NUM( relay, entsList.snapshotEntities[e] )->r.svflags;
		}
		assert( entsList.snapshotEntities[e] == state->number );
	
		// don't mark *any* missiles as solid
		if( state->svflags & SVF_PROJECTILE )
			state->solid = 0;

		frame->num_entities++;
		relay->next_client_entities++;
	}

	// sounds
	TV_Relay_BuildClientFrameSoundList( relay, clent, frame );
}
