/**
 * @file StreamHub.h
 * @brief Top-level orchestrator: N UDP sources, M WebSocket clients, push loop, trigger.
 *
 * StreamHub owns:
 *  - Up to kMaxSessions UDPSourceSessions (one per MARTe2 UDPStreamer source)
 *  - WSServer (multi-client WebSocket server)
 *  - TriggerEngine (continuous / armed / triggered FSM)
 *
 * The push loop runs at pushRateHz Hz and:
 *  1. For each configured session, reads ring buffers, applies LTTB decimation,
 *     serializes a binary data frame, broadcasts to all WS clients.
 *  2. At statsRateHz intervals, serializes a JSON stats frame and broadcasts.
 *
 * Commands are received as JSON text frames from any connected WS client via the
 * WSCommandCallback interface.
 */

#ifndef STREAMHUB_H_
#define STREAMHUB_H_

#include "CompilerTypes.h"
#include "FastPollingMutexSem.h"
#include "StreamString.h"
#include "StructuredDataI.h"
#include "Threads.h"

#include "UDPSourceSession.h"
#include "WSServer.h"
#include "TriggerEngine.h"
#include "HistoryWriter.h"

namespace StreamHub {

using MARTe::uint8;
using MARTe::uint16;
using MARTe::uint32;
using MARTe::uint64;
using MARTe::float64;
using MARTe::StreamString;
using MARTe::FastPollingMutexSem;
using MARTe::StructuredDataI;

/** Maximum number of simultaneously connected UDPStreamer sources. */
static const uint32 kMaxSessions = 32u;

/**
 * @brief Top-level StreamHub orchestrator.
 *
 * Usage:
 *   StreamHub hub;
 *   hub.Initialise(cfg);   // parse WSPort, MaxPoints, PushRate, Sources
 *   hub.Run();             // blocking push loop; returns on Stop()
 */
class StreamHub : public WSCommandCallback {
public:

    StreamHub();
    virtual ~StreamHub();

    /**
     * @brief Configure the hub from a MARTe2 StructuredDataI.
     *
     * Expected keys:
     *   WSPort       (uint32, default 8090)
     *   MaxPoints    (uint32, default 20000)  — ring buffer capacity per signal
     *   PushRate     (uint32, default 30)     — push loop rate in Hz
     *   MaxPushPoints (uint32, default 500)   — LTTB threshold for live push
     *   StatsRate    (uint32, default 1)      — stats broadcast rate in Hz
     *   +Sources { +<id> { Label=...; Addr=...; Port=... } }
     *
     * @return true on success.
     */
    bool Initialise(StructuredDataI &cfg);

    /**
     * @brief Start the WebSocket server and run the push loop (blocking).
     * Returns when Stop() is called (e.g. from SIGINT handler).
     */
    bool Run();

    /**
     * @brief Signal the push loop to exit.  Thread-safe.
     */
    void Stop();

    /* ---- WSCommandCallback (called from WS client read threads) ---------- */
    virtual void OnWSCommand(const char *json, uint32 len, uint32 slotIdx);
    virtual void OnWSClientConnected();
    virtual void OnWSClientDisconnected();

private:

    /* ---- Push loop helpers ----------------------------------------------- */

    /** One push tick: for each session emit a binary frame; handle trigger. */
    void PushData();

    /** Serialize one binary data frame for session idx into pushBuf_. */
    uint32 SerializeBinaryFrame(uint32 sessionIdx, uint8 *buf, uint32 bufSize);

    /** Broadcast JSON stats for all sessions. */
    void PushStats();

    /** Broadcast {"type":"sources"} listing. */
    void BroadcastSources();

    /** Broadcast {"type":"config","sourceId":...} for one session. */
    void BroadcastConfig(uint32 sessionIdx);

    /* ---- Trigger (push loop side) ----------------------------------------- */

    /**
     * @brief Per-tick trigger servicing: finalize captures, auto-rearm,
     * broadcast state transitions.
     */
    void TriggerTick(float64 wallNowS);

    /** Broadcast {"type":"triggerState",...} reflecting the current FSM. */
    void BroadcastTriggerState();

    /**
     * @brief Build and broadcast the version=2 binary capture frame:
     * [u8 2][f64 trigTime][f64 preSec][f64 postSec][u32 nSig]
     * {[u16 keyLen][fullKey][u32 N][t f64×N][v f64×N]}
     */
    void BroadcastTriggerCapture(float64 trigTime, float64 preSec,
                                 float64 postSec);

    /* ---- Command handlers (called from OnWSCommand) ---------------------- */

    void HandleAddSource(const char *json);
    void HandleRemoveSource(const char *json);
    void HandleSaveSources();
    void HandleGetSources();
    void HandleGetConfig(const char *json);
    void HandleGetStats();
    void HandleArm();
    void HandleDisarm();
    void HandleRearm();
    void HandleTrigStop(const char *json);
    void HandleSetTrigger(const char *json);
    void HandleZoom(const char *json, uint32 slotIdx);
    void HandleHistoryZoom(const char *json, uint32 slotIdx);
    void HandleHistoryInfo(uint32 slotIdx);
    void HandleSetMaxPoints(const char *json);
    void HandlePing(uint32 slotIdx);

