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

static unsigned int predictingTimeStamp;

int cg_numSolids;
static entity_state_t *cg_solidList[MAX_PARSE_ENTITIES];

int cg_numTriggers;
static entity_state_t *cg_triggersList[MAX_PARSE_ENTITIES];
static qboolean	cg_triggersListTriggered[MAX_PARSE_ENTITIES];

//===================
//CG_PredictionActive
//===================
qboolean CG_PredictionActive( void )
{
	if( cgs.demoPlaying || ( cg.frame.playerState.pmove.pm_flags & PMF_NO_PREDICTION ) )
		return qfalse;

	return ( cg_predict->integer );
}

//=================
//CG_PredictedEvent - shared code can fire events during prediction
//=================
void CG_PredictedEvent( int entNum, int ev, int parm )
{
	// ignore this action if it has already been predicted (the unclosed ucmd has timestamp zero)
	if( predictingTimeStamp && ( predictingTimeStamp > cg.topPredictTimeStamp ) )
	{

		// inhibit the fire event when there is a weapon change predicted
		//if( ev == EV_FIREWEAPON ) {
		//	if( cg.predictedNewWeapon && (cg.predictedNewWeapon != cg.predictedPlayerState.stats[STAT_PENDING_WEAPON]) )
		//		return;
		//}
		CG_EntityEvent( &cg_entities[entNum].current, ev, parm, qtrue );
	}
}

//===================
//CG_CheckPredictionError
//===================
void CG_CheckPredictionError( void )
{
	int delta[3];
	int frame;

	if( !CG_PredictionActive() )
		return;

	// calculate the last usercmd_t we sent that the server has processed
	frame = cg.frame.ucmdExecuted & CMD_MASK;

	// compare what the server returned with what we had predicted it to be
	VectorSubtract( cg.frame.playerState.pmove.origin, cg.predictedOrigins[frame], delta );

	// save the prediction error for interpolation
	if( abs( delta[0] ) > 128 || abs( delta[1] ) > 128 || abs( delta[2] ) > 128 )
	{
		if( cg_showMiss->integer )
			CG_Printf( "prediction miss on %i: %i\n", cg.frame.serverFrame, abs( delta[0] ) + abs( delta[1] ) + abs( delta[2] ) );
		VectorClear( cg.predictionError );          // a teleport or something
	}
	else
	{
		if( cg_showMiss->integer && ( delta[0] || delta[1] || delta[2] ) )
			CG_Printf( "prediction miss on %i: %i\n", cg.frame.serverFrame, abs( delta[0] ) + abs( delta[1] ) + abs( delta[2] ) );
		VectorCopy( cg.frame.playerState.pmove.origin, cg.predictedOrigins[frame] );
		VectorCopy( delta, cg.predictionError ); // save for error interpolation
	}
}

//====================
//CG_BuildSolidList
//====================
void CG_BuildSolidList( void )
{
	int i;

	entity_state_t *ent;

	cg_numSolids = 0;
	cg_numTriggers = 0;
	for( i = 0; i < cg.frame.numEntities; i++ )
	{
		ent = &cg.frame.parsedEntities[i & ( MAX_PARSE_ENTITIES-1 )];
		if( ent->type == ET_ITEM || //items are always triggers, but don't have a brush model
		   ( ent->solid == SOLID_BMODEL && ent->type == ET_PUSH_TRIGGER ) )
		{
			cg_triggersList[cg_numTriggers++] = ent;
		}
		else if( ent->solid )
		{
			cg_solidList[cg_numSolids++] = ent;
		}
	}
}

//====================
//CG_ClipEntityContact
//====================
static qboolean CG_ClipEntityContact( vec3_t origin, vec3_t mins, vec3_t maxs, int entNum, qboolean interpolated )
{
	centity_t *cent;
	struct cmodel_s	*cmodel;
	trace_t	tr;
	vec3_t absmins, absmaxs;
	vec3_t entorigin, entangles;

	if( !mins )
		mins = vec3_origin;
	if( !maxs )
		maxs = vec3_origin;

	// find the cmodel
	cmodel = CG_CModelForEntity( entNum );
	if( !cmodel )
		return qfalse;

	cent = &cg_entities[entNum];

	// find the origin
	if( cent->current.solid == SOLID_BMODEL )
	{                                       // special value for bmodel
		if( interpolated )
		{
			int j;
			VectorLerp( cent->prev.origin, cg.lerpfrac, cent->current.origin, entorigin );
			for( j = 0; j < 3; j++ )
				entangles[j] = LerpAngle( cent->prev.angles[j], cent->current.angles[j], cg.lerpfrac );
		}
		else
		{
			VectorCopy( cent->current.origin, entorigin );
			VectorCopy( cent->current.angles, entangles );
		}
	}
	else
	{                               // encoded bbox
		if( interpolated )
		{
			VectorLerp( cent->prev.origin, cg.lerpfrac, cent->current.origin, entorigin );
			VectorClear( entangles ); // boxes don't rotate
		}
		else
		{
			VectorCopy( cent->current.origin, entorigin );
			VectorClear( entangles ); // boxes don't rotate
		}
	}

	// convert the box to compare to absolute coordinates
	VectorAdd( origin, mins, absmins );
	VectorAdd( origin, maxs, absmaxs );
	trap_CM_TransformedBoxTrace( &tr, vec3_origin, vec3_origin, absmins, absmaxs, cmodel, MASK_ALL, entorigin, entangles );
	return tr.startsolid || tr.allsolid;
}

