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

#include "tv_relay.h"
#include "tv_upstream.h"
#include "tv_relay_svcmd.h"
#include "tv_relay_client.h"
#include "tv_downstream_clcmd.h"

const char *svc_strings[256] =
{
	"svc_bad",
	"svc_nop",
	"svc_servercmd",
	"svc_sound",
	"svc_serverdata",
	"svc_spawnbaseline",
	"svc_download",
	"svc_playerinfo",
	"svc_packetentities",
	"svc_gamecommands",
	"svc_match",
	"svc_clcack",
	"svc_servercs", //tmp jalfixme: sends reliable command as unreliable
	"svc_frame"
};

//==================
//TV_Relay_ParsePacketEntities
//
//An svc_packetentities has just been parsed, deal with the
//rest of the data stream.
//==================
static void TV_Relay_ParsePacketEntities( relay_t *relay, msg_t *msg, frame_t *oldframe, frame_t *newframe )
{
	int newnum;
	unsigned bits;
	entity_state_t *oldstate = NULL;
	int oldindex, oldnum;

	newframe->numEntities = 0;

	// delta from the entities present in oldframe
	oldindex = 0;
	if( !oldframe )
		oldnum = 99999;
	else if( oldindex >= oldframe->numEntities )
	{
		oldnum = 99999;
	}
	else
	{
		oldstate = &oldframe->parsedEntities[oldindex & ( MAX_PARSE_ENTITIES-1 )];
		oldnum = oldstate->number;
	}

	while( qtrue )
	{
		newnum = CL_ParseEntityBits( msg, &bits );
		if( newnum >= MAX_EDICTS )
			TV_Relay_Error( relay, "Bad entity number" );
		if( msg->readcount > msg->cursize )
			TV_Relay_Error( relay, "End of message in packet entities" );

		if( !newnum )
			break;

		while( oldnum < newnum )
		{ // one or more entities from the old packet are unchanged
			CL_DeltaEntity( msg, newframe, oldnum, oldstate, 0 );

			oldindex++;
			if( oldindex >= oldframe->numEntities )
			{
				oldnum = 99999;
			}
			else
			{
				oldstate = &oldframe->parsedEntities[oldindex & ( MAX_PARSE_ENTITIES-1 )];
				oldnum = oldstate->number;
			}
		}

		// delta from baseline
		if( oldnum > newnum )
		{
			if( bits & U_REMOVE )
			{
				Com_Printf( "U_REMOVE: oldnum > newnum (can't remove from baseline!)\n" );
				continue;
			}

			// delta from baseline
			CL_DeltaEntity( msg, newframe, newnum, &relay->baselines[newnum], bits );
			continue;
		}

		if( oldnum == newnum )
		{
			if( bits & U_REMOVE )
			{ // the entity present in oldframe is not in the current frame
				if( oldnum != newnum )
					Com_Printf( "U_REMOVE: oldnum != newnum\n" );

				oldindex++;
				if( oldindex >= oldframe->numEntities )
				{
					oldnum = 99999;
				}
				else
				{
					oldstate = &oldframe->parsedEntities[oldindex & ( MAX_PARSE_ENTITIES-1 )];
					oldnum = oldstate->number;
				}

				continue;
			}

			// delta from previous state
			CL_DeltaEntity( msg, newframe, newnum, oldstate, bits );

			oldindex++;
			if( oldindex >= oldframe->numEntities )
			{
				oldnum = 99999;
			}
			else
			{
				oldstate = &oldframe->parsedEntities[oldindex & ( MAX_PARSE_ENTITIES-1 )];
				oldnum = oldstate->number;
			}
			continue;
		}
	}

	// any remaining entities in the old frame are copied over
	while( oldnum != 99999 )
	{ // one or more entities from the old packet are unchanged
		CL_DeltaEntity( msg, newframe, oldnum, oldstate, 0 );

		oldindex++;
		if( oldindex >= oldframe->numEntities )
		{
			oldnum = 99999;
		}
		else
		{
			oldstate = &oldframe->parsedEntities[oldindex & ( MAX_PARSE_ENTITIES-1 )];
			oldnum = oldstate->number;
		}
	}
}

