//
//  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:   7kOutBoundMemory.cpp
//
//  Project:    6046
//
//  Author(s):  W. Arcus -- based on code supplied by Patrik Moberg.
//
//  Purpose:    
//
//  Notes:      
//

#include "StdAfx.h"
#include "7kOutBoundMemory.h"

#include "..\..\..\Utils\NetUtils\7kProtocol.h"
#include "..\..\..\Utils\NetUtils\SystemTime.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Module constants and definitions.

#define EVENTMODEAUTORESET      0
#define EVENTMODEMANUALRESET    1
#define EVENTMODE               EVENTMODEAUTORESET

namespace                                                                           // Begin anonymous namespace.
{

    const unsigned long ul7kMMFOutSize_c          = 70UL * 1024UL * 1024UL;         // 70 MBytes per 7k data center v1.10.

    LPCTSTR             lpsz7kOutBoundMMFName_c   = _T( "7kOutBoundMemory" );
    LPCTSTR             lpsz7kOutBoundMutexName_c = _T( "7kOutBoundMutex"  );
    LPCTSTR             lpsz7kOutBoundEventName_c = _T( "7kOutBoundEvent"  );

}                                                                                   // End anonymous namespace.

//////////////////////////////////////////////////////////////////////
// C7kOutBoundMemory class implementation.

C7kOutBoundMemory::C7kOutBoundMemory( void )
                  :C7kSharedMemory( ul7kMMFOutSize_c )
{
    __TRY
    {
        m_Critical.Enter();

        m_dwLastReadPoint = 0;
        m_ui64TotalRead   = 0;
    }
    __FINALLY
    {
        m_Critical.Leave();
    }
    __ENDFINALLY
}

C7kOutBoundMemory::~C7kOutBoundMemory( void )
{
    __TRY
    {
        m_Critical.Enter();

        m_dwLastReadPoint = 0;
        m_ui64TotalRead   = 0;
    }
    __FINALLY
    {
        m_Critical.Leave();
    }
    __ENDFINALLY
}

C7kOutBoundMemory::operator HANDLE ( void ) const
{
    return m_hEvent;
}

bool C7kOutBoundMemory::Open( void )
{
    return C7kSharedMemory::Open( lpsz7kOutBoundMMFName_c, lpsz7kOutBoundMutexName_c, lpsz7kOutBoundEventName_c );
}

bool C7kOutBoundMemory::IsDataAvailable( void ) const
{
    bool bBytesAvailable = false;       // Assume no new bytes are available for now.

    __TRY
    {
        m_Critical.Enter();

        ASSERT( m_hMutex != NULL );

        if ( m_hMutex == NULL )
        {
            bBytesAvailable = false;
            TRACE( _T( "C7kOutBoundMemory::IsDataAvailable(), m_hMutex is invalid.\n" ) );
        }
        else
        {
            DWORD               dwWritePoint    = 0UL;
            DWORD               dwWrapPoint     = 0UL;
            unsigned __int64    ui64TotalSize   = 0;

            ASSERT( m_hMutex != NULL );
            ASSERT( m_hTerminateMutexWaitEvent != NULL );

            HANDLE              ahWaitObjects[] = { m_hMutex, m_hTerminateMutexWaitEvent };
            const DWORD         dwEventCount    = DimOf_m( ahWaitObjects );
            ASSERT( dwEventCount == 2UL );

            if ( WaitForMultipleObjects( dwEventCount, &ahWaitObjects[ 0 ], FALSE, INFINITE ) == WAIT_OBJECT_0 )
            {
                __TRY
                {
                    if ( GetPositions( &dwWritePoint, &dwWrapPoint, &ui64TotalSize ) )
                    {
                        if ( ( ui64TotalSize > m_ui64TotalRead ) || (  m_dwLastReadPoint != dwWritePoint ) )
                        {
                            // Handle wrap-around as necessary.

                            if ( ( m_dwLastReadPoint == dwWrapPoint ) && ( dwWritePoint < dwWrapPoint ) )
                            {
                                m_dwLastReadPoint = 0UL;
                            }

                            // Check to see if there's data available to be read.

                            if ( m_dwLastReadPoint < dwWrapPoint )
                            {
                                bBytesAvailable = ( dwWritePoint <= dwWrapPoint );
                            }
                        }
                    }
                }
                __FINALLY
                {
                    ASSERT( m_hMutex != NULL );

                    if ( ! ReleaseMutex( m_hMutex ) )
                    {
                        TRACE( _T( "C7kOutBoundMemory::IsDataAvailable(), ReleaseMutex() failed, error code: %lu.\n" ), GetLastError() );
                    }
                }
                __ENDFINALLY
            }

            //TRACE( _T( "IsDataAvailable(), Write: %lu, Wrap: %lu, Delta: %ld, Last Read: %lu\n" ),  dwWritePoint,
            //                                                                                        dwWrapPoint,
            //                                                                                        static_cast<long>( dwWrapPoint - dwWritePoint ),
            //                                                                                        m_dwLastReadPoint );

        }
    }
    __FINALLY
    {
        m_Critical.Leave();
    }
    __ENDFINALLY

    return bBytesAvailable;
}