//====================
//CG_Predict_TouchTriggers
//====================
void CG_Predict_TouchTriggers( pmove_t *pm )
{
	int i;
	entity_state_t *state;

	// fixme: more accurate check for being able to touch or not
	if( pm->s->pm_type != PM_NORMAL )
	{
		return;
	}

	for( i = 0; i < cg_numTriggers; i++ )
	{
		state = cg_triggersList[i];

		if( state->type == ET_PUSH_TRIGGER )
		{
			if( !cg_triggersListTriggered[i] )
			{
				if( CG_ClipEntityContact( pm->s->origin, pm->mins, pm->maxs, state->number, qfalse ) )
				{
					GS_TouchPushTrigger( pm->passent, pm->s, state );
					cg_triggersListTriggered[i] = qtrue;
				}
			}
		}
	}
}

//====================
//CG_ClipMoveToEntities
//====================
static void CG_ClipMoveToEntities( vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int ignore, int contentmask, trace_t *tr )
{
	int i, x, zd, zu;
	trace_t	trace;
	vec3_t origin, angles;
	entity_state_t *ent;
	struct cmodel_s	*cmodel;
	vec3_t bmins, bmaxs;

	for( i = 0; i < cg_numSolids; i++ )
	{
		ent = cg_solidList[i];

		if( ent->number == ignore )
			continue;
		if( !( contentmask & CONTENTS_CORPSE ) && ( ( ent->type == ET_CORPSE ) || ( ent->type == ET_GIB ) ) )
			continue;

		if( ent->solid == SOLID_BMODEL )
		{                           // special value for bmodel
			cmodel = trap_CM_InlineModel( ent->modelindex );
			if( !cmodel )
				continue;

			VectorCopy( ent->origin, origin );
			VectorCopy( ent->angles, angles );
		}
		else
		{                           // encoded bbox
			x = 8 * ( ent->solid & 31 );
			zd = 8 * ( ( ent->solid>>5 ) & 31 );
			zu = 8 * ( ( ent->solid>>10 ) & 63 ) - 32;

			bmins[0] = bmins[1] = -x;
			bmaxs[0] = bmaxs[1] = x;
			bmins[2] = -zd;
			bmaxs[2] = zu;

			VectorCopy( ent->origin, origin );
			VectorClear( angles ); // boxes don't rotate
			cmodel = trap_CM_ModelForBBox( bmins, bmaxs );
		}

		trap_CM_TransformedBoxTrace( &trace, start, end, mins, maxs, cmodel, contentmask, origin, angles );
		if( trace.allsolid || trace.fraction < tr->fraction )
		{
			trace.ent = ent->number;
			*tr = trace;
		}
		else if( trace.startsolid )
		{
			tr->startsolid = qtrue;
		}
		if( tr->allsolid )
			return;
	}
}

//================
//CG_Trace
//================
void CG_Trace( trace_t *t, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int ignore, int contentmask )
{
	// check against world
	trap_CM_BoxTrace( t, start, end, mins, maxs, NULL, contentmask );
	t->ent = t->fraction < 1.0 ? 0 : -1; // world entity is 0
	if( t->fraction == 0 )
		return; // blocked by the world

	// check all other solid models
	CG_ClipMoveToEntities( start, mins, maxs, end, ignore, contentmask, t );
}

//================
//CG_PointContents
//================
int CG_PointContents( vec3_t point )
{
	int i;
	entity_state_t *ent;
	struct cmodel_s	*cmodel;
	int contents;

	contents = trap_CM_PointContents( point, NULL );

	for( i = 0; i < cg_numSolids; i++ )
	{
		ent = cg_solidList[i];
		if( ent->solid != SOLID_BMODEL )  // special value for bmodel
			continue;

		cmodel = trap_CM_InlineModel( ent->modelindex );
		if( cmodel )
			contents |= trap_CM_TransformedPointContents( point, cmodel, ent->origin, ent->angles );
	}

	return contents;
}


