/**
 * @file UDPSServer.cpp
 * @brief Implementation of UDPSServer — multi-client UDPS session management.
 */

#include "UDPSServer.h"

#include "AdvancedErrorManagement.h"
#include "HighResolutionTimer.h"
#include "MemoryOperationsHelper.h"
#include "StreamString.h"

#include <sys/select.h>
#include <poll.h>

namespace MARTe {

// ---------------------------------------------------------------------------
// Constructor / Destructor
// ---------------------------------------------------------------------------

UDPSServer::UDPSServer()
    : port(0u),
      maxPayloadSize(UDPS_SERVER_DEFAULT_MAX_PAYLOAD),
      dataPort(0u),
      useMulticast(false),
      clientTimeoutTicks(0u),
      numUnicastClients(0u),
      numTCPClients(0u),
      cachedConfig(NULL_PTR(uint8 *)),
      cachedConfigSize(0u),
      sendBuf(NULL_PTR(uint8 *)),
      sendBufCapacity(0u),
      configCounter(0u),
      started(false) {

    for (uint32 i = 0u; i < UDPS_SERVER_MAX_UNICAST_CLIENTS; i++) {
        unicastClients[i].ipAddr[0]     = '\0';
        unicastClients[i].clientPort    = 0u;
        unicastClients[i].lastSeenTicks = 0u;
        unicastClients[i].active        = false;
        unicastClients[i].isStatic      = false;
    }

    for (uint32 i = 0u; i < UDPS_SERVER_MAX_TCP_CLIENTS; i++) {
        tcpClients[i] = NULL_PTR(BasicTCPSocket *);
    }
}

UDPSServer::~UDPSServer() {
    (void) Stop();
}

// ---------------------------------------------------------------------------
// Initialise
// ---------------------------------------------------------------------------

bool UDPSServer::Initialise(StructuredDataI &data) {
    uint32 portU32 = 0u;
    if (data.Read("Port", portU32)) {
        port = static_cast<uint16>(portU32);
    }
    else {
        REPORT_ERROR_STATIC(ErrorManagement::ParametersError,
                            "UDPSServer: Port not specified.");
        return false;
    }
    /* port == 0 is valid: unicast push-only mode (no serverSocket bind,
     * no CONNECT/DISCONNECT/ACK reception). Clients added via AddStaticClient(). */

    StreamString mcGroup;
    if (data.Read("MulticastGroup", mcGroup) && (mcGroup.Size() > 0u)) {
        multicastGroup = mcGroup;
        useMulticast   = true;
    }

    if (useMulticast) {
        uint32 dpU32 = static_cast<uint32>(port) + 1u;
        (void) data.Read("DataPort", dpU32);
        dataPort = static_cast<uint16>(dpU32);
        if (dataPort == port) {
            REPORT_ERROR_STATIC(ErrorManagement::ParametersError,
                                "UDPSServer: DataPort (%u) must differ from Port (%u).",
                                static_cast<uint32>(dataPort), static_cast<uint32>(port));
            return false;
        }
    }

    uint32 mps = UDPS_SERVER_DEFAULT_MAX_PAYLOAD;
    (void) data.Read("MaxPayloadSize", mps);
    maxPayloadSize = mps;

    uint32 timeoutSecs = UDPS_SERVER_DEFAULT_CLIENT_TIMEOUT_S;
    (void) data.Read("ClientTimeout", timeoutSecs);
    clientTimeoutTicks = (timeoutSecs > 0u)
        ? (static_cast<uint64>(timeoutSecs) * HighResolutionTimer::Frequency())
        : 0u;

    return true;
}

// ---------------------------------------------------------------------------
// Start / Stop
// ---------------------------------------------------------------------------

bool UDPSServer::Start() {
    if (started) {
        return true;
    }

    sendBufCapacity = UDPS_HEADER_SIZE + maxPayloadSize;
    sendBuf = new uint8[sendBufCapacity];
    if (sendBuf == NULL_PTR(uint8 *)) {
        REPORT_ERROR_STATIC(ErrorManagement::FatalError,
                            "UDPSServer: Could not allocate send buffer.");
        return false;
    }

    bool ok = true;

    if (useMulticast) {
        // TCP listener for CONNECT + CONFIG
        ok = tcpListener.Open();
        if (ok) {
            ok = tcpListener.Listen(port, 5);
        }
        if (ok) {
            tcpListener.SetBlocking(false);
        }
        // UDP data socket connected to multicast group
        if (ok) {
            ok = dataSocket.Open();
        }
        if (ok) {
            ok = dataSocket.Connect(multicastGroup.Buffer(), dataPort);
        }
        if (!ok) {
            REPORT_ERROR_STATIC(ErrorManagement::FatalError,
                                "UDPSServer: Failed to open multicast sockets on port %u.",
                                static_cast<uint32>(port));
        }
    }
    else {
        // Unicast send socket (unconnected; SetDestination per Write)
        ok = uniSendSocket.Open();
        if (!ok) {
            REPORT_ERROR_STATIC(ErrorManagement::FatalError,
                                "UDPSServer: Failed to open unicast send socket.");
        }

        // Receive socket: only bind if port != 0 (skip for push-only mode)
        if (ok && (port != 0u)) {
            ok = serverSocket.Open();
            if (ok) {
                ok = serverSocket.Listen(port); // UDP "listen" = bind
            }
            if (!ok) {
                REPORT_ERROR_STATIC(ErrorManagement::FatalError,
                                    "UDPSServer: Failed to bind receive socket on port %u.",
                                    static_cast<uint32>(port));
            }
        }
    }

    started = ok;
    return ok;
}

bool UDPSServer::Stop() {
    if (!started) {
        return true;
    }

    // Evict all unicast clients
    for (uint32 i = 0u; i < UDPS_SERVER_MAX_UNICAST_CLIENTS; i++) {
        if (unicastClients[i].active) {
            EvictUnicastClient(i);
        }
    }
    numUnicastClients = 0u;

    // Close all TCP clients
    for (uint32 i = 0u; i < UDPS_SERVER_MAX_TCP_CLIENTS; i++) {
        if (tcpClients[i] != NULL_PTR(BasicTCPSocket *)) {
            EvictTCPClient(i);
        }
    }
    numTCPClients = 0u;

    // Close sockets
    if (serverSocket.IsValid()) {
        (void) serverSocket.Close();
    }
    if (uniSendSocket.IsValid()) {
        (void) uniSendSocket.Close();
    }
    if (tcpListener.IsValid()) {
        (void) tcpListener.Close();
    }
    if (dataSocket.IsValid()) {
        (void) dataSocket.Close();
    }

    // Free heap buffers
    if (cachedConfig != NULL_PTR(uint8 *)) {
        delete[] cachedConfig;
        cachedConfig     = NULL_PTR(uint8 *);
        cachedConfigSize = 0u;
    }
    if (sendBuf != NULL_PTR(uint8 *)) {
        delete[] sendBuf;
        sendBuf         = NULL_PTR(uint8 *);
        sendBufCapacity = 0u;
    }

    started = false;
    return true;
}

// ---------------------------------------------------------------------------
// ServiceClients
// ---------------------------------------------------------------------------

void UDPSServer::ServiceClients() {
    if (!started) {
        return;
    }

    if (useMulticast) {
        // Poll TCP listener for new connections (non-blocking).
        // Guard with poll() first so WaitConnection is never called on an empty
        // queue — avoids the MARTe2 "Failed accept in unblocking mode" log spam.
        if (tcpListener.IsValid()) {
            struct pollfd pfd;
            pfd.fd      = static_cast<int>(tcpListener.GetReadHandle());
            pfd.events  = POLLIN;
            pfd.revents = 0;
            bool pending = (::poll(&pfd, 1u, 0) > 0) &&
                           ((pfd.revents & POLLIN) != 0);
            BasicTCPSocket *newConn = pending
                ? tcpListener.WaitConnection(0u)
                : NULL_PTR(BasicTCPSocket *);
            if (newConn != NULL_PTR(BasicTCPSocket *)) {
                // Find free TCP client slot
                uint32 freeSlot = UDPS_SERVER_MAX_TCP_CLIENTS;
                for (uint32 i = 0u; i < UDPS_SERVER_MAX_TCP_CLIENTS; i++) {
                    if (tcpClients[i] == NULL_PTR(BasicTCPSocket *)) {
                        freeSlot = i;
                        break;
                    }
                }
                if (freeSlot < UDPS_SERVER_MAX_TCP_CLIENTS) {
                    HandleMulticastTCPConnect(newConn, freeSlot);
                }
                else {
                    REPORT_ERROR_STATIC(ErrorManagement::Warning,
                                        "UDPSServer: TCP client table full, rejecting new connection.");
                    (void) newConn->Close();
                    delete newConn;
                }
            }
        }

        // Poll existing TCP clients for DISCONNECT
        for (uint32 i = 0u; i < UDPS_SERVER_MAX_TCP_CLIENTS; i++) {
            if (tcpClients[i] == NULL_PTR(BasicTCPSocket *)) {
                continue;
            }
            // Non-blocking peek
            int fd = tcpClients[i]->GetReadHandle();
            if (fd < 0 || fd >= FD_SETSIZE) { continue; /* HI-6: skip FDs outside select range */ }
            fd_set rset;
            FD_ZERO(&rset);
            FD_SET(fd, &rset);
            struct timeval tv;
            tv.tv_sec = 0; tv.tv_usec = 0;
            int nready = select(fd + 1, &rset, NULL, NULL, &tv);
            if (nready > 0) {
                uint8  pktBuf[UDPS_HEADER_SIZE];
                uint32 recvSize = UDPS_HEADER_SIZE;
                bool   recvOk   = tcpClients[i]->Read(reinterpret_cast<char8 *>(pktBuf), recvSize);
                if (!recvOk || (recvSize == 0u)) {
                    REPORT_ERROR_STATIC(ErrorManagement::Information,
                                        "UDPSServer: TCP client disconnected (slot %u).", i);
                    EvictTCPClient(i);
                    continue;
                }
                if (recvSize >= UDPS_HEADER_SIZE) {
                    const UDPSPacketHeader *hdr =
                        reinterpret_cast<const UDPSPacketHeader *>(pktBuf);
                    if ((hdr->magic == UDPS_MAGIC) &&
                        (hdr->type  == UDPS_TYPE_DISCONNECT)) {
                        REPORT_ERROR_STATIC(ErrorManagement::Information,
                                            "UDPSServer: TCP client sent DISCONNECT (slot %u).", i);
                        EvictTCPClient(i);
                    }
                }
            }
        }
    }
    else {
        // Unicast: poll serverSocket for CONNECT / DISCONNECT / ACK
        if (!serverSocket.IsValid()) {
            return;
        }
        int fd = serverSocket.GetReadHandle();
        if (fd < 0 || fd >= FD_SETSIZE) { return; /* HI-6 */ }
        fd_set rset;
        FD_ZERO(&rset);
        FD_SET(fd, &rset);
        struct timeval tv = {0, 0};
        int nready = select(fd + 1, &rset, NULL, NULL, &tv);
        while (nready > 0) {
            uint8  pktBuf[UDPS_HEADER_SIZE + 4u];
            uint32 recvSize = static_cast<uint32>(sizeof(pktBuf));
            bool   recvOk   = serverSocket.Read(reinterpret_cast<char8 *>(pktBuf), recvSize);
            if (!recvOk || (recvSize < UDPS_HEADER_SIZE)) {
                break;
            }
            const UDPSPacketHeader *hdr =
                reinterpret_cast<const UDPSPacketHeader *>(pktBuf);
            if (hdr->magic != UDPS_MAGIC) {
                break;
            }
            InternetHost src = serverSocket.GetSource();
            if (hdr->type == UDPS_TYPE_CONNECT) {
                HandleUnicastConnect(src);
            }
            else if (hdr->type == UDPS_TYPE_DISCONNECT) {
                HandleUnicastDisconnect(src);
            }
            else if (hdr->type == UDPS_TYPE_ACK) {
                HandleUnicastAck(src);
            }

            // Check if more data is ready
            FD_ZERO(&rset);
            FD_SET(fd, &rset);
            tv.tv_sec  = 0;
            tv.tv_usec = 0;
            nready = select(fd + 1, &rset, NULL, NULL, &tv);
        }

        // Evict stale clients (if timeout is configured)
        if (clientTimeoutTicks > 0u) {
            EvictStaleUnicastClients();
        }
    }
}

// ---------------------------------------------------------------------------
// SendConfig
// ---------------------------------------------------------------------------

bool UDPSServer::SendConfig(const uint8 *payload, uint32 payloadSize) {
    if (!started) {
        return false;
    }

    // Cache for future CONNECT clients
    (void) CacheConfig(payload, payloadSize);

    configCounter++;

    bool ok = true;

    if (useMulticast) {
        // Send CONFIG over TCP to each connected client
        for (uint32 i = 0u; i < UDPS_SERVER_MAX_TCP_CLIENTS; i++) {
            if (tcpClients[i] == NULL_PTR(BasicTCPSocket *)) {
                continue;
            }
            bool sent = SendFragmentedTCP(*tcpClients[i], UDPS_TYPE_CONFIG,
                                          configCounter, payload, payloadSize);
            if (!sent) {
                REPORT_ERROR_STATIC(ErrorManagement::Warning,
                                    "UDPSServer: CONFIG send failed to TCP client %u, evicting.", i);
                EvictTCPClient(i);
                ok = false;
            }
        }
    }
    else {
        // Send CONFIG to each unicast client
        for (uint32 i = 0u; i < UDPS_SERVER_MAX_UNICAST_CLIENTS; i++) {
            if (!unicastClients[i].active) {
                continue;
            }
            InternetHost dest(unicastClients[i].clientPort, unicastClients[i].ipAddr);
            bool sent = SendFragmentedUDP(uniSendSocket, &dest, UDPS_TYPE_CONFIG,
                                          configCounter, payload, payloadSize);
            if (!sent) {
                REPORT_ERROR_STATIC(ErrorManagement::Warning,
                                    "UDPSServer: CONFIG send failed to %s:%u.",
                                    unicastClients[i].ipAddr,
                                    static_cast<uint32>(unicastClients[i].clientPort));
                ok = false;
            }
        }
    }

    return ok;
}

// ---------------------------------------------------------------------------
// SendData
// ---------------------------------------------------------------------------

bool UDPSServer::SendData(uint32 counter, const uint8 *payload, uint32 payloadSize) {
    if (!started) {
        return false;
    }

    bool ok = true;

    if (useMulticast) {
        // Single multicast write (no dest needed — socket already connected)
        bool sent = SendFragmentedUDP(dataSocket, NULL_PTR(InternetHost *),
                                      UDPS_TYPE_DATA, counter, payload, payloadSize);
        if (!sent) {
            REPORT_ERROR_STATIC(ErrorManagement::Warning,
                                "UDPSServer: DATA send to multicast group failed.");
            ok = false;
        }
    }
    else {
        for (uint32 i = 0u; i < UDPS_SERVER_MAX_UNICAST_CLIENTS; i++) {
            if (!unicastClients[i].active) {
                continue;
            }
            InternetHost dest(unicastClients[i].clientPort, unicastClients[i].ipAddr);
            bool sent = SendFragmentedUDP(uniSendSocket, &dest, UDPS_TYPE_DATA,
                                          counter, payload, payloadSize);
            if (!sent) {
                REPORT_ERROR_STATIC(ErrorManagement::Warning,
                                    "UDPSServer: DATA send failed to %s:%u.",
                                    unicastClients[i].ipAddr,
                                    static_cast<uint32>(unicastClients[i].clientPort));
                ok = false;
            }
        }
    }

    return ok;
}

// ---------------------------------------------------------------------------
// AddStaticClient
// ---------------------------------------------------------------------------

bool UDPSServer::AddStaticClient(const char8 *ip, uint16 port_) {
    if ((ip == NULL_PTR(const char8 *)) || (ip[0] == '\0')) {
        return false;
    }

    // Check for duplicate
    uint32 existing = FindUnicastClient(ip, port_);
    if (existing < UDPS_SERVER_MAX_UNICAST_CLIENTS) {
        unicastClients[existing].isStatic = true; // ensure it's marked static
        return true;
    }

    // Find free slot
    uint32 freeSlot = UDPS_SERVER_MAX_UNICAST_CLIENTS;
    for (uint32 i = 0u; i < UDPS_SERVER_MAX_UNICAST_CLIENTS; i++) {
        if (!unicastClients[i].active) {
            freeSlot = i;
            break;
        }
    }
    if (freeSlot >= UDPS_SERVER_MAX_UNICAST_CLIENTS) {
        REPORT_ERROR_STATIC(ErrorManagement::Warning,
                            "UDPSServer: Unicast client table full, cannot add static client %s:%u.",
                            ip, static_cast<uint32>(port_));
        return false;
    }

    // Populate slot
    uint32 ipLen = 0u;
    while ((ipLen < 63u) && (ip[ipLen] != '\0')) {
        unicastClients[freeSlot].ipAddr[ipLen] = ip[ipLen];
        ipLen++;
    }
    unicastClients[freeSlot].ipAddr[ipLen] = '\0';
    unicastClients[freeSlot].clientPort    = port_;
    unicastClients[freeSlot].lastSeenTicks = HighResolutionTimer::Counter();
    unicastClients[freeSlot].active        = true;
    unicastClients[freeSlot].isStatic      = true;
    numUnicastClients++;

    REPORT_ERROR_STATIC(ErrorManagement::Information,
                        "UDPSServer: Static client added: %s:%u.",
                        ip, static_cast<uint32>(port_));
    return true;
}

// ---------------------------------------------------------------------------
// Accessors
// ---------------------------------------------------------------------------

uint32 UDPSServer::GetClientCount() const {
    uint32 count = 0u;
    for (uint32 i = 0u; i < UDPS_SERVER_MAX_UNICAST_CLIENTS; i++) {
        if (unicastClients[i].active) {
            count++;
        }
    }
    for (uint32 i = 0u; i < UDPS_SERVER_MAX_TCP_CLIENTS; i++) {
        if (tcpClients[i] != NULL_PTR(BasicTCPSocket *)) {
            count++;
        }
    }
    return count;
}

bool UDPSServer::HasClients() const {
    return (GetClientCount() > 0u);
}

bool UDPSServer::IsMulticast() const {
    return useMulticast;
}

uint16 UDPSServer::GetPort() const {
    return port;
}

uint32 UDPSServer::GetMaxPayloadSize() const {
    return maxPayloadSize;
}

// ---------------------------------------------------------------------------
// Private: SendFragmentedUDP
// ---------------------------------------------------------------------------

bool UDPSServer::SendFragmentedUDP(BasicUDPSocket &sock,
                                    InternetHost   *dest,
                                    uint8           type,
                                    uint32          counter,
                                    const uint8    *payload,
                                    uint32          payloadSize) {
    uint32 maxChunk   = maxPayloadSize; // payload bytes per fragment (excl. header)
    uint32 totalFrags = (payloadSize == 0u) ? 1u :
                        ((payloadSize + maxChunk - 1u) / maxChunk);

    bool   ok   = true;
    uint32 offs = 0u;

    for (uint32 f = 0u; (f < totalFrags) && ok; f++) {
        uint32 chunkSize = payloadSize - offs;
        if (chunkSize > maxChunk) {
            chunkSize = maxChunk;
        }

        UDPSBuildHeader(sendBuf, type, counter,
                        static_cast<uint16>(f),
                        static_cast<uint16>(totalFrags),
                        chunkSize);

        if (chunkSize > 0u) {
            (void) MemoryOperationsHelper::Copy(sendBuf + UDPS_HEADER_SIZE,
                                                payload + offs,
                                                chunkSize);
        }

        uint32 sendSize = UDPS_HEADER_SIZE + chunkSize;

        if (dest != NULL_PTR(InternetHost *)) {
            (void) sock.SetDestination(*dest);
        }

        ok = sock.Write(reinterpret_cast<const char8 *>(sendBuf), sendSize);
        if (!ok) {
            REPORT_ERROR_STATIC(ErrorManagement::Warning,
                                "UDPSServer: UDP fragment %u/%u write failed.",
                                f + 1u, totalFrags);
        }

        offs += chunkSize;
    }

    return ok;
}

// ---------------------------------------------------------------------------
// Private: SendFragmentedTCP
// ---------------------------------------------------------------------------

bool UDPSServer::SendFragmentedTCP(BasicTCPSocket &sock,
                                    uint8           type,
                                    uint32          counter,
                                    const uint8    *payload,
                                    uint32          payloadSize) {
    uint32 maxChunk   = maxPayloadSize;
    uint32 totalFrags = (payloadSize == 0u) ? 1u :
                        ((payloadSize + maxChunk - 1u) / maxChunk);

    bool   ok   = true;
    uint32 offs = 0u;

    for (uint32 f = 0u; (f < totalFrags) && ok; f++) {
        uint32 chunkSize = payloadSize - offs;
        if (chunkSize > maxChunk) {
            chunkSize = maxChunk;
        }

        UDPSBuildHeader(sendBuf, type, counter,
                        static_cast<uint16>(f),
                        static_cast<uint16>(totalFrags),
                        chunkSize);

        if (chunkSize > 0u) {
            (void) MemoryOperationsHelper::Copy(sendBuf + UDPS_HEADER_SIZE,
                                                payload + offs,
                                                chunkSize);
        }

        uint32 sendSize = UDPS_HEADER_SIZE + chunkSize;
        ok = sock.Write(reinterpret_cast<const char8 *>(sendBuf), sendSize);
        if (!ok) {
            REPORT_ERROR_STATIC(ErrorManagement::Warning,
                                "UDPSServer: TCP fragment %u/%u write failed.",
                                f + 1u, totalFrags);
        }

        offs += chunkSize;
    }

    return ok;
}

// ---------------------------------------------------------------------------
// Private: EvictStaleUnicastClients
// ---------------------------------------------------------------------------

void UDPSServer::EvictStaleUnicastClients() {
    uint64 now = HighResolutionTimer::Counter();
    for (uint32 i = 0u; i < UDPS_SERVER_MAX_UNICAST_CLIENTS; i++) {
        if (!unicastClients[i].active || unicastClients[i].isStatic) {
            continue;
        }
        uint64 elapsed = now - unicastClients[i].lastSeenTicks;
        if (elapsed >= clientTimeoutTicks) {
            REPORT_ERROR_STATIC(ErrorManagement::Information,
                                "UDPSServer: Evicting stale client %s:%u.",
                                unicastClients[i].ipAddr,
                                static_cast<uint32>(unicastClients[i].clientPort));
            EvictUnicastClient(i);
        }
    }
}

// ---------------------------------------------------------------------------
// Private: HandleUnicastConnect
// ---------------------------------------------------------------------------

void UDPSServer::HandleUnicastConnect(const InternetHost &src) {
    StreamString srcAddrStr = src.GetAddress();
    const char8 *srcAddr    = srcAddrStr.Buffer();
    uint16       srcPort    = src.GetPort();

    REPORT_ERROR_STATIC(ErrorManagement::Information,
                        "UDPSServer: CONNECT from %s:%u.",
                        srcAddr, static_cast<uint32>(srcPort));

    // Check if this client is already known
    uint32 existing = FindUnicastClient(srcAddr, srcPort);
    if (existing < UDPS_SERVER_MAX_UNICAST_CLIENTS) {
        // Refresh last-seen and resend CONFIG
        unicastClients[existing].lastSeenTicks = HighResolutionTimer::Counter();
        if (cachedConfig != NULL_PTR(uint8 *)) {
            InternetHost dest(srcPort, srcAddr);
            configCounter++;
            (void) SendFragmentedUDP(uniSendSocket, &dest, UDPS_TYPE_CONFIG,
                                     configCounter, cachedConfig, cachedConfigSize);
        }
        return;
    }

    // New client — find a free slot (or evict oldest non-static)
    uint32 slot = UDPS_SERVER_MAX_UNICAST_CLIENTS;
    for (uint32 i = 0u; i < UDPS_SERVER_MAX_UNICAST_CLIENTS; i++) {
        if (!unicastClients[i].active) {
            slot = i;
            break;
        }
    }
    if (slot >= UDPS_SERVER_MAX_UNICAST_CLIENTS) {
        slot = FindOldestUnicastClient();
        if (slot < UDPS_SERVER_MAX_UNICAST_CLIENTS) {
            REPORT_ERROR_STATIC(ErrorManagement::Information,
                                "UDPSServer: Client table full — evicting %s:%u.",
                                unicastClients[slot].ipAddr,
                                static_cast<uint32>(unicastClients[slot].clientPort));
            EvictUnicastClient(slot);
        }
        else {
            REPORT_ERROR_STATIC(ErrorManagement::Warning,
                                "UDPSServer: All slots occupied by static clients; rejecting %s:%u.",
                                srcAddr, static_cast<uint32>(srcPort));
            return;
        }
    }

    // Fill slot
    uint32 addrLen = 0u;
    while ((addrLen < 63u) && (srcAddr[addrLen] != '\0')) {
        unicastClients[slot].ipAddr[addrLen] = srcAddr[addrLen];
        addrLen++;
    }
    unicastClients[slot].ipAddr[addrLen] = '\0';
    unicastClients[slot].clientPort      = srcPort;
    unicastClients[slot].lastSeenTicks   = HighResolutionTimer::Counter();
    unicastClients[slot].active          = true;
    unicastClients[slot].isStatic        = false;
    numUnicastClients++;

    // Send cached CONFIG if available
    if (cachedConfig != NULL_PTR(uint8 *)) {
        InternetHost dest(srcPort, srcAddr);
        configCounter++;
        (void) SendFragmentedUDP(uniSendSocket, &dest, UDPS_TYPE_CONFIG,
                                 configCounter, cachedConfig, cachedConfigSize);
    }
}

// ---------------------------------------------------------------------------
// Private: HandleUnicastDisconnect
// ---------------------------------------------------------------------------

void UDPSServer::HandleUnicastDisconnect(const InternetHost &src) {
    StreamString srcAddrStr = src.GetAddress();
    const char8 *srcAddr    = srcAddrStr.Buffer();
    uint16       srcPort    = src.GetPort();

    uint32 slot = FindUnicastClient(srcAddr, srcPort);
    if (slot < UDPS_SERVER_MAX_UNICAST_CLIENTS) {
        REPORT_ERROR_STATIC(ErrorManagement::Information,
                            "UDPSServer: DISCONNECT from %s:%u.",
                            srcAddr, static_cast<uint32>(srcPort));
        EvictUnicastClient(slot);
    }
}

// ---------------------------------------------------------------------------
// Private: HandleUnicastAck
// ---------------------------------------------------------------------------

void UDPSServer::HandleUnicastAck(const InternetHost &src) {
    StreamString srcAddrStr = src.GetAddress();
    const char8 *srcAddr    = srcAddrStr.Buffer();
    uint16       srcPort    = src.GetPort();

    uint32 slot = FindUnicastClient(srcAddr, srcPort);
    if (slot < UDPS_SERVER_MAX_UNICAST_CLIENTS) {
        unicastClients[slot].lastSeenTicks = HighResolutionTimer::Counter();
    }
}

// ---------------------------------------------------------------------------
// Private: FindUnicastClient
// ---------------------------------------------------------------------------

uint32 UDPSServer::FindUnicastClient(const char8 *ip, uint16 port_) const {
    for (uint32 i = 0u; i < UDPS_SERVER_MAX_UNICAST_CLIENTS; i++) {
        if (!unicastClients[i].active) {
            continue;
        }
        if (unicastClients[i].clientPort != port_) {
            continue;
        }
        // String compare
        bool match = true;
        uint32 k   = 0u;
        while ((k < 63u) && match) {
            if (unicastClients[i].ipAddr[k] != ip[k]) {
                match = false;
            }
            if (ip[k] == '\0') {
                break;
            }
            k++;
        }
        if (match) {
            return i;
        }
    }
    return UDPS_SERVER_MAX_UNICAST_CLIENTS;
}

// ---------------------------------------------------------------------------
// Private: FindOldestUnicastClient
// ---------------------------------------------------------------------------

uint32 UDPSServer::FindOldestUnicastClient() const {
    uint32 oldest     = UDPS_SERVER_MAX_UNICAST_CLIENTS;
    uint64 oldestTick = 0xFFFFFFFFFFFFFFFFuLL;

    for (uint32 i = 0u; i < UDPS_SERVER_MAX_UNICAST_CLIENTS; i++) {
        if (!unicastClients[i].active || unicastClients[i].isStatic) {
            continue;
        }
        if (unicastClients[i].lastSeenTicks < oldestTick) {
            oldestTick = unicastClients[i].lastSeenTicks;
            oldest     = i;
        }
    }
    return oldest;
}

// ---------------------------------------------------------------------------
// Private: EvictUnicastClient
// ---------------------------------------------------------------------------

void UDPSServer::EvictUnicastClient(uint32 idx) {
    if (idx >= UDPS_SERVER_MAX_UNICAST_CLIENTS) {
        return;
    }
    unicastClients[idx].active = false;
    if (numUnicastClients > 0u) {
        numUnicastClients--;
    }
}

// ---------------------------------------------------------------------------
// Private: HandleMulticastTCPConnect
// ---------------------------------------------------------------------------

void UDPSServer::HandleMulticastTCPConnect(BasicTCPSocket *newClient, uint32 idx) {
    if (idx >= UDPS_SERVER_MAX_TCP_CLIENTS) {
        return;
    }

    tcpClients[idx] = newClient;
    numTCPClients++;

    REPORT_ERROR_STATIC(ErrorManagement::Information,
                        "UDPSServer: Multicast TCP client connected (slot %u).", idx);

    // Send cached CONFIG over TCP
    if (cachedConfig != NULL_PTR(uint8 *)) {
        configCounter++;
        bool sent = SendFragmentedTCP(*newClient, UDPS_TYPE_CONFIG,
                                      configCounter, cachedConfig, cachedConfigSize);
        if (!sent) {
            REPORT_ERROR_STATIC(ErrorManagement::Warning,
                                "UDPSServer: Failed to send CONFIG to new TCP client (slot %u).", idx);
            EvictTCPClient(idx);
        }
    }
}

// ---------------------------------------------------------------------------
// Private: EvictTCPClient
// ---------------------------------------------------------------------------

void UDPSServer::EvictTCPClient(uint32 idx) {
    if (idx >= UDPS_SERVER_MAX_TCP_CLIENTS) {
        return;
    }
    if (tcpClients[idx] != NULL_PTR(BasicTCPSocket *)) {
        (void) tcpClients[idx]->Close();
        delete tcpClients[idx];
        tcpClients[idx] = NULL_PTR(BasicTCPSocket *);
        if (numTCPClients > 0u) {
            numTCPClients--;
        }
    }
}

// ---------------------------------------------------------------------------
// Private: CacheConfig
// ---------------------------------------------------------------------------

bool UDPSServer::CacheConfig(const uint8 *payload, uint32 payloadSize) {
    if (cachedConfig != NULL_PTR(uint8 *)) {
        delete[] cachedConfig;
        cachedConfig     = NULL_PTR(uint8 *);
        cachedConfigSize = 0u;
    }
    if ((payload == NULL_PTR(const uint8 *)) || (payloadSize == 0u)) {
        return true;
    }
    cachedConfig = new uint8[payloadSize];
    if (cachedConfig == NULL_PTR(uint8 *)) {
        REPORT_ERROR_STATIC(ErrorManagement::FatalError,
                            "UDPSServer: Failed to allocate cached CONFIG buffer.");
        return false;
    }
    (void) MemoryOperationsHelper::Copy(cachedConfig, payload, payloadSize);
    cachedConfigSize = payloadSize;
    return true;
}

} // namespace MARTe
