#include "Mix/Private/Parallel/Manager.h"

#ifdef _DEBUG
	#include "Mix/ScopedLock.h"
#endif //_DEBUG

namespace Mix{ namespace Parallel{

const Mix::Parallel::DEBUG_THREAD_INFO Manager::DEBUG_INIT_THREAD_INFO = { 0.0f, 0, NULL };

////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::Parallel::Manager
////////////////////////////////////////////////////////////////////////////////////////////////////

Manager* Manager::CreateInstance( void )
{
	return MIX_LIB_NEW_T( Mix::Memory::SECTION_GENERAL, Manager );
}

Manager::Manager( void ) :
m_ExeFuncHandle( NULL ),
m_TermHandle( NULL ),
m_CompHandles( NULL )
{
	m_ThreadShare.threadCount = 0;
	m_ThreadShare.pFunc = NULL;
	m_ThreadShare.pData = NULL;
	m_ThreadShare.callFuncCount = 0;
	m_ThreadShare.compHandles = NULL;

#ifdef _DEBUG
	m_ThreadShare.debID = Mix::Parallel::DEBUG_USER;

	m_DebCur = 0;
	m_DebNext = 1;
#endif //_DEBUG
}

Manager::~Manager( void )
{
}

void Manager::RefreshWorks( void )
{
	Manager::HandleList::iterator it_mw_begin = m_MapWorkHandles.begin();
	Manager::HandleList::iterator it_mw_end = m_MapWorkHandles.end();
	Manager::HandleList::iterator it_mw;

	Manager::HandleList::iterator it_wu_begin = m_WaitHandles.begin();
	Manager::HandleList::iterator it_wu_end = m_WaitHandles.end();
	Manager::HandleList::iterator it_wu;

	Manager::WorkTree::iterator it_wt_begin = m_WorkTree.begin();
	Manager::WorkTree::iterator it_wt_end = m_WorkTree.end();
	Manager::WorkTree::iterator it_wt;

	UInt32 threadNum = m_ThreadShare.threadCount;
	Manager::THREAD_DATA* threadDatum = &( m_ThreadDatum[0] );

	MIX_ASSERT( threadNum == m_ThreadDatum.size() );

	HANDLE* mapWorkHandles = &( m_MapWorkHandles[0] );

	UInt32 i;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ワークのマップを有効にする
	////////////////////////////////////////////////////////////////////////////////////////////////////

	for( it_mw = it_mw_begin; it_mw != it_mw_end; ++it_mw )
	{
		::SetEvent( *( it_mw ) );
	}

	::WaitForMultipleObjects( MIX_UIT_TO_UI32( m_CompHandles.size() ), &( m_CompHandles[0] ), TRUE, INFINITE );

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// スレッドデータの再構築
	////////////////////////////////////////////////////////////////////////////////////////////////////

	for( i = 0; i < threadNum; i++ )
	{
		Manager::THREAD_DATA& threadData = threadDatum[i];

		threadData.wakeupHandles.clear();
		threadData.wakeupHandles.resize( Manager::WAKEUP_TYPE_MAX );
		threadData.wakeupHandles[Manager::SEMA_EXE_FUNC] = m_ExeFuncHandle;
		threadData.wakeupHandles[Manager::EVENT_TERM] = m_TermHandle;
		threadData.wakeupHandles[Manager::EVENT_MAP_WORK] = mapWorkHandles[i];

		threadData.works.clear();
	}

	for( it_wt = it_wt_begin; it_wt != it_wt_end; ++it_wt )
	{
		const Manager::WorkList& works = it_wt->second;
		const Mix::Parallel::WORK* pWork = &( works[0] );
		const Mix::Parallel::WORK* pWorkEnd = pWork + works.size();

		while( pWork != pWorkEnd )
		{
			Manager::THREAD_DATA& threadData = threadDatum[pWork->threadIndex];

			threadData.wakeupHandles.push_back( pWork->hWakeup );
			threadData.works.push_back( Manager::WORK( pWork->pFunc, pWork->pData, pWork->debugID ) );

			pWork++;
		}
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// ワークのマップを無効にする
	////////////////////////////////////////////////////////////////////////////////////////////////////

	for( it_wu = it_wu_begin; it_wu != it_wu_end; ++it_wu )
	{
		::SetEvent( *( it_wu ) );
	}
}

unsigned __stdcall Manager::ThreadMain( void* pArg )
{
	Manager::THREAD_DATA* pData = static_cast<Manager::THREAD_DATA*>( pArg );
	Manager::THREAD_SHARE* pShare = pData->pShare;

	Boolean bContinue = True;
	UInt32 wakeupType;
	Long32 callFuncIndex;
	Manager::WORK* pWork;

#ifdef _DEBUG
	Mix::Parallel::DEBUG_FUNC_INFO debFuncInfo;
#endif //_DEBUG

	do
	{
		wakeupType = ::WaitForMultipleObjects( MIX_UIT_TO_UI32( pData->wakeupHandles.size() ), &( pData->wakeupHandles[0] ), FALSE, INFINITE );
		wakeupType = wakeupType - WAIT_OBJECT_0;

		switch( wakeupType )
		{
		case Manager::SEMA_EXE_FUNC:
#ifdef _DEBUG
			debFuncInfo.startTime = pData->debTimer.GetF32();
#endif //_DEBUG
			callFuncIndex = ::InterlockedExchangeAdd( &( pShare->callFuncCount ), 1 );
			pShare->pFunc( pShare->threadCount, callFuncIndex, pShare->pData );
			::SetEvent( pShare->compHandles[callFuncIndex] );
#ifdef _DEBUG
			debFuncInfo.elapsedTime = pData->debTimer.GetF32() - debFuncInfo.startTime;
			debFuncInfo.id = pShare->debID;
			pData->pDebThreadFuncInfosNext->push_back( debFuncInfo );
			pData->pDebThreadInfoNext->elapsedTime = debFuncInfo.startTime + debFuncInfo.elapsedTime;
#endif //_DEBUG
			break;

		case Manager::EVENT_TERM:
			bContinue = False;
			break;

		case Manager::EVENT_MAP_WORK:
			// MapWork が有効になったのを通知
			::SetEvent( pData->validMapWorkHandle );

			// MapWork...

			// MapWork を待機
			::WaitForSingleObject( pData->waitMapWorkHandle, INFINITE );
			break;

		default:
			MIX_ASSERT( wakeupType < pData->wakeupHandles.size() );
			pWork = &( pData->works[wakeupType - Manager::SYNC_WORK_START] );
#ifdef _DEBUG
			debFuncInfo.startTime = pData->debTimer.GetF32();
#endif //_DEBUG
			pWork->pFunc( pShare->threadCount, pData->threadIndex, pWork->pData );
#ifdef _DEBUG
			debFuncInfo.elapsedTime = pData->debTimer.GetF32() - debFuncInfo.startTime;
			debFuncInfo.id = pWork->debID;
			pData->pDebThreadFuncInfosNext->push_back( debFuncInfo );
			pData->pDebThreadInfoNext->elapsedTime = debFuncInfo.startTime + debFuncInfo.elapsedTime;
#endif //_DEBUG
			break;
		}
	}
	while( bContinue == True );

	return 0;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::Parallel::Manager
////////////////////////////////////////////////////////////////////////////////////////////////////

Boolean Manager::Initialize( void )
{
	SYSTEM_INFO sysInfo;
	UInt32 threadCount;

	// 最大スレッド数 //

	Mix::Memory::Zero( &sysInfo, sizeof( SYSTEM_INFO ) );
	::GetSystemInfo( &sysInfo );
	threadCount = sysInfo.dwNumberOfProcessors;

	// ウェイクアップ( 共有 ) 同期オブジェクト //

	m_ExeFuncHandle = ::CreateSemaphore( NULL, 0, threadCount, NULL );
	m_TermHandle = ::CreateEvent( NULL, TRUE, FALSE, NULL );

	// ウェイクアップ( 固有 ) 同期オブジェクト //

	m_MapWorkHandles.resize( threadCount );

	for( UInt32 i = 0; i < threadCount; i++ )
	{
		m_MapWorkHandles[i] = ::CreateEvent( NULL, FALSE, FALSE, NULL );
		if( m_MapWorkHandles[i] == NULL )
		{
			return False;
		}
	}

	// 完了 同期オブジェクト //

	m_CompHandles.resize( threadCount );

	for( UInt32 i = 0; i < threadCount; i++ )
	{
		m_CompHandles[i] = ::CreateEvent( NULL, FALSE, FALSE, NULL );
		if( m_CompHandles[i] == NULL )
		{
			return False;
		}
	}

	// 待機 同期オブジェクト

	m_WaitHandles.resize( threadCount );

	for( UInt32 i = 0; i < threadCount; i++ )
	{
		m_WaitHandles[i] = ::CreateEvent( NULL, FALSE, FALSE, NULL );
		if( m_WaitHandles[i] == NULL )
		{
			return False;
		}
	}

	// スレッドの作成 //

	m_ThreadShare.threadCount = threadCount;
	m_ThreadShare.compHandles = &( m_CompHandles[0] );

	m_ThreadDatum.resize( sysInfo.dwNumberOfProcessors );
	m_ThreadHandles.resize( sysInfo.dwNumberOfProcessors );

	for( UInt32 i = 0; i < threadCount; i++ )
	{
		Manager::THREAD_DATA* pThreadData = &( m_ThreadDatum[i] );
		uintptr_t uipThread;
		HANDLE hThread;

		pThreadData->pShare = &m_ThreadShare;
		pThreadData->threadIndex = i;

		pThreadData->wakeupHandles.resize( WAKEUP_TYPE_MAX );
		pThreadData->wakeupHandles[Manager::SEMA_EXE_FUNC] = m_ExeFuncHandle;
		pThreadData->wakeupHandles[Manager::EVENT_TERM] = m_TermHandle;
		pThreadData->wakeupHandles[Manager::EVENT_MAP_WORK] = m_MapWorkHandles[i];

		pThreadData->validMapWorkHandle = m_CompHandles[i];
		pThreadData->waitMapWorkHandle = m_WaitHandles[i];

#ifdef _DEBUG
		pThreadData->debTimer.Reset();
		pThreadData->debThreadInfos[0] = Manager::DEBUG_INIT_THREAD_INFO;
		pThreadData->debThreadInfos[1] = Manager::DEBUG_INIT_THREAD_INFO;
		pThreadData->pDebThreadInfoNext = &( pThreadData->debThreadInfos[m_DebNext] );
		pThreadData->pDebThreadFuncInfosNext = &( pThreadData->pDebThreadFuncInfosNext[m_DebNext] );
#endif //DEBUG

		uipThread = ::_beginthreadex( NULL, 0, Manager::ThreadMain, pThreadData, CREATE_SUSPENDED, NULL );
		if( uipThread == -1L )
		{
			return False;
		}

		hThread = reinterpret_cast<HANDLE>( uipThread );
		m_ThreadHandles[i] = hThread;

		if( ::SetThreadAffinityMask( hThread, ( 1 << i ) ) == 0 )
		{
			return False;
		}

		ResumeThread( hThread );
	}

	return True;
}

void Manager::Dispose( void )
{
	if( m_ThreadHandles.size() > 0 )
	{
		Manager::HandleList::iterator it_begin = m_ThreadHandles.begin();
		Manager::HandleList::iterator it_end = m_ThreadHandles.end();
		Manager::HandleList::iterator it;

		MIX_ASSERT( m_TermHandle != NULL );

		::SetEvent( m_TermHandle );
		::WaitForMultipleObjects( MIX_UIT_TO_UI32( m_ThreadHandles.size() ), &( m_ThreadHandles[0] ), TRUE, INFINITE );

		for( it = it_begin; it != it_end; ++it )
		{
			HANDLE hThread = ( *it );

			::CloseHandle( hThread );
			hThread = NULL;
		}

		m_ThreadHandles.clear();
	}

	if( m_WaitHandles.size() )
	{
		Manager::HandleList::iterator it_begin = m_WaitHandles.begin();
		Manager::HandleList::iterator it_end = m_WaitHandles.end();
		Manager::HandleList::iterator it;

		for( it = it_begin; it != it_end; ++it )
		{
			HANDLE hWait = ( *it );

			::CloseHandle( hWait );
			hWait = NULL;
		}

		m_WaitHandles.clear();
	}

	if( m_CompHandles.size() )
	{
		Manager::HandleList::iterator it_begin = m_CompHandles.begin();
		Manager::HandleList::iterator it_end = m_CompHandles.end();
		Manager::HandleList::iterator it;

		for( it = it_begin; it != it_end; ++it )
		{
			HANDLE hComp = ( *it );

			::CloseHandle( hComp );
			hComp = NULL;
		}

		m_CompHandles.clear();
	}

	if( m_MapWorkHandles.size() )
	{
		Manager::HandleList::iterator it_begin = m_MapWorkHandles.begin();
		Manager::HandleList::iterator it_end = m_MapWorkHandles.end();
		Manager::HandleList::iterator it;

		for( it = it_begin; it != it_end; ++it )
		{
			HANDLE hMapWork = ( *it );

			::CloseHandle( hMapWork );
			hMapWork = NULL;
		}

		m_MapWorkHandles.clear();
	}

	if( m_ExeFuncHandle != NULL )
	{
		::CloseHandle( m_ExeFuncHandle );
		m_ExeFuncHandle = NULL;
	}

	if( m_TermHandle != NULL )
	{
		::CloseHandle( m_TermHandle );
		m_TermHandle = NULL;
	}
}

#ifdef _DEBUG

void Manager::Debug_Update( void )
{
	Manager::ThreadDataList::iterator it_begin = m_ThreadDatum.begin();
	Manager::ThreadDataList::iterator it_end = m_ThreadDatum.end();
	Manager::ThreadDataList::iterator it;

	Mix::Parallel::DEBUG_THREAD_INFO* debTHInfosCur;
	Mix::Parallel::DEBUG_THREAD_INFO* debTHInfosNext;
	Mix::STL::Vector<Mix::Memory::SECTION_GENERAL, Mix::Parallel::DEBUG_FUNC_INFO>* debFuncInfosCur;
	Mix::STL::Vector<Mix::Memory::SECTION_GENERAL, Mix::Parallel::DEBUG_FUNC_INFO>* debFuncInfosNext;
	Mix::STL::Vector<Mix::Memory::SECTION_GENERAL, Mix::Parallel::DEBUG_FUNC_INFO>* debFuncInfos;
	UInt32 debFuncInfoNum;

	m_DebCur = ( m_DebCur + 1 ) % 2;
	m_DebNext = ( m_DebNext + 1 ) % 2;

	for( it = it_begin; it != it_end; ++it )
	{
		Manager::THREAD_DATA* pData = &( *it );

		debTHInfosCur = &( pData->debThreadInfos[m_DebCur] );
		debTHInfosNext = &( pData->debThreadInfos[m_DebNext] );

		debFuncInfosCur = &( pData->debThreadFuncInfos[m_DebCur] );
		debFuncInfosNext = &( pData->debThreadFuncInfos[m_DebNext] );

		pData->pDebThreadInfoNext = debTHInfosNext;
		pData->pDebThreadFuncInfosNext = debFuncInfosNext;

		debFuncInfos = debFuncInfosCur;
		debFuncInfoNum = MIX_UIT_TO_UI32( debFuncInfos->size() );

		if( debFuncInfoNum > 0 )
		{
			debTHInfosCur->funcCount = debFuncInfoNum;
			debTHInfosCur->funcInfos = &( ( *debFuncInfos )[0] );
		}
		else
		{
			debTHInfosCur->funcCount = 0;
			debTHInfosCur->funcInfos = NULL;
		}

		debTHInfosNext->elapsedTime = 0.0f;
		debTHInfosNext->funcCount = 0;
		debTHInfosNext->funcInfos = NULL;

		debFuncInfosNext->clear();

		pData->debTimer.Reset();
	}
}

#endif //_DEBUG

////////////////////////////////////////////////////////////////////////////////////////////////////
// Mix::Parallel::IManager
////////////////////////////////////////////////////////////////////////////////////////////////////

UInt32 Manager::GetThreadCount( void ) const
{
	return m_ThreadShare.threadCount;
}

HANDLE Manager::MapWorks( UInt32 numWork, Mix::Parallel::WORK* works )
{
	if( ( numWork == 0 ) ||
		( works == NULL ) )
	{
		return NULL;
	}

	UInt32* pHandle;
	Manager::WorkTree::iterator it_wt;

	pHandle = reinterpret_cast<UInt32*>( MIX_LIB_MALLOC( Mix::Memory::SECTION_GENERAL, sizeof( UInt32 ) ) );
	if( pHandle == NULL )
	{
		return NULL;
	}

	m_WorkTree.insert( Manager::WorkTree::value_type( pHandle, Manager::WorkList( numWork ) ) );

	it_wt = m_WorkTree.find( pHandle );
	MIX_ASSERT( it_wt != m_WorkTree.end() );
	Mix::Memory::Copy( &( it_wt->second[0] ), works, sizeof( Mix::Parallel::WORK ) * numWork );

	RefreshWorks();

	return pHandle;
}

Boolean Manager::UnmapWorks( HANDLE handle )
{
	Manager::WorkTree::iterator it_wt;

	it_wt = m_WorkTree.find( handle );
	if( it_wt == m_WorkTree.end() )
	{
		return False;
	}

	UInt32* pHandle;

	pHandle = static_cast<UInt32*>( it_wt->first );
	MIX_LIB_FREE( pHandle );

	m_WorkTree.erase( it_wt );

	RefreshWorks();

	return True;
}

Boolean Manager::ExecuteFunction( UInt32 threadNum, FunctionPtr pFunc, void* pData, UInt32 debugID )
{
	if( pFunc == NULL )
	{
		return False;
	}

	UInt32 compMax = MIX_CLAMP( 1, threadNum, m_ThreadShare.threadCount );

	// 実行前の設定

#ifdef _DEBUG
	m_ThreadShare.debID = debugID;
#endif //_DEBUG

	m_ThreadShare.pFunc = pFunc;
	m_ThreadShare.pData = pData;
	::InterlockedExchange( &( m_ThreadShare.callFuncCount ), 0 );

	// 実行の開始を通知
	::ReleaseSemaphore( m_ExeFuncHandle, compMax, NULL );

	// 実行中...

	// 実行の完了を待機
	::WaitForMultipleObjects( compMax, &( m_CompHandles[0] ), TRUE, INFINITE );

	return True;
}

const Mix::Parallel::DEBUG_THREAD_INFO& Manager::Debug_GetThreadInfo( UInt32 threadIndex ) const
{
#ifdef _DEBUG
	return m_ThreadDatum[threadIndex].debThreadInfos[m_DebCur];
#else //_DEBUG
	return Manager::DEBUG_INIT_THREAD_INFO;
#endif //_DEBUG
}

}}