bool C7kOutBoundMemory::Retrieve7kRecord(   BYTE                *pbyRecordData,
                                            unsigned long       &rulBytesRead,
                                            unsigned long       &rulTimeStamp,
                                            const unsigned long &rulMaxBufferSize )
{
    bool bSuccess = false;      // Assume failure for now.

    rulBytesRead  = 0UL;

    __TRY
    {
        m_Critical.Enter();

        DWORD            dwWritePoint,
                         dwWrapPoint;

        unsigned __int64 ui64TotalSize;

        if ( pbyRecordData == NULL )
        {
            ThrowMessage_m( "pbyRecordData is NULL" );
        }
        else if ( m_hMutex == NULL )
        {
            ThrowMessage_m( "m_hMutex is NULL" );
        }
        else if ( m_hTerminateMutexWaitEvent == NULL )
        {
            ThrowMessage_m( "m_hTerminateMutexWaitEvent is NULL" );
        }
        else
        {
            HANDLE ahWaitObjects[] = { m_hMutex, m_hTerminateMutexWaitEvent };
            DWORD  dwEventCount    = DimOf_m( ahWaitObjects );
            ASSERT( dwEventCount == 2UL );

            if ( WaitForMultipleObjects( dwEventCount, &ahWaitObjects[ 0 ], FALSE, INFINITE ) == WAIT_OBJECT_0 )
            {
                // Mutex guarded section is within the second try/catch block to ensure the mutex is released
                // even in the event of an exception.

                try
                {
                    rulTimeStamp = CSystemTime::GetTickCount();

                    if ( ! GetPositions( &dwWritePoint, &dwWrapPoint, &ui64TotalSize ) )
                    {
                        ThrowMessage_m( "GetPositions() failed" );
                    }
                    else if ( dwWritePoint == m_dwLastReadPoint )
                    {
                        bSuccess = true;
                    }
                    else
                    {
                        // Handle wrap-around if necessary.

                        if ( ( m_dwLastReadPoint == dwWrapPoint ) && ( dwWritePoint < dwWrapPoint ) )
                        {
                            m_dwLastReadPoint = 0;
                        }

                        const DWORD dwMemorySize = GetMemorySize();

                        if ( ( ui64TotalSize - m_ui64TotalRead ) >= dwMemorySize )
                        {
                            m_dwLastReadPoint = dwWritePoint;
                            m_ui64TotalRead   = ui64TotalSize;
                        }

                        // Read and discard the NF.

                        const DWORD dwNetworkFrameSize = sizeof( NETWORKFRAMEHEADER );

                        NETWORKFRAMEHEADER sNetworkFrameHeader;

                        if ( ! CopyData( reinterpret_cast<BYTE *>( &sNetworkFrameHeader ), dwNetworkFrameSize, m_dwLastReadPoint ) )
                        {
                            ThrowMessage_m( "Can't read 7k network frame header" );
                        }

                        if ( sNetworkFrameHeader.m_unVersion != un7kSocketProtocolVersion_c )
                        {
                            TRACE( _T( "INVALID NETWORK FRAME, Version: %u\n" ), sNetworkFrameHeader.m_unVersion );
                            m_dwLastReadPoint = dwWritePoint;
                            m_ui64TotalRead   = ui64TotalSize;
                            ThrowMessage_m( "Invalid network header version" );
                        }

                        const DWORD dwDataRecordFrameSize = sizeof( DATARECORDFRAME );

                        if ( ! CopyData( &pbyRecordData[ 0 ], dwDataRecordFrameSize, m_dwLastReadPoint + dwNetworkFrameSize ) )
                        {
                            ThrowMessage_m( "Can't read 7k header" );
                        }

                        const DATARECORDFRAME * const pDRF = reinterpret_cast<const DATARECORDFRAME *>( &pbyRecordData[ 0 ] );

                        if ( ! pDRF->IsValid() )
                        {
                            TRACE( _T( "INVALID RECORD: %lu\n" ), pDRF->m_ulRecordType );
                            //ThrowMessage_m( "Invalid 7k header detected at read point" );
                        }

                        if ( pDRF->m_ulSize > dwMemorySize )
                        {
                            m_dwLastReadPoint = dwWritePoint;
                            m_ui64TotalRead   = ui64TotalSize;
                            ThrowMessage_m( "Buffer overrun" );
                        }

                        if ( pDRF->m_ulSize > rulMaxBufferSize )
                        {
                            TRACE( _T( "pbyRecordData is too small for the record\n" ) );
                        }

                        if ( ! CopyData( &pbyRecordData[ dwDataRecordFrameSize ],
                                            pDRF->m_ulSize - dwDataRecordFrameSize,
                                                m_dwLastReadPoint + dwNetworkFrameSize + dwDataRecordFrameSize ) )
                        {
                            ThrowMessage_m( "Can't read 7k record data" );
                        }

                        // Move the read pointers.

                        m_dwLastReadPoint += ( pDRF->m_ulSize + dwNetworkFrameSize );
                        m_ui64TotalRead   += ( pDRF->m_ulSize + dwNetworkFrameSize );

                        if ( ( m_dwLastReadPoint == dwWrapPoint ) && ( dwWritePoint < dwWrapPoint ) )
                        {
                            m_dwLastReadPoint = 0;
                        }

                        // Finally, set the number of bytes 7k record bytes read thus indicating to clients 
                        // that a 7k record has been retrieved.

                        rulBytesRead = pDRF->m_ulSize;

                        bSuccess = C7kProtocol::IsValid7kRecord( pbyRecordData, rulBytesRead );

                        if ( ! bSuccess )
                        {
                            rulBytesRead = 0UL;
                            TRACE( _T( "C7kOutBoundMemory::Retrieve7kRecord(), 7k record is invalid !\n" ) );
                        }
                    }
                }
                catch ( LPCTSTR lpszMessage )
                {
                    bSuccess = false;
                    TRACE( _T( "C7kOutBoundMemory::Retrieve7kRecord(), LEVEL 2: %s.\n" ), lpszMessage );
                }
                catch ( ... )
                {
                    bSuccess = false;
                    TRACE( _T( "C7kOutBoundMemory::Retrieve7kRecord(), Unspecified exception caught (LEVEL 2)\n" ) );
                }

                ASSERT( m_hMutex != NULL );

                if ( ! ReleaseMutex( m_hMutex ) )
                {
                    TRACE( _T( "C7kOutBoundMemory::Retrieve7kRecord(), ReleaseMutex() failed, error code: %lu.\n" ), GetLastError() );
                }
            }
        }
    }
    __FINALLY
    {
        m_Critical.Leave();
    }
    __ENDFINALLY

    return bSuccess;
}

