///////////////////////////////////////////////////////////////////////////////
/// @file deThread.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 "deThread.hpp"
#include "deThreadPool.hpp"
#include <IOSTREAM>

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

// static declaration
deSynchObject deThread::m_SynchObject;

//--------------------------------------------------------------------
// deThread()
//--------------------------------------------------------------------
deThread::deThread()
{
	//printf( "deThread::deThread()\n" );
	m_ThreadCtx.m_AttachedProcess		= NULL;
	m_ThreadCtx.m_dwTID					= NULL;
	m_ThreadCtx.m_hThread				= NULL;
	m_ThreadCtx.m_ProcessParam			= NULL;
	m_pThreadFunc						= Run;
	m_Status							= STATUS_NONE;
}

//--------------------------------------------------------------------
// ~deThread()
//--------------------------------------------------------------------
deThread::~deThread()
{
	//printf( "deThread::~deThread()\n" );
	if ( m_Status > STATUS_NONE )
		Stop(true);
}

//--------------------------------------------------------------------
// DWORD Start( void * arg = NULL )
//--------------------------------------------------------------------
bool deThread::Start( void )
{
	if( m_pThreadFunc )
	{
		m_SynchObject.Lock(this);

		// create events used for thread
		m_ThreadCtx.m_Events[STATUS_RUN]	= CreateEvent( NULL, FALSE, FALSE, NULL );
		m_ThreadCtx.m_Events[STATUS_STOP]	= CreateEvent( NULL, FALSE, FALSE, NULL );
		m_ThreadCtx.m_Events[STATUS_WAKEUP]	= CreateEvent( NULL, FALSE, FALSE, NULL );
		m_ThreadCtx.m_Events[STATUS_DEAD]	= CreateEvent( NULL, FALSE, FALSE, NULL );

		// create event used when the process ptr changes
		m_ThreadCtx.m_ProcessChangeEvent	= CreateEvent( NULL, FALSE, FALSE, NULL );

		m_Status					= STATUS_RUN;		

		m_ThreadCtx.m_dwExitCode	= (DWORD)-1;
		m_ThreadCtx.m_hThread		= NULL;
		m_ThreadCtx.m_dwTID			= (DWORD)-1;
		m_ThreadCtx.m_pParent		= NULL;
		m_ThreadCtx.m_hThread		= CreateThread( NULL, 0, Run, this, 0, &m_ThreadCtx.m_dwTID );

		SetEvent( m_ThreadCtx.m_Events[STATUS_RUN] );

		m_SynchObject.Unlock();

		return true;
	}
	else
		return false;
}

//--------------------------------------------------------------------
//	Info: Stops the thread.
//	
//	This function stops the current thread. 
//	We can force kill a thread which results in a TerminateThread.
//--------------------------------------------------------------------
DWORD deThread::Stop( bool bForceKill )
{
	if ( m_Status > STATUS_NONE && m_Status < STATUS_STOP )
	{
		DWORD rtn = 0;

		m_SynchObject.Lock(this);

		m_Status = STATUS_STOP;

		SetEvent( m_ThreadCtx.m_Events[STATUS_STOP] );

		// now wait until it actally happends
		if( bForceKill )
		{
			GetExitCodeThread( m_ThreadCtx.m_hThread, &m_ThreadCtx.m_dwExitCode);

			if ( m_ThreadCtx.m_dwExitCode == STILL_ACTIVE )
				TerminateThread( m_ThreadCtx.m_hThread, DWORD(-1) );
		}
		else
		{
			rtn = WaitForSingleObject( m_ThreadCtx.m_Events[STATUS_DEAD], INFINITE );
			
			GetExitCodeThread( m_ThreadCtx.m_hThread, &m_ThreadCtx.m_dwExitCode);
		}

		m_ThreadCtx.m_hThread	= NULL;
		m_Status				= STATUS_DEAD;

		rtn = m_ThreadCtx.m_dwExitCode;

		// destroy events
		CloseHandle( m_ThreadCtx.m_Events[STATUS_RUN] );
		CloseHandle( m_ThreadCtx.m_Events[STATUS_STOP] );
		CloseHandle( m_ThreadCtx.m_Events[STATUS_WAKEUP] );
		CloseHandle( m_ThreadCtx.m_Events[STATUS_DEAD] );
		CloseHandle( m_ThreadCtx.m_ProcessChangeEvent );

		m_ThreadCtx.m_ProcessChangeEvent	= NULL;
		m_ThreadCtx.m_Events[STATUS_RUN]	= NULL;
		m_ThreadCtx.m_Events[STATUS_STOP]	= NULL;
		m_ThreadCtx.m_Events[STATUS_WAKEUP]	= NULL;
		m_ThreadCtx.m_Events[STATUS_DEAD]	= NULL;

		m_SynchObject.Unlock();

		return rtn;
	}
	else
		return -1;
}

