#include "Mix/Private/Dynamics/ObjectDragger.h"

#include "Mix/Dynamics/IWorld.h"
#include "Mix/Dynamics/IObject.h"
#include "Mix/Dynamics/IRigidBody.h"
#include "Mix/Dynamics/IObjectListener.h"
#include "Mix/Private/Dynamics/PointJoint.h"

namespace Mix{ namespace Dynamics{

////////////////////////////////////////////////////////////////////////////////////////////////////
// ObjectDragger::Context
////////////////////////////////////////////////////////////////////////////////////////////////////

ObjectDragger::Context::Context( Mix::Dynamics::IWorld* pWorld ) :
m_pWorld( NULL ),
m_pJoint( NULL ),
m_pObject( NULL ),
m_Dist( 0.0f )
{
	MIX_ASSERT( pWorld != NULL );

	MIX_ADD_REF( pWorld );
	m_pWorld = pWorld;
}

ObjectDragger::Context::~Context( void )
{
	Free();

	MIX_RELEASE( m_pWorld );
}

Boolean ObjectDragger::Context::CatchStart(	const Mix::Vector3& eyePos,
											const Mix::Vector3& rayFrom,
											const Mix::Vector3& rayTo, 
											UInt16 filter )
{
	MIX_ASSERT( m_pObject == NULL );
	MIX_ASSERT( m_pJoint == NULL );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ワールドとレイのテストを行う
	////////////////////////////////////////////////////////////////////////////////////////////////////

	Mix::Dynamics::IWorld::TEST_RESULT result;

	if( m_pWorld->TestRay( rayFrom, rayTo, filter, filter, NULL, result ) == True )
	{
		if( result.pObject->GetType() == Mix::Dynamics::IObject::RIGIDBODY )
		{
			Mix::Dynamics::IRigidBody* pRigidBody = static_cast<Mix::Dynamics::IRigidBody*>( result.pObject );

			if( pRigidBody->GetStatus() == Mix::Dynamics::IRigidBody::DEFAULT )
			{
				Mix::Vector3 pivotA = pRigidBody->GetWorldMatrix().ToInverse() * result.worldPos;

				m_pJoint = Mix::Dynamics::PointJoint::CreateInstance( pRigidBody, pivotA );
				if( m_pJoint != NULL )
				{
					if( m_pJoint->Initialize( L"ObjectDragger" ) == True )
					{
						m_pJoint->SetPivotB( result.worldPos );
						m_pWorld->AddJoint( m_pJoint );

						m_pObject = pRigidBody;
						m_pObject->AddListener( this );
						m_pObject->Activate();

						m_Dist = ( result.worldPos - eyePos ).GetLength();
					}
					else
					{
						return False;
					}
				}
				else
				{
					return False;
				}
			}
			else
			{
				return False;
			}
		}
	}
	else
	{
		return False;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////

	return True;
}

void ObjectDragger::Context::CatchContinue( const Mix::Vector3& eyePos, const Mix::Vector3& rayTo )
{
	if( m_pObject == NULL )
	{
		return;
	}

	MIX_ASSERT( m_pWorld != NULL );
	MIX_ASSERT( m_pJoint != NULL );

	Mix::Vector3 dir;
	Mix::Vector3 pivotB;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 新規ピボットＢを求める
	////////////////////////////////////////////////////////////////////////////////////////////////////

	dir = ( rayTo - eyePos ).ToNormalize() * m_Dist;
	pivotB = ( eyePos + dir );

	m_pJoint->SetPivotB( pivotB );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// オブジェクトを起こす
	////////////////////////////////////////////////////////////////////////////////////////////////////

	m_pObject->Activate();
}

void ObjectDragger::Context::Free( void )
{
	if( m_pJoint != NULL )
	{
		m_pWorld->RemoveJoint( m_pJoint );
		MIX_RELEASE( m_pJoint );
	}

	if( m_pObject != NULL )
	{
		m_pObject->RemoveListener( this );
		m_pObject = NULL;
	}
}

Mix::Dynamics::IWorld* ObjectDragger::Context::GetWorldPtr( void )
{
	return m_pWorld;
}

Mix::Dynamics::IObject* ObjectDragger::Context::GetObjectPtr( void )
{
	return m_pObject;
}

void ObjectDragger::Context::OnAddedToWorld( Mix::Dynamics::IObject* pObject, Mix::Dynamics::IWorld* pWorld )
{
}

void ObjectDragger::Context::OnRemovedFromWorld( Mix::Dynamics::IObject* pObject, Mix::Dynamics::IWorld* pWorld )
{
	Free();
}

void ObjectDragger::Context::OnContact( Mix::Dynamics::IObject* pObject, const Mix::Dynamics::MANIFOLD& manifold )
{
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// ObjectDragger
////////////////////////////////////////////////////////////////////////////////////////////////////

ObjectDragger* ObjectDragger::CreateInstance( Mix::Dynamics::IWorld* pWorld )
{
	return MIX_LIB_NEW_T( Mix::Memory::SECTION_DYNAMICS, ObjectDragger, pWorld );
}

ObjectDragger::ObjectDragger( Mix::Dynamics::IWorld* pWorld ) :
m_CatchFilter( 0xFFFF ),
m_pContext( MIX_LIB_NEW_T( Mix::Memory::SECTION_DYNAMICS, ObjectDragger::Context, pWorld ) )
{
	MIX_ASSERT( m_pContext != NULL );
}

ObjectDragger::~ObjectDragger( void )
{
	if( m_pContext != NULL )
	{
		//コンテキストがオブジェクトに対してリスナーとして渡しているため、
		//解放を行ってしまうと、余計に参照カウンタがデクリメントされるので、
		//先にオブジェクトからコンテキストを外した後で解放する
		m_pContext->Free();
		MIX_RELEASE( m_pContext );
	}
}

void ObjectDragger::GetWorld( Mix::Dynamics::IWorld** ppWorld )
{
	Mix::Dynamics::IWorld* pWorld = m_pContext->GetWorldPtr();

	MIX_ADD_REF( pWorld );
	( *ppWorld ) = pWorld;
}

UInt16 ObjectDragger::GetCatchFilter( void ) const
{
	return m_CatchFilter;
}

void ObjectDragger::SetCatchFilter( UInt16 filter )
{
	m_CatchFilter = filter;
}

Boolean ObjectDragger::IsCatch( void ) const
{
	return ( m_pContext->GetObjectPtr() != NULL );
}

Boolean ObjectDragger::GetCatchObject( Mix::Dynamics::IObject** ppObject )
{
	Mix::Dynamics::IObject* pObject = m_pContext->GetObjectPtr();

	if( pObject != NULL )
	{
		MIX_ADD_REF( pObject );
		( *ppObject ) = pObject;
	}
	else
	{
		return False;
	}

	return True;
}

Boolean ObjectDragger::CatchStart( const Mix::Vector3& eyePos, const Mix::Vector3& rayFrom, const Mix::Vector3& rayTo )
{
	Free();

	return m_pContext->CatchStart( eyePos, rayFrom, rayTo, m_CatchFilter );
}

void ObjectDragger::CatchContinue( const Mix::Vector3& eyePos, const Mix::Vector3& rayTo )
{
	m_pContext->CatchContinue( eyePos, rayTo );
}

void ObjectDragger::Free( void )
{
	m_pContext->Free();
}

}}
