//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"

#include <stdarg.h>
#include "engine/IEngineSound.h"
#include "filesystem.h"
#include "igamemovement.h"
#include "engine/IEngineTrace.h"
#include "engine/ivmodelinfo.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

extern CMoveData *g_pMoveData;

class CMoveHelperClient : public IMoveHelper
{
public:
					CMoveHelperClient( void );
	virtual			~CMoveHelperClient( void );

	char const*		GetName( EntityHandle_t handle ) const;

	// touch lists
	virtual void	ResetTouchList( void );
	virtual bool	AddToTouched( const trace_t& tr, const Vector& impactvelocity );
	virtual void	ProcessImpacts( void );

	// Numbered line printf
	virtual void	Con_NPrintf( int idx, char const* fmt, ... );
		
	virtual bool	PlayerFallingDamage(void);
	virtual void	PlayerSetAnimation( PLAYER_ANIM eAnim );

	// These have separate server vs client impementations
	virtual void	StartSound( const Vector& origin, int channel, char const* sample, float volume, soundlevel_t soundlevel, int fFlags, int pitch );
	virtual void	StartSound( const Vector& origin, const char *soundname ); 
	virtual void	PlaybackEventFull( int flags, int clientindex, unsigned short eventindex, float delay, Vector& origin, Vector& angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 );
	virtual IPhysicsSurfaceProps *GetSurfaceProps( void );

	virtual bool IsWorldEntity( const CBaseHandle &handle );

private:
	// results, tallied on client and server, but only used by server to run SV_Impact.
	// we store off our velocity in the trace_t structure so that we can determine results
	// of shoving boxes etc. around.
	struct touchlist_t
	{
		Vector	deltavelocity;
		trace_t trace;

		touchlist_t() = default;

	private:
		touchlist_t( const touchlist_t &src );
	};

	CUtlVector<touchlist_t>			m_TouchList;
};	

//-----------------------------------------------------------------------------
// Singleton
//-----------------------------------------------------------------------------

IMPLEMENT_MOVEHELPER();

static CMoveHelperClient s_MoveHelperClient;


//-----------------------------------------------------------------------------
// Constructor 
//-----------------------------------------------------------------------------
CMoveHelperClient::CMoveHelperClient( void )
{
	SetSingleton( this );
}

