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

*/
// cl_ents.c -- entity parsing and management

#include "client.h"

/*
=========================================================================

FRAME PARSING

=========================================================================
*/


/*
=============
CL_ParseDeltaMatchState
=============
*/
void CL_ParseDeltaMatchState( msg_t *msg, frame_t *oldframe, frame_t *newframe )
{
	match_state_t	*matchstate;
	int				bitmask;

	// start from old state or 0 if none
	matchstate = &newframe->match;
	if( oldframe ) {
		*matchstate = oldframe->match;
	} else {
		memset( matchstate, 0, sizeof(match_state_t) );
	}

	bitmask = MSG_ReadByte( msg );

	if( bitmask & MATCHSTATE_FLAG_STATE )
		matchstate->state = MSG_ReadByte( msg );

	if( bitmask & MATCHSTATE_FLAG_TIMELIMIT ) {
		int timelimitmask = MSG_ReadLong( msg );
		if( timelimitmask & MATCHSTATE_EXTENDEDTIME_BIT ) {
			matchstate->extendedtime = qtrue;
			timelimitmask &= ~MATCHSTATE_EXTENDEDTIME_BIT;
		} else {
			matchstate->extendedtime = qfalse;
		}
		matchstate->timelimit = timelimitmask;
	}

	if( bitmask & MATCHSTATE_FLAG_CLOCK_MSECS )
		matchstate->clock_msecs = MSG_ReadByte( msg );

	if( bitmask & MATCHSTATE_FLAG_CLOCK_SECS )
		matchstate->clock_secs = MSG_ReadByte( msg );

	if( bitmask & MATCHSTATE_FLAG_CLOCK_MINUTES )
		matchstate->clock_mins = MSG_ReadShort( msg );
}

/*
===================
CL_ParsePlayerstate
===================
*/
void CL_ParsePlayerstate( msg_t *msg, frame_t *oldframe, frame_t *newframe )
{
	int			flags;
	player_state_t	*state;
	int			i, b;
	int			statbits;

	state = &newframe->playerState;

	// clear to old value before delta parsing
	if( oldframe )
		*state = oldframe->playerState;
	else
		memset( state, 0, sizeof(*state) );

	flags = MSG_ReadByte( msg );
	if( flags & PS_MOREBITS1 )
	{
		b = MSG_ReadByte( msg );
		flags |= b<<8;
	}
	if( flags & PS_MOREBITS2 )
	{
		b = MSG_ReadByte( msg );
		flags |= b<<16;
	}
	if( flags & PS_MOREBITS3 )
	{
		b = MSG_ReadByte( msg );
		flags |= b<<24;
	}

	//
	// parse the pmove_state_t
	//
	if( flags & PS_M_TYPE )
		state->pmove.pm_type = MSG_ReadByte( msg );

	if( flags & PS_M_ORIGIN0 )
		state->pmove.origin[0] = MSG_ReadInt3( msg );
	if( flags & PS_M_ORIGIN1 )
		state->pmove.origin[1] = MSG_ReadInt3( msg );
	if( flags & PS_M_ORIGIN2 )
		state->pmove.origin[2] = MSG_ReadInt3( msg );

	if( flags & PS_M_VELOCITY0 )
		state->pmove.velocity[0] = MSG_ReadInt3( msg );
	if( flags & PS_M_VELOCITY1 )
		state->pmove.velocity[1] = MSG_ReadInt3( msg );
	if( flags & PS_M_VELOCITY2 )
		state->pmove.velocity[2] = MSG_ReadInt3( msg );

	if( flags & PS_M_TIME )
		state->pmove.pm_time = MSG_ReadByte( msg );

	if( flags & PS_M_FLAGS )
		state->pmove.pm_flags = MSG_ReadShort( msg );

	if( flags & PS_M_DELTA_ANGLES0 )
		state->pmove.delta_angles[0] = MSG_ReadShort( msg );
	if( flags & PS_M_DELTA_ANGLES1 )
		state->pmove.delta_angles[1] = MSG_ReadShort( msg );
	if( flags & PS_M_DELTA_ANGLES2 )
		state->pmove.delta_angles[2] = MSG_ReadShort( msg );

	if( flags & PS_EVENT )
		state->event = MSG_ReadShort( msg );

	if( flags & PS_VIEWANGLES )
	{
		state->viewangles[0] = MSG_ReadAngle16( msg );
		state->viewangles[1] = MSG_ReadAngle16( msg );
		state->viewangles[2] = MSG_ReadAngle16( msg );
	}

	if( flags & PS_M_GRAVITY )
		state->pmove.gravity = MSG_ReadShort( msg );

	if( flags & PS_FOV )
		state->fov = MSG_ReadByte( msg );

	if( flags & PS_POVNUM )
		state->POVnum = MSG_ReadByte( msg );

	if( flags & PS_VIEWHEIGHT )
		state->viewheight = MSG_ReadChar( msg );

	if( flags & PS_PMOVESTATS ) {
		for( i = 0 ; i < PM_STAT_SIZE ; i++ )
			state->pmove.stats[i] = MSG_ReadShort( msg );
	}

	if( flags & PS_WEAPONLIST ) {
		// parse weaponlist
		statbits = MSG_ReadShort( msg );
		for( i = 0; i < MAX_WEAPLIST_STATS; i++ ) {
			if( statbits & (1<<i) ) {
				state->weaponlist[i][0] = MSG_ReadByte( msg );
				state->weaponlist[i][1] = MSG_ReadByte( msg );
				state->weaponlist[i][2] = MSG_ReadByte( msg );
			}
		}
	}

	// parse stats
	statbits = MSG_ReadLong( msg );
	for( i = 0; i < PS_MAX_STATS; i++ ) {
		if( statbits & (1<<i) )
			state->stats[i] = MSG_ReadShort( msg );
	}
}

