/**
 * @file UDPStreamer.h
 * @brief Header file for class UDPStreamer
 * @date 13/05/2026
 * @author Martino Ferrari
 *
 * @copyright Copyright 2015 F4E | European Joint Undertaking for ITER and
 * the Development of Fusion Energy ('Fusion for Energy').
 * Licensed under the EUPL, Version 1.1 or - as soon they will be approved
 * by the European Commission - subsequent versions of the EUPL (the "Licence")
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl
 *
 * @warning Unless required by applicable law or agreed to in writing,
 * software distributed under the Licence is distributed on an "AS IS"
 * basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the Licence permissions and limitations under the Licence.
 *
 * @details This header file contains the declaration of the class UDPStreamer
 * with all of its public, protected and private members. It may also include
 * definitions for inline methods which need to be visible to the compiler.
 */

#ifndef UDPSTREAMER_H_
#define UDPSTREAMER_H_

/*---------------------------------------------------------------------------*/
/*                        Standard header includes                           */
/*---------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------*/
/*                        Project header includes                            */
/*---------------------------------------------------------------------------*/
#include "CompilerTypes.h"
#include "EmbeddedServiceMethodBinderI.h"
#include "EventSem.h"
#include "FastPollingMutexSem.h"
#include "MemoryDataSourceI.h"
#include "SingleThreadService.h"
#include "StreamString.h"
#include "UDPSServer.h"

/*---------------------------------------------------------------------------*/
/*                           Class declaration                               */
/*---------------------------------------------------------------------------*/