    /* ---- Binary recorder commands --------------------------------------- */

    /** Arm recording on all enabled sessions (optional "sourceId" filter). */
    void HandleRecStart(const char *json);
    /** Disarm recording on all enabled sessions (optional "sourceId" filter). */
    void HandleRecStop(const char *json);
    /** Unicast a {"type":"recStatus",...} snapshot to one client. */
    void HandleRecInfo(uint32 slotIdx);
    /** Broadcast a {"type":"recStatus",...} snapshot to all clients. */
    void BroadcastRecStatus();

    /** Append the full recStatus message body into a growable buffer. */
    void AppendRecStatusJSON(char *&buf, uint32 &off, uint32 &cap);

    /* ---- Sources persistence (Go SourceConfig schema) --------------------- */

    /**
     * @brief Start a new dynamic session from "host:port" + options.
     * Generates the session id ("s1", "s2", ...).
     * @return true if the session was started.
     */
    bool AddSourceInternal(const char *label, const char *addrPort,
                           const char *mcGroup, uint16 dataPort);

    /**
     * @brief Load sources from sourcesFile_ (JSON array of
     * {"label","addr","multicastGroup","dataPort"}) and start them.
     */
    void LoadSourcesFile();

    /* ---- Tiny JSON helpers ----------------------------------------------- */

    /**
     * @brief Extract a string field from flat JSON: "key":"value".
     * @return true if found; value written into out (up to outSize-1 chars).
     */
    static bool JsonGetString(const char *json, const char *key,
                              char *out, uint32 outSize);

    /**
     * @brief Extract a numeric field from flat JSON: "key":number.
     * @return true if found.
     */
    static bool JsonGetFloat(const char *json, const char *key, float64 &out);
    static bool JsonGetUint32(const char *json, const char *key, uint32 &out);
    static bool JsonGetBool(const char *json, const char *key, bool &out);

    /* ---- Members --------------------------------------------------------- */

    UDPSourceSession  sessions_[kMaxSessions];
    uint32            numSessions_;       ///< Count of active sessions (not an upper slot bound)
    bool              sessionActive_[kMaxSessions]; ///< Per-slot in-use flag (slots are not compacted on removal)
    bool              configBroadcast_[kMaxSessions]; ///< True once BroadcastConfig was sent for this session
    FastPollingMutexSem sessionsMutex_;  ///< Serializes add/remove (WS threads); push thread reads flags lock-free

    WSServer          wsServer_;
    TriggerEngine     trigger_;
    HistoryWriter     history_;

    /* Recorder configuration (parsed once, applied to each session). */
    RecorderConfig    recorderCfg_;

    /* Configuration */
    uint16  wsPort_;
    uint32  maxPoints_;
    uint32  pushRateHz_;
    uint32  maxPushPoints_;
    uint32  statsRateHz_;
    uint32  ringTemporal_;   ///< Ring capacity for multi-element (waveform) signals
    uint32  ringScalar_;     ///< Ring capacity for scalar signals
    StreamString sourcesFile_; ///< Persistent dynamic source list (JSON)
    uint32  nextSourceId_;   ///< Counter for generated session ids ("sN")

    /* Push loop state */
    volatile bool  running_;
    uint32         tickCount_;      ///< incremented each push tick

    /* Deferred setMaxPoints (applied by the push loop, not the WS thread) */
    volatile bool  pendingMaxPointsSet_;
    uint32         pendingMaxPoints_;

    /* Per-push scratch buffer (~8 MiB): one binary frame at a time */
    static const uint32 kPushBufSize = 8u * 1024u * 1024u;
    uint8  *pushBuf_;

    /* Decimated output scratch (LTTB): maxPushPoints × 2 arrays per signal */
    float64 *lttbT_;
    float64 *lttbV_;

    /* Ring-buffer read scratch for the push loop (preallocated in Initialise) */
    static const uint32 kPushScratchPts = 262144u; ///< Max new pts/signal/tick
    float64 *pushT_;
    float64 *pushV_;

    /* Per-(session, signal) monotonic read cursors for ReadSignalSince.
     * Only new samples since the last tick are pushed (fixes re-LTTB
     * corruption of overlapping windows). */
    uint64  pushCursor_[kMaxSessions][UDPSS_MAX_SIGNALS];

    /* Trigger servicing state (push thread only) */
    static const uint32 kTrigCapturePts = 20000u; ///< LTTB cap per captured signal
    TrigState lastTrigState_;   ///< Last broadcast FSM state
    bool      rearmPending_;    ///< Normal-mode auto-rearm scheduled
    float64   rearmAtWallS_;    ///< Wall time of the scheduled auto-rearm
};

} /* namespace StreamHub */

#endif /* STREAMHUB_H_ */
