#include "StdAfx.h"
#include "ResourceManager.h"

#include "RenderSystem.h"
#include "Stream.h"
#include "EngineFile.h"
#include "ImageReader.h"
#include "CallbackStreamPAK.h"

cResourceManager* cResourceManager::mpSingleton = 0;
NiCriticalSection cResourceManager::mObjectListCriticalSection;
NiCriticalSection cResourceManager::mSoundListCriticalSection;
NiCriticalSection cResourceManager::mTexturePaletteCriticalSection;

cResourceManager::cResourceManager()
: mD3DTextureMap( 1024 )
, mShadowTexture( 0 )
{
	assert( mpSingleton == 0 && "bad singleton!" );
	mpSingleton = this;

	/// 초기값을 파일로 변경하면서 속도 측정
	mNodeArray.SetSize( 800 );
	mNodeArray.SetGrowBy( 50 );

	mModelArray.SetSize( 50 );
	mModelArray.SetGrowBy( 10 );

	/// 텍스처 팔레트를 생성
	mTexturePalette = NiNew cTexturePalette( 8192 );
	mMapTexturePalette = NiNew cTexturePalette( 8192 );

	///
	mpStreamPAK = NiNew cCallbackStreamPACK();
	mpStreamPAK->SetTexturePalette( mTexturePalette );

}

cResourceManager::~cResourceManager()
{
	mMapIndexArray.Clear();

	///
	mpStreamPAK->RemoveAllObjects();
	SAFE_NIDELETE(mpStreamPAK);

	///
	mTexturePaletteCriticalSection.Lock();
	{
		mTexturePalette->RemoveAllTextures();
		mTexturePalette = 0;

		mMapTexturePalette->RemoveAllTextures();
		mMapTexturePalette = 0;
	}
	mTexturePaletteCriticalSection.Unlock();

	///
	{
		cD3DTextureMap::cIterator i = mD3DTextureMap.Begin();
		cD3DTextureMap::cIterator iend = mD3DTextureMap.End();

		for( ; i != iend; ++i )
		{
			LPDIRECT3DTEXTURE9 tex = (LPDIRECT3DTEXTURE9)i->mSecond;
			tex->Release();
		}
		mD3DTextureMap.Clear();
	}

	if( mSoundMap.IsEmpty() == false )
	{
		cSoundMap::cIterator i, end;
		NiAudioSource* p = 0;

		i = mSoundMap.Begin();
		end = mSoundMap.End();
		for( ; i != end; ++i )
		{
			p = (NiAudioSource*)(*i).mSecond;
			SAFE_NIDELETE( p );
		}
		mSoundMap.Clear();
	}

	///
	mpSingleton = 0;
}

bool cResourceManager::Init()
{
	/// 그림자 텍스처를 로딩
	mShadowTexture = LoadTexture( "Data/2DData/shadow.tga", false );
	assert( mShadowTexture );

	if( mShadowTexture == 0 )
		return false;

	mMapIndexArray.Reserve(200);

	return true;
}

void cResourceManager::Close()
{
	mTexturePaletteCriticalSection.Lock();
	{
		mMapTexturePalette->RemoveAllTextures();
	}
	mTexturePaletteCriticalSection.Unlock();

	for( unsigned int i = 0, end = mMapIndexArray.GetSize(); i < end; ++i )
	{
		unsigned int index = mMapIndexArray[i];

		NiNode* n = GetObjectAt( index );
		if( n )
			RemoveObject( n );
	}
	mMapIndexArray.Clear();
}

void cResourceManager::Exit()
{
	/// 만약 쓰레드가 돌고 있다면 종료한다.
	FinishThread();
}

void cResourceManager::Clear()
{
	mModelArray.RemoveAll();
	mModelIndexMap.RemoveAll();
	RemoveAllObjects();
}

