///////////////////////////////////////////////////////////////////////////////
/// @file deThreadPool.cpp
///
/// @brief 
///
/// @author trajar
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2003 Novus Delta, LLC. All Rights Reserved.
///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date Jun 2002
/// @author trajar
/// @remarks Creation
///
///////////////////////////////////////////////////////////////////////////////

#include "deThreadPool.hpp"

#ifdef	_DEBUG
 #include	<crtdbg.h> 
 #define new new(_NORMAL_BLOCK,__FILE__, __LINE__)
#endif

// static declartions
//-----------------------------------------------
DENET_API deThreadPool * deThreadPool::m_Singleton = NULL;

inline deThreadPool::deThreadPool(void)
{
	deASSERT( !m_Singleton );
	m_Singleton = this;
}

inline deThreadPool::~deThreadPool(void)
{
	DestroyAllThreads(true);
	m_Singleton = NULL;
}

bool deThreadPool::CreateThreads( int iNum )
{
	while( iNum > 0 )
	{
		deThread * thread		= new deThread();
		m_stkIdleThreads.push( thread );
		--iNum;
	}

	return true;
}

int deThreadPool::ActivateQueuedJobs( int iNumCreatedThreads )
{
	// lock thread pool
	m_SynchObject.Lock( this );

	int		iNumActivated	= 0;
	int		iNumIdle		= m_stkIdleThreads.size();
	int		iNumQueued		= m_queWaitingJobs.size();

	while( (iNumQueued > 0) && ( (iNumIdle > 0) || (iNumCreatedThreads > 0) ) )
	{
		deThreadPool::JobDesc *	job = m_queWaitingJobs.front();

		if( iNumIdle > 0 )
		{
			// set up job			
			deThread * thread		= m_stkIdleThreads.top();	m_stkIdleThreads.pop();
			job->m_pThread			= thread;
			deASSERT( job->m_pProcess );

			// set up thread process
			thread->AttachProcess( job->m_pProcess, job->m_pProcessData );
			
			// determine if it has been started yet
			deThread::eStatus status = thread->GetStatus();
			if( status > deThread::STATUS_NONE && status < deThread::STATUS_DEAD )
				thread->Wakeup();
			else
				thread->Start();

			m_aActiveJobs.push_back( job );
		}
		else
		{
			// create a new thread to handle this job
			
			deThread * thread		= new deThread();
			job->m_pThread			= thread;
			deASSERT( job->m_pProcess );

			// set up thread process
			thread->AttachProcess( job->m_pProcess, job->m_pProcessData );
			thread->Start();

			m_aActiveJobs.push_back( job );
			iNumCreatedThreads--;
		}

		iNumActivated++;

		m_queWaitingJobs.pop();
		iNumIdle		= m_stkIdleThreads.size();
		iNumQueued		= m_queWaitingJobs.size();
	}

	// unlock thread pool
	m_SynchObject.Unlock();

	return iNumActivated;
}