namespace MARTe {

/**
 * @brief Publishing mode for the background sender thread.
 *
 * - Strict:     send one packet every Synchronise() call (default).
 * - Accumulate: buffer N successive snapshots and flush either when the next
 *               snapshot would exceed MaxPayloadSize or when 1/MinRefreshRate
 *               has elapsed.  Scalars expand to arrays; the first scalar with
 *               Unit="us"/"ns" is auto-promoted as the FullArray time reference.
 * - Decimate:   send one packet every Ratio Synchronise() calls.
 */
typedef enum {
    UDPStreamerPublishStrict     = 0u, /**< Send on every RT cycle (default) */
    UDPStreamerPublishAccumulate = 1u, /**< Accumulate until size/time limit; then flush */
    UDPStreamerPublishDecimate   = 2u  /**< Send 1 packet every Ratio cycles */
} UDPStreamerPublishMode;

/**
 * @brief Quantization types for float signals.
 */
typedef enum {
    UDPStreamerQuantNone   = 0u,  /**< No quantization; raw wire format */
    UDPStreamerQuantUint8  = 1u,  /**< Quantize to uint8 [0, 255] */
    UDPStreamerQuantInt8   = 2u,  /**< Quantize to int8 [-127, 127] */
    UDPStreamerQuantUint16 = 3u,  /**< Quantize to uint16 [0, 65535] */
    UDPStreamerQuantInt16  = 4u   /**< Quantize to int16 [-32767, 32767] */
} UDPStreamerQuantType;

/**
 * @brief Time reference modes for signals.
 */
typedef enum {
    UDPStreamerTimePacket      = 0u, /**< Use the packet-level timestamp (HRT at Synchronise time) */
    UDPStreamerTimeFullArray   = 1u, /**< Time signal has same NumberOfElements; one timestamp per element */
    UDPStreamerTimeFirstSample = 2u, /**< Time signal is scalar = timestamp of first element */
    UDPStreamerTimeLastSample  = 3u  /**< Time signal is scalar = timestamp of last element */
} UDPStreamerTimeMode;

/**
 * @brief Per-signal metadata used at runtime for serialization.
 */
struct UDPStreamerSignalInfo {
    StreamString        name;           /**< Signal name */
    TypeDescriptor      type;           /**< MARTe2 type descriptor */
    UDPStreamerQuantType quantType;      /**< Quantization type (none = raw copy) */
    uint8               numDimensions;  /**< Number of dimensions (0=scalar, 1=array, 2=matrix) */
    uint32              numElements;    /**< Total number of elements (rows * cols) */
    uint32              numRows;        /**< Number of rows */
    uint32              numCols;        /**< Number of columns */
    float64             rangeMin;       /**< Min of physical range (for quantization) */
    float64             rangeMax;       /**< Max of physical range (for quantization) */
    UDPStreamerTimeMode  timeMode;       /**< How time is encoded for this signal */
    float64             samplingRate;   /**< Hz, used for First/LastSample modes */
    uint32              timeSignalIdx;  /**< Index of the time-reference signal; 0xFFFFFFFFu = PacketTime */
    StreamString        unit;           /**< Physical unit string */
    uint32              srcByteSize;    /**< Bytes in MARTe2 memory */
    uint32              wireByteSize;   /**< Bytes on the wire (may differ when quantized) */
    uint32              bufferOffset;   /**< Byte offset in the flat MemoryDataSourceI memory buffer */
    bool                accumulated;    /**< True when this signal is batched (one snapshot per RT cycle) in Accumulate mode */
};

/**
 * @brief A DataSource that streams MARTe2 signals to UDP clients.
 *
 * @details This output DataSource accepts signals from GAMs and forwards them
 * asynchronously over the network. A dedicated background thread handles all
 * network I/O so that the real-time thread is only blocked by a fast spinlock
 * and a memcpy during Synchronise().
 *
 * Two operating modes are selected by the presence or absence of MulticastGroup:
 *
 * @par Unicast mode (default — no MulticastGroup)
 * The server opens a single UDP socket on Port. The client initiates the session
 * by sending a CONNECT packet to that port. The server replies with a CONFIG
 * packet on the same socket and subsequently sends DATA packets directly to the
 * client's address. Any number of clients may connect sequentially (one at a
 * time); a new CONNECT evicts the previous client.
 *
 * @par Multicast mode (MulticastGroup specified)
 * The server opens a TCP listener on Port for control traffic and a UDP socket
 * aimed at MulticastGroup:DataPort for data traffic. The client:
 *   1. Connects to Port via TCP and sends a CONNECT packet.
 *   2. Receives the CONFIG packet over TCP.
 *   3. Joins the multicast group (MulticastGroup:DataPort) to receive DATA packets.
 * Multiple clients may receive data simultaneously by joining the same group.
 * A new TCP CONNECT evicts the previous client from the control channel; the
 * multicast data stream continues regardless.
 *
 * @par Packet protocol (both modes)
 * - Client → Server: CONNECT (initiates session), DISCONNECT (terminates session),
 *   ACK (optional, for packet-loss monitoring).
 * - Server → Client: CONFIG (signal metadata), DATA (signal values, possibly
 *   fragmented into multiple datagrams if payload exceeds MaxPayloadSize).
 *
 * @par Top-level configuration parameters
 * | Parameter       | Type    | Default | Description |
 * |-----------------|---------|---------|-------------|
 * | Port            | uint16  | 44500   | TCP control port (multicast) or UDP server port (unicast). Values ≤ 1024 produce a warning. |
 * | MulticastGroup  | string  | *(absent)* | **Enables multicast mode.** IPv4 multicast address, e.g. `"239.0.0.1"`. Must be in 224.0.0.0/4. Absent or empty = unicast. |
 * | DataPort        | uint16  | Port+1  | UDP port for multicast DATA datagrams. Ignored in unicast mode. Must be non-zero and differ from Port. |
 * | MaxPayloadSize  | uint32  | 1400    | Maximum bytes of signal payload per UDP datagram (excluding the 17-byte header). Larger signals are fragmented. |
 * | PublishingMode  | string  | Strict  | `Strict`: send one packet every Synchronise() call. `Auto`: rate-limited; flush only when MinRefreshRate interval has elapsed. |
 * | MinRefreshRate  | float64 | —       | Required when PublishingMode = Auto. Flush frequency in Hz (e.g. 120.0). |
 * | MaxBatchSize    | uint32  | 1       | Optional when PublishingMode = Auto. Number of RT cycles to accumulate before flushing one packet. Scalar signals are expanded to arrays of MaxBatchSize elements; the first scalar with Unit="us" or "ns" is auto-promoted as the per-sample FullArray timestamp reference for all other scalars. When omitted or 1, the most-recent single value is sent at MinRefreshRate. |
 * | CPUMask         | uint32  | 0xFFFFFFFF | CPU affinity bitmask for the background thread. |
 * | StackSize       | uint32  | (MARTe2 default) | Stack size in bytes for the background thread. |
 *
 * @par Per-signal configuration parameters
 * | Parameter        | Type    | Default     | Description |
 * |------------------|---------|-------------|-------------|
 * | Type             | string  | —           | MARTe2 type name (uint8, int16, float32, float64, …). Mandatory. |
 * | NumberOfDimensions | uint8 | 0           | 0 = scalar, 1 = array, 2 = matrix. |
 * | NumberOfElements | uint32  | 1           | Total element count (rows × cols for matrices). |
 * | Unit             | string  | ""          | Physical unit label, e.g. `"Pa"` or `"m/s"`. Transmitted in CONFIG for display purposes. |
 * | RangeMin         | float64 | 0.0         | Minimum of the physical range. Required when QuantizedType is not `none`. |
 * | RangeMax         | float64 | 1.0         | Maximum of the physical range. Required when QuantizedType is not `none`. |
 * | QuantizedType    | string  | none        | Wire quantization for float signals: `none` \| `uint8` \| `int8` \| `uint16` \| `int16`. Reduces wire bandwidth at the cost of precision. Only valid for float32/float64 signals. |
 * | TimeMode         | string  | PacketTime  | How the signal's time axis is encoded (see below). |
 * | TimeSignal       | string  | —           | Name of the signal that carries timestamps. Required when TimeMode ≠ PacketTime. |
 * | SamplingRate     | float64 | —           | Signal sampling rate in Hz. Required when TimeMode = FirstSample or LastSample. |
 *
 * @par TimeMode values
 * | Value        | Description |
 * |--------------|-------------|
 * | PacketTime   | Uses the HRT counter captured at Synchronise() time as the single packet timestamp. No dedicated time signal needed. |
 * | FullArray    | TimeSignal has the same NumberOfElements as this signal; one timestamp per element. |
 * | FirstSample  | TimeSignal is a scalar = timestamp of the first element; subsequent elements are spaced by 1/SamplingRate. |
 * | LastSample   | TimeSignal is a scalar = timestamp of the last element; elements are spaced backwards by 1/SamplingRate. |
 *
 * @par Example — unicast mode
 * <pre>
 * +Streamer = {
 *     Class            = UDPStreamer
 *     Port             = 44500
 *     MaxPayloadSize   = 1400
 *     PublishingMode   = "Strict"
 *     Signals = {
 *         Time = {
 *             Type = uint64
 *         }
 *         Pressure = {
 *             Type                = float32
 *             NumberOfDimensions  = 1
 *             NumberOfElements    = 100
 *             Unit                = "Pa"
 *             RangeMin            = 0.0
 *             RangeMax            = 1000000.0
 *             QuantizedType       = uint16
 *             TimeMode            = LastSample
 *             TimeSignal          = Time
 *             SamplingRate        = 10000.0
 *         }
 *         Temperature = {
 *             Type     = float64
 *             Unit     = "degC"
 *             TimeMode = PacketTime
 *         }
 *     }
 * }
 * </pre>
 *
 * @par Example — multicast mode
 * <pre>
 * +Streamer = {
 *     Class            = UDPStreamer
 *     Port             = 44500           // TCP control port
 *     MulticastGroup   = "239.0.0.1"     // Enables multicast mode
 *     DataPort         = 44501           // UDP data port (default: Port+1)
 *     MaxPayloadSize   = 1400
 *     PublishingMode   = "Auto"
 *     MinRefreshRate   = 60
 *     Signals = {
 *         Time = {
 *             Type = uint64
 *         }
 *         Voltage = {
 *             Type                = float32
 *             NumberOfDimensions  = 1
 *             NumberOfElements    = 1000
 *             Unit                = "V"
 *             RangeMin            = -10.0
 *             RangeMax            = 10.0
 *             QuantizedType       = int16
 *             TimeMode            = FirstSample
 *             TimeSignal          = Time
 *             SamplingRate        = 100000.0
 *         }
 *     }
 * }
 * </pre>
 */
class UDPStreamer : public MemoryDataSourceI, public EmbeddedServiceMethodBinderI {
public:
    CLASS_REGISTER_DECLARATION()

