/* HINT: That is a thread entry point and wrapper for the real serverloop. */
 OpcUa_Void OpcUa_P_SocketManager_ServerLoopThread(OpcUa_Void* a_pArgument)
{
    OpcUa_StatusCode                uStatus                 = OpcUa_Good;                   /* only needed for internal reasons */
    OpcUa_InternalSocketManager*    pInternalSocketManager  = (OpcUa_InternalSocketManager*)a_pArgument;
    OpcUa_Int32                     iSocketManagerSlot;

    OpcUa_Trace(OPCUA_TRACE_LEVEL_INFO, "NetworkThread: Message Loop started...\n");

    do
    {
        uStatus = OpcUa_P_SocketManager_ServeLoopInternal(  pInternalSocketManager,
                                                            OpcUa_UInt32_Max,
                                                            OpcUa_False);

        if(OpcUa_IsEqual(OpcUa_GoodShutdownEvent))
        {
            /* leave this loop if a shutdown was signalled */
            break;
        }

    } while(OpcUa_IsGood(uStatus));

    /* Debug Output */
    OpcUa_Trace(OPCUA_TRACE_LEVEL_INFO, "NetworkThread: Message Loop shutting down! (0x%08X)\n", uStatus);

    if(pInternalSocketManager->Flags.bSpawnThreadOnAccept != 0)
    {
#if OPCUA_USE_SYNCHRONISATION
        OpcUa_P_Mutex_Lock(pInternalSocketManager->pMutex);
#endif /* OPCUA_USE_SYNCHRONISATION */

        for(iSocketManagerSlot = 0; iSocketManagerSlot < OPCUA_SOCKET_MAXMANAGERS; iSocketManagerSlot++)
        {
            if(pInternalSocketManager->pSocketManagers[iSocketManagerSlot] != OpcUa_Null)
            {
                OpcUa_InternalSocketManager *pSpawnedSocketManager = pInternalSocketManager->pSocketManagers[iSocketManagerSlot];
                pInternalSocketManager->pSocketManagers[iSocketManagerSlot] = OpcUa_Null;

                pSpawnedSocketManager->pThreadToJoin  = pInternalSocketManager->pThreadToJoin;
                pInternalSocketManager->pThreadToJoin = pSpawnedSocketManager->pThread;
                pSpawnedSocketManager->pThread        = OpcUa_Null;

                OpcUa_P_SocketManager_InterruptLoop(pSpawnedSocketManager,
                                                    OPCUA_SOCKET_SHUTDOWN_EVENT,
                                                    OpcUa_False);
            }
        }

#if OPCUA_USE_SYNCHRONISATION
        OpcUa_P_Mutex_Unlock(pInternalSocketManager->pMutex);
#endif /* OPCUA_USE_SYNCHRONISATION */

        if(pInternalSocketManager->pThreadToJoin != OpcUa_Null)
        {
            OpcUa_P_Thread_Delete(&pInternalSocketManager->pThreadToJoin);
        }
    }

    return;
}
/*============================================================================
 * Delete
 *===========================================================================*/
OpcUa_Void OpcUa_Thread_Delete(OpcUa_Thread* a_pThread)
{
    OpcUa_ThreadInternal* pThread = OpcUa_Null;

    if(a_pThread == OpcUa_Null || *a_pThread == OpcUa_Null)
    {
        return;
    }

    pThread = *((OpcUa_ThreadInternal**)a_pThread);

    if(pThread->IsRunning != OpcUa_False)
    {
        return;
    }

    if(pThread->ShutdownEvent)
    {
        OPCUA_P_SEMAPHORE_DELETE(&(pThread->ShutdownEvent));
    }

    if(pThread->RawThread)
    {
        OpcUa_P_Thread_Delete(&(pThread->RawThread));
    }

    if(pThread->Mutex)
    {
        OPCUA_P_MUTEX_DELETE(&(pThread->Mutex));
    }

    pThread->ThreadData = OpcUa_Null;
    pThread->ThreadMain = OpcUa_Null;


    OpcUa_Free(*a_pThread);

    *a_pThread = OpcUa_Null;

    return;
}
/*============================================================================
 * Delete SocketManager Type (closes all sockets)
 *===========================================================================*/
