//
//  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:   UDPSocket.cpp
//
//  Project:    6046
//
//  Author(s):  W. Arcus
//
//  Purpose:    
//
//  Notes:      
//

#include "StdAfx.h"
#include "UDPSocket.h"

#include "SystemTime.h"

#pragma comment( lib, "ws2_32.lib" )

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// CUDPSocket class implementation.

CUDPSocket::CUDPSocket( const EMODE                            &reMode,
                        const unsigned short                   &runPort,
                        const bool                             &rbBroadcast,
                        const unsigned long                    &rulSendAddress,
                        PFN_DATACALLBACK                        pfnCallback,
                        void *                                  pvCallbackParam,
                        const unsigned long                     rulMaxBufferSize )

           :m_ulBufferSize  ( rulMaxBufferSize )
{
    ManageDllState_m();

    m_bInitialized      = false;

    m_bBroadcast        = rbBroadcast;
    m_eMode             = reMode;

    m_hSocket           = INVALID_SOCKET;
    m_unPort            = runPort;
    m_ulSendAddress     = rulSendAddress;

    m_pServer           = NULL;

    m_pfnCallback       = pfnCallback;
    m_pvCallbackParam   = pvCallbackParam;

    try
    {
        // Initialize the socket descriptor.

        if ( ( m_hSocket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ) ) == INVALID_SOCKET )
        {
            ThrowMessage_m( "socket() failed" );
        }

        if ( ! Mode( reMode, runPort ) )
        {
            ThrowMessage_m( "Mode() failed" );
        }

        m_bInitialized = true;
    }
    catch ( LPCTSTR lpszMessage )
    {
        m_bInitialized = false;
        TRACE( _T( "CUDPSocket::CUDPSocket(), %s.\n" ), lpszMessage );
    }
    catch ( ... )
    {
        m_bInitialized = false;
        TRACE( _T( "CUDPSocket::CUDPSocket(), Unspecified exception caught.\n" ) );
    }

    if ( ! m_bInitialized )
    {
        CloseSocket();
        ShutdownServer();
    }
}

CUDPSocket::~CUDPSocket( void )
{
    ManageDllState_m();

    try
    {
        CloseSocket();

        ShutdownServer();

        m_bBroadcast    = false;
        m_eMode         = modeClient;

        m_hSocket       = INVALID_SOCKET;
        m_unPort        = 0U;
        m_ulSendAddress = 0UL;

        m_pfnCallback     = NULL;
        m_pvCallbackParam = NULL;
    }
    catch ( ... )
    {
        TRACE( _T( "CUDPSocket::CUDPSocket(), Unspecified exception caught\n" ) );
    }
}

bool CUDPSocket::IsInitialized( void ) const
{
    ManageDllState_m();

    return m_bInitialized;
}

CUDPSocket::EMODE CUDPSocket::Mode( void ) const
{
    ManageDllState_m();

    return m_eMode;
}

bool CUDPSocket::Send(  const BYTE             *pbyData,
                        const unsigned long    &rulBytes )
{
    ManageDllState_m();

    bool bSuccess = false;

    try
    {
        if ( rulBytes == 0UL )
        {
            bSuccess = true;            // Nothing to do.
        }
        else if ( pbyData != NULL )
        {

            ASSERT( m_hSocket != INVALID_SOCKET );

            SOCKADDR_IN sSocketAddress = { 0 };

            sSocketAddress.sin_family           = PF_INET;
            sSocketAddress.sin_port             = htons( m_unPort );
            sSocketAddress.sin_addr.S_un.S_addr = htonl( m_ulSendAddress );

            if ( m_bBroadcast )
            {
                sSocketAddress.sin_addr.S_un.S_addr = htonl( INADDR_BROADCAST );
            }

            const int iSendCode = ::sendto(  m_hSocket,
                                                 reinterpret_cast<char *>( const_cast<BYTE *>( pbyData ) ),
                                                     static_cast<int>( rulBytes ), 
                                                         0,
                                                             reinterpret_cast<SOCKADDR *>( &sSocketAddress ),
                                                                 sizeof( SOCKADDR_IN ) );

            if ( iSendCode == SOCKET_ERROR )
            {
                throw ::WSAGetLastError();
            }
            else if ( static_cast<unsigned long>( iSendCode ) == rulBytes )
            {
                bSuccess = true;

#if 0
                TRACE( _T( "Sent %d bytes to address %s (port: %d).\n" ),   iSendCode,
                                                                            inet_ntoa( sSocketAddress.sin_addr ),
                                                                            ntohs( sSocketAddress.sin_port ) );
#endif
            }
            else
            {
                bSuccess = false;
                TRACE( _T( "CUDPSocket::Send(), sendto() unable to send %lu bytes (%d sent).\n" ), rulBytes, iSendCode );
            }
        }
        else
        {
            TRACE( _T( "CUDPSocket::Send(), Invalid data pointer.\n" ) );
        }
    }
    catch ( const int &riErrorCode )
    {
        bSuccess = false;
        TRACE( _T( "CUDPSocket::Send(), failed with error code: %d\n" ), riErrorCode );
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CUDPSocket::Send(), %s.\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CUDPSocket::Send(), Unspcified exception caught.\n" ) );
    }

    return bSuccess;
}