CMoveHelperClient::~CMoveHelperClient( void )
{
	SetSingleton( 0 );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : const char
//-----------------------------------------------------------------------------
char const* CMoveHelperClient::GetName( EntityHandle_t handle ) const
{
	return "";
}

//-----------------------------------------------------------------------------
// Touch list
//-----------------------------------------------------------------------------

void CMoveHelperClient::ResetTouchList( void )
{
	m_TouchList.RemoveAll();
}

//-----------------------------------------------------------------------------
// Adds to the touched list 
//-----------------------------------------------------------------------------

bool CMoveHelperClient::AddToTouched( const trace_t& tr, const Vector& impactvelocity )
{
    int i;

	auto pPlayer = C_BasePlayer::GetLocalPlayer();
	if ( !pPlayer )
        return false;

    if ( !tr.m_pEnt )
        return false;

    if ( tr.m_pEnt == pPlayer->GetBaseEntity() )
	{
		Assert( !"CMoveHelperClient::AddToTouched:  Tried to add self to touchlist!!!" );
		return false;
    }

	// Look for duplicates
	for (i = 0; i < m_TouchList.Size(); i++)
	{
		if (m_TouchList[i].trace.m_pEnt == tr.m_pEnt)
		{
			return false;
		}
	}

	i = m_TouchList.AddToTail();
	m_TouchList[i].trace = tr;
	VectorCopy( impactvelocity, m_TouchList[i].deltavelocity );

	return true;
}

void CMoveHelperClient::ProcessImpacts( void )
{
	C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
	if ( !pPlayer )
		return;

	// Relink in order to build absorigin and absmin/max to reflect any changes
	//  from prediction.  Relink will early out on SOLID_NOT

	// TODO: Touch triggers on the client
	//pPlayer->PhysicsTouchTriggers();

	// Don't bother if the player ain't solid
	if ( pPlayer->IsSolidFlagSet( FSOLID_NOT_SOLID ) )
		return;

	// Save off the velocity, cause we need to temporarily reset it
	Vector vel = pPlayer->GetAbsVelocity();

	// Touch other objects that were intersected during the movement.
	for (int i = 0 ; i < m_TouchList.Size(); i++)
    {
        // We didn't touch an entity ...
        if (!m_TouchList[i].trace.m_pEnt)
            continue;
        
		// Run the impact function as if we had run it during movement.
		C_BaseEntity *entity = ClientEntityList().GetEnt( m_TouchList[i].trace.m_pEnt->entindex() );
		if ( !entity )
			continue;

		Assert( entity != pPlayer );
		// Don't ever collide with self!!!!
		if ( entity == pPlayer )
			continue;

		// Reconstruct trace results.
		m_TouchList[i].trace.m_pEnt = entity;

		// Use the velocity we had when we collided, so boxes will move, etc.
		pPlayer->SetAbsVelocity( m_TouchList[i].deltavelocity );

		entity->PhysicsImpact( pPlayer, m_TouchList[i].trace );
	}

	// Restore the velocity
	pPlayer->SetAbsVelocity( vel );

	// So no stuff is ever left over, sigh...
	ResetTouchList();
}

void CMoveHelperClient::StartSound( const Vector& origin, const char *soundname )
{
	if ( !soundname )
		return;

	CLocalPlayerFilter filter;
	filter.UsePredictionRules();
	C_BaseEntity::EmitSound( filter, SOUND_FROM_LOCAL_PLAYER, soundname, &origin );
}


//-----------------------------------------------------------------------------
// Play a sound
//-----------------------------------------------------------------------------

void CMoveHelperClient::StartSound( const Vector& origin, int channel, 
	char const* pSample, float volume, soundlevel_t soundlevel, int fFlags, int pitch )
{
	if ( pSample )
	{
		C_BaseEntity::PrecacheScriptSound( pSample );
		CLocalPlayerFilter filter;
		filter.UsePredictionRules();

		EmitSound_t ep;
		ep.m_nChannel = channel;
		ep.m_pSoundName =  pSample;
		ep.m_flVolume = volume;
		ep.m_SoundLevel = soundlevel;
		ep.m_nPitch = pitch;
		ep.m_pOrigin = &origin;

		C_BaseEntity::EmitSound( filter, SOUND_FROM_LOCAL_PLAYER, ep );
	}
}

//-----------------------------------------------------------------------------
// Play a event
//-----------------------------------------------------------------------------

void CMoveHelperClient::PlaybackEventFull( int flags, int clientindex, unsigned short eventindex, float delay, Vector& origin, Vector& angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 )
{
	// TODO
	if (g_pMoveData->m_bFirstRunOfFunctions )
	{
	}
}

//-----------------------------------------------------------------------------
// Surface properties interface
//-----------------------------------------------------------------------------

IPhysicsSurfaceProps *CMoveHelperClient::GetSurfaceProps( void )
{
	extern IPhysicsSurfaceProps *physprops;
	return physprops;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : bDeveloper - 
//			*pFormat - 
//			... - 
//-----------------------------------------------------------------------------
void CMoveHelperClient::Con_NPrintf( int idx, char const* pFormat, ...)
{
	va_list marker;
	char msg[8192];

	va_start(marker, pFormat);
	Q_vsnprintf(msg, sizeof( msg ), pFormat, marker);
	va_end(marker);
	
#if defined( CSTRIKE_DLL ) || defined( DOD_DLL ) // reltodo
	engine->Con_NPrintf( idx, "%s", msg );
#else
	engine->Con_NPrintf( idx, msg );
#endif
}

//-----------------------------------------------------------------------------
// Purpose: Called when the player falls onto a surface fast enough to take
//			damage, according to the rules in CGameMovement::CheckFalling.
// Output : Returns true if the player survived the fall, false if they died.
//-----------------------------------------------------------------------------
bool CMoveHelperClient::PlayerFallingDamage(void)
{
	// Do nothing; falling damage is applied in MoveHelper_Server::PlayerFallingDamage.
	return(true);
}


//-----------------------------------------------------------------------------
// Purpose: Sets an animation in the player.
// Input  : eAnim - Animation to set.
//-----------------------------------------------------------------------------
void CMoveHelperClient::PlayerSetAnimation( PLAYER_ANIM eAnim )
{
	 // Do nothing on the client. Animations are set on the server.
}

bool CMoveHelperClient::IsWorldEntity( const CBaseHandle &handle )
{
	return handle == cl_entitylist->GetNetworkableHandle( 0 );
}