#include "Mix/Tool/Win32/Core/Dynamics/Impl/StaticMesh.h"
#include "Mix/Tool/Win32/Core/Dynamics/Impl/World.h"

#include "btBulletCollisionCommon.h"
#include "btBulletDynamicsCommon.h"

namespace Mix{ namespace Tool{ namespace Win32{ namespace Dynamics{ namespace Impl{

const Mix::Tool::Win32::Dynamics::MATERIAL StaticMesh::DUMMY_MATERIAL( 0, 0, 0.5f, 0.0f );

////////////////////////////////////////////////////////////////////////////////////////////////////
// StaticMesh
////////////////////////////////////////////////////////////////////////////////////////////////////

StaticMesh::StaticMesh( void ) :
m_pWorld( NULL ),
m_pMesh( NULL ),
m_pShape( NULL ),
m_pMotionState( NULL ),
m_pRigidBody( NULL ),
m_pSerializer( NULL ),
m_Group( FG_STATIC ),
m_GroupMask( FG_ALL )
{
}

StaticMesh::~StaticMesh( void )
{
	Release();
}

void StaticMesh::SetWorldPtr( Mix::Tool::Win32::Dynamics::Impl::World* pWorld )
{
	m_pWorld = pWorld;
}

const Mix::Tool::Win32::Dynamics::MATERIAL* StaticMesh::GetMaterialPtrByPolygonIndex( unsigned int polygonIndex ) const
{
	if( m_PolygonMaterials.size() <= polygonIndex )
	{
		return &( StaticMesh::DUMMY_MATERIAL );
	}

	unsigned int materialIndex = m_PolygonMaterials[polygonIndex];

	if( m_Materials.size() <= materialIndex )
	{
		return &( StaticMesh::DUMMY_MATERIAL );
	}

	return &( m_Materials[materialIndex] );
}

btRigidBody* StaticMesh::Bullet_GetRigidBodyPtr( void )
{
	return m_pRigidBody;
}

void StaticMesh::BeginRefresh( void )
{
	if( m_pWorld != NULL )
	{
		m_pWorld->DetachCollisionObject( this, false );
	}
}

void StaticMesh::EndRefresh( void )
{
	if( m_pWorld != NULL )
	{
		m_pWorld->AttachCollisionObject( this, false );
	}
}

void StaticMesh::Release( void )
{
	MIX_DELETE( m_pSerializer );
	MIX_DELETE( m_pRigidBody );
	MIX_DELETE( m_pMotionState );
	MIX_DELETE( m_pShape );
	MIX_DELETE( m_pMesh );
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// IStaticMesh
////////////////////////////////////////////////////////////////////////////////////////////////////

bool StaticMesh::Load(	const void* vertices,
						unsigned int vertexStride,
						unsigned int vertexNum,
						const unsigned int* polygonIndices,
						const unsigned int* polygonMaterials,
						unsigned int polygonNum,
						const Mix::Tool::Win32::Dynamics::MATERIAL* materials,
						unsigned int materialNum,
						int capacity )
{
	if( ( vertices == NULL ) ||
		( vertexStride == 0 ) ||
		( vertexNum == 0 ) ||
		( polygonIndices == NULL ) ||
		( polygonNum == 0 ) ||
		( materialNum == 0 ) ||
		( capacity == 0 ) )
	{
		return false;
	}

	btIndexedMesh indexedMesh;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// リフレッシュ開始
	////////////////////////////////////////////////////////////////////////////////////////////////////

	BeginRefresh();

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// データの作成
	////////////////////////////////////////////////////////////////////////////////////////////////////

	indexedMesh.m_numTriangles = polygonNum;
	indexedMesh.m_triangleIndexBase = reinterpret_cast<const unsigned char*>( polygonIndices );
	indexedMesh.m_triangleIndexStride = sizeof( unsigned int[3] );
	indexedMesh.m_numVertices = static_cast<int>( vertexNum );
	indexedMesh.m_vertexBase = reinterpret_cast<const unsigned char*>( vertices );
	indexedMesh.m_vertexStride = static_cast<int>( vertexStride );
	indexedMesh.m_indexType = PHY_INTEGER;
	indexedMesh.m_vertexType = PHY_FLOAT;

	m_pMesh = new btTriangleMesh();
	if( m_pMesh != NULL )
	{
		m_pMesh->addIndexedMesh( indexedMesh );
	}
	else
	{
		Release();
		return false;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// モーションステートの作成
	////////////////////////////////////////////////////////////////////////////////////////////////////

	m_pMotionState = new btDefaultMotionState();
	if( m_pMotionState == NULL )
	{
		Release();
		return false;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// シェイプの作成
	////////////////////////////////////////////////////////////////////////////////////////////////////

	m_pShape = new btBvhTriangleMeshShape( m_pMesh, true, true );
	if( m_pShape != NULL )
	{
		m_pShape->setUserPointer( NULL );
	}
	else
	{
		Release();
		return false;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// リジッドボディの作成
	////////////////////////////////////////////////////////////////////////////////////////////////////

	m_pRigidBody = new btRigidBody( 0.0f, m_pMotionState, m_pShape );
	if( m_pRigidBody != NULL )
	{
		int collFlags = m_pRigidBody->getCollisionFlags();

		MIX_SET_BIT( collFlags, btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK );

		m_pRigidBody->setCollisionFlags( collFlags );
		m_pRigidBody->setUserPointer( this );
	}
	else
	{
		Release();
		return false;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ポリゴンマテリアルリスト
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( polygonMaterials != NULL )
	{
		m_PolygonMaterials.resize( polygonNum );
		::CopyMemory( &( m_PolygonMaterials[0] ), polygonMaterials, sizeof( unsigned int ) * polygonNum );
	}
	else
	{
		m_PolygonMaterials.resize( polygonNum, 0 );
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// マテリアルリスト
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( materials != NULL )
	{
		m_Materials.resize( materialNum );
		::CopyMemory( &( m_Materials[0] ), materials, sizeof( Mix::Tool::Win32::Dynamics::MATERIAL ) * materialNum );
	}
	else
	{
		m_Materials.resize( materialNum, Mix::Tool::Win32::Dynamics::MATERIAL() );
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// シリアライズ
	////////////////////////////////////////////////////////////////////////////////////////////////////

	m_pSerializer = new btDefaultSerializer( 1024 * 1024 * capacity );
	if( m_pSerializer == NULL )
	{
		Release();
		return false;
	}

	int ss = m_pShape->calculateSerializeBufferSize();

	m_pSerializer->startSerialization();

	m_pShape->serializeSingleShape( m_pSerializer );
	if( ( m_pSerializer->getBufferPointer() == NULL ) ||
		( m_pSerializer->getCurrentBufferSize() <= 0 ) )
	{
		Release();
		return false;
	}

	m_pSerializer->finishSerialization();

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// リフレッシュ終了
	////////////////////////////////////////////////////////////////////////////////////////////////////

	EndRefresh();

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

	return true;
}

unsigned int StaticMesh::GetShapeBufferSize( void ) const
{
	if( m_pSerializer == NULL )
	{
		return 0;
	}

	return static_cast<unsigned int>( m_pSerializer->getCurrentBufferSize() );
}

const void* StaticMesh::GetShapeBufferPtr( void ) const
{
	if( m_pSerializer == NULL )
	{
		return NULL;
	}

	return m_pSerializer->getBufferPointer();
}

unsigned int StaticMesh::GetPolygonNum( void ) const
{
	return m_PolygonMaterials.size();
}

unsigned int StaticMesh::GetPolygonMaterial( unsigned int polygonIndex ) const
{
	if( m_PolygonMaterials.size() <= polygonIndex )
	{
		return 0;
	}

	return m_PolygonMaterials[polygonIndex];
}

bool StaticMesh::SetPolygonMaterial( unsigned int polygonIndex, unsigned int materialIndex )
{
	if( m_PolygonMaterials.size() <= polygonIndex )
	{
		return false;
	}

	m_PolygonMaterials[polygonIndex] = materialIndex;

	return true;
}

const unsigned int* StaticMesh::GetPolygonMaterials( void ) const
{
	if( m_PolygonMaterials.size() == 0 )
	{
		return NULL;
	}

	return &( m_PolygonMaterials[0] );
}

unsigned int StaticMesh::GetMaterialNum( void ) const
{
	return m_Materials.size();
}

const Mix::Tool::Win32::Dynamics::MATERIAL* StaticMesh::GetMaterialPtr( unsigned int index ) const
{
	if( m_Materials.size() <= index )
	{
		return NULL;
	}

	return &( m_Materials[index] );
}

bool StaticMesh::SetMaterial( unsigned int index, const Mix::Tool::Win32::Dynamics::MATERIAL& material )
{
	if( m_Materials.size() <= index )
	{
		return false;
	}

	m_Materials[index] = material;

	return true;
}

const Mix::Tool::Win32::Dynamics::MATERIAL* StaticMesh::GetMaterials( void ) const
{
	if( m_Materials.size() == 0 )
	{
		return NULL;
	}

	return &( m_Materials[0] );
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// ICollisionObject
////////////////////////////////////////////////////////////////////////////////////////////////////

short StaticMesh::GetGroup( void ) const
{
	return m_Group;
}

void StaticMesh::SetGroup( short data )
{
	BeginRefresh();
	m_Group = data;
	EndRefresh();
}

short StaticMesh::GetGroupMask( void ) const
{
	return m_GroupMask;
}

void StaticMesh::SetGroupMask( short data )
{
	BeginRefresh();
	m_GroupMask = data;
	EndRefresh();
}

D3DXQUATERNION StaticMesh::GetWorldRotation( void ) const
{
	D3DXQUATERNION ret;

	if( m_pRigidBody != NULL )
	{
		btQuaternion rot = m_pRigidBody->getWorldTransform().getRotation();

		ret.x = rot.x();
		ret.y = rot.y();
		ret.z = rot.z();
		ret.w = rot.w();
	}
	else
	{
		D3DXQuaternionIdentity( &ret );
	}

	return ret;
}

void StaticMesh::SetWorldRotation( const D3DXQUATERNION& rot )
{
	if( m_pRigidBody != NULL )
	{
		btTransform worldTransform = m_pRigidBody->getWorldTransform();

		worldTransform.setRotation( btQuaternion( rot.x, rot.y, rot.z, rot.w ) );

		m_pRigidBody->setWorldTransform( worldTransform );
	}
}

D3DXVECTOR3 StaticMesh::GetWorldPosition( void ) const
{
	D3DXVECTOR3 ret;

	if( m_pRigidBody != NULL )
	{
		const btVector3& pos = m_pRigidBody->getWorldTransform().getOrigin();

		ret.x = pos.x();
		ret.y = pos.y();
		ret.z = pos.z();
	}
	else
	{
		ret.x = 0.0f;
		ret.y = 0.0f;
		ret.z = 0.0f;
	}

	return ret;
}

void StaticMesh::SetWorldPosition( const D3DXVECTOR3& pos )
{
	if( m_pRigidBody != NULL )
	{
		btTransform worldTransform = m_pRigidBody->getWorldTransform();

		worldTransform.setOrigin( btVector3( pos.x, pos.y, pos.z ) );

		m_pRigidBody->setWorldTransform( worldTransform );
	}
}

D3DXMATRIX StaticMesh::GetWorldMatrix( void ) const
{
	D3DXQUATERNION rot = GetWorldRotation();
	D3DXVECTOR3 pos = GetWorldPosition();
	D3DXMATRIX mat;

	D3DXMatrixRotationQuaternion( &mat, &rot );
	
	mat._41 = pos.x;
	mat._42 = pos.y;
	mat._43 = pos.z;
	mat._44 = 1.0f;

	return mat;
}

void StaticMesh::SetWorldMatrix( const D3DXMATRIX& mat )
{
	D3DXQUATERNION rot;
	D3DXVECTOR3 pos;

	Mix::Tool::Win32::Matrix_Decompose( mat, NULL, &rot, &pos );

	SetWorldTransform( rot, pos );
}

void StaticMesh::SetWorldTransform( const D3DXQUATERNION& rot, const D3DXVECTOR3& pos )
{
	if( m_pRigidBody != NULL )
	{
		btTransform worldTransform = m_pRigidBody->getWorldTransform();

		worldTransform.setRotation( btQuaternion( rot.x, rot.y, rot.z, rot.w ) );
		worldTransform.setOrigin( btVector3( pos.x, pos.y, pos.z ) );

		m_pRigidBody->setWorldTransform( worldTransform );
	}
}

bool StaticMesh::IsInWorld( void ) const
{
	return ( m_pWorld != NULL );
}

void StaticMesh::Draw( Mix::Tool::Win32::Graphics::LineHelper* pLineHelper, const D3DXVECTOR4& color )
{
}

}}}}}