    /**
     * @brief Constructor. Initialises all members to safe defaults.
     */
    UDPStreamer();

    /**
     * @brief Destructor. Stops the background thread and frees allocated memory.
     */
    virtual ~UDPStreamer();

    /**
     * @brief Parses top-level configuration parameters.
     * @details Reads Port, MaxPayloadSize, CPUMask, StackSize, PublishingMode,
     * MinRefreshRate, MulticastGroup, and DataPort from the configuration node.
     * Sets useMulticast and dataPort when MulticastGroup is present and valid.
     * @return true if all mandatory parameters are valid and consistent.
     */
    virtual bool Initialise(StructuredDataI &data);

    /**
     * @brief Validates signal types and builds the per-signal metadata array.
     * @details Reads per-signal custom fields (Unit, RangeMin, RangeMax, QuantizedType,
     * TimeMode, TimeSignal, SamplingRate) from signalsDatabase and populates signalInfos[].
     * @return true if all signal configurations are valid.
     */
    virtual bool SetConfiguredDatabase(StructuredDataI &data);

    /**
     * @brief Allocates signal memory (via parent) plus readyBuffer, scratchBuffer, wireBuffer.
     * @return true if all memory allocations succeed.
     */
    virtual bool AllocateMemory();

    /**
     * @brief Returns the broker name for the given signal direction.
     * @return "MemoryMapSynchronisedOutputBroker" for OutputSignals; "" otherwise.
     */
    virtual const char8 *GetBrokerName(StructuredDataI &data,
                                       const SignalDirection direction);

