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

#include "Mix/Dynamics/IWorld.h"
#include "Mix/Dynamics/IStaticMesh.h"
#include "Mix/Graphics/IDevice.h"

#include "Mix/Private/Graphics/Common/Manager.h"
#include "Mix/Private/Scene/Common/TerrainMesh.h"
#include "Mix/Private/Scene/Common/TerrainNode.h"
#include "Mix/Private/Scene/Common/TerrainCollider.h"

namespace Mix{ namespace Scene{ namespace Common{

const wchar_t* TerrainModel::FAILED_CLONE = L"地形モデルの複製に失敗";

TerrainModel* TerrainModel::CreateInstance( const wchar_t* pName )
{
	return MIX_LIB_NEW_T( Mix::Memory::SECTION_SCENE, TerrainModel, pName );
}

TerrainModel::TerrainModel( const wchar_t* pName ) :
m_Name( pName ),
m_pCollider( NULL ),
m_UserIndex( 0 ),
m_pUserPtr( NULL ),
m_bDraw( True ),
m_bLocalLighting( True ),
m_TRPriority( 0 ),
m_DefWCResult( Mix::Scene::WCR_FRONT )
{
}

TerrainModel::~TerrainModel( void )
{
	for( TerrainModel::NodeList::iterator it = m_NodeList.begin(); it != m_NodeList.end(); ++it )
	{
		MIX_LIB_DELETE_T( TerrainNode, ( *it ) );
	}

	if( m_pCollider != NULL )
	{
		m_pCollider->Dispose();
		MIX_RELEASE( m_pCollider );
	}
}

void TerrainModel::Attach( void )
{
	RendererObject::SetRendering( True );
}

void TerrainModel::Detach( void )
{
	RendererObject::SetRendering( False );
}

const Mix::Matrix4x4& TerrainModel::GetWorldMatrix( void ) const
{
	return m_WorldMatrix;
}

Mix::Scene::Common::TerrainNode** TerrainModel::AllocateNodeList( size_t count )
{
	MIX_ASSERT( count > 0 );
	MIX_ASSERT( m_NodeList.size() == 0 );

	m_NodeList.reserve( count );

	for( size_t i = 0; i < count; i++ )
	{
		Mix::Scene::Common::TerrainNode* pNode = MIX_LIB_NEW_T( Mix::Memory::SECTION_SCENE, TerrainNode );
		if( pNode != NULL )
		{
			m_NodeList.push_back( pNode );
		}
		else
		{
			return NULL;
		}
	}

	return &( m_NodeList[0] );
}

Mix::Scene::Common::TerrainNode* TerrainModel::GetRootNodePtr( void ) const
{
	MIX_ASSERT( m_NodeList.size() > 0 );

	return m_NodeList[0];
}

Boolean TerrainModel::CreateCollider( Mix::Dynamics::IStaticMesh* pStaticMesh )
{
	MIX_ASSERT( pStaticMesh != NULL );
	MIX_ASSERT( m_pCollider == NULL );

	m_pCollider = Mix::Scene::Common::TerrainCollider::CreateInstance( this, pStaticMesh );
	if( m_pCollider == NULL )
	{
		return False;
	}

	return True;
}

void TerrainModel::UpdateNodeBounds( Mix::Scene::Common::TerrainNode* pNode )
{
	UIntT childCount = pNode->GetChildCount();
	UIntT i;

	pNode->UpdateBounds( m_WorldPos );

	for( i = 0; i < childCount; i++ )
	{
		UpdateNodeBounds( pNode->GetChildPtr( i ) );
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::Scene::Common::Model
////////////////////////////////////////////////////////////////////////////////////////////////////

void TerrainModel::AttachDynamics( Mix::Dynamics::IWorld* pWorld, Mix::Dynamics::IObjectListener* pObjectListener )
{
	MIX_ASSERT( pWorld != NULL );
	MIX_ASSERT( pObjectListener != NULL );

	if( m_pCollider != NULL )
	{
		m_pCollider->Attach( pWorld, pObjectListener );
	}
}

void TerrainModel::DetachDynamics( Mix::Dynamics::IWorld* pWorld )
{
	MIX_ASSERT( pWorld != NULL );

	if( m_pCollider != NULL )
	{
		m_pCollider->Detach( pWorld );
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::Scene::ITerrainModel
////////////////////////////////////////////////////////////////////////////////////////////////////

const wchar_t* TerrainModel::GetName( void ) const
{
	return m_Name.GetConstPtr();
}

void TerrainModel::SetWorldPosition( const Mix::Vector3& pos )
{
	m_WorldPos = pos;
	m_WorldMatrix.SetTranslation( m_WorldPos );

	if( m_pCollider != NULL )
	{
		MIX_ASSERT( m_pCollider->GetObjectPtr() != NULL );
		m_pCollider->GetObjectPtr()->SetWorldPosition( m_WorldPos );
	}

	UpdateNodeBounds( m_NodeList[0] );
}

const Mix::Vector3& TerrainModel::GetWorldPosition( void ) const
{
	return m_WorldPos;
}

Boolean TerrainModel::ExistsCollider( void ) const
{
	return ( m_pCollider != NULL );
}

Boolean TerrainModel::GetCollider( Mix::Scene::ITerrainCollider** ppCollider )
{
	if( m_pCollider != NULL )
	{
		MIX_ADD_REF( m_pCollider );
		( *ppCollider ) = m_pCollider;
	}
	else
	{
		return False;
	}

	return True;
}

Boolean TerrainModel::Clone( Mix::Scene::ITerrainModel** ppTerrainModel, UInt32 flags )
{
	if( ppTerrainModel == NULL )
	{
		MIX_LOG_ERROR( L"%s : %s : Name[%s] : ppTerrainModel[%s]", TerrainModel::FAILED_CLONE, Mix::STR_ILLEGALARG, m_Name.GetConstPtr(),
			MIX_LOG_PTR( ppTerrainModel ) );

		return False;
	}

	Boolean bSharedMaterial = ( MIX_TESTBIT( flags, ITerrainModel::C_SHARED_MATERIAL ) == ITerrainModel::C_SHARED_MATERIAL );

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

	TerrainModel* pTerrainModel = TerrainModel::CreateInstance( m_Name.GetConstPtr() );
	if( pTerrainModel == NULL )
	{
		MIX_LOG_ERROR( L"%s : %s : Name[%s]", TerrainModel::FAILED_CLONE, Mix::STR_OUTOFMEMORY, m_Name.GetConstPtr() );
		return False;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// エンティティの設定
	////////////////////////////////////////////////////////////////////////////////////////////////////

	pTerrainModel->SetDraw( IsDraw() );
	pTerrainModel->SetLocalLighting( IsLocalLighting() );
	pTerrainModel->SetTransparencyPriority( GetTransparencyPriority() );
	pTerrainModel->SetDefaultWaterContainsResult( GetDefaultWaterContainsResult() );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ノードを複製
	////////////////////////////////////////////////////////////////////////////////////////////////////

	TerrainModel::NodeList::iterator it_node_begin = m_NodeList.begin();
	TerrainModel::NodeList::iterator it_node_end = m_NodeList.end();
	TerrainModel::NodeList::iterator it_node;

	TerrainNode** srcNodexList = &( m_NodeList[0] );
	size_t nodeCount = m_NodeList.size();

	TerrainNode** dstNodeList = pTerrainModel->AllocateNodeList( nodeCount );
	if( dstNodeList == NULL )
	{
		MIX_LOG_ERROR( L"%s : %s : Name[%s]", TerrainModel::FAILED_CLONE, Mix::STR_OUTOFMEMORY, m_Name.GetConstPtr() );
		MIX_RELEASE( pTerrainModel );
		return False;
	}

	for( size_t i = 0; i < nodeCount; i++ )
	{
		TerrainNode* pSrcNode = srcNodexList[i];
		TerrainNode* pDstNode = dstNodeList[i];

		IntT parentIndex = pSrcNode->GetParentIndex();

		pDstNode->SetBounds( pSrcNode->GetDefaultBounds() );
		pDstNode->SetMesh( pSrcNode->GetMeshPtr() );
		pDstNode->SetParent( parentIndex, ( parentIndex >= 0 )? dstNodeList[parentIndex] : NULL );
		pDstNode->ReserveChildList( pSrcNode->GetChildCount() );
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// マテリアルを複製
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( Model::CloneMaterials( pTerrainModel, bSharedMaterial ) == False )
	{
		MIX_LOG_ERROR( L"%s : %s : Name[%s]", TerrainModel::FAILED_CLONE, Mix::STR_OUTOFMEMORY, m_Name.GetConstPtr() );
		MIX_RELEASE( pTerrainModel );
		return False;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ダイナミクス : スタティックメッシュを複製
	////////////////////////////////////////////////////////////////////////////////////////////////////

	MIX_ADD_REF( m_pCollider );
	pTerrainModel->m_pCollider = m_pCollider;

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

	( *ppTerrainModel ) = pTerrainModel;

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

	return True;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::Scene::IModel
////////////////////////////////////////////////////////////////////////////////////////////////////

UInt32 TerrainModel::GetMaterialSlotCount( void ) const
{
	return Model::GetMaterialSlotCount();
}

const wchar_t* TerrainModel::GetMaterialSlotName( UInt32 slotIndex ) const
{
	return Model::GetMaterialSlotName( slotIndex );
}

UInt32 TerrainModel::GetMaterialSlotIndex( const wchar_t* pSlotName )
{
	return Model::GetMaterialSlotIndex( pSlotName );
}

Boolean TerrainModel::GetMaterialByIndex( UInt32 slotIndex, Mix::Scene::IMaterial** ppMaterial )
{
	return Model::GetMaterialByIndex( slotIndex, ppMaterial );
}

Boolean TerrainModel::GetMaterialByName( const wchar_t* pSlotName, Mix::Scene::IMaterial** ppMaterial )
{
	return Model::GetMaterialByName( pSlotName, ppMaterial );
}

Boolean TerrainModel::SetMaterialByIndex( UInt32 slotIndex, Mix::Scene::IMaterial* pMaterial )
{
	return Model::SetMaterialByIndex( slotIndex, pMaterial );
}

Boolean TerrainModel::SetMaterialByName( const wchar_t* pSlotName, Mix::Scene::IMaterial* pMaterial )
{
	return Model::SetMaterialByName( pSlotName, pMaterial );
}

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

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

void* TerrainModel::GetUserPtr( void ) const
{
	return m_pUserPtr;
}

void TerrainModel::SetUserPtr( void* pData )
{
	m_pUserPtr = pData;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::Scene::IEntity
////////////////////////////////////////////////////////////////////////////////////////////////////

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

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

Boolean TerrainModel::IsLocalLighting( void ) const
{
	return m_bLocalLighting;
}

void TerrainModel::SetLocalLighting( Boolean state )
{
	m_bLocalLighting = state;
}

UInt32 TerrainModel::GetTransparencyPriority( void ) const
{
	return m_TRPriority;
}

void TerrainModel::SetTransparencyPriority( UInt32 priority )
{
	m_TRPriority = priority;
}

Mix::Scene::WATER_CONTAINS_RESULT TerrainModel::GetDefaultWaterContainsResult( void ) const
{
	return m_DefWCResult;
}

void TerrainModel::SetDefaultWaterContainsResult( Mix::Scene::WATER_CONTAINS_RESULT result )
{
	m_DefWCResult = result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::Scene::IRendererObject
////////////////////////////////////////////////////////////////////////////////////////////////////

Mix::Scene::IRendererObject::TYPE TerrainModel::GetType( void ) const
{
	return Mix::Scene::IRendererObject::TERRAIN_MODEL;
}

Boolean TerrainModel::IsRendering( void ) const
{
	return RendererObject::IsRendering();
}

}}}