NiTexture* cResourceManager::LoadTexture( const cString& pathName, bool /*useMipMap*/ )
{
	/// 이미 존재하는지 검사
	NiTexturePtr nitex = 0;

	mTexturePaletteCriticalSection.Lock();
	{
		nitex = mTexturePalette->GetTexture( pathName.Cstr() );
	}
	mTexturePaletteCriticalSection.Unlock();

	if( nitex )
		return nitex;

	/// 텍스처 파일을 로딩
	NiPixelDataPtr pixelData = 0;
	cString ext;
	::GetFileExtension( &ext, pathName );
	ext.ToLower();

	if( ext == "nif" )
	{
		cFileLoader loader;
		if( loader.Open( pathName, true ) == false )
		{
			return 0;
		}

		/// 픽셀 데이타를 로딩
		cTextureStream stream( pathName );

		if( stream.Load( (char*)loader.GetBufferPtr(), loader.GetSize() ) == false )
		{
			return 0;
		}

		pixelData = stream.mPixelData;
	}
	else if( ext == "tga" )
	{
		cFileLoader loader;
		if( loader.Open( pathName, true ) == false )
		{
			assert( 0 );
			return 0;
		}

		cEngineFile file( loader.GetFile() );
		cTGAReader tgaReader;
		pixelData = tgaReader.ReadFile( file, 0 );
	}
	else
	{
		assert( 0 );
		return 0;
	}

	if( pixelData == 0 )
	{
		assert( 0 );
		return 0;
	}

	/// 픽셀 데이타로부터 엔진 텍스처를 생성
	NiTexture::FormatPrefs prefs;
	prefs.m_eMipMapped = pixelData->GetNumMipmapLevels() == 1 ? NiTexture::FormatPrefs::NO : NiTexture::FormatPrefs::YES;

	nitex = NiSourceTexture::Create( pixelData );

	if( nitex == 0 )
	{
		assert( 0 );
		return 0;
	}

	mTexturePaletteCriticalSection.Lock();
	{
		mTexturePalette->SetTexture( pathName.Cstr(), nitex );
	}
	mTexturePaletteCriticalSection.Unlock();

	return nitex;
}

LPDIRECT3DTEXTURE9 cResourceManager::LoadD3DTexture( const cString& pathName, bool useMipMap, DWORD filter, DWORD mipFilter )
{
	///
	cD3DTextureMap::cIterator i = mD3DTextureMap.Find( pathName );

	if( i != mD3DTextureMap.End() )
	{
		return (LPDIRECT3DTEXTURE9)i->mSecond;
	}

	/// 텍스처 로딩
	cFileLoader loader;
	if( loader.Open( pathName, true ) == false )
	{
		assert( 0 );
		return 0;
	}

	LPDIRECT3DTEXTURE9 tex = 0;
	cRenderer* renderer = RENDERSYS->GetRenderer();
	HRESULT ret = ::D3DXCreateTextureFromFileInMemoryEx( renderer->GetD3DDevice(), loader.GetBufferPtr(), loader.GetSize(),
		0, 0, useMipMap ? 0 : 1, 0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED, filter, mipFilter, 0, 0, 0,
		&tex );

	if( FAILED(ret) || tex == 0 )
	{
		assert( 0 );
		return 0;
	}

	/// 텍스처 맵에 추가
	mD3DTextureMap.Insert( pathName, tex );
	return tex;
}

LPDIRECT3DTEXTURE9 cResourceManager::GetD3DTexture( const cString& pathName )
{
	cD3DTextureMap::cIterator i = mD3DTextureMap.Find( pathName );

	return i != mD3DTextureMap.End() ? (LPDIRECT3DTEXTURE9)i->mSecond : 0;
}

