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

#include "Mix/Memory.h"
#include "Mix/IO/IReader.h"
#include "Mix/Graphics/IDevice.h"
#include "Mix/Graphics/IVertexBuffer.h"
#include "Mix/Graphics/IIndexBuffer.h"

#include "Mix/Private/Scene/Common/ActorModel.h"
#include "Mix/Private/Scene/Common/ActorNode.h"

namespace Mix{ namespace Scene{ namespace Common{

const wchar_t* ActorMesh::CREATE_FAILED = L"アクターメッシュの作成に失敗";

ActorMesh* ActorMesh::CreateInstance( void )
{
	return MIX_LIB_NEW_T( Mix::Memory::SECTION_SCENE, ActorMesh );
}

ActorMesh* ActorMesh::CreateInstance( Mix::Graphics::IDevice* pDevice, Mix::IO::IReader* pReader, const wchar_t* pName )
{
	MIX_ASSERT( pDevice != NULL );
	MIX_ASSERT( pReader != NULL );
	MIX_ASSERT( MIX_STR_LENGTH( pName ) > 0 );

	ActorMesh* pMesh = MIX_LIB_NEW_T( Mix::Memory::SECTION_SCENE, ActorMesh );
	if( pMesh == NULL )
	{
		MIX_LOG_ERROR( L"%s : %s : Name[%s]", CREATE_FAILED, Mix::STR_OUTOFMEMORY, pName );
		return NULL;
	}

	MM_DESC desc;
	UInt32 vertexBuffSize;
	UInt32 indexBuffSize;
	Mix::Graphics::INDEX_FORMAT indexFmt;
	void* pTemp;
	Mix::String name;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ファイルヘッダ
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( pReader->Read( &desc, sizeof( desc ) ) != sizeof( desc ) )
	{
		MIX_LOG_ERROR( L"%s : %s : Name[%s]", CREATE_FAILED, Mix::STR_ILLEGALFORMAT, pName );
		MIX_LIB_DELETE_T( ActorMesh, pMesh );
		return NULL;
	}

	if( ( desc.vertexStride == 0 ) ||
		( desc.vertexCount == 0 ) ||
		( ( desc.indexStride != 2 ) && ( desc.indexStride != 4 ) ) ||
		( desc.indexCount == 0 ) )
	{
		MIX_LOG_ERROR( L"%s : %s : Name[%s]", CREATE_FAILED, Mix::STR_ILLEGALFORMAT, pName );
		MIX_LIB_DELETE_T( ActorMesh, pMesh );
		return NULL;
	}

	vertexBuffSize = desc.vertexStride * desc.vertexCount;
	indexBuffSize = desc.indexStride * desc.indexCount;
	indexFmt = ( desc.indexStride == 2 )? Mix::Graphics::INDEX_USHORT : Mix::Graphics::INDEX_UINT;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ボーン
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( desc.boneNum > 0 )
	{
		UInt32 readSize = sizeof( ActorMesh::MM_BONE ) * desc.boneNum;
		Mix::STL::Vector<Mix::Memory::SECTION_SCENE, ActorMesh::MM_BONE> boneList;
		ActorMesh::MM_BONE* pSrcBone;
		ActorMesh::MM_BONE* pSrcBoneEnd;

		boneList.resize( desc.boneNum );
		if( pReader->Read( &( boneList[0] ), readSize ) != readSize )
		{
			MIX_LOG_ERROR( L"%s : %s : Name[%s]", CREATE_FAILED, Mix::STR_ILLEGALFORMAT, pName );
			MIX_LIB_DELETE_T( ActorMesh, pMesh );
			return NULL;
		}

		pMesh->m_BoneList.reserve( desc.boneNum );

		pSrcBone = &( boneList[0] );
		pSrcBoneEnd = pSrcBone + boneList.size();
		while( pSrcBone != pSrcBoneEnd )
		{
			ActorMesh::BONE bone;

			bone.linkNodeName = pSrcBone->linkNodeName;
			bone.pLinkNodePtr = NULL;
			bone.pLinkNodeWorldMat = NULL;
			bone.offsetMat = pSrcBone->offsetMat;
			bone.worldMat = bone.offsetMat;

			pMesh->m_BoneList.push_back( bone );

			pSrcBone++;
		}
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ボーン行列
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( desc.boneMatNum > 0 )
	{
		UInt32 readSize = sizeof( UInt32 ) * desc.boneMatNum;

		pMesh->m_BoneMatTable.resize( desc.boneMatNum );

		if( pReader->Read( &( pMesh->m_BoneMatTable[0] ), readSize ) != readSize )
		{
			MIX_LOG_ERROR( L"%s : %s : Name[%s]", CREATE_FAILED, Mix::STR_ILLEGALFORMAT, pName );
			MIX_LIB_DELETE_T( ActorMesh, pMesh );
			return NULL;
		}

		pMesh->m_WorldMatrixList.resize( desc.boneMatNum );
	}
	else
	{
		pMesh->m_WorldMatrixList.resize( 1 );
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ビューボリューム
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( desc.viewVolumeNum > 0 )
	{
		UInt32 readSize = sizeof( ActorMesh::VIEW_VOLUME ) * desc.viewVolumeNum;

		pMesh->m_ViewVolumeList.resize( desc.viewVolumeNum );

		if( pReader->Read( &( pMesh->m_ViewVolumeList[0] ), readSize ) != readSize )
		{
			MIX_LOG_ERROR( L"%s : %s : Name[%s]", CREATE_FAILED, Mix::STR_ILLEGALFORMAT, pName );
			MIX_LIB_DELETE_T( ActorMesh, pMesh );
			return NULL;
		}
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 描画テーブル
	////////////////////////////////////////////////////////////////////////////////////////////////////

	pMesh->m_DrawTable.reserve( desc.drawNum );

	for( UInt32 i = 0; i < desc.drawNum; i++ )
	{
		ActorMesh::MM_DRAW_DESC drawDesc;
		ActorMesh::DRAW* pDraw = NULL;

		if( pReader->Read( &drawDesc, sizeof( drawDesc ) ) != sizeof( drawDesc ) )
		{
			MIX_LOG_ERROR( L"%s : %s : Name[%s]", CREATE_FAILED, Mix::STR_ILLEGALFORMAT, pName );
			MIX_LIB_DELETE_T( ActorMesh, pMesh );
			return NULL;
		}

		pMesh->m_DrawTable.push_back( ActorMesh::DRAW() );
		pDraw = &( pMesh->m_DrawTable.back() );

		pDraw->materialSlotIndex = drawDesc.slotIndex;

		//不透明
		if( drawDesc.opSubsetNum > 0 )
		{
			UInt32 readSize = sizeof( ActorMesh::OPACITY_SUBSET ) * drawDesc.opSubsetNum;

			pDraw->opSubsets.resize( drawDesc.opSubsetNum );

			if( pReader->Read( &( pDraw->opSubsets[0] ), readSize ) != readSize )
			{
				MIX_LOG_ERROR( L"%s : %s : Name[%s]", CREATE_FAILED, Mix::STR_ILLEGALFORMAT, pName );
				MIX_LIB_DELETE_T( ActorMesh, pMesh );
				return NULL;
			}
		}

		//半透明
		for( UInt32 j = 0; j < Mix::Scene::MATERIAL_TRANSPARENCY_MAX; j++ )
		{
			UInt32 subsetNum = drawDesc.trSubsetNum[j];

			if( subsetNum == 0 )
			{
				continue;
			}

			UInt32 readSize = sizeof( ActorMesh::TRANSPARENCY_SUBSET ) * subsetNum;

			pDraw->trSubsets[j].resize( subsetNum );

			if( pReader->Read( &( pDraw->trSubsets[j][0] ), readSize ) != readSize )
			{
				MIX_LOG_ERROR( L"%s : %s : Name[%s]", CREATE_FAILED, Mix::STR_ILLEGALFORMAT, pName );
				MIX_LIB_DELETE_T( ActorMesh, pMesh );
				return NULL;
			}
		}
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// バーテックスバッファ
	////////////////////////////////////////////////////////////////////////////////////////////////////

	pTemp = MIX_LIB_MALLOC( Mix::Memory::SECTION_SCENE, vertexBuffSize );
	if( pTemp == NULL )
	{
		MIX_LOG_ERROR( L"%s : %s : Name[%s]", CREATE_FAILED, Mix::STR_OUTOFMEMORY, pName );
		MIX_LIB_FREE( pTemp );
		return NULL;
	}

	if( pReader->Read( pTemp, vertexBuffSize ) != vertexBuffSize )
	{
		MIX_LOG_ERROR( L"%s : %s : Name[%s]", CREATE_FAILED, Mix::STR_ILLEGALFORMAT, pName );
		MIX_LIB_FREE( pTemp );
		return NULL;
	}

	name.Sprintf( L"%s\\VertexBuffer", pName );
	if( pDevice->CreateVertexBuffer(	desc.vertexCount, desc.vertexStride,
										False, 0,
										pTemp,
										&( pMesh->m_pVertexBuffer ),
										name.GetConstPtr() ) == False )
	{
		MIX_LIB_FREE( pTemp );
		return NULL;
	}

	MIX_LIB_FREE( pTemp );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// インデックスバッファ
	////////////////////////////////////////////////////////////////////////////////////////////////////

	pTemp = MIX_LIB_MALLOC( Mix::Memory::SECTION_SCENE, indexBuffSize );
	if( pTemp == NULL )
	{
		MIX_LOG_ERROR( L"%s : %s : Name[%s]", CREATE_FAILED, Mix::STR_OUTOFMEMORY, pName );
		MIX_LIB_FREE( pTemp );
		return NULL;
	}

	if( pReader->Read( pTemp, indexBuffSize ) != indexBuffSize )
	{
		MIX_LOG_ERROR( L"%s : %s : Name[%s]", CREATE_FAILED, Mix::STR_ILLEGALFORMAT, pName );
		MIX_LIB_FREE( pTemp );
		return NULL;
	}

	name.Sprintf( L"%s\\IndexBuffer", pName );
	if( pDevice->CreateIndexBuffer(	indexFmt, desc.indexCount,
									False, 0,
									pTemp,
									&( pMesh->m_pIndexBuffer ),
									name.GetConstPtr() ) == False )
	{
		MIX_LIB_FREE( pTemp );
		return NULL;
	}

	MIX_LIB_FREE( pTemp );

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

	return pMesh;
}

ActorMesh::ActorMesh( void ) :
m_bDraw( True ),
m_pWorldMat( NULL )
{
}

ActorMesh::~ActorMesh( void )
{
}

Boolean ActorMesh::IsDraw( void ) const
{
	return m_bDraw;
}

void ActorMesh::SetDraw( Boolean state )
{
	m_bDraw = state;
}

void ActorMesh::Link( const Mix::Matrix4x4* pWorldMat )
{
	MIX_ASSERT( pWorldMat != NULL );

	m_pWorldMat = pWorldMat;
}

void ActorMesh::Finalize( Mix::Scene::Common::ActorModel* pActorModel )
{
	MIX_ASSERT( pActorModel != NULL );

	ActorMesh::BONE* pBone = ( m_BoneList.size() > 0 )? &( m_BoneList[0] ) : NULL;
	ActorMesh::BONE* pBoneEnd = pBone + m_BoneList.size();

	while( pBone != pBoneEnd )
	{
		pBone->pLinkNodePtr = pActorModel->GetNodePtr( pBone->linkNodeName.GetConstPtr() );
		pBone->pLinkNodeWorldMat = pBone->pLinkNodePtr->GetWorldMatrixPtr();
		MIX_ASSERT( pBone->pLinkNodePtr != NULL );

		pBone++;
	}
}

void ActorMesh::Update( void )
{
	MIX_ASSERT( m_pWorldMat != NULL );

	UIntT boneCount = m_BoneList.size();

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ワールド行列を更新
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( boneCount == 0 )
	{
		m_WorldMatrixList[0] = *m_pWorldMat;
	}
	else
	{
		const ActorMesh::BONE* boneList = &( m_BoneList[0] );

		ActorMesh::BONE* pBone = &( m_BoneList[0] );
		ActorMesh::BONE* pBoneEnd = pBone + boneCount;

		const UInt32* pBoneIndex = &( m_BoneMatTable[0] );
		const UInt32* pBoneIndexEnd = pBoneIndex + m_BoneMatTable.size();

		Mix::Matrix4x4* pWorldMat = &( m_WorldMatrixList[0] );

		while( pBone != pBoneEnd )
		{
//			pBone->worldMat = pBone->offsetMat * pBone->pLinkNodePtr->GetWorldMatrix();
			pBone->worldMat = pBone->offsetMat * ( *pBone->pLinkNodeWorldMat );
			pBone++;
		}

		while( pBoneIndex != pBoneIndexEnd )
		{
			*pWorldMat++ = boneList[*pBoneIndex++].worldMat;
		}
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ビューボリュームの更新
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( boneCount > 0 )
	{
		const ActorMesh::VIEW_VOLUME* pViewVolume = &( m_ViewVolumeList[0] );
		const ActorMesh::BONE* pBone = &( m_BoneList[0] );
		const ActorMesh::BONE* pBoneEnd = pBone + boneCount;
		const Mix::Matrix4x4& rootBoneMat = pBone->worldMat;

		Mix::Geometry::Sphere boneBounds;

		m_Bounds.center = rootBoneMat * pViewVolume->pos;
		m_Bounds.radius = rootBoneMat.TransformSR( pViewVolume->vec ).GetLength();

		pViewVolume++;
		pBone++;

		while( pBone != pBoneEnd )
		{
			const Mix::Matrix4x4& boneMat = pBone->worldMat;

			boneBounds.center = boneMat * pViewVolume->pos;
			boneBounds.radius = Mix::SqrtF( boneMat.TransformSR( pViewVolume->vec ).GetLengthSqr() );
//			boneBounds.radius = boneMat.TransformSR( pViewVolume->vec ).GetLength();

			m_Bounds += boneBounds;

			pViewVolume++;
			pBone++;
		}
	}
	else
	{
		const ActorMesh::VIEW_VOLUME& viewVolume = m_ViewVolumeList[0];

		m_Bounds.center = ( *m_pWorldMat ) * viewVolume.pos;
		m_Bounds.radius = Mix::SqrtF( m_pWorldMat->TransformSR( viewVolume.vec ).GetLength() );
//		m_Bounds.radius = worldMat.TransformSR( viewVolume.vec ).GetLength();
	}
}

const Mix::Geometry::Sphere& ActorMesh::GetBounds( void ) const
{
	return m_Bounds;
}

UInt32 ActorMesh::GetBoneCount( void ) const
{
	return MIX_UIT_TO_UI32( m_BoneList.size() );
}

const ActorMesh::WorldMatrixList& ActorMesh::GetWorldMatrixList( void ) const
{
	return m_WorldMatrixList;
}

const ActorMesh::DrawTable& ActorMesh::GetDrawTable( void ) const
{
	return m_DrawTable;
}

Boolean ActorMesh::Clone( ActorMesh** ppMesh )
{
	MIX_ASSERT( ppMesh != NULL );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// インターフェースの作成
	////////////////////////////////////////////////////////////////////////////////////////////////////

	ActorMesh* pMesh = ActorMesh::CreateInstance();
	if( pMesh == NULL )
	{
		return False;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ボーンリストを複製
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( m_BoneList.size() > 0 )
	{
		ActorMesh::BoneList::iterator it_begin = m_BoneList.begin();
		ActorMesh::BoneList::iterator it_end = m_BoneList.end();
		ActorMesh::BoneList::iterator it;

		pMesh->m_BoneList.reserve( m_BoneList.size() );

		for( it = it_begin; it != it_end; ++it )
		{
			const ActorMesh::BONE& srcBone = ( *it );
			ActorMesh::BONE dstBone;

			dstBone.linkNodeName = srcBone.linkNodeName;
			dstBone.pLinkNodePtr = NULL;
			dstBone.pLinkNodeWorldMat = NULL;
			dstBone.offsetMat = srcBone.offsetMat;
			dstBone.worldMat = srcBone.offsetMat;

			pMesh->m_BoneList.push_back( dstBone );
		}
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ボーン行列リストを作成
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( m_BoneMatTable.size() > 0 )
	{
		pMesh->m_BoneMatTable = m_BoneMatTable;
		pMesh->m_WorldMatrixList.resize( m_BoneMatTable.size() );
	}
	else
	{
		pMesh->m_WorldMatrixList.resize( 1 );
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ビューボリュームを複製
	////////////////////////////////////////////////////////////////////////////////////////////////////

	pMesh->m_ViewVolumeList = m_ViewVolumeList;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 描画テーブルを複製
	////////////////////////////////////////////////////////////////////////////////////////////////////

	pMesh->m_DrawTable = m_DrawTable;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// バーテックスバッファを共有
	////////////////////////////////////////////////////////////////////////////////////////////////////

	MIX_ADD_REF( m_pVertexBuffer );
	pMesh->m_pVertexBuffer = m_pVertexBuffer;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// インデックスバッファを共有
	////////////////////////////////////////////////////////////////////////////////////////////////////

	MIX_ADD_REF( m_pIndexBuffer );
	pMesh->m_pIndexBuffer = m_pIndexBuffer;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 譲渡
	////////////////////////////////////////////////////////////////////////////////////////////////////

	( *ppMesh ) = pMesh;

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

	return True;
}

void ActorMesh::Dispose( void )
{
	ActorMesh::BoneList::iterator it_begin = m_BoneList.begin();
	ActorMesh::BoneList::iterator it_end = m_BoneList.end();
	ActorMesh::BoneList::iterator it;

	for( it = it_begin; it != it_end; ++it )
	{
		it->pLinkNodePtr = NULL;
		it->pLinkNodeWorldMat = NULL;
	}

	m_pWorldMat = NULL;
}

}}}