float predictedSteps[CMD_BACKUP]; // for step smoothing
//=================
//CG_PredictAddStep
//=================
static void CG_PredictAddStep( int virtualtime, int predictiontime, float stepSize )
{

	float oldStep;
	int delta;

	// check for stepping up before a previous step is completed
	delta = cg.realTime - cg.predictedStepTime;
	if( delta < PREDICTED_STEP_TIME )
	{
		oldStep = cg.predictedStep * ( (float)( PREDICTED_STEP_TIME - delta ) / (float)PREDICTED_STEP_TIME );
	}
	else
	{
		oldStep = 0;
	}

	cg.predictedStep = oldStep + stepSize;
	cg.predictedStepTime = cg.realTime - ( predictiontime - virtualtime );
}

//=================
//CG_PredictSmoothSteps
//=================
static void CG_PredictSmoothSteps( void )
{
	int outgoing;
	int frame;
	usercmd_t cmd;
	int i;
	int virtualtime = 0, predictiontime = 0;

	cg.predictedStepTime = 0;
	cg.predictedStep = 0;

	trap_NET_GetCurrentState( NULL, &outgoing, NULL );

	i = outgoing;
	while( predictiontime < PREDICTED_STEP_TIME )
	{
		if( outgoing - i >= CMD_BACKUP )
			break;

		frame = i & CMD_MASK;
		trap_NET_GetUserCmd( frame, &cmd );
		predictiontime += cmd.msec;
		i--;
	}

	// run frames
	while( ++i <= outgoing )
	{
		frame = i & CMD_MASK;
		trap_NET_GetUserCmd( frame, &cmd );
		virtualtime += cmd.msec;

		if( predictedSteps[frame] )
		{
			CG_PredictAddStep( virtualtime, predictiontime, predictedSteps[frame] );
		}
	}
}

//=================
//CG_PredictMovement
//
//Sets cg.predictedVelocty, cg.predictedOrigin and cg.predictedAngles
//=================
void CG_PredictMovement( void )
{
	int executed, outgoing;
	int frame;
	pmove_t	pm;
	pmove_state_t ps;
	player_state_t *playerState;

	cg.predictedTime = 0;

	trap_NET_GetCurrentState( NULL, &outgoing, NULL );
	executed = cg.frame.ucmdExecuted;

	// if we are too far out of date, just freeze
	if( outgoing - executed >= CMD_BACKUP )
	{
		if( cg_showMiss->integer )
			CG_Printf( "exceeded CMD_BACKUP\n" );
		return;
	}

	playerState = &cg.frame.playerState; // start from the final position

	// copy current state to pmove
	memset( &pm, 0, sizeof( pm ) );
	ps = playerState->pmove;
	pm.s = &ps;
	pm.passent = playerState->POVnum;
	if( playerState->stats[STAT_GAMETYPE] == GAMETYPE_RACE )
		pm.contentmask = MASK_DEADSOLID;
	else
		pm.contentmask = MASK_PLAYERSOLID;

	pm.max_walljumps = GS_GameType_MaxWallJumps( playerState->stats[STAT_GAMETYPE] );
	pm.movestyle = cgs.clientInfo[cgs.playerNum].movestyle;

	// clear the triggered toggles for this prediction round
	memset( &cg_triggersListTriggered, qfalse, sizeof( cg_triggersListTriggered ) );

	// run frames
	while( ++executed <= outgoing )
	{
		frame = executed & CMD_MASK;
		trap_NET_GetUserCmd( frame, &pm.cmd );

		cg.predictedTime += pm.cmd.msec;
		predictingTimeStamp = pm.cmd.serverTimeStamp;

		Pmove( &pm );

		// update the prediction values
		VectorCopy( pm.s->velocity, cg.predictedVelocity );
		VectorCopy( pm.s->origin, cg.predictedOrigin );
		VectorCopy( pm.viewangles, cg.predictedAngles );

		//copy for stair smoothing
		predictedSteps[frame] = pm.step;

		// save for debug checking
		VectorCopy( pm.s->origin, cg.predictedOrigins[frame] ); // store for prediction error checks

		if( cg.topPredictTimeStamp < predictingTimeStamp )
			cg.topPredictTimeStamp = predictingTimeStamp;
	}

	CG_PredictSmoothSteps();
}
