//
//  Copyright © 2001 - 2002, 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:    Implements the CClientConnections class -- a container for the various
//              6046 remote socket connections as a linked list.
//
//  Notes:      1)  The member ClientFromSocketIndex() needs to be guarded externally
//                  for thread safe access.
//

#pragma warning( disable : 4786 )       // Disable this little gem from Microsoft. Keep
                                        // before the first header is included.

#include "StdAfx.h"
#include "ClientConnections.h"

#include "..\Utils\NetUtils\7kCommand.h"
#include "..\Utils\NetUtils\DynamicBuffer.h"

#include <vector>

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// CClientConnections implementation. 

CClientConnections::CClientConnections( const CLogger       *pLogger,
                                        const unsigned long &rulMaxMessageLength,
                                        const unsigned int  &ruiListenPort )

                   :m_pLogger( pLogger ),
                    m_ulMaxMessageLength( rulMaxMessageLength ),
                    m_uiListenPort( ruiListenPort )

{
    m_bInitialized = false;

    try
    {
        m_ClientAccessGuard.Enter();
        m_ClientList.clear();
        m_ClientAccessGuard.Leave();

        if ( m_pLogger == NULL )
        {
            throw _T( "m_pLogger is NULL" );
        }

        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_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 )
{
    m_bInitialized = false;

    __TRY
    {
        m_ClientAccessGuard.Enter();
        m_ClientList.clear();
        m_ClientAccessGuard.Leave();
    }
    __FINALLY
    {
        if ( m_pSocketServer != NULL )
        {
            delete m_pSocketServer;
            m_pSocketServer = NULL;
        }
    }
    __ENDFINALLY
}

bool CClientConnections::IsInitialized( void ) const
{
    return m_bInitialized;
}

bool CClientConnections::Send(  const int           &riSocketIndex,
                                const BYTE          *pbyRecord,
                                const unsigned long &rulBytes )
{
    // Used to send a pre-formatted 7k message to the client identified by its socket index.
    // Use overload below to format and send command otherwise.

    bool bSuccess = false;      // Assume failure for now.

    ASSERT( m_pSocketServer != NULL );

    if ( m_pSocketServer != NULL )
    {
        if ( rulBytes == 0UL )
        {
            bSuccess = true;
        }
        else if ( pbyRecord != NULL )
        {
            bSuccess = m_pSocketServer->Send( const_cast<BYTE *>(pbyRecord), rulBytes, riSocketIndex );
        }
        else
        {
            bSuccess = false;
        }
    }

    return bSuccess;
}

bool CClientConnections::Disconnect( const int &riSocketIndex )
{
    bool bSuccess = false;

    // Socket connection to client is about to be destroyed so firstly we unsubscribe to all messages for the
    // given socket object (identified by its socket index).

    m_ClientAccessGuard.Enter();

    try
    {
        ConnectedClientListIterator_t pClient;

        if ( ClientFromSocketIndex( riSocketIndex, pClient ) )
        {
            if ( ! pClient->UnsubscribeAll() )
            {
                TRACE( _T( "CClientConnections::Disconnect(), pClient->UnsubscribeAll() failed\n" ) );
            }

            m_ClientList.erase( pClient );
        }

        bSuccess = true;
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CClientConnections::Disconnect(), Unspecified exception caught\n" ) );
    }

    m_ClientAccessGuard.Leave();

    if ( m_pSocketServer != NULL )
    {
        m_pSocketServer->DestroySocket( riSocketIndex );
    }

    return bSuccess;
}

bool CClientConnections::SendAlarm( const EALARMID      &reAlarmId,
                                    const BYTE          *pbyData,
                                    const unsigned long &rulSize )
{
    bool bSuccess = false;

    try
    {
        m_ClientAccessGuard.Enter();

        try
        {
            bSuccess = true;

            ConnectedClientListIterator_t pClient;

            for ( pClient = m_ClientList.begin(); pClient != m_ClientList.end(); pClient++ )
            {
                if ( pClient->IsAlarmReportable( reAlarmId ) )
                {
                    const int iSocketIndex = pClient->SocketIndex();

                    if ( ! Send( iSocketIndex, pbyData, rulSize ) )
                    {
                        TRACE( _T( "CClientConnections::SendAlarm(), Socket index %s failed\n" ), iSocketIndex );
                        bSuccess = false;
                        continue;
                    }
                }
            }
        }
        catch ( ... )
        {
            bSuccess = false;
        }

        m_ClientAccessGuard.Leave();
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CClientConnections::SendToAllClients(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

bool CClientConnections::ReportActiveAlarms( const int &riSocketIndex, CAlarm &rAlarm )
{
    bool bSuccess = false;

    try
    {
        m_ClientAccessGuard.Enter();

        try
        {
            bSuccess = true;

            ConnectedClientListIterator_t pClient;

            if ( ! ClientFromSocketIndex( riSocketIndex, pClient ) )
            {
                ThrowMessage_m( "CClientFromSocketIndex() failed" );
            }

            std::vector<ALARMENTRY>             AlarmList;
            std::vector<ALARMENTRY>::iterator   pAlarm;

            if ( rAlarm.RetrieveAlarms( AlarmList ) )
            {
                bSuccess = true;

                if ( ! AlarmList.empty() )
                {
                    bSuccess = true;

                    CDynamicBuffer<BYTE> AlarmRecord( g_dwMaxAlarmMessageLength );

                    for ( pAlarm = AlarmList.begin(); pAlarm != AlarmList.end(); pAlarm++ )
                    {
                        if ( pClient->IsAlarmReportable( pAlarm->m_eAlarmId ) )
                        {
                            AlarmRecord.Reset();

                            if ( ! rAlarm.Set7kAlarmRecord( &(*pAlarm), AlarmRecord ) )
                            {
                                bSuccess = false;
                                TRACE( _T( "CClientConnections::ReportActiveAlarms(), Retrieve7kAlarmRecord() failed for socket index: %d\n" ), riSocketIndex );
                                continue;
                            }

                            if ( ! Send( riSocketIndex, AlarmRecord.GetAt( 0 ), sizeof( BYTE ) * AlarmRecord.Size() ) )
                            {
                                bSuccess = false;
                                TRACE( _T( "CClientConnections::ReportActiveAlarms(), Send failed for socket index: %d\n" ), riSocketIndex );
                                continue;
                            }
                        }
                    }
                }
            }
            else
            {
                bSuccess = false;
            }
        }
        catch ( LPCTSTR lpszMessage )
        {
            bSuccess = false;
            TRACE( _T( "CClientConnections::ReportActiveAlarms(), %s.\n"), lpszMessage );
        }
        catch ( ... )
        {
            bSuccess = false;
            TRACE( _T( "CClientConnections::ReportActiveAlarms(), Unspecified exception caught.\n" ) );
        }

        m_ClientAccessGuard.Leave();
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CClientConnections::ReportActiveAlarms(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

bool CClientConnections::AlarmMode( const int                      &riSocketIndex,  
                                    const EALARMID                 &reAlarmId,      
                                    const int                      &riAlarmAction )
{
    bool bSuccess = false;

    m_ClientAccessGuard.Enter();

    try
    {
        ConnectedClientListIterator_t pClient;

        if ( ClientFromSocketIndex( riSocketIndex, pClient ) )
        {
            bSuccess = pClient->AlarmMode( reAlarmId, riAlarmAction );
        }
    }
    catch ( ... )
    {
        bSuccess = false;
    }

    m_ClientAccessGuard.Leave();

    return bSuccess;
}

///////////////////////////////////////////////////////////////////////////////
// Private members.

void CClientConnections::SocketRxHandler(   BYTE   *pbyData,
                                            PVOID   lpvParam,
                                            ULONG   ulNumBytes,
                                            INT     iSocketIndex,
                                            ULONG   ulTimeStamp,
                                            INT     iReasonForCallback )
{
    bool                bSuccess = false;
    CClientConnections *pthis    = static_cast<CClientConnections *>(lpvParam);

    if ( pthis == NULL )
    {
        bSuccess = false;
        TRACE( _T( "CClientConnections::SocketRxHandler(), pthis is NULL\n" ) );
    }
    else
    {
        try
        {
            switch ( iReasonForCallback )
            {
                case callbackConnect:

                    bSuccess = pthis->Connect( iSocketIndex );
                    break;

                case callbackDisconnect:

                    bSuccess = pthis->PostDisconnect( iSocketIndex );
                    break;

                case callbackData:

                    bSuccess = pthis->Dispatch( pbyData, ulNumBytes, ulTimeStamp, iSocketIndex );
                    break;

                default:

                    TRACE( _T( "CClientConnections::SocketRxHandler(), unhandled callback\n" ) );
                    bSuccess = true;
                    break;
            }
        }
        catch ( ... )
        {
            bSuccess = false;
            TRACE( _T( "CClientConnections::SocketRxHandler(), Unspecified exception caught\n" ) );
        }
    }

    ASSERT( bSuccess );
}

inline
bool CClientConnections::Connect( const int &riSocketIndex )
{
    m_ClientAccessGuard.Enter();
    m_ClientList.push_back( CConnectedClient( riSocketIndex ) );
    m_ClientAccessGuard.Leave();

    return true;
}

inline
bool CClientConnections::PostDisconnect( const int &riSocketIndex )
{
    bool bSuccess = false;

    try
    {
        CLogger *pLogger = const_cast<CLogger *>( m_pLogger );

        if ( pLogger != NULL )
        {
            bSuccess = ( pLogger->PostThreadMessage( CLogger::messageKillClientConnection, riSocketIndex ) != 0 );
        }
    }
    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 )
{
    bool bSuccess = false;

    try
    {
        if ( rulNumBytes == 0 )
        {
            bSuccess = true;
        }
        else if ( pbyData != NULL )
        {
            // Check to see if the message is a valid 7k record. If so, decode and queue the command
            // otherwise route it to the logger object for further handling -- namely, dispatch to the
            // appropriate sensor.

            ConnectedClientListIterator_t pClient;

            m_ClientAccessGuard.Enter();
            bool bIndexFound = ClientFromSocketIndex( riSocketIndex, pClient );
            m_ClientAccessGuard.Leave();

            if ( bIndexFound )
            {
                C7kProtocol     Record;

                PRECORDHEADER7K psRecordHeader   = NULL;
                PBYTE           pbyDynamicStream = NULL;
                unsigned long   ulDynamicBytes   = 0UL;

                if ( Record.DecodeRecord( pbyData, rulNumBytes, false, psRecordHeader, pbyDynamicStream, ulDynamicBytes ) )
                {
                    ASSERT( psRecordHeader != NULL );

                    if ( psRecordHeader->IsValid() )
                    {
                        if ( psRecordHeader->m_ulRecordType == C7kCommand::RecordType() )
                        {
                            bSuccess = DecodeAndQueueCommand( pbyData, rulNumBytes, rulTimeStamp, riSocketIndex );
                        }
                        else
                        {
                            ASSERT( m_pLogger != NULL );
                            bSuccess = (const_cast<CLogger *>( m_pLogger ))->Queue7kMessageForRouting( pbyData, rulNumBytes, rulTimeStamp, riSocketIndex );
                        }
                    }
                }
            }
        }
        else
        {
            bSuccess = false;
        }
    }
    catch ( ... )
    {
        bSuccess = false;
    }

    return bSuccess;
}

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 = std::find( m_ClientList.begin(), m_ClientList.end(), riSocketIndex );
    }
    catch ( ... )
    {
        rpClientItem = m_ClientList.end();
    }

    return ( rpClientItem != m_ClientList.end() );
}

inline
bool CClientConnections::DecodeAndQueueCommand( const BYTE           *pbyData,
                                                const unsigned long  &rulNumBytes,
                                                const unsigned long  &rulTimeStamp,
                                                const int            &riSocketIndex )
{
    bool bSuccess = false;

    // Extract the command and its parameters from the 7k payload controller command record type
    // and then queue the command for subsequent handling by the payload controller's main
    // processing loop.

    CString                     Parameters;
    int                         iSensorIndex = -1;
    unsigned long               ulAction     = 0UL;
    unsigned long               ulCommand    = 0UL;

    if ( Decode7kCommand( pbyData, rulNumBytes, ulCommand, iSensorIndex, ulAction, Parameters ) )
    {
        CLogger *pLogger = const_cast<CLogger *>( m_pLogger );

        ASSERT( pLogger != NULL );

        if ( pLogger != NULL )
        {
            bSuccess = pLogger->QueueCommand( ulCommand, iSensorIndex, ulAction, Parameters, rulTimeStamp, riSocketIndex );
            ASSERT( bSuccess );
        }
    }

    return bSuccess;
}

bool CClientConnections::Decode7kCommand(   const BYTE                  *pbyData,
                                            const unsigned long         &rulNumBytes, 
                                            unsigned long               &rulCommand,
                                            int                         &riSensorIndex,
                                            unsigned long               &rulAction,
                                            CString                     &rParameters )
{
    // Given a 7k encoded data frame, extract the command code and parameters for subsequent
    // dispatch to the relevant command handler.

    bool bSuccess = false;      // Assume failure for now.

    rParameters.Empty();

    rulCommand      = 0UL;
    riSensorIndex   = 0;
    rulAction       = 0UL;

    if ( rulNumBytes == 0UL )
    {
        bSuccess = true;
    }
    else if ( pbyData != NULL )
    {
        unsigned long   ulCommand;
        C7kCommand      Command;

        if ( Command.Decode( const_cast<BYTE *>( pbyData ), rulNumBytes, false, riSensorIndex, ulCommand, rulAction ) )
        {
            rulCommand = ulCommand;
            rParameters = Command.Command();
            bSuccess = true;
        }
    }
    else
    {
        bSuccess = false;
    }

    if ( ! bSuccess )
    {
        TRACE( _T( "CClientConnections::Decode7kCommand(), failed\n" ) );
    }

    return bSuccess;
}

bool CClientConnections::Subscribe( const int           &riSocketIndex,
                                    const int           &riSensorIndex,
                                    const unsigned long &rulRecordNumber,
                                    const bool          &rbEnable )
{
    bool bSuccess = false;      // Assume false for now.

    ConnectedClientListIterator_t pClient;

    m_ClientAccessGuard.Enter();

    if ( ClientFromSocketIndex( riSocketIndex, pClient ) )
    {
        if ( rbEnable )
        {
            bSuccess = pClient->Subscribe( riSensorIndex, rulRecordNumber );
        }
        else
        {
            bSuccess = pClient->Unsubscribe( riSensorIndex, rulRecordNumber );
        }
    }

    m_ClientAccessGuard.Leave();

    return bSuccess;
}

bool CClientConnections::Verbose( const int &riSocketIndex ) const
{
    bool bIsVerbose = false;                // quite mode by default.

    ConnectedClientListIterator_t pClient;

    m_ClientAccessGuard.Enter();

    if ( (const_cast<CClientConnections *>( this ))->ClientFromSocketIndex( riSocketIndex, pClient ) )
    {
        bIsVerbose = pClient->Verbose();
    }

    m_ClientAccessGuard.Leave();

    return bIsVerbose;
}

bool CClientConnections::Verbose(   const int   &riSocketIndex,
                                    const bool  &rbVerbose )
{
    bool bSuccess = false;

    ConnectedClientListIterator_t pClient;

    m_ClientAccessGuard.Enter();

    if ( const_cast<CClientConnections *>(this)->ClientFromSocketIndex( riSocketIndex, pClient ) )
    {
        pClient->Verbose( rbVerbose );
        bSuccess = true;
    }

    m_ClientAccessGuard.Leave();

    ASSERT( bSuccess );

    return bSuccess;
}

bool CClientConnections::SendToSubscribingClients(  const int           &riSensorIndex,
                                                    const unsigned long &rulRecordType,
                                                    const BYTE          *pbyData,
                                                    const unsigned long &rulSize )
{
    bool bSuccess = false;

    try
    {
        m_ClientAccessGuard.Enter();

        try
        {
            bSuccess = true;

            ConnectedClientListIterator_t pClient;

            for ( pClient = m_ClientList.begin(); pClient != m_ClientList.end(); pClient++ )
            {
                if ( pClient->IsSubscribedRecord( riSensorIndex, rulRecordType ) )
                {
                    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 during subscribed client transmission" );
        }
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CClientConnections::SendToSubscribingClients(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CClientConnections::SendToSubscribingClients(), unspecified exception caught\n" ) );
    }

    return bSuccess;
}

bool CClientConnections::SendToAllClients(  const BYTE          *pbyData,
                                            const unsigned long &rulSize )
{
    bool bSuccess = false;

    try
    {
        m_ClientAccessGuard.Enter();

        try
        {
            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;
}

bool CClientConnections::SubscribingClients(    int                *&rpiSocketIndexes,
                                                int                 &riNumberOfClients,
                                                const int           &riSensorIndex,
                                                const unsigned long &rulRecordType )
{
    bool bSuccess = false;              // Assume failure for now.

    ASSERT( rpiSocketIndexes != NULL );

    riNumberOfClients = 0;
    rpiSocketIndexes  = NULL;

    try
    {
        ConnectedClientListIterator_t pClient;
        std::vector<int>              SubscribeList;

        m_ClientAccessGuard.Enter();

        for ( pClient = m_ClientList.begin(); pClient != m_ClientList.end(); pClient++ )
        {
            if ( pClient->IsSubscribedRecord( riSensorIndex, rulRecordType ) )
            {
                SubscribeList.push_back( pClient->SocketIndex() );
            }
        }

        m_ClientAccessGuard.Leave();

        if ( ( riNumberOfClients = static_cast<int>(SubscribeList.size()) ) > 0 )
        {
            rpiSocketIndexes = new int [ riNumberOfClients ];
            ASSERT( rpiSocketIndexes != NULL );

            if ( rpiSocketIndexes == NULL )
            {
                ThrowMessage_m( "Can't allocate subscribing client list" );
            }
            else
            {
                int                         iItem;
                std::vector<int>::iterator  pItem;

                for ( iItem = 0, pItem = SubscribeList.begin();
                        pItem != SubscribeList.end();
                            pItem++, iItem++ )
                {
                    rpiSocketIndexes[ iItem ] = *pItem;
                }
            }
        }

        bSuccess = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CClientConnections::SubscribingClients(), %s\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CClientConnections::SubscribingClients(), unspecified exception caught\n" ) );
    }

    if ( ! bSuccess )
    {
        riNumberOfClients = 0;

        if ( rpiSocketIndexes != NULL )
        {
            delete [] rpiSocketIndexes;
            rpiSocketIndexes = NULL;
        }
    }

    return ( ( riNumberOfClients > 0 ) && ( rpiSocketIndexes != NULL ) );
}


