#ifndef UDPS_CLIENT_H_
#define UDPS_CLIENT_H_

/**
 * @file UDPSClient.h
 * @brief Auto-reconnecting UDPS receiver client (C++ MARTe2 library class).
 *
 * UDPSClient runs its own background thread (via SingleThreadService).
 * On start it connects to a UDPSServer, receives CONFIG + DATA packets, and
 * reassembles fragmented updates.  If the server goes silent for longer than
 * SilenceTimeout, it automatically disconnects and retries.
 *
 * Unicast mode: sends CONNECT to server UDP port; receives on an ephemeral UDP port.
 * Multicast mode: TCP to server port for CONFIG; joins UDP multicast group for DATA.
 *
 * Threading: UDPSClientListener callbacks are invoked from the internal receive
 * thread — the implementation must be thread-safe with respect to the caller.
 */

#include "BasicTCPSocket.h"
#include "BasicUDPSocket.h"
#include "EmbeddedServiceMethodBinderI.h"
#include "ExecutionInfo.h"
#include "HighResolutionTimer.h"
#include "InternetHost.h"
#include "SingleThreadService.h"
#include "StreamString.h"
#include "StructuredDataI.h"
#include "UDPSProtocol.h"

namespace MARTe {

// ---------------------------------------------------------------------------
// Listener interface
// ---------------------------------------------------------------------------

/**
 * @brief Callback interface for UDPSClient events.
 *
 * Implement this interface and pass an instance to UDPSClient::SetListener().
 * All callbacks are invoked from the UDPSClient internal thread.
 */
class UDPSClientListener {
public:
    virtual ~UDPSClientListener() {}

    /** Called when a fully-reassembled CONFIG payload is available. */
    virtual void OnUDPSConfig(const uint8 *payload, uint32 payloadSize) {}

    /** Called when a fully-reassembled DATA payload is available. */
    virtual void OnUDPSData(const uint8 *payload, uint32 payloadSize) {}

    /**
     * @brief Called for every received DATA datagram (fragment).
     * @param counter  DATA packet counter from the UDPS header.
     * @param nBytes   Raw datagram size (header + payload).
     * @param complete True iff this fragment completed the DATA reassembly.
     */
    virtual void OnUDPSFragment(uint32 counter, uint32 nBytes, bool complete) {}

    /** Called when the connection to the server has been established. */
    virtual void OnUDPSConnected() {}

    /** Called when the connection has been lost or closed. */
    virtual void OnUDPSDisconnected() {}
};

// ---------------------------------------------------------------------------
// UDPSClient
// ---------------------------------------------------------------------------

/**
 * @brief UDPS receiver client with auto-reconnect and fragment reassembly.
 */
class UDPSClient : public EmbeddedServiceMethodBinderI {
public:

    /** Maximum number of concurrent in-flight reassembly slots. */
    static const uint32 UDPS_CLIENT_MAX_REASSEMBLY_SLOTS = 4u;

    /** Maximum size (bytes) of a single reassembled packet payload. A UDPS
     *  update for one source is fragmented into MaxPayloadSize chunks; this is
     *  the ceiling on the reassembled total, so it bounds the largest multi-
     *  fragment packet the client can deliver. Sized for large array bursts
     *  (e.g. 8x10000 float32 ~= 320 KiB) with headroom; stays well within the
     *  256-fragment span the recvMask[32] tracks at typical chunk sizes. */
    static const uint32 UDPS_CLIENT_MAX_PACKET_BYTES = 1048576u; // 1 MiB

    /** Default silence timeout before reconnect (seconds). */
    static const uint32 UDPS_CLIENT_DEFAULT_SILENCE_TIMEOUT_S = 5u;

    /** Default delay between reconnect attempts (seconds). */
    static const uint32 UDPS_CLIENT_DEFAULT_RECONNECT_DELAY_S = 2u;

    /** Default maximum payload size (bytes, excluding 17-byte header). */
    static const uint32 UDPS_CLIENT_DEFAULT_MAX_PAYLOAD = 1400u;

    /** Default OS UDP receive socket buffer size (bytes). The Linux default
     *  (rmem_default, typically ~208 KiB) is easily overrun by high-throughput
     *  sources (e.g. multi-hundred-KiB bursts every few ms), causing silent
     *  kernel-level datagram drops. 4 MiB gives generous burst headroom. */
    static const uint32 UDPS_CLIENT_DEFAULT_RECV_BUFFER = 4194304u; // 4 MiB

    UDPSClient();
    virtual ~UDPSClient();