//--------------------------------------------------------------------
// Sleep( dwTime )
//--------------------------------------------------------------------
bool deThread::Sleep( void )
{
	if ( m_Status > STATUS_NONE )
	{
		m_SynchObject.Lock(this);

		m_Status = STATUS_SLEEP;
		
		// tell parent pool we are idle
		deThreadPool::GetMgr()->NotifyOnIdle( this );

		m_SynchObject.Unlock();

		return true;
	}
	else
		return false;
}

//--------------------------------------------------------------------
// Wakeup( dwTime )
//--------------------------------------------------------------------
bool deThread::Wakeup( void )
{
	if ( m_Status > STATUS_NONE )
	{
		m_SynchObject.Lock(this);

		m_Status = STATUS_RUN;

		SetEvent( m_ThreadCtx.m_Events[STATUS_WAKEUP] );

		m_SynchObject.Unlock();

		return true;
	}
	else
		return false;
}

//--------------------------------------------------------------------
//	GetExitCode()
//--------------------------------------------------------------------
DWORD deThread::GetExitCode()  
{ 
	m_SynchObject.Lock(this);

	if ( m_Status > STATUS_NONE )
	{
		GetExitCodeThread(m_ThreadCtx.m_hThread, (LPDWORD)&m_ThreadCtx.m_dwExitCode);
	}
	
	DWORD rtn = m_ThreadCtx.m_dwExitCode;

	m_SynchObject.Unlock();

	return rtn;
}

inline deThread::eStatus deThread::GetStatus(void) const
{
	return m_Status;
}

//--------------------------------------------------------------------
// AttachProcess( WINAPI * ptr )
//--------------------------------------------------------------------
void deThread::AttachProcess( ActiveProcess ptr, void * param )
{
	m_SynchObject.Lock(this);
	
	m_ThreadCtx.m_AttachedProcess		= ptr;
	m_ThreadCtx.m_ProcessParam			= param;
	SetEvent( m_ThreadCtx.m_ProcessChangeEvent );

	m_SynchObject.Unlock();
}

//--------------------------------------------------------------------
// DetachProcess(void)
//--------------------------------------------------------------------
void deThread::DetachProcess(void)
{
	m_SynchObject.Lock(this);
	
	m_ThreadCtx.m_AttachedProcess		= NULL;
	SetEvent( m_ThreadCtx.m_ProcessChangeEvent );

	m_SynchObject.Unlock();
}

//--------------------------------------------------------------------
// DWORD Run( void * )
//--------------------------------------------------------------------
DWORD WINAPI deThread::Run( LPVOID param )
{ 
	m_SynchObject.Lock( NULL );

	deThread *		pParent		= reinterpret_cast<deThread*>(param);
	bool			bStop		= false;
	DWORD			dwRtn		= 0;
	int				iNumEvents	= deThread::STATUS_MAX_NUMBER;
	HANDLE  		Events[deThread::STATUS_MAX_NUMBER+1];
	ActiveProcess 	foo			= pParent->m_ThreadCtx.m_AttachedProcess;
	void		*	foo_data	= pParent->m_ThreadCtx.m_ProcessParam;

	deThread::eStatus		status = deThread::STATUS_UNKNOWN;
	deThread::ThreadContext	thrCtx;

	memcpy( &Events,  pParent->m_ThreadCtx.m_Events, sizeof(HANDLE)*iNumEvents );
	memcpy( &thrCtx, &pParent->m_ThreadCtx, sizeof(deThread::ThreadContext) );

	// and the last event is for a process ptr change
	Events[deThread::STATUS_MAX_NUMBER] = pParent->m_ThreadCtx.m_ProcessChangeEvent;
	iNumEvents++;

	m_SynchObject.Unlock();

	while( !bStop )
	{
		dwRtn = WaitForMultipleObjects( iNumEvents, Events, false, 5000 );

		if( dwRtn != WAIT_TIMEOUT )
		{
			// we triggered an event
			status = (deThread::eStatus)(dwRtn-WAIT_OBJECT_0);

			if( status == STATUS_RUN )
			{
				if( foo )
					foo( foo_data );
				pParent->Sleep();
			}
			else if( status == STATUS_STOP )
			{
				// stop the while loop
				bStop = true;
			}
			else if( status == STATUS_WAKEUP )
			{
				// we woke up, now run
				if( foo )
					foo( foo_data );
				pParent->Sleep();
			}
			else if( status == STATUS_DEAD )
			{
				// we should be dead already...
				bStop = true;
			}
			else if( status == STATUS_MAX_NUMBER )
			{
				// this is the code for a change in process ptr
				m_SynchObject.Lock( NULL );
				foo			= pParent->m_ThreadCtx.m_AttachedProcess;
				foo_data	= pParent->m_ThreadCtx.m_ProcessParam;
				m_SynchObject.Unlock();
			}
		}
		else
		{
			// we timed out, keep waiting
		}
	}

	// tell everyone this thread is dead!
	SetEvent( Events[deThread::STATUS_DEAD] );

	return 1;
}