///////////////////////////////////////////////////////////////////////////////
// CUDPSocket class implementation.

inline
bool CUDPSocket::Listen( void )
{
    bool bSuccess = false;

    ASSERT( m_pServer   == NULL );

    if ( m_pServer == NULL )
    {
        try
        {
            bSuccess = ( ( m_pServer = new CServer( *this, m_ulBufferSize ) ) != NULL );
        }
        catch ( ... )
        {
            bSuccess = false;
            TRACE( _T( "CUDPSocket::Listen(), Error constructing m_pServer" ) );
        }
    }
    else
    {
        bSuccess = true;
    }

    if ( ! bSuccess )
    {
        ShutdownServer();
    }

    return bSuccess;
}

inline
bool CUDPSocket::ReadBytes( BYTE                   *pbyData,
                            unsigned long          &rulBytes,
                            const unsigned long    &rulMaxBufferSize )
{
    bool bSuccess = false;

    rulBytes = 0UL;

    try
    {
        if ( pbyData == NULL )
        {
            ThrowMessage_m( "pbyData is NULL" );
        }

        SOCKADDR_IN sSocketAddress = { 0 };

        ASSERT( sizeof( SOCKADDR_IN ) == sizeof( SOCKADDR ) );
        int iAddressSize = static_cast<int>( sizeof( SOCKADDR_IN ) );

        // Receive a datagram on the bound port number.

        const int iSocketCode = ::recvfrom( m_hSocket,
                                                reinterpret_cast<char *>( pbyData ),
                                                    static_cast<int>( rulMaxBufferSize ),
                                                        0,
                                                            reinterpret_cast<SOCKADDR *>( &sSocketAddress ),
                                                                &iAddressSize );

        if ( ( iSocketCode == SOCKET_ERROR ) || ( iSocketCode <= 0 ) )
        {
            throw ::WSAGetLastError();
        }

        rulBytes = static_cast<unsigned long>( iSocketCode );

        bSuccess = true;

#if 0
        TRACE( _T( "UDP Datagram received from: \n\tIP Address: %s \n\tPort     : %u\n\tBytes    : %d\n" ),  inet_ntoa( sSocketAddress.sin_addr ),
                                                                                                            ntohs( sSocketAddress.sin_port ),
                                                                                                            iSocketCode );
#endif

    }
    catch ( int iErrorCode )
    {
        bSuccess = false;
        TRACE( _T( "CUDPSocket::ReadBytes(), ::recvfrom() failed with code: %d.\n" ), iErrorCode );
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CUDPSocket::ReadBytes(), %s.\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CUDPSocket::ReadBytes(), Unspecified exception caught.\n" ) );
    }

    return bSuccess;
}

inline
bool CUDPSocket::Mode(  const EMODE             &reMode,
                        const unsigned short    &runPort )
{
    bool bSuccess = false;

    try
    {
        int iBufferSize = static_cast<int>( m_ulBufferSize );

        SOCKADDR_IN sSocketAddress = { 0 };

        sSocketAddress.sin_family       = AF_INET;
        sSocketAddress.sin_port         = htons( runPort );
        sSocketAddress.sin_addr.s_addr  = htonl( INADDR_ANY );

        m_eMode = reMode;

        switch ( reMode )
        {
            case modeClient:

                {
                    int  iSocketTimeout = 1000;
                    BOOL bBroadcast     = ( m_bBroadcast ) ? TRUE : FALSE;

                    if ( ::setsockopt( m_hSocket, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char *>( &iBufferSize ), sizeof( int ) ) == SOCKET_ERROR )
                    {
                        throw WSAGetLastError();
                    }

                    if ( ::setsockopt( m_hSocket, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<char *>( &iSocketTimeout ), sizeof( int ) ) == SOCKET_ERROR )
                    {
                        throw WSAGetLastError();
                    }

                    if ( ::setsockopt( m_hSocket, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<char *>( &bBroadcast ), sizeof( BOOL ) ) == SOCKET_ERROR )
                    {
                        throw WSAGetLastError();
                    }

                    if ( ::bind( m_hSocket, reinterpret_cast<SOCKADDR*>( &sSocketAddress ), sizeof( SOCKADDR_IN ) ) == SOCKET_ERROR )
                    {
                        throw WSAGetLastError();
                    }
                }

                bSuccess = true;
                break;

            case modeServer:

                {
                    BOOL bReuseAddress = TRUE;

                    // Set the various socket options.

                    if ( ::setsockopt( m_hSocket, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>( &bReuseAddress ), sizeof( BOOL ) ) == SOCKET_ERROR )
                    {
                        throw WSAGetLastError();
                    }

                    if ( ::setsockopt( m_hSocket, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char *>( &iBufferSize ), sizeof( int ) ) == SOCKET_ERROR )
                    {
                        throw WSAGetLastError();
                    }

                    if ( ::bind( m_hSocket, reinterpret_cast<SOCKADDR *>( &sSocketAddress ), sizeof( SOCKADDR_IN ) ) == SOCKET_ERROR )
                    {
                        throw WSAGetLastError();
                    }

                    if ( ! Listen() )
                    {
                        ThrowMessage_m( "Listen() failed\n" );
                    }
                }

                bSuccess = true;
                break;

            default:

                bSuccess = false;
                TRACE( _T( "CUDPSocket::Mode(), Unrecognized mode!!\n" ) );
                break;
        }
    }
    catch( const int &riError )
    {
        bSuccess = false;
        TRACE( _T( "CUDPSocket::Mode(), socket failure, code: %d\n"), riError );
    }
    catch ( LPCTSTR lpszMessage )
    {
        bSuccess = false;
        TRACE( _T( "CUDPSocket::Mode(), %s.\n" ), lpszMessage );
    }
    catch ( ... )
    {
        bSuccess = false;
        TRACE( _T( "CUDPSocket::Mode(), Unspecified exception caught\n" ) );
    }

    if ( ! bSuccess )
    {
        CloseSocket();
        ShutdownServer();
    }

    return bSuccess;
}