OpcUa_Void OPCUA_DLLCALL OpcUa_P_SocketManager_Delete(OpcUa_SocketManager* a_pSocketManager)
{
    OpcUa_InternalSocketManager* pInternalSocketManager = OpcUa_Null;
    OpcUa_UInt32                 uintIndex              = 0;

#if !OPCUA_MULTITHREADED
    if(a_pSocketManager == OpcUa_Null && OpcUa_Socket_g_SocketManager != OpcUa_Null)
    {
        a_pSocketManager = &OpcUa_Socket_g_SocketManager;
    }
#endif

    if(a_pSocketManager == OpcUa_Null || *a_pSocketManager == OpcUa_Null)
    {
        OpcUa_Trace(OPCUA_TRACE_LEVEL_ERROR, "OpcUa_SocketManager_Delete: Invalid Socket Manager!\n");
        return;
    }

    pInternalSocketManager = (OpcUa_InternalSocketManager*)*a_pSocketManager;

    /* send shutdown event to serveloop */
    OpcUa_P_SocketManager_InterruptLoop(pInternalSocketManager, OPCUA_SOCKET_SHUTDOWN_EVENT, OpcUa_False);

#if OPCUA_MULTITHREADED
    if(pInternalSocketManager->pThread != OpcUa_Null)
    {
        OpcUa_P_Thread_Delete(&(pInternalSocketManager->pThread));
    }
    else
    {
        OpcUa_Trace(OPCUA_TRACE_LEVEL_ERROR, "OpcUa_SocketManager_Delete: Invalid Thread Handle!\n");
        return;
    }
#endif /* OPCUA_MULTITHREADED */

#if OPCUA_USE_SYNCHRONISATION
    OpcUa_P_Mutex_Lock(pInternalSocketManager->pMutex);
#endif /* OPCUA_USE_SYNCHRONISATION */

    /* handle the socket list content (close, cleanup, etc.) */
    if(pInternalSocketManager->pSockets != OpcUa_Null)
    {
        for(uintIndex = 0; uintIndex < pInternalSocketManager->uintMaxSockets; uintIndex++)
        {
            OpcUa_Socket pSocketTemp = &(pInternalSocketManager->pSockets[uintIndex]);

            if(   (pInternalSocketManager->pSockets[uintIndex].bSocketIsInUse != OpcUa_False)
               && (pInternalSocketManager->pSockets[uintIndex].bInvalidSocket == OpcUa_False))
            {
                OpcUa_Socket_HandleEvent(pSocketTemp, OPCUA_SOCKET_CLOSE_EVENT);
                OpcUa_P_RawSocket_Close(pInternalSocketManager->pSockets[uintIndex].rawSocket);
            }

            OpcUa_Socket_Clear(pSocketTemp);
        }

        OpcUa_P_Memory_Free(pInternalSocketManager->pSockets);
    } /* if(pInternalSocketManager->pSockets != OpcUa_Null) */

    OpcUa_P_RawSocket_Close(pInternalSocketManager->pCookie);

#if OPCUA_USE_SYNCHRONISATION
    OpcUa_P_Mutex_Unlock(pInternalSocketManager->pMutex);
    OpcUa_P_Mutex_Delete(&pInternalSocketManager->pMutex);
#endif /* OPCUA_USE_SYNCHRONISATION */

#if OPCUA_MULTITHREADED
    if(pInternalSocketManager->pStartupSemaphore != OpcUa_Null)
    {
        OpcUa_P_Semaphore_Delete(&pInternalSocketManager->pStartupSemaphore);
    }
    if(pInternalSocketManager->pSocketManagers != OpcUa_Null)
    {
        OpcUa_P_Memory_Free(pInternalSocketManager->pSocketManagers);
    }
#endif

    OpcUa_P_Memory_Free(*a_pSocketManager);
    *a_pSocketManager = OpcUa_Null;

    return;
}
/*============================================================================
 * Create a new socket manager or initialize the global one (OpcUa_Null first).
 *===========================================================================*/
