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

#include "Mix/Scene/IMaterial.h"
#include "Mix/Private/Scene/Common/Manager.h"

namespace Mix{ namespace Scene{ namespace Common{

Model::Model( void ) :
m_bAttachUpdateMaterials( False )
{
}

Model::~Model( void )
{
	if( m_MaterialSlotList.size() > 0 )
	{
		Model::MaterialSlotList::iterator it_ms_begin = m_MaterialSlotList.begin();
		Model::MaterialSlotList::iterator it_ms_end = m_MaterialSlotList.end();
		Model::MaterialSlotList::iterator it_ms;

		for( it_ms = it_ms_begin; it_ms != it_ms_end; ++it_ms )
		{
			MIX_RELEASE( it_ms->pInterface );
		}
	}
}

UInt32 Model::GetMaterialSlotCount( void ) const
{
	return MIX_UIT_TO_UI32( m_MaterialSlotList.size() );
}

UInt32 Model::GetMaterialSlotIndex( const wchar_t* pName )
{
	MIX_ASSERT( MIX_STR_LENGTH( pName ) > 0 );

	Model::IndexMap::iterator it = m_MaterialSlotIndexMap.find( pName );
	if( it == m_MaterialSlotIndexMap.end() )
	{
		return 0xFFFFFFFF;
	}

	return it->second;
}

const wchar_t* Model::GetMaterialSlotName( UInt32 index ) const
{
	MIX_ASSERT( m_MaterialSlotList.size() > index );

	return m_MaterialSlotList[index].name.GetConstPtr();
}

Boolean Model::SetMaterialByIndex( UInt32 index, Mix::Scene::IMaterial* pMaterial )
{
	MIX_ASSERT( m_MaterialSlotList.size() > index );
	MIX_ASSERT( pMaterial != NULL );

	Model::MATERIAL_SLOT* pSlot = &( m_MaterialSlotList[index] );

	if( pSlot->pInterface != pMaterial )
	{
		////////////////////////////////////////////////////////////////////////////////////////////////////

		if( pSlot->pInterface->NeedsUpdate() == True )
		{
			if( m_bAttachUpdateMaterials == True )
			{
				Mix::Scene::Common::Manager* pSceneMgr = Mix::Scene::GetInternalManagerPtr();
				MIX_ASSERT( pSceneMgr != NULL );

				pSceneMgr->RemoveUpdateMaterial( pSlot->pInterface );
			}

			Mix::STL::Vector_FirstErase( m_UpdateMaterialPtrList, pSlot->pInterface );
		}

		MIX_RELEASE( pSlot->pInterface );

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

		MIX_ADD_REF( pMaterial );

		if( pMaterial->NeedsUpdate() == True )
		{
			MIX_ASSERT( std::find( m_UpdateMaterialPtrList.begin(), m_UpdateMaterialPtrList.end(), pMaterial ) == m_UpdateMaterialPtrList.end() );
			m_UpdateMaterialPtrList.push_back( pMaterial );

			if( m_bAttachUpdateMaterials == True )
			{
				Mix::Scene::Common::Manager* pSceneMgr = Mix::Scene::GetInternalManagerPtr();
				MIX_ASSERT( pSceneMgr != NULL );

				pSceneMgr->AddUpdateMaterial( pSlot->pInterface );
			}
		}

		pSlot->pInterface = pMaterial;
	}

	return True;
}

Boolean Model::SetMaterialByName( const wchar_t* pName, Mix::Scene::IMaterial* pInterface )
{
	return SetMaterialByIndex( GetMaterialSlotIndex( pName ), pInterface );
}

Boolean Model::GetMaterialByIndex( UInt32 index, Mix::Scene::IMaterial** ppInterface )
{
	MIX_ASSERT( ppInterface != NULL );

	if( m_MaterialSlotList.size() <= index )
	{
		return False;
	}

	Model::MATERIAL_SLOT* pSlot = &( m_MaterialSlotList[index] );

	MIX_ADD_REF( pSlot->pInterface );
	( *ppInterface ) = pSlot->pInterface;

	return True;
}

Boolean Model::GetMaterialByName( const wchar_t* pName, Mix::Scene::IMaterial** ppInterface )
{
	return GetMaterialByIndex( GetMaterialSlotIndex( pName ), ppInterface );
}

Mix::Scene::IMaterial* Model::GetMaterialPtr( UInt32 index ) const
{
	MIX_ASSERT( m_MaterialSlotList.size() > index );

	return m_MaterialSlotList[index].pInterface;
}

void Model::ReserveMaterialSlots( UIntT count )
{
	MIX_ASSERT( m_MaterialSlotList.size() == 0 );

	m_MaterialSlotList.reserve( count );
}

void Model::AddMaterialSlot( const wchar_t* pName, Mix::Scene::IMaterial* pMaterial )
{
	MIX_ASSERT( MIX_STR_LENGTH( pName ) > 0 );
	MIX_ASSERT( pMaterial != NULL );
	MIX_ASSERT( m_MaterialSlotIndexMap.find( pName ) == m_MaterialSlotIndexMap.end() );

	m_MaterialSlotIndexMap.insert( std::pair<Mix::StringW, UInt32>( pName, static_cast<UInt32>( m_MaterialSlotList.size() ) ) );
	m_MaterialSlotList.push_back( Model::MATERIAL_SLOT( pName, pMaterial ) );

	if( pMaterial->NeedsUpdate() == True )
	{
		MIX_ASSERT( std::find( m_UpdateMaterialPtrList.begin(), m_UpdateMaterialPtrList.end(), pMaterial ) == m_UpdateMaterialPtrList.end() );
		m_UpdateMaterialPtrList.push_back( pMaterial );
	}
}

Boolean Model::CloneMaterials( Model* pModel, Boolean bShared )
{
	MIX_ASSERT( pModel != NULL );
	MIX_ASSERT( pModel->m_MaterialSlotIndexMap.size() == 0 );
	MIX_ASSERT( pModel->m_MaterialSlotList.size() == 0 );

	Model::MaterialSlotList::iterator it_begin = m_MaterialSlotList.begin();
	Model::MaterialSlotList::iterator it_end = m_MaterialSlotList.end();
	Model::MaterialSlotList::iterator it;

	pModel->ReserveMaterialSlots( m_MaterialSlotList.size() );

	if( bShared == True )
	{
		for( it = it_begin; it != it_end; ++it )
		{
			const Model::MATERIAL_SLOT& slot = ( *it );

			MIX_ADD_REF( slot.pInterface );

			pModel->AddMaterialSlot( slot.name.GetConstPtr(), slot.pInterface );
		}
	}
	else
	{
		for( it = it_begin; it != it_end; ++it )
		{
			const Model::MATERIAL_SLOT& slot = ( *it );

			IMaterial* pMaterial = NULL;

			if( slot.pInterface->Clone( &pMaterial ) == False )
			{
				return False;
			}

			pModel->AddMaterialSlot( slot.name.GetConstPtr(), pMaterial );
		}
	}

	return True;
}

void Model::AttachUpdateMaterials( void )
{
	if( m_bAttachUpdateMaterials == False )
	{
		if( m_UpdateMaterialPtrList.size() > 0 )
		{
			Mix::Scene::Common::Manager* pSceneMgr = Mix::Scene::GetInternalManagerPtr();
			MIX_ASSERT( pSceneMgr != NULL );

			Model::MaterialPtrList::iterator it_mp_begin = m_UpdateMaterialPtrList.begin();
			Model::MaterialPtrList::iterator it_mp_end = m_UpdateMaterialPtrList.end();
			Model::MaterialPtrList::iterator it_mp;

			for( it_mp = it_mp_begin; it_mp != it_mp_end; ++it_mp )
			{
				pSceneMgr->AddUpdateMaterial( *it_mp );
			}
		}

		m_bAttachUpdateMaterials = True;
	}
}

void Model::DetachUpdateMaterials( void )
{
	if( m_bAttachUpdateMaterials == True )
	{
		if( m_UpdateMaterialPtrList.size() > 0 )
		{
			Mix::Scene::Common::Manager* pSceneMgr = Mix::Scene::GetInternalManagerPtr();
			MIX_ASSERT( pSceneMgr != NULL );

			Model::MaterialPtrList::iterator it_mp_begin = m_UpdateMaterialPtrList.begin();
			Model::MaterialPtrList::iterator it_mp_end = m_UpdateMaterialPtrList.end();
			Model::MaterialPtrList::iterator it_mp;

			for( it_mp = it_mp_begin; it_mp != it_mp_end; ++it_mp )
			{
				pSceneMgr->RemoveUpdateMaterial( *it_mp );
			}
		}

		m_bAttachUpdateMaterials = False;
	}
}

}}}
