//
//  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:   ClientConnections.cpp
//
//  Project:    6046.
//
//  Author(s):  W. Arcus
//
//  Purpose:    
//
//  Notes:
//

#include "StdAfx.h"
#include "ClientConnections.h"

#include <vector>

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// CClientConnections implementation. 

using std::find;
using std::vector;

CClientConnections::CClientConnections  (   const unsigned long &rulMaxMessageLength,
                                            const unsigned int  &ruiListenPort,
                                            PFN_DATACALLBACK     pfnDataCallback,
                                            void *               pvCallbackParam )

                   :m_ulMaxMessageLength(   rulMaxMessageLength ),

                    m_uiListenPort      (   ruiListenPort )
{
    try
    {
        m_bInitialized    = false;

        m_pfnDataCallback = pfnDataCallback;
        m_pvCallbackParam = pvCallbackParam;

        __TRY
        {
            m_ClientAccessGuard.Enter();
            m_ClientList.clear();
        }
        __FINALLY
        {
            m_ClientAccessGuard.Leave();
        }
        __ENDFINALLY

        if ( ( m_pSocketServer = new CSocketServer( SocketRxHandler, this, m_ulMaxMessageLength, m_uiListenPort ) ) == NULL )
        {
            throw _T( "m_pSocketServer is NULL" );
        }

        if ( ! m_pSocketServer->IsInitialized() )
        {
            throw _T( "m_pSocketServer is not initialized" );
        }

        m_WatchThread.Parent( this );

        m_bInitialized = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        m_bInitialized = false;
        TRACE( _T( "CClientConnections::CClientConnections(), %s\n" ), lpszMessage );
        ASSERT( false );
    }
    catch ( ... )
    {
        m_bInitialized = false;
        TRACE( _T( "CClientConnections::CClientConnections(), unspecified exception caught\n" ) );
        ASSERT( false );
    }
}

CClientConnections::~CClientConnections( void )
{
    __TRY
    {
        if ( m_pSocketServer != NULL )
        {
            delete m_pSocketServer;
        }

        __TRY
        {
            m_ClientAccessGuard.Enter();
            m_ClientList.clear();
        }
        __FINALLY
        {
            m_ClientAccessGuard.Leave();
        }
        __ENDFINALLY

    }
    __FINALLY
    {
        m_pfnDataCallback = NULL;
        m_pvCallbackParam = NULL;
        m_pSocketServer   = NULL;
        m_bInitialized    = false;
    }
    __ENDFINALLY
}

bool CClientConnections::IsInitialized( void ) const
{
    return m_bInitialized;
}

bool CClientConnections::Send(  const int           &riSocketIndex,
                                const BYTE          *pbyRecord,
                                const unsigned long &rulBytes )
{
    // Sends a 7k record on the specified socket.

    bool bSuccess = false;                              // Assume failure for now.

    ASSERT( m_pSocketServer != NULL );

    if ( m_pSocketServer == NULL )
    {
        bSuccess = false;
        ASSERT( false );
    }
    else if ( rulBytes == 0UL )
    {
        bSuccess = true;                                // Nothing to do.
    }
    else if ( pbyRecord == NULL )
    {
        bSuccess = false;
        ASSERT( false );
    }
    else
    {
        bSuccess = m_pSocketServer->Send( const_cast<BYTE *>( pbyRecord ), rulBytes, riSocketIndex );
    }

    return bSuccess;
}

bool CClientConnections::Disconnect( const int &riSocketIndex )
{
    __TRY
    {
        m_ClientAccessGuard.Enter();

        ConnectedClientListIterator_t pClient;

        if ( ClientFromSocketIndex( riSocketIndex, pClient ) )
        {
            // Add any per client clean up/invokations here as necessary.

            m_ClientList.erase( pClient );
        }
    }
    __FINALLY
    {
        m_ClientAccessGuard.Leave();

        if ( m_pSocketServer != NULL )
        {
            m_pSocketServer->DestroySocket( riSocketIndex );
        }
    }
    __ENDFINALLY

    return true;
}

///////////////////////////////////////////////////////////////////////////////
// Private members.

void CClientConnections::SocketRxHandler(   BYTE   *pbyData,
                                            PVOID   lpvParam,
                                            ULONG   ulNumBytes,
                                            INT     iSocketIndex,
                                            ULONG   ulTimeStamp,
                                            INT     iReasonForCallback )
{
    CClientConnections *pthis = static_cast<CClientConnections *>( lpvParam );

    if ( pthis == NULL )
    {
        TRACE( _T( "CClientConnections::SocketRxHandler(), pthis is NULL\n" ) );
        ASSERT( false );
    }
    else switch ( iReasonForCallback )
    {
        case callbackConnect:

            if ( ! pthis->Connect( iSocketIndex ) )
            {
                TRACE( _T( "CClientConnections::SocketRxHandler(), pthis->Connect() failed.\n" ) );
            }

            break;

        case callbackDisconnect:

            if ( ! pthis->PostDisconnect( iSocketIndex ) )
            {
                TRACE( _T( "CClientConnections::SocketRxHandler(), pthis->PostDisconnect() failed.\n" ) );
            }

            break;

        case callbackData:

            if ( ! pthis->Dispatch( pbyData, ulNumBytes, ulTimeStamp, iSocketIndex ) )
            {
                TRACE( _T( "CClientConnections::SocketRxHandler(), pthis->Dispatch() failed.\n" ) );
            }

            break;

        default:

            TRACE( _T( "CClientConnections::SocketRxHandler(), unhandled callback\n" ) );
            break;
    }
}