inline
void CUDPSocket::CloseSocket( void )
{
    if ( m_hSocket != INVALID_SOCKET )
    {
        ::closesocket( m_hSocket );
        m_hSocket = INVALID_SOCKET;
    }
}

inline
void CUDPSocket::ShutdownServer( void )
{
    if ( m_pServer != NULL )
    {
        delete m_pServer;
        m_pServer = NULL;
    }
}

///////////////////////////////////////////////////////////////////////////////
// CUDPServer::CServer class implementation.

CUDPSocket::CServer::CServer(   CUDPSocket            &rParent,
                                const unsigned long   &rulBufferSize )

                    :CBaseThread( INFINITE, THREAD_PRIORITY_NORMAL ),
                     m_rParent( rParent ),
                     m_ulBufferSize( rulBufferSize )
{
    try
    {
        ASSERT( m_ulBufferSize > 0UL );

        if ( ( m_pbySocketData = new BYTE [ m_ulBufferSize ] ) == NULL )
        {
            ThrowMessage_m( "m_pbySocketData is NULL" );
        }

        if ( ! CBaseThread::IsInitialized() )
        {
            ThrowMessage_m( "Can't initialize base threaded class" );
        }

        if ( CBaseThread::ResumeThread() == m_dwResumeFailCode )
        {
            ThrowMessage_m( "Can't resume base classes thread" );
        }
    }
    catch ( LPCTSTR lpszMessage )
    {
        TRACE( _T( "CUDPSocket::CServer::CServer(), %s.\n" ), lpszMessage );
    }
    catch ( ... )
    {
        TRACE( _T( "CUDPSocket::CServer::CServer(), Unspecified exception caught.\n" ) );
    }
}

CUDPSocket::CServer::~CServer( void )
{
    try
    {
        if ( ! CBaseThread::TerminateThread( 1000 ) )
        {
            TRACE( _T( "CUDPSocket::CServer::~CServer(), CBaseThread::TerminateThread() failed\n" ) );
        }

        if ( m_pbySocketData != NULL )
        {
            delete [] m_pbySocketData;
            m_pbySocketData = NULL;
        }
    }
    catch ( ... )
    {
        TRACE( _T( "CUDPSocket::CServer::~CServer(), Unspecified exception caught.\n" ) );
    }
}

void CUDPSocket::CServer::WatchCycle( void )
{
    TRACE( _T( "Entered CUDPSocket::CServer reciever thread\n" ) );

    try
    {
        PFN_DATACALLBACK pfnCallback     = m_rParent.m_pfnCallback;
        void *           pvCallbackParam = m_rParent.m_pvCallbackParam;

        ASSERT( pfnCallback != NULL );

        while ( IsActive() )
        {
            PumpMessages();

            try
            {
                if ( IsActive() )
                {
                    unsigned long ulBytesRead = 0UL;

                    if ( ! m_rParent.ReadBytes( m_pbySocketData, ulBytesRead, m_ulBufferSize ) )
                    {
                        TRACE( _T( "CUDPSocket::CServer::WatchCycle(), m_rParent.ReadBytes() failed\n" ) );
                        SetActive( FALSE );
                        break;
                    }

                    if ( ! ( pfnCallback ) ( pvCallbackParam, m_pbySocketData, ulBytesRead, CSystemTime::GetTickCount() ) )
                    {
                        TRACE( _T( "CUDPSocket::CServer::WatchCycle(), (*ppfnCallback)() failed\n" ) );
                        SetActive( FALSE );
                        break;
                    }
                }
            }
            catch ( ... )
            {
                TRACE( _T( "CUDPSocket::CServer::WatchCycle(), Unspecified exception caught.\n" ) );
            }
        }
    }
    catch ( LPCTSTR lpszMessage )
    {
        TRACE( _T( "CUDPSocket::CServer::WatchCycle(), %s.\n" ), lpszMessage );
    }
    catch ( ... )
    {
        TRACE( _T( "CUDPSocket::CServer::WatchCycle(), Unspecified exception caught\n" ) );
    }

    TRACE( _T( "CUDPSocket::CServer reciever thread is terminating.\n" ) );
}