OpcUa_StatusCode OPCUA_DLLCALL OpcUa_P_SocketManager_Create(OpcUa_SocketManager*    a_pSocketManager,
                                                            OpcUa_UInt32            a_nSockets,
                                                            OpcUa_UInt32            a_nFlags)
{
    OpcUa_InternalSocketManager*    pInternalSocketManager   = OpcUa_Null;

OpcUa_InitializeStatus(OpcUa_Module_Socket, "SocketManager_Create");

    if(a_nFlags & 0xFFFFFFF8)
    {
        return OpcUa_BadInvalidArgument;
    }

    if(a_nSockets > OPCUA_P_SOCKETMANAGER_NUMBEROFSOCKETS)
    {
        return OpcUa_BadInvalidArgument;
    }

    /* set number of socket to maximum */
    if(a_nSockets == 0)
    {
        a_nSockets = OPCUA_P_SOCKETMANAGER_NUMBEROFSOCKETS;
    }

    a_nSockets += 1; /* add signal socket to requested sockets */

#if !OPCUA_MULTITHREADED
    if(a_pSocketManager == OpcUa_Null && OpcUa_Socket_g_SocketManager == OpcUa_Null)
    {
        a_pSocketManager = &OpcUa_Socket_g_SocketManager;
    }
#endif

    if(a_pSocketManager == OpcUa_Null)
    {
        return OpcUa_BadInvalidArgument;
    }

    *a_pSocketManager = OpcUa_SocketManager_Alloc();
    OpcUa_GotoErrorIfAllocFailed(*a_pSocketManager);

    pInternalSocketManager = (OpcUa_InternalSocketManager*)*a_pSocketManager;

    OpcUa_SocketManager_Initialize(pInternalSocketManager);

#if OPCUA_USE_SYNCHRONISATION
    uStatus = OpcUa_P_Mutex_Create(&pInternalSocketManager->pMutex);
    OpcUa_GotoErrorIfBad(uStatus);
#endif /* OPCUA_USE_SYNCHRONISATION */

    /* preallocate socket structures for all possible sockets (maxsockets) */
    uStatus = OpcUa_SocketManager_CreateSockets((OpcUa_SocketManager)pInternalSocketManager, a_nSockets);
    OpcUa_GotoErrorIfBad(uStatus);

    pInternalSocketManager->uintLastExternalEvent  = OPCUA_SOCKET_NO_EVENT;

    /* set the behaviour flags */
    if((a_nFlags & OPCUA_SOCKET_SPAWN_THREAD_ON_ACCEPT)    != OPCUA_SOCKET_NO_FLAG)
    {
        pInternalSocketManager->Flags.bSpawnThreadOnAccept      = OpcUa_True;
    }

    if((a_nFlags & OPCUA_SOCKET_REJECT_ON_NO_THREAD)       != OPCUA_SOCKET_NO_FLAG)
    {
        pInternalSocketManager->Flags.bRejectOnThreadFail       = OpcUa_True;
    }

    if((a_nFlags & OPCUA_SOCKET_DONT_CLOSE_ON_EXCEPT)      != OPCUA_SOCKET_NO_FLAG)
    {
        pInternalSocketManager->Flags.bDontCloseOnExcept        = OpcUa_True;
    }

    uStatus = OpcUa_P_SocketManager_NewSignalSocket(pInternalSocketManager);
    OpcUa_GotoErrorIfBad(uStatus);

#if OPCUA_MULTITHREADED
    if (pInternalSocketManager->Flags.bSpawnThreadOnAccept)
    {
        /* create a semaphore with no free resources for which a host can wait to be signalled. */
        uStatus = OpcUa_P_Semaphore_Create(&pInternalSocketManager->pStartupSemaphore, 0, 1);
        OpcUa_GotoErrorIfBad(uStatus);

        pInternalSocketManager->pSocketManagers = OpcUa_P_Memory_Alloc(sizeof(OpcUa_InternalSocketManager*) * OPCUA_SOCKET_MAXMANAGERS);
        OpcUa_GotoErrorIfAllocFailed(pInternalSocketManager->pSocketManagers);
        OpcUa_MemSet(pInternalSocketManager->pSocketManagers, 0, sizeof(OpcUa_InternalSocketManager*) * OPCUA_SOCKET_MAXMANAGERS);
    }

    /* if multithreaded, create and start the server thread if the list is not the global list. */
    uStatus = OpcUa_P_Thread_Create(&pInternalSocketManager->pThread); /* make raw thread */
    OpcUa_GotoErrorIfBad(uStatus);

    uStatus = OpcUa_P_Thread_Start( pInternalSocketManager->pThread,
                                    OpcUa_P_SocketManager_ServerLoopThread,
                                    (OpcUa_Void*)pInternalSocketManager);
    OpcUa_GotoErrorIfBad(uStatus);
#endif /* OPCUA_MULTITHREADED */

OpcUa_ReturnStatusCode;
OpcUa_BeginErrorHandling;

    if(pInternalSocketManager != OpcUa_Null)
    {
#if OPCUA_MULTITHREADED
        if(pInternalSocketManager->pThread != OpcUa_Null)
        {
            OpcUa_P_Thread_Delete(&pInternalSocketManager->pThread);
        }
        if(pInternalSocketManager->pSocketManagers != OpcUa_Null)
        {
            OpcUa_P_Memory_Free(pInternalSocketManager->pSocketManagers);
        }
        if(pInternalSocketManager->pStartupSemaphore != OpcUa_Null)
        {
            OpcUa_P_Semaphore_Delete(&pInternalSocketManager->pStartupSemaphore);
        }
#endif /* OPCUA_MULTITHREADED */
        if(pInternalSocketManager->pCookie != (OpcUa_RawSocket)OPCUA_P_SOCKET_INVALID)
        {
            OpcUa_P_RawSocket_Close(pInternalSocketManager->pCookie);
        }
        if(pInternalSocketManager->pSockets != OpcUa_Null)
        {
            if(pInternalSocketManager->pSockets[0].rawSocket != (OpcUa_RawSocket)OPCUA_P_SOCKET_INVALID)
            {
                OpcUa_P_RawSocket_Close(pInternalSocketManager->pSockets[0].rawSocket);
            }
            OpcUa_P_Memory_Free(pInternalSocketManager->pSockets);
        }
#if OPCUA_USE_SYNCHRONISATION
        if(pInternalSocketManager->pMutex != OpcUa_Null)
        {
            OpcUa_P_Mutex_Delete(&pInternalSocketManager->pMutex);
        }
#endif /* OPCUA_USE_SYNCHRONISATION */
        OpcUa_P_Memory_Free(pInternalSocketManager);
    }

    *a_pSocketManager = OpcUa_Null;

OpcUa_FinishErrorHandling;
}