    /**
     * @brief Read configuration from a StructuredDataI node.
     *
     * Expected keys:
     *  - ServerAddr      (char*)  Server IPv4 address. Required.
     *  - Port            (uint16) Server UDP port (unicast) or TCP listen port (multicast). Required.
     *  - MulticastGroup  (char*)  IPv4 multicast address; presence enables multicast mode.
     *  - DataPort        (uint16) UDP multicast data port (defaults to Port+1).
     *  - SilenceTimeout  (uint32) Seconds of no data before reconnect. Default 5.
     *  - ReconnectDelay  (uint32) Seconds to wait between reconnect attempts. Default 2.
     *  - MaxPayloadSize  (uint32) Max payload bytes per datagram, excluding header. Default 1400.
     *  - CPUMask         (uint32) CPU affinity mask for the receive thread. Default 0xFFFFFFFF.
     *  - StackSize       (uint32) Stack size for the receive thread. Default 65536.
     *  - RecvBufferSize  (uint32) OS UDP receive socket buffer size (bytes). Default 4 MiB.
     */
    bool Initialise(StructuredDataI &data);

    /**
     * @brief Register the event listener.  Must be called before Start().
     */
    void SetListener(UDPSClientListener *listener);

    /**
     * @brief Start the receive thread.
     * @return true on success.
     */
    bool Start();

    /**
     * @brief Stop the receive thread and close all sockets.
     * @return true on success.
     */
    bool Stop();

    /**
     * @brief Internal thread entry point (EmbeddedServiceMethodBinderI).
     * @details Do NOT call directly.
     */
    virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info);

private:

    // -------------------------------------------------------------------------
    // Fragment reassembly slot
    // -------------------------------------------------------------------------
    struct UDPSReassemblySlot {
        uint32 counter;         ///< Packet counter this slot belongs to
        uint8  type;            ///< UDPS_TYPE_DATA or UDPS_TYPE_CONFIG
        uint16 totalFragments;  ///< Expected fragment count
        uint16 receivedFragments; ///< How many we have so far
        uint8  recvMask[32];    ///< Bitmask: bit f set iff fragment f received
        uint8  payload[UDPS_CLIENT_MAX_PACKET_BYTES]; ///< Assembled payload buffer
        uint64 firstSeenTicks;  ///< For GC (2 s stale detection)
        bool   active;          ///< Slot in use
        uint32 chunkSize;       ///< Payload bytes per fragment (from first fragment)
        uint32 assembledBytes;  ///< Exact total payload bytes placed so far
    };

    // -------------------------------------------------------------------------
    // Connection state machine helpers
    // -------------------------------------------------------------------------
    bool Connect();
    void Disconnect();
    bool ReceiveAndProcess();

    bool ConnectUnicast();
    bool ConnectMulticast();

    /** Set the OS receive buffer size (SO_RCVBUF) on a UDP socket's raw handle. */
    void SetRecvBufferSize(BasicUDPSocket &sock);

    void ProcessDatagram(const uint8 *buf, uint32 size);
    /** Read one full UDPS frame (header + payload) from the TCP control socket. */
    bool ReceiveTCPFrame();
    /** Read exactly n bytes from the TCP control socket. */
    bool ReadExactTCP(uint8 *dst, uint32 n);
    /** @return true iff this fragment completed the reassembly (payload delivered). */
    bool PlaceFragment(const UDPSPacketHeader *hdr, const uint8 *payload, uint32 payloadBytes);
    void GcReassemblySlots();
    void DeliverAssembled(UDPSReassemblySlot &slot);

    // -------------------------------------------------------------------------
    // Configuration
    // -------------------------------------------------------------------------
    StreamString serverAddr;
    uint16       serverPort;
    StreamString multicastGroup;
    uint16       dataPort;
    bool         useMulticast;
    uint64       silenceTimeoutTicks;
    uint64       reconnectDelayTicks;
    uint32       maxPayloadSize;
    uint32       cpuMask;
    uint32       stackSize;
    uint32       recvBufferSize;

    // -------------------------------------------------------------------------
    // Runtime state
    // -------------------------------------------------------------------------
    UDPSClientListener *listener;
    SingleThreadService threadService;
    bool                connected;
    uint64              lastDataTicks;  ///< Ticks at last received DATA/CONFIG
    uint64              disconnectTick; ///< Ticks when we disconnected (for delay)

    // Unicast
    BasicUDPSocket recvSocket;  ///< Bound to ephemeral port; receives DATA
    uint16         localPort;   ///< Ephemeral port we're listening on

    // Multicast
    BasicTCPSocket tcpSocket;    ///< TCP connection to server (for CONNECT + CONFIG)
    BasicUDPSocket mcastSocket;  ///< Joined multicast group

    // Reassembly
    UDPSReassemblySlot reassemblySlots[UDPS_CLIENT_MAX_REASSEMBLY_SLOTS];
    uint64             lastGcTicks;  ///< Ticks at last GC run

    // Receive scratch buffer
    uint8 recvBuf[65535u + UDPS_HEADER_SIZE];
};

} // namespace MARTe

#endif // UDPS_CLIENT_H_
