//
//  Copyright © 2004, RESON Inc. All Rights Reserved.
//
//  No part of this file may be reproduced or transmitted in any form or by
//  any means, electronic or mechanical, including photocopy, recording, or
//  information storage or retrieval system, without permission in writing
//  from RESON Inc.
//
//  Filename:   SecondaryHandling.cpp
//
//  Project:    6046
//
//  Author(s):  W. Arcus
//
//  Purpose:    
//
//  Notes:      
//

#pragma warning( disable : 4786 )                                               // Disable this little gem from Microsoft. Keep
                                                                                // before the first header is included.

#include "StdAfx.h"
#include "SecondaryHandling.h"
#include "Logger.h"

#include "..\Utils\NetUtils\SystemTime.h"

namespace                                                                       // Begin anonymous namespace.
{
    const unsigned long ulMax7kRecordSize_c     = 8UL * 1024UL * 1024UL;        // 8 MBytes.

    enum EEVENTLISTINDEX
    {
        eventTerminate              = 0,
        eventProcessQueuedData,

        // ... insert new items before here to maintain an accurate count.

        eventMaxEvents
    };
}                                                                               // End anonymous namespace.

//////////////////////////////////////////////////////////////////////
// CSecondaryHandling class implementation.

CSecondaryHandling::CSecondaryHandling  (   const CLogger  *pLogger )

                   :CBaseThread         (   INFINITE,
                                            THREAD_PRIORITY_NORMAL ),

                    m_ulMaxBufferSize   (   ulMax7kRecordSize_c ),
                    m_pLogger           (   pLogger )

{
    ASSERT( m_pLogger != NULL );

    m_ulNextRecordToRead = 0UL;
    m_pSensorData        = NULL;
    m_pby7kRecord        = NULL;
    m_bProcessingEnable  = false;

    try
    {
        if ( ( m_hTerminateEvent = CreateEvent( NULL, TRUE, FALSE, NULL ) ) == NULL )
        {
            TRACE( _T( "" ) );
        }

        if ( ( m_pby7kRecord = new BYTE [ m_ulMaxBufferSize ] ) == NULL )
        {
            ThrowMessage_m( "Can't allocate storage for secondary processing buffer" );
        }

        ASSERT( m_pby7kRecord != NULL );

        if ( ( m_pSensorData = new CPLCReadSensorData() ) == NULL )
        {
            ThrowMessage_m( "Can't construct sensor data reader" );
        }

        ASSERT( m_pSensorData != NULL );

        if ( ! m_pSensorData->IsInitialized() )
        {
            ThrowMessage_m( "Sensor data reader failed to correctly initialize" );
        }

        if ( ResumeThread() == m_dwResumeFailCode )
        {
            ThrowMessage_m( "ResumeThread() failed." );
        }
    }
    catch ( LPCTSTR lpszMessage )
    {
        TRACE( _T( "CSecondaryHandling::CSecondaryHandling(), %s.\n" ), lpszMessage );
    }
    catch ( ... )
    {
        TRACE( _T( "CSecondaryHandling::CSecondaryHandling(), unspecified exception caught.\n" ) );
    }
}

CSecondaryHandling::~CSecondaryHandling( void )
{
    __TRY
    {
        ThreadProcessing( false );
        
        if ( m_hTerminateEvent != NULL )
        {
            SetEvent( m_hTerminateEvent );
        }

        if ( ! CBaseThread::WaitForThreadTermination( 2000 ) )
        {
            TRACE( _T( "CSecondaryHandling::~CSecondaryHandling(), TerminateThread() timed out or failed.\n" ) );
        }

        if ( m_hTerminateEvent != NULL )
        {
            CloseHandle( m_hTerminateEvent );
            m_hTerminateEvent = NULL;
        }
    }
    __FINALLY
    {
        if ( m_pSensorData != NULL )
        {
            delete m_pSensorData;
            m_pSensorData = NULL;
        }

        if ( m_pby7kRecord != NULL )
        {
            delete [] m_pby7kRecord;
            m_pby7kRecord = NULL;
        }
    }
    __ENDFINALLY
}

void CSecondaryHandling::ThreadProcessing( const bool &rbEnable )
{
    __TRY
    {
        m_FlagsCriticalSection.Enter();
        m_bProcessingEnable = rbEnable;
    }
    __FINALLY
    {
        m_FlagsCriticalSection.Leave();
    }
    __ENDFINALLY
}

