#pragma once

#include "protocol.h"
#include "signal_buffer.h"
#include "udp_client.h"
#include <atomic>
#include <chrono>
#include <cstdint>
#include <memory>
#include <mutex>
#include <string>
#include <vector>

/** Time calibration: maps timer ticks from one time-signal → Unix wall-clock seconds.
 *
 *  Mirrors the WebUI Go client approach exactly:
 *    timer_to_sec = 1e-9 for uint64 (HRT nanoseconds), 1e-6 for all others (microseconds)
 *    calib_offset = wallT - timerVal * timer_to_sec   (set once on first packet)
 *    t = calib_offset + timerVal * timer_to_sec
 */
struct TimeCalib {
    double calib_offset = 0.0;   // wallT_unix - timerVal * timer_to_sec
    double timer_to_sec = 1e-6;  // 1e-9 for uint64, 1e-6 for uint32/other
    bool   valid        = false;
};

/** Per-signal display + buffer state. */
struct SignalState {
    SignalDef                      def;
    std::shared_ptr<SignalBuffer>  buffer;   // high-res ring buffer (last ~1-60 s)
    std::shared_ptr<SignalBuffer>  archive;  // low-res ~1 kHz history (last ~17 min)
    double                         archive_last_t = -1e18; // ingest-thread only

    // Render-thread state (only modified on render thread after initial setup)
    bool    visible    = true;
    uint8_t plot_mask  = 0;      // bitmask: bit k set → signal is shown on plot k (0–3)
    float   color[4]  = {1.f, 1.f, 1.f, 1.f};   // RGBA
    float   line_width = 1.5f;
    bool    use_envelope = false; // true → PlotShaded, false → PlotLine
};

/**
 * One MARTe2 UDPStreamer source.
 * Owns a UDPClient (ingest thread) and a vector of SignalState objects
 * populated when CONFIG arrives.
 */
class Source {
public:
    Source(std::string label, std::string host, uint16_t port,
           std::string multicast_group = "", uint16_t data_port = 0);
    ~Source();

    void start();
    void stop();

    // ── Identification ────────────────────────────────────────────────
    const std::string& label() const { return label_; }
    const std::string& host()  const { return host_;  }
    uint16_t           port()  const { return port_;  }
    bool               connected() const { return connected_.load(std::memory_order_relaxed); }

    // ── Signal list (protect with signals_mutex while iterating) ──────
    mutable std::mutex         signals_mutex;
    std::vector<SignalState>   signal_states;   // indexed by signal_idx
    std::atomic<bool>          config_dirty{false};  // set by ingest, cleared by render

    // ── Stats ─────────────────────────────────────────────────────────
    struct Stats {
        uint64_t packets_rx   = 0;
        uint64_t packets_lost = 0;
        uint64_t bytes_rx     = 0;
    };
    Stats stats() const;

    // High-res ring buffer capacity (≈60 s @ 100 kHz, ≈1.2 s @ 5 MHz)
    static constexpr size_t MAX_SIGNAL_SAMPLES  = 6'000'000;
    // Low-res archive capacity: ~1000 Hz effective rate → ~17 min history
    static constexpr size_t MAX_ARCHIVE_SAMPLES = 1'000'000;
    // Minimum time between archive writes (1 ms = 1000 Hz)
    static constexpr double ARCHIVE_DT          = 1.0 / 1000.0;

private:
    // Called from ingest thread
    void handle_config(const std::vector<SignalDef>& defs);
    void handle_data(const DataPacket& dp, const std::vector<SignalDef>& defs);

    std::string              label_, host_;
    uint16_t                 port_;
    std::shared_ptr<UDPClient> client_;
    std::atomic<bool>        connected_{false};

    // Time calibrations indexed by protocol signal index (size = defs.size())
    std::vector<TimeCalib>   time_calibs_;

    // Spatial-array expansion map.
    // Indexed by protocol signal index; maps to the first signal_states entry
    // for that signal and how many entries it occupies (1 for scalars/temporal).
    struct SigExpand {
        int      start_state_idx;
        uint32_t num_elements;   // 1 for scalar/temporal, N for spatial
    };
    std::vector<SigExpand>   sig_expand_;

    // Stats (written by ingest thread, read by render — relaxed OK for display)
    std::atomic<uint64_t>    stat_rx_{0};
    std::atomic<uint64_t>    stat_lost_{0};
    std::atomic<uint64_t>    stat_bytes_{0};
};
