//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//

#include "cbase.h"

#include "physics_friction.h"
#include "vphysics/friction.h"

#include "ivp_mindist.hxx"
#include "ivp_mindist_intern.hxx"
#include "ivp_listener_collision.hxx"
#include "ivp_friction.hxx"


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

class CFrictionSnapshot : public IPhysicsFrictionSnapshot
{
public:
	CFrictionSnapshot( IVP_Real_Object *pObject );
	~CFrictionSnapshot();

	bool IsValid();

	// Object 0 is this object, Object 1 is the other object
	IPhysicsObject *GetObject( int index );
	int GetMaterial( int index );

	void GetContactPoint( Vector &out );
	void GetSurfaceNormal( Vector &out );
	float GetNormalForce();
	float GetEnergyAbsorbed();
	void RecomputeFriction();
	void ClearFrictionForce();

	void MarkContactForDelete();
	void DeleteAllMarkedContacts( bool wakeObjects );
	void NextFrictionData();
	float GetFrictionCoefficient();


private:
	void SetFrictionSynapse( IVP_Synapse_Friction *pSet );
	CUtlVector<IVP_Real_Object *> *m_pDeleteList;
	IVP_Real_Object			*m_pObject;
	IVP_Synapse_Friction	*m_pFriction;
	IVP_Contact_Point		*m_pContactPoint;
	int						m_synapseIndex;
};

CFrictionSnapshot::CFrictionSnapshot( IVP_Real_Object *pObject ) : m_pObject(pObject)
{
	m_pDeleteList = NULL;
	SetFrictionSynapse( pObject->get_first_friction_synapse() );
}

CFrictionSnapshot::~CFrictionSnapshot()
{
	delete m_pDeleteList;
}

void CFrictionSnapshot::DeleteAllMarkedContacts( bool wakeObjects )
{
	if ( !m_pDeleteList )
		return;

	for ( int i = 0; i < m_pDeleteList->Count(); i++ )
	{
		if ( wakeObjects )
		{
			m_pDeleteList->Element(i)->ensure_in_simulation();
		}
		DeleteAllFrictionPairs( m_pObject, m_pDeleteList->Element(i) );
	}
	m_pFriction = NULL;
}

void CFrictionSnapshot::SetFrictionSynapse( IVP_Synapse_Friction *pSet )
{
	if ( pSet )
	{
		m_pFriction = pSet;
		m_pContactPoint = pSet->get_contact_point();
		m_synapseIndex = (pSet == m_pContactPoint->get_synapse(0)) ? 0 : 1;
	}
	else
	{
		m_pFriction = NULL;
		m_pContactPoint = NULL;
		m_synapseIndex = 0;
	}
}

bool CFrictionSnapshot::IsValid()
{
	return m_pFriction != NULL ? true : false;
}

IPhysicsObject *CFrictionSnapshot::GetObject( int index )
{
	IVP_Synapse_Friction *pFriction = m_pFriction;
	if ( index == 1 )
	{
		pFriction = m_pContactPoint->get_synapse(!m_synapseIndex);
	}
	return static_cast<IPhysicsObject *>(pFriction->get_object()->client_data);
}

void CFrictionSnapshot::MarkContactForDelete() 
{ 
	IVP_Synapse_Friction *pFriction = m_pContactPoint->get_synapse(!m_synapseIndex);
	IVP_Real_Object *pObject = pFriction->get_object();
	Assert(pObject != m_pObject);
	if ( pObject != m_pObject )
	{
		if ( !m_pDeleteList )
		{
			m_pDeleteList = new CUtlVector<IVP_Real_Object *>;
		}
		m_pDeleteList->AddToTail( pObject );
	}
}

int CFrictionSnapshot::GetMaterial( int index )
{
	IVP_Material *ivpMats[2];

    m_pContactPoint->get_material_info(ivpMats);
	
	// index 1 is the other one
	index ^= m_synapseIndex;

	return physprops->GetIVPMaterialIndex( ivpMats[index] );
}

void CFrictionSnapshot::GetContactPoint( Vector &out )
{
	ConvertPositionToHL( *m_pContactPoint->get_contact_point_ws(), out ); 
}

void CFrictionSnapshot::GetSurfaceNormal( Vector &out )
{
	float sign[2] = {1,-1};
	IVP_U_Float_Point normal;
	IVP_Contact_Point_API::get_surface_normal_ws(const_cast<IVP_Contact_Point *>(m_pContactPoint), &normal );
	ConvertDirectionToHL( normal, out );
	out *= sign[m_synapseIndex];
	VectorNormalize(out);
}

float CFrictionSnapshot::GetFrictionCoefficient()
{
	return m_pContactPoint->get_friction_factor();
}

float CFrictionSnapshot::GetNormalForce()
{
	return ConvertDistanceToHL( IVP_Contact_Point_API::get_vert_force( m_pContactPoint ) );
}

float CFrictionSnapshot::GetEnergyAbsorbed()
{
	return ConvertEnergyToHL( IVP_Contact_Point_API::get_eliminated_energy( m_pContactPoint ) );
}

void CFrictionSnapshot::RecomputeFriction()
{
	m_pContactPoint->recompute_friction();
}

void CFrictionSnapshot::ClearFrictionForce()
{
	m_pContactPoint->set_friction_to_neutral();
}

void CFrictionSnapshot::NextFrictionData()
{
	SetFrictionSynapse( m_pFriction->get_next() );
}

IPhysicsFrictionSnapshot *CreateFrictionSnapshot( IVP_Real_Object *pObject )
{
	return new CFrictionSnapshot( pObject );
}

void DestroyFrictionSnapshot( IPhysicsFrictionSnapshot *pSnapshot )
{
	delete pSnapshot;
}


void DeleteAllFrictionPairs( IVP_Real_Object *pObject0, IVP_Real_Object *pObject1 )
{
	pObject0->unlink_contact_points_for_object( pObject1 );
}