//
//  Copyright (c) 2001, Reson, Inc. All Rights Reserved.
//
//  Filename:   CBaseThread.cpp
//
//  Project:    6046
//
//  Author(s):  W. Arcus
//
//  Purpose:    Class implementation file for CBaseThread class.
//
//  Notes:      
//

#include "StdAfx.h"

#include "BaseThread.h"

#include <string.h>                         // General string support.
#include <stdlib.h>

// Internal module constants.

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// CBaseThread class implementation -- starting with public methods.

CBaseThread::CBaseThread(   DWORD   dwDwellPeriod,
                            int     iPriority )

            :m_dwResumeFailCode( 0xffffffff ),
             m_dwMessageQueueTimeout( 100 )
{
    ManageDllState_m();

    m_bInitialized       = false;
    m_bActive            = FALSE;
    m_dwThreadID         = 0;
    m_pThread            = NULL;
    m_dwDwellPeriod      = dwDwellPeriod;
    m_hMessageQueueEvent = NULL;

    try
    {
        // Create an internal event to signal when the worker thread's message queue has been created
        // thus allowing PostThreadMessage() to succeed.

        m_hMessageQueueEvent = ::CreateEvent( NULL, TRUE, FALSE, NULL );

        ASSERT( m_hMessageQueueEvent != NULL );

        if ( m_hMessageQueueEvent == NULL )
        {
            TRACE( _T( "CBaseThread::CBaseThread(), Can't create message queue event" ) );
        }

        m_pThread = ::AfxBeginThread( ThreadProc,                       // Thread procedure.
                                        this,                           // Parameter of the thread.
                                            iPriority,                  // Thread priority.
                                                0,                      // Use default stack size.
                                                    CREATE_SUSPENDED,   // Suspended initially.
                                                        NULL );         // Default security.

        if ( ( m_pThread != NULL ) && ( m_pThread->m_hThread != NULL ) )
        {
            // Initialize thread members.

            m_hThread       = m_pThread->m_hThread;
            m_dwThreadID    = m_pThread->m_nThreadID;

            m_bActive       = TRUE;
            m_bInitialized  = true;
        }
    }
    catch( ... )
    {
        m_dwThreadID   = 0;
        m_bActive      = FALSE;
        m_bInitialized = false;
        TRACE( "CBaseThread::CBaseThread(), Unknown exception caught\n" );
    }
}

CBaseThread::~CBaseThread( void )
{
    ManageDllState_m();

    m_bInitialized  = false;

    m_bActive       = FALSE;
    m_pThread       = NULL;
    m_dwThreadID    = 0;
    m_dwDwellPeriod = 0;
    m_hThread       = NULL;

    if ( m_hMessageQueueEvent != NULL )
    {
        ::CloseHandle( m_hMessageQueueEvent );
        m_hMessageQueueEvent = NULL;
    }
}

DWORD CBaseThread::ResumeThread( void )
{
    ManageDllState_m();

    if ( m_pThread != NULL )
    {
        return ( m_pThread->ResumeThread() );
    }

    return m_dwResumeFailCode;
}

bool CBaseThread::IsInitialized( void ) const
{
    ManageDllState_m();

    return m_bInitialized;
}

BOOL CBaseThread::PostThreadMessage(    UINT    uiMsg,
                                        WPARAM  wParam,
                                        LPARAM  lParam )
{
    ManageDllState_m();

    DWORD dwThreadID = ThreadID();

    if ( dwThreadID != 0 )
    {
        if ( ( m_hMessageQueueEvent != NULL ) && ( ::WaitForSingleObject( m_hMessageQueueEvent, m_dwMessageQueueTimeout ) != WAIT_TIMEOUT ) )
        {
            return ::PostThreadMessage( dwThreadID, uiMsg, wParam, lParam );
        }
    }

    return FALSE;
}

//////////////////////////////////////////////////////////////////////////////
// CBaseThread's protected and private members.

BOOL CBaseThread::TerminateThread( DWORD dwTimeOut )
{
    ManageDllState_m();

    BOOL bSuccess = TRUE;

    //TRACE( "In CBaseThread::TerminateThread() \n" );

    if ( m_pThread != NULL )
    {
        bSuccess = PostThreadMessage( threadMessageTerminate );

        if ( bSuccess && ( dwTimeOut != 0 ) )
        {
            bSuccess = WaitForThreadTermination( dwTimeOut );
        }

        m_hThread = NULL;
    }

    return bSuccess;
}

UINT CBaseThread::ThreadProc( LPVOID lpvParam )
{
    DWORD dwThreadID = 0UL;

    try
    {
        if ( lpvParam == NULL )
        {
            ThrowMessage_m( "lpvParam is NULL" );
        }

        CBaseThread *pthis = static_cast<CBaseThread *>( lpvParam );

        dwThreadID = pthis->ThreadID();

        // Firstly, force the system to create the thread's message queue so that subsequent PostThreadMessage()
        // won't fail. See MSDN documention for ::PostThreadMessage().

        MSG sMsg;

        ::PeekMessage( &sMsg, NULL, threadMessageNoOp, threadMessageNoOp, PM_NOREMOVE );

        // Signal the thread queue has been created.

        if ( pthis->m_hMessageQueueEvent != NULL )
        {
            ::SetEvent( pthis->m_hMessageQueueEvent );
        }

        // Execute the main thread handler procedure (an overrideable method).

        TRACE( "CBaseThread::ThreadProc(), Starting thread watch procedure (thread id: %lu (0x%x)).\n", dwThreadID, dwThreadID );

        pthis->WatchCycle();
    }
    catch ( LPCTSTR lpszMessage )
    {
        TRACE( _T( "CBaseThread::ThreadProc(), %s\n." ), lpszMessage );
    }
    catch ( ... )
    {
        TRACE( "CBaseThread::ThreadProc(), Unspecified exception caught\n" );
    }

    TRACE( "CBaseThread::ThreadProc(), Thread terminating (thread id: %lu (0x%x)).\n", dwThreadID, dwThreadID );

    return 0;
}