/*
=================
CL_ParseEntityBits

Returns the entity number and the header bits
=================
*/
int CL_ParseEntityBits( msg_t *msg, unsigned *bits )
{
	unsigned	b, total;
	int			number;

	total = MSG_ReadByte( msg );
	if (total & U_MOREBITS1)
	{
		b = MSG_ReadByte( msg );
		total |= b<<8;
	}
	if (total & U_MOREBITS2)
	{
		b = MSG_ReadByte( msg );
		total |= b<<16;
	}
	if (total & U_MOREBITS3)
	{
		b = MSG_ReadByte( msg );
		total |= b<<24;
	}

	if (total & U_NUMBER16)
		number = MSG_ReadShort( msg );
	else
		number = MSG_ReadByte( msg );

	*bits = total;

	return number;
}

/*
==================
CL_ParseDelta

Can go from either a baseline or a previous packet_entity
==================
*/
void CL_ParseDelta( msg_t *msg, entity_state_t *from, entity_state_t *to, int number, unsigned bits )
{
	// set everything to the state we are delta'ing from
	*to = *from;

	VectorCopy (from->origin, to->old_origin);
	to->number = number;

	if (bits & U_SOLID)
		to->solid = MSG_ReadShort( msg );

	if (bits & U_MODEL)
		to->modelindex = MSG_ReadByte( msg );
	if (bits & U_MODEL2)
		to->modelindex2 = MSG_ReadByte( msg );
	
	if (bits & U_FRAME8)
		to->frame = MSG_ReadByte( msg );
	if (bits & U_FRAME16)
		to->frame = MSG_ReadShort( msg );

	if ((bits & U_SKIN8) && (bits & U_SKIN16))		//used for laser colors
		to->skinnum = MSG_ReadLong( msg );
	else if (bits & U_SKIN8)
		to->skinnum = MSG_ReadByte( msg );
	else if (bits & U_SKIN16)
		to->skinnum = MSG_ReadShort( msg );

	if ( (bits & (U_EFFECTS8|U_EFFECTS16)) == (U_EFFECTS8|U_EFFECTS16) )
		to->effects = MSG_ReadLong( msg );
	else if (bits & U_EFFECTS8)
		to->effects = MSG_ReadByte( msg );
	else if (bits & U_EFFECTS16)
		to->effects = MSG_ReadShort( msg );

	if ( (bits & (U_RENDERFX8|U_RENDERFX16)) == (U_RENDERFX8|U_RENDERFX16) )
		to->renderfx = MSG_ReadLong( msg );
	else if (bits & U_RENDERFX8)
		to->renderfx = MSG_ReadByte( msg );
	else if (bits & U_RENDERFX16)
		to->renderfx = MSG_ReadShort( msg );

	if (bits & U_ORIGIN1)
		to->origin[0] = MSG_ReadCoord( msg );
	if (bits & U_ORIGIN2)
		to->origin[1] = MSG_ReadCoord( msg );
	if (bits & U_ORIGIN3)
		to->origin[2] = MSG_ReadCoord( msg );
		
	if ( (bits & U_ANGLE1) && (to->solid == SOLID_BMODEL) )
		to->angles[0] = MSG_ReadAngle16( msg );
	else if (bits & U_ANGLE1)
		to->angles[0] = MSG_ReadAngle( msg );

	if ( (bits & U_ANGLE2) && (to->solid == SOLID_BMODEL) )
		to->angles[1] = MSG_ReadAngle16( msg );
	else if (bits & U_ANGLE2)
		to->angles[1] = MSG_ReadAngle( msg );

	if ( (bits & U_ANGLE3) && (to->solid == SOLID_BMODEL) )
		to->angles[2] = MSG_ReadAngle16( msg );
	else if (bits & U_ANGLE3)
		to->angles[2] = MSG_ReadAngle( msg );

	if (bits & U_OLDORIGIN)
		MSG_ReadPos( msg, to->old_origin);

	if (bits & U_TYPE) {
		to->type = MSG_ReadByte( msg );
		to->takedamage = to->type & ET_INVERSE;
		to->type &= ~ET_INVERSE;
	}

	if (bits & U_SOUND)
		to->sound = MSG_ReadByte( msg );

	if ( bits & U_EVENT ) {
		int event;

		event = MSG_ReadByte( msg );
		if ( event & EV_INVERSE ) {
			to->events[0] = event & ~EV_INVERSE;
			to->eventParms[0] = MSG_ReadByte( msg );
		} else {
			to->events[0] = event;
			to->eventParms[0] = 0;
		}
	} else {
		to->events[0] = 0;
		to->eventParms[0] = 0;
	}

	if ( bits & U_EVENT2 ) {
		int event;

		event = MSG_ReadByte( msg );
		if ( event & EV_INVERSE ) {
			to->events[1] = event & ~EV_INVERSE;
			to->eventParms[1] = MSG_ReadByte( msg );
		} else {
			to->events[1] = event;
			to->eventParms[1] = 0;
		}
	} else {
		to->events[1] = 0;
		to->eventParms[1] = 0;
	}

	if (bits & U_WEAPON)
		to->weapon = MSG_ReadByte( msg );

	if (bits & U_LIGHT)
		to->light = MSG_ReadLong( msg );

	if( bits & U_TEAM )
		to->team = MSG_ReadByte( msg );
}