    /**
     * @brief Called by the RT thread after all GAMs have written output signals.
     * @details Copies signal memory to readyBuffer under a fast spinlock, then posts
     * the dataSem to wake the background thread. Returns immediately.
     * @return true always.
     */
    virtual bool Synchronise();

    /**
     * @brief Opens the server socket and starts the background thread.
     * @return true if the socket and thread start successfully.
     */
    virtual bool PrepareNextState(const char8 *const currentStateName,
                                  const char8 *const nextStateName);

    /**
     * @brief Background thread entry point.
     * @details Handles client CONNECT/DISCONNECT/ACK and sends DATA packets.
     */
    virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info);

    /**
     * @brief Returns the listening port number.
     */
    uint16 GetPort() const;

    /**
     * @brief Returns the maximum payload size per UDP datagram.
     */
    uint32 GetMaxPayloadSize() const;

    /**
     * @brief Returns true if a client is currently connected.
     */
    bool IsClientConnected() const;

    /**
     * @brief Returns true when multicast mode is active (MulticastGroup was set in config).
     */
    bool IsMulticast() const;

private:
    /**
     * @brief Serializes the CONFIG payload into buf and sets payloadSize.
     * @return true if serialization succeeds.
     */
    bool BuildConfigPayload(uint8 *buf, uint32 bufSize, uint32 &payloadSize);

    /**
     * @brief Quantizes and serializes all signals from srcBuf into wireBuffer.
     * @param[in]  srcBuf     Flat source buffer (signal data in MARTe2 format).
     * @param[in]  timestamp  HRT counter to embed as packet timestamp.
     */
    void QuantizeAndSerialize(const uint8 *srcBuf, uint64 timestamp);

