#include "Mix/Private/Scene/Common/DynamicsObject.h"

#include "Mix/Dynamics/IWorld.h"
#include "Mix/Dynamics/IObject.h"
#include "Mix/Dynamics/IObjectListener.h"
#include "Mix/Scene/IContactListener.h"

namespace Mix{ namespace Scene{ namespace Common{

DynamicsObject::DynamicsObject( void ) :
m_pWorld( NULL ),
m_pObjectListener( NULL ),
m_bEnabled( True ),
m_UserIndex( 0 ),
m_UserPtr( NULL )
{
	m_ObjectWrapper.pObject = NULL;
	m_ObjectWrapper.pInternalObject = NULL;
}

DynamicsObject::~DynamicsObject( void )
{
	MIX_ASSERT( m_pWorld == NULL );
	MIX_ASSERT( m_pObjectListener == NULL );

	if( m_ContactListenerList.size() > 0 )
	{
		DynamicsObject::ContactListenerList::iterator it_begin = m_ContactListenerList.begin();
		DynamicsObject::ContactListenerList::iterator it_end = m_ContactListenerList.end();
		DynamicsObject::ContactListenerList::iterator it;

		for( it = it_begin; it != it_end; ++it )
		{
			MIX_RELEASE( ( *it ) );
		}
	}
}

void DynamicsObject::Initialize( Mix::Scene::IDynamicsObject* pDynamicsObject )
{
	MIX_ASSERT( pDynamicsObject != NULL );

	Mix::Dynamics::IObject* pInternalObject = GetInternalObjectPtr();
	MIX_ASSERT( pInternalObject != NULL );
	MIX_ASSERT( pInternalObject->GetUserPtr() == NULL );

	m_ObjectWrapper.pObject = pDynamicsObject;
	m_ObjectWrapper.pInternalObject = this;

	pInternalObject->SetUserPtr( &m_ObjectWrapper );
}

Boolean DynamicsObject::IsEnabled( void ) const
{
	return m_bEnabled;
}

void DynamicsObject::SetEnabled( Boolean state )
{
	if( m_bEnabled != state )
	{
		if( m_pWorld != NULL )
		{
			Mix::Dynamics::IObject* pInternalObject = GetInternalObjectPtr();
			MIX_ASSERT( pInternalObject != NULL );

			if( state == True )
			{
				m_pWorld->AddObject( pInternalObject );
			}
			else
			{
				m_pWorld->RemoveObject( pInternalObject );
			}
		}

		m_bEnabled = state;
	}
}

Boolean DynamicsObject::HasContactListener( void ) const
{
	return ( m_ContactListenerList.size() > 0 );
}

Boolean DynamicsObject::ContainsContactListener( Mix::Scene::IContactListener* pListener ) const
{
	if( pListener == NULL )
	{
		return False;
	}

	return ( std::find( m_ContactListenerList.begin(), m_ContactListenerList.end(), pListener ) != m_ContactListenerList.end() );
}

Boolean DynamicsObject::AddContactListener( Mix::Scene::IContactListener* pListener )
{
	if( pListener == NULL )
	{
		return False;
	}

	if( std::find( m_ContactListenerList.begin(), m_ContactListenerList.end(), pListener ) == m_ContactListenerList.end() )
	{
		MIX_ADD_REF( pListener );
		m_ContactListenerList.push_back( pListener );

		if( ( m_pObjectListener != NULL ) &&
			( m_ContactListenerList.size() == 1 ) )
		{
			Mix::Dynamics::IObject* pInternalObject = GetInternalObjectPtr();
			MIX_ASSERT( pInternalObject != NULL );

			pInternalObject->AddListener( m_pObjectListener );
		}
	}
	else
	{
		return False;
	}

	return True;
}

Boolean DynamicsObject::RemoveContactListener( Mix::Scene::IContactListener* pListener )
{
	if( pListener == NULL )
	{
		return False;
	}

	DynamicsObject::ContactListenerList::iterator it = std::find( m_ContactListenerList.begin(), m_ContactListenerList.end(), pListener );

	if( it != m_ContactListenerList.end() )
	{
		Mix::Scene::IContactListener* pContactListener = ( *it );

		m_ContactListenerList.erase( it );
		MIX_RELEASE( pContactListener );

		if( ( m_pObjectListener != NULL ) &&
			( m_ContactListenerList.size() == 0 ) )
		{
			Mix::Dynamics::IObject* pInternalObject = GetInternalObjectPtr();
			MIX_ASSERT( pInternalObject != NULL );

			pInternalObject->RemoveListener( m_pObjectListener );
		}
	}
	else
	{
		return False;
	}

	return True;
}

void DynamicsObject::ClearContactListener( void )
{
	if( m_ContactListenerList.size() > 0 )
	{
		Mix::Dynamics::IObject* pInternalObject = GetInternalObjectPtr();
		MIX_ASSERT( pInternalObject != NULL );

		DynamicsObject::ContactListenerList::iterator it_begin = m_ContactListenerList.begin();
		DynamicsObject::ContactListenerList::iterator it_end = m_ContactListenerList.end();
		DynamicsObject::ContactListenerList::iterator it;

		for( it = it_begin; it != it_end; ++it )
		{
			MIX_RELEASE( ( *it ) );
		}

		m_ContactListenerList.clear();

		if( m_pObjectListener != NULL )
		{
			pInternalObject->RemoveListener( m_pObjectListener );
		}
	}
}

Int32 DynamicsObject::GetUserIndex( void ) const
{
	return m_UserIndex;
}

void DynamicsObject::SetUserIndex( Int32 index )
{
	m_UserIndex = index;
}

void* DynamicsObject::GetUserPtr( void ) const
{
	return m_UserPtr;
}

void DynamicsObject::SetUserPtr( void* ptr )
{
	m_UserPtr = ptr;
}

void DynamicsObject::Attach( Mix::Dynamics::IWorld* pWorld, Mix::Dynamics::IObjectListener* pObjectListener )
{
	MIX_ASSERT( pWorld != NULL );
	MIX_ASSERT( pObjectListener != NULL );
	MIX_ASSERT( m_pWorld == NULL );

	Mix::Dynamics::IObject* pInternalObject = GetInternalObjectPtr();
	MIX_ASSERT( pInternalObject != NULL );
	MIX_ASSERT( pInternalObject->GetUserPtr() == &m_ObjectWrapper );

	MIX_ADD_REF( pWorld );
	m_pWorld = pWorld;

	MIX_ADD_REF( pObjectListener );
	m_pObjectListener = pObjectListener;

	if( m_ContactListenerList.size() > 0 )
	{
		pInternalObject->AddListener( m_pObjectListener );
	}

	if( m_bEnabled == True )
	{
		m_pWorld->AddObject( pInternalObject );
	}
}

void DynamicsObject::Detach( Mix::Dynamics::IWorld* pWorld )
{
	MIX_ASSERT( pWorld != NULL );
	MIX_ASSERT( m_pWorld != NULL );

	Mix::Dynamics::IObject* pInternalObject = GetInternalObjectPtr();
	MIX_ASSERT( pInternalObject != NULL );

	m_pWorld->RemoveObject( pInternalObject );
	pInternalObject->RemoveListener( m_pObjectListener );

	MIX_RELEASE( m_pObjectListener );
	MIX_RELEASE( m_pWorld );
}

void DynamicsObject::NotifyContact( Mix::Scene::IDynamicsObject* pObjectA,
									Mix::Scene::IDynamicsObject* pObjectB,
									UInt32 pointCount,
									const Mix::Scene::DYNAMICS_CONTACT_POINT* points )
{
	DynamicsObject::ContactListenerList::iterator it_begin = m_ContactListenerList.begin();
	DynamicsObject::ContactListenerList::iterator it_end = m_ContactListenerList.end();
	DynamicsObject::ContactListenerList::iterator it;

	for( it = it_begin; it != it_end; ++it )
	{
		( *it )->OnContact( pObjectA, pObjectB, pointCount, points );
	}
}

}}}
