#ifndef UDPS_SERVER_H_
#define UDPS_SERVER_H_

/**
 * @file UDPSServer.h
 * @brief Multi-client UDPS session-management and fragmented-send helper.
 *
 * UDPSServer is a plain C++ helper (not a MARTe2 Object) that encapsulates:
 *  - Unicast mode: up to UDPS_SERVER_MAX_UNICAST_CLIENTS simultaneous UDP clients
 *    that self-register via CONNECT datagrams.
 *  - Multicast mode: TCP listener for CONNECT + CONFIG delivery; UDP multicast
 *    socket for DATA datagrams.
 *  - Static clients: pre-configured destinations that never need to send CONNECT
 *    (backward-compat for push-to-fixed-IP use cases).
 *
 * Threading model: UDPSServer is NOT thread-safe.  All public methods must be
 * called from the same thread (the owner's Execute() thread).
 */

#include "BasicTCPSocket.h"
#include "BasicUDPSocket.h"
#include "InternetHost.h"
#include "StreamString.h"
#include "StructuredDataI.h"
#include "UDPSProtocol.h"

namespace MARTe {

/**
 * @brief Multi-client / multicast UDPS server helper.
 *
 * Usage pattern:
 * @code
 *   UDPSServer server;
 *   server.Initialise(data);          // in component Initialise()
 *   server.Start();                   // in PrepareNextState() / Start()
 *   // inside Execute() loop:
 *   server.ServiceClients();          // poll CONNECT / DISCONNECT / ACK
 *   if (server.HasClients()) {
 *       server.SendConfig(cfgBuf, cfgSize);  // when config changes
 *       server.SendData(counter, dataBuf, dataSize);
 *   }
 *   server.Stop();                    // in StopCurrentStateExecution()
 * @endcode
 */
class UDPSServer {
public:

    /** Maximum number of simultaneous unicast clients. */
    static const uint32 UDPS_SERVER_MAX_UNICAST_CLIENTS = 16u;

    /** Maximum number of simultaneous multicast TCP control connections. */
    static const uint32 UDPS_SERVER_MAX_TCP_CLIENTS = 8u;

    /** Default stale-client eviction timeout (seconds). */
    static const uint32 UDPS_SERVER_DEFAULT_CLIENT_TIMEOUT_S = 30u;

    /** Default maximum UDP payload size (bytes, EXCLUDING the 17-byte header). */
    static const uint32 UDPS_SERVER_DEFAULT_MAX_PAYLOAD = 1400u;

    UDPSServer();
    ~UDPSServer();

    /**
     * @brief Read configuration from a StructuredDataI node.
     *
     * Expected keys (all optional except Port for unicast):
     *  - Port            (uint16) UDP port to bind (unicast) or TCP listen port (multicast).
     *  - MulticastGroup  (char*) IPv4 multicast address; presence enables multicast mode.
     *  - DataPort        (uint16) UDP port used for DATA datagrams in multicast mode
     *                             (defaults to Port+1 if omitted).
     *  - MaxPayloadSize  (uint32) Max payload bytes per datagram, excluding header.
     *                             Defaults to UDPS_SERVER_DEFAULT_MAX_PAYLOAD.
     *  - ClientTimeout   (uint32) Seconds before a silent unicast client is evicted.
     *                             Defaults to UDPS_SERVER_DEFAULT_CLIENT_TIMEOUT_S.
     *                             Set to 0 to disable timeout-based eviction.
     */
    bool Initialise(StructuredDataI &data);

    /**
     * @brief Open sockets and begin accepting clients.
     * @return true on success.
     */
    bool Start();

    /**
     * @brief Close all sockets and evict all clients.  Frees cached CONFIG.
     */
    bool Stop();

    /**
     * @brief Non-blocking poll for incoming CONNECT / DISCONNECT / ACK messages.
     *
     * Must be called regularly from the owner's Execute() main loop.
     * In multicast mode also polls the TCP listener for new connections.
     */
    void ServiceClients();

    /**
     * @brief Send a CONFIG payload to all active clients.
     *
     * Also stores a copy as the cached CONFIG, which is automatically sent to
     * any new CONNECT client.
     *
     * @param payload     Pointer to fully-assembled CONFIG payload (caller-owned).
     * @param payloadSize Byte count of payload.
     * @return true if at least one send succeeded (or no clients are connected).
     */
    bool SendConfig(const uint8 *payload, uint32 payloadSize);

    /**
     * @brief Send a DATA payload to all active clients.
     *
     * @param counter     Per-update sequence counter (same for all fragments).
     * @param payload     Pointer to fully-assembled DATA payload (caller-owned).
     * @param payloadSize Byte count of payload.
     * @return true if all sends succeeded.
     */
    bool SendData(uint32 counter, const uint8 *payload, uint32 payloadSize);

    /**
     * @brief Add a static unicast destination that never needs to send CONNECT.
     *
     * The entry is never evicted by the timeout mechanism.  Duplicate
     * (ip, port) pairs are silently ignored.
     *
     * @param ip   Destination IPv4 address string.
     * @param port Destination UDP port.
     * @return true if the client was added (or was already present).
     */
    bool AddStaticClient(const char8 *ip, uint16 port);

    /** @return Number of currently active clients (unicast + TCP). */
    uint32 GetClientCount() const;