/*
   ==================
   TV_Relay_ParseStartSoundPacket
   ==================
 */
static void TV_Relay_ParseStartSoundPacket( relay_t *relay, frame_t *frame, msg_t *msg )
{
	vec3_t pos;
	int flags, channel, entnum, sound_num;
	float volume, attenuation;
	sound_t	*sound;

	flags = MSG_ReadByte( msg );
	sound_num = MSG_ReadByte( msg );

	// entity relative
	channel = MSG_ReadShort( msg );
	entnum = channel>>3;
	if( entnum > MAX_EDICTS )
		TV_Relay_Error( relay, "Invalid entity number in sound packet", entnum );
	channel &= 7;

	// positioned in space
	if( ( flags & ( SND_POS0_8|SND_POS0_16 ) ) == SND_POS0_8 )
		pos[0] = MSG_ReadChar( msg );
	else if( ( flags & ( SND_POS0_8|SND_POS0_16 ) ) == SND_POS0_16 )
		pos[0] = MSG_ReadShort( msg );
	else
		pos[0] = MSG_ReadInt3( msg );

	if( ( flags & ( SND_POS1_8|SND_POS1_16 ) ) == SND_POS1_8 )
		pos[1] = MSG_ReadChar( msg );
	else if( ( flags & ( SND_POS1_8|SND_POS1_16 ) ) == SND_POS1_16 )
		pos[1] = MSG_ReadShort( msg );
	else
		pos[1] = MSG_ReadInt3( msg );

	if( ( flags & ( SND_POS2_8|SND_POS2_16 ) ) == SND_POS2_8 )
		pos[2] = MSG_ReadChar( msg );
	else if( ( flags & ( SND_POS2_8|SND_POS2_16 ) ) == SND_POS2_16 )
		pos[2] = MSG_ReadShort( msg );
	else
		pos[2] = MSG_ReadInt3( msg );

	if( flags & SND_VOLUME )
		volume = MSG_ReadByte( msg ) / 255.0;
	else
		volume = DEFAULT_SOUND_PACKET_VOLUME;

	if( flags & SND_ATTENUATION )
		attenuation = MSG_ReadByte( msg );
	else
		attenuation = DEFAULT_SOUND_PACKET_ATTENUATION;

	if( frame->numsounds >= MAX_PARSE_SOUNDS )
		return;

	sound = &frame->sounds[frame->numsounds];
	VectorCopy( pos, sound->pos );
	sound->entnum = entnum;
	sound->channel = channel;
	sound->num = sound_num;
	sound->volume = volume;
	sound->attenuation = attenuation;

	frame->numsounds++;
}