NiAudioSource* cResourceManager::LoadSound( const cString& fileName )
{
	cString tempName = fileName;
	tempName.TrimLeft( "./\\" );

	/// 이미 로딩되었는지 검사
	NiAudioSource* src = GetSoundByName( tempName );
	if( src )
		return src;

	/// 엔진 노드를 생성
	NiAudioSystem* as = NiAudioSystem::GetAudioSystem();
	src = as->CreateSource( NiAudioSource::TYPE_AMBIENT );

	cString str;
	str.Format( "Sound/%s", fileName.Cstr() );
	src->SetFilename( str.Cstr() );
	src->SetStreamed( false );
	src->SetAllowSharing( true );

	/// 사운드를 로딩
	if( src->Load() == false )
	{
		assert( 0 );
		return 0;
	}

	mSoundListCriticalSection.Lock();
	{
		if( mSoundMap.Insert( tempName, src ) == false )
		{
			assert(0);
		}
	}
	mSoundListCriticalSection.Unlock();

	return src;
}

bool cResourceManager::LoadNIF( const cString& pathName, NiAVObject** nifRoot )
{
	cString tempName = pathName;
	tempName.TrimLeft( "./\\" );

	/// 이미 로딩되었는지 검사
	NiNode* n = GetObjectByName( tempName );
	if( n )
	{
		if( nifRoot )
			*nifRoot = n;
		return true;
	}

	///
	cFileLoader loader;
	if( loader.Open( tempName.Cstr(), true ) == false )
	{
		return false;
	}

	///
	cStream stream( tempName.Cstr(), mTexturePalette );
	mTexturePalette->SetStream( &stream );

	if( stream.Load( (char*)loader.GetBufferPtr(), loader.GetSize() ) == false )
	{
		return false;
	}

	n = NiDynamicCast( NiNode, stream.GetObjectAt( 0 ) );

	AddObject( n, tempName.Cstr() );

	stream.RemoveAllObjects();
	mTexturePalette->SetStream( 0 );

	if( nifRoot )
		*nifRoot = n;
	return true;
}

bool cResourceManager::LoadMapNIF( const cString& pathName )
{
	cString tempName = pathName;
	tempName.TrimLeft( "./\\" );

	/// 이미 로딩되었는지 검사
	if( GetObjectByName( tempName ) )
	{
		return true;
	}

	///
	cFileLoader loader;
	if( loader.Open( tempName.Cstr(), true ) == false )
	{
		return false;
	}

	///
	cStream stream( tempName.Cstr(), mMapTexturePalette );
	mMapTexturePalette->SetStream( &stream );

	if( stream.Load( (char*)loader.GetBufferPtr(), loader.GetSize() ) == false )
	{
		return false;
	}

	NiNode* n = NiDynamicCast( NiNode, stream.GetObjectAt( 0 ) );

	unsigned int index = AddObject( n, tempName.Cstr() );
	if( index != UINT_MAX )
		mMapIndexArray.PushBack( index );

	stream.RemoveAllObjects();
	mMapTexturePalette->SetStream( 0 );
	return true;
}

/// 주의!! 일단, 로딩큐에 파일명만을 채운다.
void cResourceManager::LoadThreadNIF( const char* fileName )
{
	if( !GetObjectByName( fileName ) )
	{
		mpStreamPAK->AddLoadFile( fileName );
	}
}

bool cResourceManager::LoadKFM( const cString& pathName )
{
	cString tempName = pathName;
	tempName.TrimLeft( "./\\" );

	/// 이미 로딩되었는지 검사
	if( GetModelByName( tempName ) )
	{
		return true;
	}

	///
	cModelInstance* model = cModelInstance::Create( tempName.Cstr() );
	if( model == 0 )
	{
		return false;
	}

	AddModel( model, tempName );
	return true;
}

/// 주의!! 로딩쓰레드와 메인쓰레드에서 동시에 호출되므로 사용시 항상 주의
unsigned int cResourceManager::AddObject( NiNode* n, const cString& pathName )
{
	if( n == 0 ) 
	{
		assert( 0 );
		return UINT_MAX;
	}

	cString tempName = pathName;
	::GetFileName( &tempName, pathName );

	unsigned int i = UINT_MAX;
	mObjectListCriticalSection.Lock();
	{
		/// 쓰레드 로딩중인 데이터를 메인쓰레드에서 임의로 집어 넣는것을 방지하기 위한 점검 코드..
		if( mObjectIndexMap.GetAt( tempName.Cstr(), i ) )
		{
			mObjectListCriticalSection.Unlock();
			return UINT_MAX;
		}

		if( tempName.IsEmpty() == false )
		{
			n->SetName( tempName.Cstr() );
		}

		i = mNodeArray.AddFirstEmpty( n );
		mObjectIndexMap.SetAt( tempName.Cstr(), i );
	}
	mObjectListCriticalSection.Unlock();

	return i;
}