void CBaseThread::SetActive( BOOL bActive )
{
    ManageDllState_m();

    m_StateGuard.Enter();
    m_bActive = bActive;
    m_StateGuard.Leave();
}

BOOL CBaseThread::IsActive( void )
{
    ManageDllState_m();

    BOOL bActive;

    m_StateGuard.Enter();
    bActive = m_bActive;
    m_StateGuard.Leave();

    return bActive;
}

BOOL CBaseThread::IsInactive( void )
{
    ManageDllState_m();

    BOOL bInactive;

    m_StateGuard.Enter();
    bInactive = ! m_bActive;
    m_StateGuard.Leave();

    return bInactive;
}

void CBaseThread::PumpMessages( void )
{ 
    ManageDllState_m();

    MSG sMsg;

    while ( IsActive() && ::PeekMessage( &sMsg, NULL, 0, 0, PM_NOREMOVE ) )
    {
        // Check to see if it's one of our recognized messages. If it is, handle it otherwise,
        // just pump the messages in the normal manner.

        if ( ProcessMessage( &sMsg ) )
        {
            // Remove the handled message from the queue

            PeekMessage( &sMsg, NULL, 0, 0, PM_REMOVE );
        }
        else if ( ! m_pThread->PumpMessage() )
        {
            // An error occured during pumping - so terminate the worker thread.

            TRACE( "CBaseThread::PumpMessages(), Error pumping messages - thread terminating." );
            SetActive( FALSE );
            break;
        }
    }
}

BOOL CBaseThread::WaitForThreadTermination( DWORD dwTimeOut )
{
    ManageDllState_m();

    // Wait for the thread to terminate, if it hasn't already terminated.

    if ( m_hThread != NULL )
    {
        return ( WaitForSingleObject( m_hThread, dwTimeOut ) == WAIT_OBJECT_0 );
    }

    return true;
}

inline
DWORD CBaseThread::ThreadID( void ) const
{
    DWORD dwThreadID = 0UL;

    m_StateGuard.Enter();
    dwThreadID = m_dwThreadID;
    m_StateGuard.Leave();

    return dwThreadID;
}

//////////////////////////////////////////////////////////////////////////////
// Virtual methods that maybe overridden.

BOOL CBaseThread::ProcessMessage( MSG *psMsg )
{
    ManageDllState_m();

    // Any specific messages should be processed in the overridden form of this member
    // however, when a message is not explicitly handled by it, this base class should be
    // invoked to check for termination messages or other specific handling.

    BOOL bMessageProcessed = FALSE;

    switch ( psMsg->message )
    {
        case threadMessageTerminate:
        case WM_CLOSE:

            SetActive( FALSE );
            bMessageProcessed = TRUE;
            break;

        default:

            bMessageProcessed = FALSE;
            break;
    }

    return bMessageProcessed;
}

void CBaseThread::WatchCycle( void )
{
    ManageDllState_m();

    // This basic form just supports queued thread message processing.
    // Overriden forms of this member may incorporate other events etc by supplying an array
    // of handles to the MsgWaitForMultipleObjects (see commented sample below).

    DWORD       dwSignalState;
    const DWORD dwEventCount = 0;

    // Typically thread object creation and initialization is done here.


    while ( IsActive() )
    {
        // Block this thread and wait for any significant event.

        dwSignalState = ::MsgWaitForMultipleObjects(    dwEventCount,                   // Number of Win32 events to wait on.
                                                        NULL,//&m_hEventList[ 0 ],      // Event handle array.
                                                        FALSE,                          // Wait for any one of the specified event types.
                                                        m_dwDwellPeriod,                // Timeout processing period in ms.
                                                        QS_ALLINPUT );                  // Unblock on any message in the thread's queue.

        // The following priority scheme is implemented below:
        //
        //      1) Termination request              - signaled by a boolean flag.
        //      2) Win32 events                     - Not used in this virtual member.
        //      3) Queued thread messages           - E.g. Status and other thread messages.
        //      4) Timeout processing               - E.g. General health checking etc.

        if ( IsInactive() )
        {
            break;
        }
        else if ( ( dwEventCount  >  0 )             &&
                  ( dwSignalState >= WAIT_OBJECT_0 ) &&
                  ( dwSignalState <= ( WAIT_OBJECT_0 + dwEventCount - 1 ) ) )
        {
            // Handle input event objects here, triggered event is m_hEventList[ dwSignalState - WAIT_OBJECT_0 ]
        }
        else if ( dwSignalState == ( WAIT_OBJECT_0 + dwEventCount ) )
        {
            PumpMessages();
        }
        else if ( dwSignalState == WAIT_TIMEOUT )
        {
            // Handle time out processing here.
        }
        else
        {
            // Must be an abandoned mutex - meaningless to this base class member.
        }
    }

    // Typically thread object clean up is done here.
}