bool C7kOutBoundMemory::ResetEvent( void ) const
{
    // Nothing to do for now since the 7k sonar is using auto-reset events.
    // This may change in the future, however, to use manual reset events per disabled code.

#if ( defined ( EVENTMODE ) && ( EVENTMODE == EVENTMODEMANUALRESET ) )

    if ( m_hEvent != NULL )
    {
        return ( ::ResetEvent( m_hEvent ) != FALSE );
    }

    return false;

#else

    return true;

#endif
}

bool C7kOutBoundMemory::DoesExist( void )
{
    // Static member to pre-test for existance of the 7kCenter running.

    bool    bExists = false;
    HANDLE  hEvent  = NULL;

    __TRY
    {
        hEvent = OpenEvent( EVENT_ALL_ACCESS, TRUE, lpsz7kOutBoundEventName_c );
        bExists = ( hEvent != NULL );
    }
    __FINALLY
    {
        if ( hEvent != NULL )
        {
            CloseHandle( hEvent );
            hEvent = NULL;
        }
    }
    __ENDFINALLY

    return bExists;
}

bool C7kOutBoundMemory::Write( const BYTE *pbyData, const DWORD &rdwSize )
{
    // THIS ROUTINE IS CURRENTLY PROHIBITED (AND UNTESTED) THUS DISABLED. ACCORDINGLY, IT RETURNS A FAIL CODE,
    // 6046 isn't permited to write to the outbound 7k sonar MMF. So this method
    // is private until this policy is relaxed and the need to do so arises.

    bool bSuccess = false;          // Assume failure for now.

#if 1

    UNREFERENCED_PARAMETER( pbyData );
    UNREFERENCED_PARAMETER( rdwSize );

#else
    
    ASSERT( m_pbyMMFMemory != NULL );

    if ( ( rdwSize <= ( m_dwTotalMMFSize - m_dwHeaderSize ) ) && ( m_psMMFHeader != NULL ) )
    {
        ASSERT( m_hMutex != NULL );

        if ( ( m_hMutex == NULL ) || ( m_hEvent == NULL ) )
        {
            bSuccess = false;
            TRACE( _T( "C7kSharedMemory::Write(), m_hMutex and/or m_hEvent are NULL\n" ) );
        }
        else
        {
            ASSERT( m_hMutex != NULL );
            ASSERT( m_hTerminateMutexWaitEvent != NULL );

            HANDLE ahWaitObjects[] = { m_hMutex, m_hTerminateMutexWaitEvent };
            DWORD  dwEventCount    = DimOf_m( ahWaitObjects );

            if ( WaitForMultipleObjects( dwEventCount, &ahWaitObjects[ 0 ], FALSE, INFINITE ) == WAIT_OBJECT_0 )
            {
                __TRY
                {
                    DWORD dwWritePosition = m_psMMFHeader->dwWrite;

                    // Memory wrap ?

                    if ( m_psMMFHeader->dwWrite + rdwSize >= m_psMMFHeader->dwSize )
                    {
                        m_psMMFHeader->dwWrap = m_psMMFHeader->dwWrite;
                        dwWritePosition = m_dwHeaderSize;
                    }

                    // Copy the data to the MMF.
    
                    memcpy( &m_pbyMMFMemory[ dwWritePosition ], pbyData, rdwSize );
    
                    // Set write pointer and total size.

                    m_psMMFHeader->dwWrite = dwWritePosition + rdwSize;
                    m_psMMFHeader->uliTotalSize.QuadPart += rdwSize;

                    bSuccess = true;
                }
                __FINALLY
                {
                    ASSERT( m_hMutex != NULL );

                    if ( m_hMutex != NULL )
                    {
                        if ( ! ReleaseMutex( m_hMutex ) )
                        {
                            TRACE( _T( "C7kOutBoundMemory::Write(), ReleaseMutex() failed, error code: %lu.\n" ), GetLastError() );
                        }
                    }
                }
                __ENDFINALLY

                if ( bSuccess )
                {
                    ASSERT( m_hEvent != NULL );
                    PulseEvent( m_hEvent );
                }
            }
        }
    }

#endif

    return bSuccess;
}