/*
==================
CL_DeltaEntity

Parses deltas from the given base and adds the resulting entity
to the current frame
==================
*/
void CL_DeltaEntity( msg_t *msg, frame_t *frame, int newnum, entity_state_t *old, unsigned bits )
{
	entity_state_t	*state;

	state = &frame->parsedEntities[frame->numEntities & (MAX_PARSE_ENTITIES-1)];
	frame->numEntities++;
	CL_ParseDelta( msg, old, state, newnum, bits );
}

//==================
//CL_ParsePacketEntities
//
//An svc_packetentities has just been parsed, deal with the
//rest of the data stream.
//==================
void CL_ParsePacketEntities( 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(1)
	{
		newnum = CL_ParseEntityBits( msg, &bits );
		if( newnum >= MAX_EDICTS )
			Com_Error( ERR_DROP, "CL_ParsePacketEntities: bad number:%i", newnum );
		if( msg->readcount > msg->cursize)
			Com_Error( ERR_DROP, "CL_ParsePacketEntities: end of message" );

		if( !newnum )
			break;

		while( oldnum < newnum )
		{	// one or more entities from the old packet are unchanged
			if( cl_shownet->integer == 3 )
				Com_Printf("   unchanged: %i\n", oldnum);

			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;
			}
		}

		if( bits & U_REMOVE )
		{	// the entity present in oldframe is not in the current frame
			if( cl_shownet->integer == 3 )
				Com_Printf( "   remove: %i\n", newnum );
			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;
		}

		if( oldnum == newnum )
		{	// delta from previous state
			if( cl_shownet->integer == 3 )
				Com_Printf( "   delta: %i\n", newnum );

			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;
		}

		if( oldnum > newnum )
		{	// delta from baseline
			if( cl_shownet->integer == 3 )
				Com_Printf( "   baseline: %i\n", newnum );

			CL_DeltaEntity( msg, newframe, newnum, &cl_baselines[newnum], bits );
			continue;
		}

	}

	// any remaining entities in the old frame are copied over
	while( oldnum != 99999 )
	{	// one or more entities from the old packet are unchanged
		if( cl_shownet->integer == 3 )
			Com_Printf( "   unchanged: %i\n", oldnum );

		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;
		}
	}
}