    /** @return true if at least one client is active. */
    bool HasClients() const;

    /** @return true if server is in multicast mode. */
    bool IsMulticast() const;

    /** @return Configured port number. */
    uint16 GetPort() const;

    /** @return Configured maximum payload size (excluding header). */
    uint32 GetMaxPayloadSize() const;

private:

    // -------------------------------------------------------------------------
    // Per-client state (unicast)
    // -------------------------------------------------------------------------
    struct UDPSClientEntry {
        char8  ipAddr[64];    ///< Client IPv4 address string
        uint16 clientPort;    ///< Client source port
        uint64 lastSeenTicks; ///< HighResolutionTimer ticks at last contact
        bool   active;        ///< Slot is in use
        bool   isStatic;      ///< Never evicted by timeout; no CONNECT required
    };

    // -------------------------------------------------------------------------
    // Private helpers
    // -------------------------------------------------------------------------

    /**
     * @brief Send a fragmented UDPS packet via a UDP socket.
     *
     * @param sock        Socket to write through.
     * @param dest        If non-NULL, SetDestination is called before each write
     *                    (unicast unconnected socket).  If NULL, assumes the socket
     *                    is already connected (multicast dataSocket).
     * @param type        UDPS_TYPE_DATA or UDPS_TYPE_CONFIG.
     * @param counter     Sequence counter.
     * @param payload     Payload bytes.
     * @param payloadSize Payload byte count.
     * @return true if all fragments were written without error.
     */
    bool SendFragmentedUDP(BasicUDPSocket &sock,
                           InternetHost   *dest,
                           uint8           type,
                           uint32          counter,
                           const uint8    *payload,
                           uint32          payloadSize);

    /**
     * @brief Send a fragmented UDPS packet via a TCP socket.
     *
     * Used in multicast mode to push CONFIG to each TCP control client.
     */
    bool SendFragmentedTCP(BasicTCPSocket &sock,
                           uint8           type,
                           uint32          counter,
                           const uint8    *payload,
                           uint32          payloadSize);

    /** Evict unicast clients that have been silent longer than clientTimeoutTicks. */
    void EvictStaleUnicastClients();

    /** Handle a CONNECT datagram received on serverSocket. */
    void HandleUnicastConnect(const InternetHost &src);

    /** Handle a DISCONNECT datagram received on serverSocket. */
    void HandleUnicastDisconnect(const InternetHost &src);

    /** Handle an ACK datagram received on serverSocket. */
    void HandleUnicastAck(const InternetHost &src);

    /**
     * @brief Find a unicast client slot by IP + port.
     * @return Slot index, or UDPS_SERVER_MAX_UNICAST_CLIENTS if not found.
     */
    uint32 FindUnicastClient(const char8 *ip, uint16 port) const;

    /**
     * @brief Find the oldest (lowest lastSeenTicks) non-static unicast client.
     * @return Slot index, or UDPS_SERVER_MAX_UNICAST_CLIENTS if none.
     */
    uint32 FindOldestUnicastClient() const;

    /** Close slot @p idx and mark it inactive. */
    void EvictUnicastClient(uint32 idx);

    /** Accept a new multicast TCP connection and send cached CONFIG. */
    void HandleMulticastTCPConnect(BasicTCPSocket *newClient, uint32 idx);

    /** Close TCP client slot @p idx. */
    void EvictTCPClient(uint32 idx);

    /** Cache a copy of a CONFIG payload for future CONNECT clients. */
    bool CacheConfig(const uint8 *payload, uint32 payloadSize);

    // -------------------------------------------------------------------------
    // Configuration
    // -------------------------------------------------------------------------
    uint16      port;
    uint32      maxPayloadSize;
    StreamString multicastGroup;
    uint16      dataPort;
    bool        useMulticast;
    uint64      clientTimeoutTicks; ///< 0 = disabled

    // -------------------------------------------------------------------------
    // Unicast state
    // -------------------------------------------------------------------------
    UDPSClientEntry unicastClients[UDPS_SERVER_MAX_UNICAST_CLIENTS];
    uint32          numUnicastClients;

    BasicUDPSocket  serverSocket;    ///< Bound to port; receives CONNECT/DISCONNECT/ACK
    BasicUDPSocket  uniSendSocket;   ///< Unconnected; SetDestination before each Write

    // -------------------------------------------------------------------------
    // Multicast state
    // -------------------------------------------------------------------------
    BasicTCPSocket  tcpListener;
    BasicTCPSocket *tcpClients[UDPS_SERVER_MAX_TCP_CLIENTS];
    uint32          numTCPClients;
    BasicUDPSocket  dataSocket;     ///< Connected to multicastGroup:dataPort

    // -------------------------------------------------------------------------
    // Shared state
    // -------------------------------------------------------------------------
    uint8  *cachedConfig;        ///< Last SendConfig() payload (heap-allocated)
    uint32  cachedConfigSize;

    uint8  *sendBuf;             ///< Pre-allocated fragment TX buffer
    uint32  sendBufCapacity;     ///< UDPS_HEADER_SIZE + maxPayloadSize

    uint32  configCounter;       ///< Sequence counter for CONFIG packets
    bool    started;
};

} // namespace MARTe

#endif // UDPS_SERVER_H_