//================
//TV_Relay_ParseFrameSnap
//================
static frame_t *TV_Relay_ParseFrameSnap( relay_t *relay, msg_t *msg, frame_t *newframe )
{
	int cmd, len;
	frame_t	*deltaframe;
	int numplayers, flags;
	int framediff, i, numtargets, target;
	char *text;
	gcommand_t *gcmd;

	memset( newframe, 0, sizeof( frame_t ) );
	len = MSG_ReadShort( msg ); // length
	newframe->serverTime = (unsigned int)MSG_ReadLong( msg );
	newframe->serverFrame = MSG_ReadLong( msg );
	newframe->deltaFrameNum = MSG_ReadLong( msg );
	newframe->ucmdExecuted = MSG_ReadLong( msg );

	flags = MSG_ReadByte( msg );
	newframe->delta = ( flags & FRAMESNAP_FLAG_DELTA );
	newframe->multipov = ( flags & FRAMESNAP_FLAG_MULTIPOV );
	newframe->allentities = ( flags & FRAMESNAP_FLAG_ALLENTITIES );

	MSG_ReadByte( msg ); // rate

	// validate the new frame
	newframe->valid = qfalse;

	// If the frame is delta compressed from data that we
	// no longer have available, we must suck up the rest of
	// the frame, but not use it, then ask for a non-compressed
	// message
	if( !newframe->delta )
	{
		newframe->valid = qtrue; // uncompressed frame
		deltaframe = NULL;
	}
	else
	{
		if( newframe->deltaFrameNum <= 0 )
		{
			Com_Printf( "Invalid delta frame (not supposed to happen!).\n" );
			deltaframe = NULL;
		}
		else
		{
			deltaframe = &relay->frames[newframe->deltaFrameNum & UPDATE_MASK];
			if( !deltaframe->valid )
			{
				// should never happen
				Com_Printf( "Delta from invalid frame (not supposed to happen!).\n" );
			}
			else if( deltaframe->serverFrame != newframe->deltaFrameNum )
			{
				// The frame that the server did the delta from
				// is too old, so we can't reconstruct it properly.
				Com_Printf( "Delta frame too old.\n" );
			}
			else
			{
				newframe->valid = qtrue; // valid delta parse
			}
		}
	}

	// read game commands
	cmd = MSG_ReadByte( msg );
	if( cmd != svc_gamecommands )
		TV_Relay_Error( relay, "Not gamecommands" );

	while( ( framediff = MSG_ReadShort( msg ) ) != -1 )
	{
		text = MSG_ReadString( msg );

		// see if it's valid and not yet handled
		if( newframe->valid && ( !relay->lastFrame || !relay->lastFrame->valid ||
		                         newframe->serverFrame - framediff > relay->lastFrame->serverFrame ) )
		{
			newframe->numgamecommands++;
			if( newframe->numgamecommands > MAX_PARSE_GAMECOMMANDS )
				TV_Relay_Error( relay, "Too many gamecommands" );
			if( newframe->gamecommandsDataHead + strlen( text ) >= sizeof( newframe->gamecommandsData ) )
				TV_Relay_Error( relay, "Too much gamecommands" );

			gcmd = &newframe->gamecommands[newframe->numgamecommands - 1];
			Q_strncpyz( newframe->gamecommandsData + newframe->gamecommandsDataHead, text,
			            sizeof( newframe->gamecommandsData ) - newframe->gamecommandsDataHead );
			gcmd->commandOffset = newframe->gamecommandsDataHead;
			newframe->gamecommandsDataHead += strlen( text ) + 1;

			if( newframe->multipov )
			{
				numtargets = MSG_ReadByte( msg );
				if( !numtargets )
				{
					gcmd->all = qtrue;
				}
				else
				{
					gcmd->all = qfalse;
					for( i = 0; i < numtargets; i++ )
					{
						target = MSG_ReadByte( msg );
						gcmd->targets[target] = qtrue;
					}
				}
			}
			else
			{
				gcmd->all = qtrue;
			}
		}
		else if( newframe->multipov ) // otherwise, ignore it
		{
			numtargets = MSG_ReadByte( msg );
			for( i = 0; i < numtargets; i++ )
				MSG_ReadByte( msg );
		}
	}

	// read areabits
	len = MSG_ReadByte( msg );
	if( len > sizeof( newframe->areabits ) )
		TV_Relay_Error( relay, "Invalid areabits size: %i", len );
	memset( newframe->areabits, 0, sizeof( newframe->areabits ) );
	MSG_ReadData( msg, &newframe->areabits, len );

	// read match info
	cmd = MSG_ReadByte( msg );
	if( cmd != svc_match )
		TV_Relay_Error( relay, "Not match info" );
	CL_ParseDeltaMatchState( msg, deltaframe, newframe );

	// read playerinfos
	numplayers = 0;
	while( ( cmd = MSG_ReadByte( msg ) ) )
	{
		if( cmd != svc_playerinfo )
			TV_Relay_Error( relay, "Not playerinfo" );
		if( deltaframe && deltaframe->numplayers >= numplayers )
			CL_ParsePlayerstate( msg, &deltaframe->playerStates[numplayers], &newframe->playerStates[numplayers] );
		else
			CL_ParsePlayerstate( msg, NULL, &newframe->playerStates[numplayers] );
		numplayers++;
	}
	newframe->numplayers = numplayers;
	newframe->playerState = newframe->playerStates[0];

	// read packet entities
	cmd = MSG_ReadByte( msg );
	if( cmd != svc_packetentities )
		TV_Relay_Error( relay, "Not packetentities" );

	TV_Relay_ParsePacketEntities( relay, msg, deltaframe, newframe );

	while( ( cmd = MSG_ReadByte( msg ) ) )
	{
		if( cmd != svc_sound )
			TV_Relay_Error( relay, "Not sound" );
		TV_Relay_ParseStartSoundPacket( relay, newframe, msg );
	}

	return deltaframe;
}