unsigned int cResourceManager::AddModel( cModelInstance* model, const cString& pathName )
{
	if( model == 0 ) 
	{
		assert( 0 );
		return UINT_MAX;
	}

	cString tempName = pathName;
	::GetFileName( &tempName, pathName );

	unsigned int i = mModelArray.AddFirstEmpty( model );
	mModelIndexMap.SetAt( tempName.Cstr(), i );

	return i;
}

/// 주의!! 로딩쓰레드와 메인쓰레드에서 동시에 호출되므로 사용시 항상 주의
void cResourceManager::RemoveObject( const char* objectName )
{
	assert(::strlen(objectName));
	if( ::strlen(objectName) == 0 )
	{
		return;
	}

	mObjectListCriticalSection.Lock();
	{
		unsigned int objectIdx;
		if( mObjectIndexMap.GetAt( objectName, objectIdx ) )
		{
			NiNode* n = mNodeArray.GetAt( objectIdx );
			if( n )
			{
				/// 전체 구동상의 문제는 아니지만, 리소스의 관리를 다시한번 점검해보자!!
				assert(n->GetRefCount() == 1);

				mObjectIndexMap.RemoveAt( objectName );
				mNodeArray.RemoveAt( objectIdx );
			}
			n = 0;
		}
	}
	mObjectListCriticalSection.Unlock();
}

/// 주의!! 로딩쓰레드와 메인쓰레드에서 동시에 호출되므로 사용시 항상 주의
void cResourceManager::RemoveObject( NiNode* n )
{
	assert(n);
	if(!n) return;

	mObjectListCriticalSection.Lock();
	{
		NiNode* pTemp = 0;
		const char* name = 0;
		unsigned int objectIdx = UINT_MAX;
		for( unsigned int i = 0; i < mNodeArray.GetSize(); ++i )
		{
			pTemp = mNodeArray.GetAt( i );
			if( pTemp == n )
			{
				/// 전체 구동상의 문제는 아니지만, 리소스의 관리를 다시한번 점검해보자!!
				assert(pTemp->GetRefCount() == 1);

				mNodeArray.RemoveAt( i );
				pTemp = 0;

				NiTMapIterator kIter = mObjectIndexMap.GetFirstPos();
				while( kIter )
				{
					mObjectIndexMap.GetNext( kIter, name, objectIdx );
					if( objectIdx == i )
					{
						mObjectIndexMap.RemoveAt( name );
						break;
					}
				}
			}
		}
	}
	mObjectListCriticalSection.Unlock();
}

void cResourceManager::RemoveAllObjects()
{
	mObjectListCriticalSection.Lock();
	{
#ifdef _DEBUG
		NiAVObject* pobj = 0;
		for( unsigned int i = 0; i<mNodeArray.GetSize(); ++i )
		{
			pobj = mNodeArray.GetAt( i );
			if( pobj )
			{
				/// 전체 구동상의 문제는 아니지만, 리소스의 관리를 다시한번 점검해보자!!
				assert(pobj->GetRefCount() == 1);
				pobj = 0;
			}
		}
#endif
		mNodeArray.RemoveAll();
		mObjectIndexMap.RemoveAll();
	}
	mObjectListCriticalSection.Unlock();
}

void cResourceManager::RemoveAllModels()
{
#ifdef _DEBUG
	cModelInstance* pModel = 0;
	for( unsigned int i = 0; i<mModelArray.GetSize(); ++i )
	{
		pModel = mModelArray.GetAt( i );
		if( pModel )
		{
			/// 전체 구동상의 문제는 아니지만, 리소스의 관리를 다시한번 점검해보자!!
			assert(pModel->GetRefCount() == 1);
			pModel = 0;
		}
	}
#endif

	mModelArray.RemoveAll();
	mModelIndexMap.RemoveAll();
}