//================
//CL_ParseFrameSnap
//================
frame_t *CL_ParseFrameSnap( msg_t *msg, frame_t *newframe )
{
	int			cmd;
	int			len;
	frame_t		*old;

	memset( newframe, 0, sizeof(frame_t) );
	newframe->serverTime = MSG_ReadLong( msg );
	newframe->serverFrame = MSG_ReadLong( msg );
	newframe->deltaFrame = MSG_ReadLong( msg );
	cl.suppressCount = MSG_ReadByte( msg );
#ifdef RATEKILLED
	cl.suppressCount = 0;
#endif

	if( cl_shownet->integer == 3 )
		Com_Printf( "   frame:%i  delta:%i\n", newframe->serverFrame,
		newframe->deltaFrame );

	// 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->deltaFrame <= 0 )
	{
		newframe->valid = qtrue;		// uncompressed frame
		old = NULL;
		cls.demowaiting = qfalse;	// we can start recording now
	}
	else
	{
		old = &cl.frames[newframe->deltaFrame & UPDATE_MASK];
		if( !old->valid )
		{	// should never happen
			Com_Printf( "Delta from invalid frame (not supposed to happen!).\n" );
		}
		if( old->serverFrame != newframe->deltaFrame )
		{	// 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 areabits
	len = MSG_ReadByte( msg );
	MSG_ReadData( msg, &newframe->areabits, len );

	// read match info
	cmd = MSG_ReadByte( msg );
	SHOWNET( msg, svc_strings[cmd] );
	if( cmd != svc_match )
		Com_Error( ERR_DROP, "CL_ParseFrame: not match info" );
	CL_ParseDeltaMatchState( msg, old, newframe );

	// read playerinfo
	cmd = MSG_ReadByte( msg );
	SHOWNET( msg, svc_strings[cmd] );
	if( cmd != svc_playerinfo )
		Com_Error( ERR_DROP, "CL_ParseFrame: not playerinfo" );
	CL_ParsePlayerstate( msg, old, newframe );

	// read packet entities
	cmd = MSG_ReadByte( msg );
	SHOWNET( msg, svc_strings[cmd] );
	if( cmd != svc_packetentities )
		Com_Error( ERR_DROP, "CL_ParseFrame: not packetentities" );
	CL_ParsePacketEntities( msg, old, newframe );

	return old;
}

//================
//CL_ParseFrame
//================
void CL_ParseFrame( msg_t *msg ) {
	static frame_t newframe;
	frame_t *deltaframe, *lerpframe = NULL;

	if( cl.curFrame && cl.curFrame->valid )
		lerpframe = &cl.frames[cl.curFrame->serverFrame & UPDATE_MASK];

	deltaframe = CL_ParseFrameSnap( msg, &newframe );

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

	if( !newframe.valid )
		return;
	
	// getting a valid frame message ends the connection process
	if( cls.state != CA_ACTIVE )
		CL_SetClientState( CA_ACTIVE );
	cl.soundPrepped = qtrue;	// can start mixing ambient sounds

	// jalfixme: we will probably buffer frame snaps later on,
	// and this servertimedelta adjustment must be done when the frame is fired.
	// We're firing it as we get it, so we do it here by now
	cl.serverTimeDelta = newframe.serverTime - cls.realtime;
	if( cls.realtime + cl.serverTimeDelta < 0 )
		cl.serverTimeDelta = 0;
	cl.serverTime = cls.realtime + cl.serverTimeDelta; // adjust servertime again to newest delta
	// at this point, we have cl.serverTime having the same time as in the server, BUT LAGGED.
	// we have to find the lag offset and remove it from cl.serverTime.
	// (to do)

	// save the frame off in the backup array for later delta comparisons
	cl.frames[newframe.serverFrame & UPDATE_MASK] = newframe;
	// update curframe pointer
	cl.curFrame = &cl.frames[newframe.serverFrame & UPDATE_MASK];

	//mark next three frames as invalid in the backup
	cl.frames[(newframe.serverFrame+1) & UPDATE_MASK].valid = qfalse;
	cl.frames[(newframe.serverFrame+2) & UPDATE_MASK].valid = qfalse;
	cl.frames[(newframe.serverFrame+3) & UPDATE_MASK].valid = qfalse;

	if( lerpframe ) {
		//Com_Printf( "newframe.serverTime:%u lerpFrame.serverTime:%u delta:%u\n", newframe.serverTime, lerpframe->serverTime, newframe.serverTime - lerpframe->serverTime );
		if( cls.demoplaying ) {
			if( cl_timedemo->integer ) {
				cls.demoplay_lastsnaptime = 0;
			}
			else {
				cls.demoplay_lastsnaptime = newframe.serverTime - lerpframe->serverTime;
				// jalfixme: We should know the server pps and clamp this time to them
			}
		}

		CL_GameModule_NewFrameSnap( &cl.frames[newframe.serverFrame & UPDATE_MASK], lerpframe );
	}
}