//================
//TV_Relay_ParseFrame
//================
static void TV_Relay_ParseFrame( relay_t *relay, msg_t *msg )
{
	static frame_t newframe;
	frame_t *deltaframe;
	double sum;
	int max, min, i;

	deltaframe = TV_Relay_ParseFrameSnap( relay, msg, &newframe );

	//ignore older than already received
	if( relay->lastFrame && newframe.serverFrame <= relay->lastFrame->serverFrame )
	{
		if( relay->lastFrame->serverFrame == newframe.serverFrame )
			Com_Printf( "Frame %i received twice\n", relay->lastFrame->serverFrame );
		else
			Com_Printf( "Dropping older frame snap\n" );
		return;
	}

	if( !newframe.valid )
	{
		Com_Printf( "Invalid frame\n" );
		return;
	}

	if( !relay->lastFrame || !relay->lastFrame->valid )
	{
		for( i = 0; i < MAX_TIME_DELTAS; i++ )
			relay->serverTimeDeltas[i] = newframe.serverTime - tvs.realtime;
	}
	else
	{
		relay->serverTimeDeltasHead = ( relay->serverTimeDeltasHead + 1 ) % MAX_TIME_DELTAS;
		relay->serverTimeDeltas[relay->serverTimeDeltasHead] = newframe.serverTime - tvs.realtime;
	}

	// ignore biggest and smallest value and use the avarage of the rest
	sum = 0;
	min = max = relay->serverTimeDeltas[0];
	for( i = 0; i < MAX_TIME_DELTAS; i++ )
	{
		sum += relay->serverTimeDeltas[i];
		if( relay->serverTimeDeltas[i] < min )
			min = relay->serverTimeDeltas[i];
		if( relay->serverTimeDeltas[i] > max )
			max = relay->serverTimeDeltas[i];
	}
	relay->serverTimeDelta = ( sum - max - min ) / ( MAX_TIME_DELTAS - 2 );
	relay->serverTime = tvs.realtime + relay->serverTimeDelta;

	// save the frame off in the backup array for later delta comparisons
	relay->frames[newframe.serverFrame & UPDATE_MASK] = newframe;
	// update lastframe pointer
	relay->lastFrame = &relay->frames[newframe.serverFrame & UPDATE_MASK];

	// getting a valid frame message ends the upstream process
	if( relay->state != CA_ACTIVE )
	{
		int i;
		client_t *client;

		relay->state = CA_ACTIVE;

		for( i = 0, client = tvs.clients; i < tv_maxclients->integer; i++, client++ )
		{
			if( client->state != CS_CONNECTED )  // AWAITING?
				continue;
			if( client->relay != relay )
				continue;
			TV_Downstream_New_f( client );
		}
	}
}

/*
   ==================
   TV_Relay_ParseServerData
   ==================
 */
static void TV_Relay_ParseServerData( relay_t *relay, msg_t *msg )
{
	int i, numpure;
	purelist_t *new;

	TV_Relay_ClearState( relay );

	relay->state = CA_CONNECTED;
	relay->map_checksum = 0;

	// parse protocol version number
	i = MSG_ReadLong( msg );

	if( i != APP_PROTOCOL_VERSION )
		TV_Relay_Error( relay, "Server returned version %i, not %i", i, APP_PROTOCOL_VERSION );

	relay->servercount = MSG_ReadLong( msg );
	relay->snapFrameTime = (unsigned int)MSG_ReadShort( msg );
	relay->baseServerTime = (unsigned int)MSG_ReadLong( msg );

	Q_strncpyz( relay->basegame, MSG_ReadString( msg ), sizeof( relay->basegame ) );
	Q_strncpyz( relay->game, MSG_ReadString( msg ), sizeof( relay->game ) );

	// parse player entity number
	relay->playernum = MSG_ReadShort( msg );

	// get the full level name
	Q_strncpyz( relay->levelname, MSG_ReadString( msg ), sizeof( relay->levelname ) );

	relay->sv_bitflags = MSG_ReadByte( msg );

	// using upstream->reliable won't work for TV_Relay_ParseServerMessage
	// in case of reliable demo following unreliable demo, causing "clack message while reliable" error
	relay->reliable = ( ( relay->sv_bitflags & SV_BITFLAGS_RELIABLE ) ? qtrue : qfalse );

	// pure list

	// clean old, if necessary
	while( relay->purelist )
	{
		new = relay->purelist->next;
		Mem_Free( relay->purelist->filename );
		Mem_Free( relay->purelist );
		relay->purelist = new;
	}

	// add new
	numpure = MSG_ReadShort( msg );
	while( numpure > 0 )
	{
		new = Mem_Alloc( relay->upstream->mempool, sizeof( purelist_t ) );
		new->filename = TV_Upstream_CopyString( relay->upstream, MSG_ReadString( msg ) );
		new->checksum = MSG_ReadLong( msg );
		new->next = relay->purelist;
		relay->purelist = new;

		numpure--;
	}
}