deThread * deThreadPool::ActivateIdleThread( deThread::ActiveProcess process, void * param, DWORD paramSize, DWORD dwDesc, bool bCreateThread )
{
	// lock thread pool
	m_SynchObject.Lock( this );

	int		iNum = m_stkIdleThreads.size();

	if( iNum > 0 )
	{
		// set up job
		deThreadPool::JobDesc *	job = new deThreadPool::JobDesc();
		deThread * thread		= m_stkIdleThreads.top();	m_stkIdleThreads.pop();
		
		job->m_pThread			= thread;
		job->m_pProcess			= process;
		job->m_dwDesc			= dwDesc;
		if( param )
		{
			job->m_pProcessData	= new BYTE[paramSize];
			memcpy( job->m_pProcessData, param, paramSize );
		}
		else
			job->m_pProcessData	= NULL;

		deASSERT( job->m_pProcess );

		// set up thread process
		thread->AttachProcess( job->m_pProcess, job->m_pProcessData );

		// determine if it has been started yet
		deThread::eStatus status = thread->GetStatus();
		if( status > deThread::STATUS_NONE && status < deThread::STATUS_DEAD )
			thread->Wakeup();
		else
			thread->Start();

		m_aActiveJobs.push_back( job );

		// unlock thread pool
		m_SynchObject.Unlock();

		return thread;
	}
	else if( bCreateThread )
	{
		// create a new thread to handle this job
		// set up job
		deThreadPool::JobDesc *	job = new deThreadPool::JobDesc();
		deThread * thread		= new deThread();
		job->m_pThread			= thread;
		job->m_pProcess			= process;
		job->m_dwDesc			= dwDesc;
		if( param )
		{
			job->m_pProcessData	= new BYTE[paramSize];
			memcpy( job->m_pProcessData, param, paramSize );
		}
		else
			job->m_pProcessData	= NULL;

		deASSERT( job->m_pProcess );

		// set up thread process
		thread->AttachProcess( job->m_pProcess, job->m_pProcessData );
		thread->Start();

		m_aActiveJobs.push_back( job );

		// unlock thread pool
		m_SynchObject.Unlock();

		return thread;
	}
	else
	{
		// we don't have any idle threads, throw it on the queue
		// set up job
		deThreadPool::JobDesc *	job = new deThreadPool::JobDesc();
		job->m_pThread			= NULL;
		job->m_pProcess			= process;
		job->m_dwDesc			= dwDesc;
		if( param )
		{
			job->m_pProcessData	= new BYTE[paramSize];
			memcpy( job->m_pProcessData, param, paramSize );
		}
		else
			job->m_pProcessData	= NULL;

		deASSERT( job->m_pProcess );

		m_queWaitingJobs.push( job );

		// unlock thread
		m_SynchObject.Unlock();

		return NULL;
	}
}

bool deThreadPool::DestroyAllThreads( bool bForceKill )
{
	// lock thread pool
	m_SynchObject.Lock( this );

	bool bActuallyClosedThreads = !m_stkIdleThreads.empty() || !m_aActiveJobs.empty();

	// destroy all idle threads
	while( !m_stkIdleThreads.empty() )
	{
		deThread * thread = m_stkIdleThreads.top();
		thread->Stop( bForceKill );
		SAFE_DELETE( thread );
		m_stkIdleThreads.pop();
	}

	// now destroy all active jobs
	while( !m_aActiveJobs.empty() )
	{
		deThreadPool::aJobs::iterator itr = m_aActiveJobs.begin();
		(*itr)->m_pThread->Stop( bForceKill );
		SAFE_DELETE( (*itr)->m_pThread );
		SAFE_DELETE( (*itr) );

		m_aActiveJobs.erase( itr );
	}

	// now all qeued jobs
	while( !m_queWaitingJobs.empty() )
	{
		deThreadPool::JobDesc * job = m_queWaitingJobs.front();
		SAFE_DELETE( job->m_pThread );
		SAFE_DELETE( job );		
		m_queWaitingJobs.pop();
	}

	// unlock thread pool
	m_SynchObject.Unlock();

	return bActuallyClosedThreads;
}

DWORD deThreadPool::GetNumActiveThreads(void)
{
	return m_aActiveJobs.size();
}

DWORD deThreadPool::GetNumIdleThreads(void)
{
	return m_stkIdleThreads.size();
}

bool deThreadPool::NotifyOnIdle( deThread * pThread )
{
	// lock thread pool
	m_SynchObject.Lock( this );

	deThread::eStatus	status = pThread->GetStatus();

	// ensure that it is idle (sleeping)
	if( status = deThread::STATUS_SLEEP )
	{
		aJobs::iterator	itr	= m_aActiveJobs.begin();
		
		while( itr != m_aActiveJobs.end() && itr != NULL )
		{
			if( (*itr)->m_pThread == pThread )
			{
				// kill the thread on completion?
				if( (*itr)->m_dwDesc & deThreadPool::JobDesc::JOB_KILL_THREAD_ON_COMPLETION )
				{
					pThread->Stop();
					SAFE_DELETE( pThread );
				}
				else
				{
					// if not, put it on the idle stack
					m_stkIdleThreads.push( pThread );
				}

				// we found the thread, close the job
				SAFE_DELETE( (*itr) );
				m_aActiveJobs.erase( itr );

				// active any queued jobs we may now have
				this->ActivateQueuedJobs(0);

				m_SynchObject.Unlock();
				return true;
			}

			++itr;
		}
	}

	// unlock thread pool
	m_SynchObject.Unlock();

	return false;
}