///////////////////////////////////////////////////////////////////////////////
// Private members.

void CSecondaryHandling::WatchCycle( void )
{
    // Base class override for main thread processing.

    __try
    {
        // Create thread objects as appropriate.

        if ( ! InitializeThreadObjects() )
        {
            TRACE( _T( "CSecondaryHandling::WatchCycle(), InitializeThreadObjects() failed\n" ) );
            __leave;
        }

        ASSERT( m_pSensorData != NULL );

        HANDLE  ahEventList[] = {
                                    m_hTerminateEvent,                                  // Thread termination event handle.
                                    m_pSensorData->GetAvailableDataEvent()              // Pool data available for reading.
                                };

        const DWORD dwEventCount = static_cast<DWORD>( DimOf_m( ahEventList ) );

        // Id count should match the number of handles in the array; see EEVENTLISTINDEX enum block at top of this file.

        ASSERT( dwEventCount == static_cast<DWORD>( eventMaxEvents ) );

        while ( IsActive() )
        {
            // Block this thread and wait for any significant event or message.

            DWORD dwSignalState = ::MsgWaitForMultipleObjects(  dwEventCount,           // Number of waitable kernel objects (events, timers etc) to wait for.
                                                                &ahEventList[ 0 ],      // 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 in effect:
            //
            //  1) Termination request.
            //  2) Queued thread messages.
            //  3) Win32 kernel object events.
            //  4) Timeout processing (not relevant here).

            if ( IsInactive() )
            {
                break;
            }
            else if ( dwSignalState == ( WAIT_OBJECT_0 + dwEventCount ) )
            {
                PumpMessages();
            }
            else if ( ( dwSignalState >= WAIT_OBJECT_0 ) && ( dwSignalState <= ( WAIT_OBJECT_0 + dwEventCount - 1UL ) ) )
            {
                ProcessEvents( &ahEventList[ 0 ], dwSignalState - WAIT_OBJECT_0 );
            }
            else if ( dwSignalState == WAIT_TIMEOUT )
            {
                TRACE( _T( "CSecondaryHandling::WatchCycle(), Timeout detected.\n" ) );
            }
            else if ( ( dwSignalState >= WAIT_ABANDONED_0 ) && ( dwSignalState <= ( WAIT_ABANDONED_0 + dwEventCount - 1UL ) ) )
            {
                TRACE( _T( "CSecondaryHandling::WatchCycle(), Abandoned mutex detected.\n" ) );
            }
            else
            {
                TRACE( _T( "CSecondaryHandling::WatchCycle(), Unknown event ...!!!!\n" ) );
            }
        }
    }
    __finally
    {
        // Ensure we always attempt to terminate thread objects.

        if ( ! TerminateThreadObjects() )
        {
            TRACE( _T( "CSecondaryHandling::WatchCycle(), TerminateThreadObjects() failed" ) );
        }
    }

    TRACE( _T( "CSecondaryHandling::WatchCycle(), About to terminate \n" ) );
}

BOOL CSecondaryHandling::ProcessMessage( MSG *psMsg )
{
    //  For now, there are no specific messages to handle so defer handling to our base class.

    //  If we later wish to handle messages then we need to do the following:
    //
    //  a)  Use psMsg->message to determine the message type (from the call to PostThreadMessage()).
    //  b)  Handle the message and set the return code to TRUE.
    //  c)  Any message we don't care about, defer to our base class handler CBaseThread::ProcessMessage().
    //  d)  Return false ONLY if an abnormal condition occurs (c.f. CBaseThread for consequences of doing so).

    return CBaseThread::ProcessMessage( psMsg );
}

inline
bool CSecondaryHandling::InitializeThreadObjects( void )
{
    bool bSuccess = false;

    try
    {
        bSuccess = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CSecondaryHandling::InitializeThreadObjects(), %s.\n"), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CSecondaryHandling::InitializeThreadObjects(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

inline
bool CSecondaryHandling::TerminateThreadObjects( void )
{
    return true;
}

inline
void CSecondaryHandling::ProcessEvents( const HANDLE *phEvents, const DWORD &rdwHandleIndex )
{
    ASSERT( phEvents[ rdwHandleIndex ] != NULL );

    if ( ( rdwHandleIndex < static_cast<DWORD>( eventMaxEvents ) ) &&
         ( phEvents[ rdwHandleIndex ] != NULL                    )  )
    {
        switch ( rdwHandleIndex )
        {
            case eventTerminate:

                SetActive( FALSE );
                break;

            case eventProcessQueuedData:

                if ( ! ProcessQueuedSensorData() )
                {
                    TRACE( _T( "CSecondaryHandling::ProcessEvents(), ProcessQueuedSensorData() failed.\n" ) );
                }

                break;

            default:
                break;
        }
    }
}

inline
bool CSecondaryHandling::ProcessQueuedSensorData( void )
{
    bool bSuccess           = false;
    bool bProcessingEnable  = false;

    __TRY
    {
        m_FlagsCriticalSection.Enter();
        bProcessingEnable = m_bProcessingEnable;
    }
    __FINALLY
    {
        m_FlagsCriticalSection.Leave();
    }
    __ENDFINALLY

    if ( ! bProcessingEnable )
    {
        bSuccess = true;
    }
    else
    {
        try
        {
            ASSERT( m_pby7kRecord != NULL );
            ASSERT( m_pSensorData != NULL );

            unsigned long ulTotalRecordBytes = 0UL;
            unsigned long ulTimeStamp        = 0UL;
            int           iSensorIndex       = 0;

            bSuccess = true;

            while ( IsActive() && m_pSensorData->Read( m_ulNextRecordToRead, m_pby7kRecord, ulTotalRecordBytes, iSensorIndex, ulTimeStamp, m_ulMaxBufferSize ) )
            {
                //TRACE( _T( "Sensor data record read, Next: %lu, Total bytes: %lu, SensorIndex: %d\n" ), m_ulNextRecordToRead, ulTotalRecordBytes, iSensorIndex );

                if ( C7kProtocol::IsValid7kRecord( m_pby7kRecord, ulTotalRecordBytes ) )
                {
                    if ( ! DispatchToSubscribingClients( m_pby7kRecord, ulTotalRecordBytes, ulTimeStamp, iSensorIndex ) )
                    {
                        TRACE( _T( "CSecondaryHandling::ProcessQueuedSensorData(), DispatchToSubscribingClients() failed!!!\n" ) );
                        bSuccess = false;
                    }

                    if ( ! HandleProcessing( m_pby7kRecord, ulTotalRecordBytes, ulTimeStamp, iSensorIndex ) )
                    {
                        TRACE( _T( "CSecondaryHandling::ProcessQueuedSensorData(), HandleProcessing() failed!!!\n" ) );
                        bSuccess = false;
                    }
                }
                else
                {
                    bSuccess = false;
                    TRACE( _T( "CSecondaryHandling::ProcessQueuedSensorData(), Sensor data record is not a valid 7k record!!\n" ) );
                }
            }
        }
        catch ( LPCTSTR lpszMessage )
        {
            bSuccess = false;
            TRACE( _T( "CSecondaryHandling::ProcessQueuedSensorData(), %s.\n" ), lpszMessage );
        }
        catch ( ... )
        {
            bSuccess = false;
            TRACE( _T( "CSecondaryHandling::ProcessQueuedSensorData(), Unspecified exception caught.\n" ) );
        }
    }

    return bSuccess;
}

inline
bool CSecondaryHandling::DispatchToSubscribingClients(  const BYTE             *pbyData,
                                                        const unsigned long    &rulSize,
                                                        const unsigned long    &rulTimestamp,
                                                        const int              &riSensorIndex )
{
    bool bSuccess = false;

    ASSERT( m_pLogger != NULL );

    if ( m_pLogger != NULL )
    {
        try
        {
            bSuccess = const_cast<CLogger *>( m_pLogger)->DispatchToSubscribingClients( pbyData, rulSize, rulTimestamp, riSensorIndex );
        }
        catch ( ... )
        {
            bSuccess = false;
        }
    }
    
    return bSuccess;
}

inline
bool CSecondaryHandling::HandleProcessing(  const BYTE             *pbyData,
                                            const unsigned long    &rulSize,
                                            const unsigned long    &rulTimestamp,
                                            const int              &riSensorIndex )
{
    UNREFERENCED_PARAMETER( pbyData );
    UNREFERENCED_PARAMETER( rulSize );
    UNREFERENCED_PARAMETER( rulTimestamp );
    UNREFERENCED_PARAMETER( riSensorIndex );

    // Not implemented to date. QC and other processing to go in here.

    return true;
}