NiAudioSource* cResourceManager::GetSoundByName( const cString& fileName )
{
	cString tempName = fileName;
	::GetFileName( &tempName, fileName );

	NiAudioSource* src = 0;
	mSoundListCriticalSection.Lock();
	{
		src = mSoundMap.GetAt( tempName );
	}
	mSoundListCriticalSection.Unlock();

	return src;
}

NiNode* cResourceManager::GetObjectAt( unsigned int objcetIdx )
{
	NiNode* n = 0;
	mObjectListCriticalSection.Lock();
	{
		/// 전체 구동상의 문제는 아니지만, 리소스의 관리를 다시한번 점검해보자!!
		assert(objcetIdx < mNodeArray.GetSize());

		n = mNodeArray.GetAt( objcetIdx );
	}
	mObjectListCriticalSection.Unlock();

	return n;
}

NiNode* cResourceManager::GetObjectByName( const cString& pathName )
{
	cString tempName = pathName;
	::GetFileName( &tempName, pathName );

	mObjectListCriticalSection.Lock();
	{
		unsigned int i = UINT_MAX;
		if( mObjectIndexMap.GetAt( tempName.Cstr(), i ) )
		{
			mObjectListCriticalSection.Unlock();
			return GetObjectAt( i );
		}
	}
	mObjectListCriticalSection.Unlock();

	return 0;
}

cModelInstance* cResourceManager::GetModelAt( unsigned int modelIdx )
{
	/// 전체 구동상의 문제는 아니지만, 리소스의 관리를 다시한번 점검해보자!!
	assert(modelIdx < mModelArray.GetSize());

	return mModelArray.GetAt( modelIdx );
}

cModelInstance* cResourceManager::GetModelByName( const cString& pathName )
{
	cString tempName = pathName;
	::GetFileName( &tempName, pathName );

	unsigned int i;
	if( mModelIndexMap.GetAt( tempName.Cstr(), i ) )
	{
		return GetModelAt( i );
	}

	return 0;
}

NiAudioSource* cResourceManager::CloneSoundByName( const cString& fileName )
{
	NiAudioSource* src = LoadSound( fileName );

	if( src == 0 )
		return 0;

	NiCloningProcess cloning;
	cloning.m_eCopyType = NiObjectNET::COPY_EXACT;

	return (NiAudioSource*)src->CreateClone(cloning);
}

NiNode* cResourceManager::CloneObjectAt( unsigned int objectIdx )
{
	NiNode* n = GetObjectAt( objectIdx );

	if( n )
	{
		NiCloningProcess cloning;
		cloning.m_eCopyType = NiObjectNET::COPY_EXACT;
		return (NiNode*)n->Clone( cloning );
	}

	return 0;
}

NiNode* cResourceManager::CloneObjectByName( const cString& pathName )
{
	NiNode* n = GetObjectByName( pathName );

	if( n )
	{
		NiCloningProcess cloning;
		cloning.m_eCopyType = NiObjectNET::COPY_EXACT;

		return (NiNode*)n->Clone( cloning );
	}

	return 0;
}

void cResourceManager::Process()
{
	/// 쓰레드 로딩 루틴..
	mpStreamPAK->Process();
}

void cResourceManager::FinishThread()
{
	mpStreamPAK->ClearQueue();

	NiStream::LoadState loadState;
	NiStream::ThreadStatus status;
	status = mpStreamPAK->BackgroundLoadPoll( &loadState );
	while( status != NiStream::IDLE )
	{
		/// 쓰레드 종료를 좀더 효율적으로 하기위해서 필요..
		NiSleep( 1 ); 

		status = mpStreamPAK->BackgroundLoadPoll( &loadState );
	}

	mpStreamPAK->BackgroundLoadFinish();
	mpStreamPAK->RemoveAllObjects();
}