/*
   ==================
   TV_Relay_ParseBaseline
   ==================
 */
static void TV_Relay_ParseBaseline( relay_t *relay, msg_t *msg )
{
	entity_state_t *es;
	unsigned bits;
	int newnum;
	entity_state_t nullstate;

	memset( &nullstate, 0, sizeof( nullstate ) );
	newnum = CL_ParseEntityBits( msg, &bits );
	es = &relay->baselines[newnum];
	CL_ParseDelta( msg, &nullstate, es, newnum, bits );
}

//================
//TV_Relay_ParseServerMessage
//================
void TV_Relay_ParseServerMessage( relay_t *relay, msg_t *msg )
{
	int cmd;

	assert( relay && relay->state >= CA_HANDSHAKE );
	assert( msg );

	// parse the message
	while( relay->state >= CA_HANDSHAKE )
	{
		if( msg->readcount > msg->cursize )
			TV_Relay_Error( relay, "Bad server message" );

		cmd = MSG_ReadByte( msg );
		/*if( cmd == -1 )
		    Com_Printf( "%3i:CMD %i %s\n", msg->readcount-1, cmd, "EOF" );
		   else
		    Com_Printf( "%3i:CMD %i %s\n", msg->readcount-1, cmd, !svc_strings[cmd] ? "bad" : svc_strings[cmd] );*/

		if( cmd == -1 )
			break;

		// other commands
		switch( cmd )
		{
		default:
			TV_Relay_Error( relay, "Illegible server message" );

		case svc_nop:
			break;

		case svc_servercmd:
			if( !relay->reliable )
			{
				int cmdNum = MSG_ReadLong( msg );
				if( cmdNum < 0 )
					TV_Relay_Error( relay, "Invalid cmdNum value" );
				if( cmdNum <= relay->lastExecutedServerCommand )
				{
					MSG_ReadString( msg ); // read but ignore
					break;
				}
				relay->lastExecutedServerCommand = cmdNum;
			}
			// fall trough
		case svc_servercs: // configstrings from demo files. they don't have acknowledge
			TV_Relay_ParseServerCommand( relay, msg );
			break;

		case svc_serverdata:
			if( relay->upstream->demoplaying )
				TV_Relay_ReconnectClients( relay );

			if( relay->state == CA_HANDSHAKE )
			{
				Cbuf_Execute(); // make sure any stuffed commands are done
				TV_Relay_ClearState( relay );
				TV_Relay_ParseServerData( relay, msg );
			}
			else
			{
				return; // ignore rest of the packet (serverdata is always sent alone)
			}
			break;

		case svc_spawnbaseline:
			TV_Relay_ParseBaseline( relay, msg );
			break;

		case svc_download:
			//CL_ParseDownload( msg );
			break;

		case svc_clcack:
			if( relay->reliable )
				TV_Relay_Error( relay, "clack message while reliable" );
			MSG_ReadLong( msg ); // reliableAcknowledge
			MSG_ReadLong( msg ); // ucmdAcknowledged
			break;

		case svc_frame:
			TV_Relay_ParseFrame( relay, msg );
			break;

		case svc_sound:
		case svc_playerinfo:
		case svc_packetentities:
		case svc_match:
			TV_Relay_Error( relay, "Out of place frame data" );
		}
	}
}