    /**
     * @brief Serializes an accumulated batch into wireBuffer.
     * @param src        Flat buffer [numSamples × totalSrcBytes], slot 0 = oldest.
     * @param timestamps HRT counters per slot; timestamps[0] is the packet timestamp.
     * @param numSamples Actual number of filled slots to serialize.
     */
    void SerializeAccumulated(const uint8 *src, const uint64 *timestamps, uint32 numSamples);

    /**
     * @brief Maps a MARTe2 TypeDescriptor to the UDPS_TYPECODE_* constants.
     */
    static uint8 TypeDescriptorToCode(TypeDescriptor td);

    /* Configuration parameters */
    uint16 port;                        /**< UDP server port */
    uint32 maxPayloadSize;              /**< Max payload bytes per UDP packet (excluding header) */
    uint32 cpuMask;                     /**< Background thread CPU affinity */
    uint32 stackSize;                   /**< Background thread stack size */
    UDPStreamerPublishMode publishMode;  /**< Strict or Auto publishing mode */
    float64 minRefreshRate;             /**< Minimum flush rate (Hz) for Auto mode */
    uint64 flushPeriodTicks;            /**< HRT ticks per flush interval (computed from minRefreshRate) */
    /* Accumulate mode — dynamic batch parameters */
    uint32          maxBatchCount;        /**< Max snapshots that fit in MaxPayloadSize (Accumulate) */
    uint32          singleCycleWireBytes; /**< Wire bytes for ALL signals (scalar and array) per snapshot */
    volatile uint64 lastPublishTs;        /**< HRT counter of last successful flush (Accumulate mode) */
    uint8          *accumBuffer;          /**< Heap: [maxBatchCount × totalSrcBytes] linear fill */
    uint64         *accumTimestamps;      /**< Heap: [maxBatchCount] HRT counter per snapshot */
    uint32          accumFill;            /**< Slots filled in accumBuffer (0..maxBatchCount) */
    uint64         *readyTimestamps;      /**< Heap: [maxBatchCount] HRT for completed ready batch */
    uint64         *scratchTimestamps;    /**< Heap: [maxBatchCount] background-thread local copy */
    uint32          readyFill;            /**< Snapshot count in the ready batch */
    /* Decimate mode */
    uint32          decimateRatio;        /**< Send 1 packet every decimateRatio Synchronise() calls */
    uint32          decimateCounter;      /**< Current decimate cycle counter */

    /* Signal metadata */
    uint32                 numSigs;       /**< Number of signals */
    UDPStreamerSignalInfo  *signalInfos;   /**< Per-signal metadata array */

    /* Double-buffering for RT safety */
    uint8               *readyBuffer;    /**< Protected copy of signal memory for background thread */
    uint8               *scratchBuffer;  /**< Local copy used during serialization (avoids holding lock) */
    uint32               totalSrcBytes;  /**< Total bytes of signal data (= stateMemorySize) */
    FastPollingMutexSem  bufMutex;       /**< Protects readyBuffer against concurrent access */
    EventSem             dataSem;        /**< Wakes background thread when new data is ready */
    volatile uint64      syncTimestamp;  /**< HRT counter captured in Synchronise() */

    /* Wire serialization buffer */
    uint8  *wireBuffer;      /**< Pre-allocated buffer for the serialized DATA payload */
    uint32  totalWireBytes;  /**< Total bytes of all signals after quantization + 8-byte timestamp prefix */

    /* Networking — handled by UDPSServer */
    UDPSServer      server;          /**< Multi-client session manager (unicast and multicast) */

    /* Thread management */
    SingleThreadService executor;    /**< Background thread service */

    /* Packet sequencing */
    uint32 packetCounter; /**< Incremented for each DATA update sent */
};

} /* namespace MARTe */

#endif /* UDPSTREAMER_H_ */