inline
bool CClientConnections::Connect( const int &riSocketIndex )
{
    __TRY
    {
        m_ClientAccessGuard.Enter();
        m_ClientList.push_back( CConnectedClient( riSocketIndex ) );
    }
    __FINALLY
    {
        m_ClientAccessGuard.Leave();
    }
    __ENDFINALLY

    return true;
}

inline
bool CClientConnections::PostDisconnect( const int &riSocketIndex )
{
    bool bSuccess = false;

    try
    {
        bSuccess = ( m_WatchThread.PostThreadMessage( CClientWatchThread::messageDisconnect, static_cast<WPARAM>( riSocketIndex ) ) != FALSE );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CClientConnections::Disconnect(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

inline
bool CClientConnections::Dispatch(  const BYTE          *pbyData,
                                    const unsigned long &rulNumBytes,
                                    const unsigned long &rulTimeStamp,
                                    const int           &riSocketIndex )
{
    DBG_UNREFERENCED_PARAMETER( riSocketIndex );

    try
    {
        if ( ( pbyData != NULL ) && ( m_pfnDataCallback != NULL ) )
        {
            ASSERT( m_pvCallbackParam != NULL );
            ( m_pfnDataCallback ) ( m_pvCallbackParam, pbyData, rulNumBytes, rulTimeStamp );
        }
    }
    catch ( ... )
    {
        TRACE( _T( "CClientConnections::Dispatch(), Unspecified exception caught.\n" ) );
    }

    return true;
}

inline 
bool CClientConnections::ClientFromSocketIndex( const int                     &riSocketIndex,
                                                ConnectedClientListIterator_t &rpClientItem )
{
    // Caution: this member must be protected with the m_ClientAccessGuard critical section since
    // it doesn't use it internally to safeguarde the m_ClientList object.

    rpClientItem = m_ClientList.end();

    try
    {
        rpClientItem = find( m_ClientList.begin(), m_ClientList.end(), riSocketIndex );
    }
    catch ( ... )
    {
        rpClientItem = m_ClientList.end();
    }

    return ( rpClientItem != m_ClientList.end() );
}

bool CClientConnections::SendToAllClients(  const BYTE          *pbyData,
                                            const unsigned long &rulSize )
{
    bool bSuccess = false;

    try
    {
        try
        {
            m_ClientAccessGuard.Enter();

            bSuccess = true;

            ConnectedClientListIterator_t pClient;

            for ( pClient = m_ClientList.begin(); pClient != m_ClientList.end(); pClient++ )
            {
                const int iSocketIndex = pClient->SocketIndex();

                if ( ! Send( iSocketIndex, pbyData, rulSize ) )
                {
                    TRACE( _T( "Socket index %s failed\n" ), iSocketIndex );
                    bSuccess = false;
                    continue;
                }
            }
        }
        catch ( ... )
        {
            bSuccess = false;
        }

        m_ClientAccessGuard.Leave();

        if ( ! bSuccess )
        {
            ThrowMessage_m( "Failed trying to transmit data over socket" );
        }
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CClientConnections::SendToAllClients(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CClientConnections::SendToAllClients(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

///////////////////////////////////////////////////////////////////////////////
// CClientConnections::CClientWatchThread internal helper.

CClientConnections::CClientWatchThread::CClientWatchThread( void )
{
}

CClientConnections::CClientWatchThread::~CClientWatchThread( void )
{
    if ( ! CBaseThread::TerminateThread( 1000 ) )
    {
       TRACE( _T( "CClientConnections::~CClientConnections(), m_WatchThread.TerminateThread() failed.\n" ) );
    }
}

BOOL CClientConnections::CClientWatchThread::ProcessMessage( MSG *psMsg )
{
    BOOL bMessageProcessed = FALSE;

    switch ( psMsg->message )
    {
        case messageDisconnect:

            bMessageProcessed = true;

            if ( m_pParent == NULL )
            {
                TRACE( _T( "CClientConnections::CClientWatchThread::ProcessMessage(), m_pParent is NULL.\n" ) );
            }
            else if ( ! m_pParent->Disconnect( static_cast<int>( psMsg->wParam ) ) )
            {
                TRACE( _T( "CClientConnections::CClientWatchThread::ProcessMessage(), m_pParent->Disconnect() failed.\n" ) );
            }

            break;

        default:

            bMessageProcessed = CBaseThread::ProcessMessage( psMsg );
            break;
    }

    return bMessageProcessed;
}

