// generic_stim.h - wiring-driven stimulus for ANY generated VSI twin.
//
// apply_glue_generic.py reads the twin's .vsi.cmd and decides, per component
// signal, one of three roles:
//   PRODUCE  the signal is the source end of a `connect signals` line, or belongs
//            to the FIRST component defining a CAN/LIN frame id: it carries a
//            counter, distinct per signal (k), advancing every PERIOD steps
//   CONSUME  the destination end of a `connect signals`, or the same-position
//            signal of every OTHER component on that frame id: monitored
//   TRACE    everything else (Ethernet sockets carry no signals): logged only
// Values stay within 8 bits because the generator lays CAN frames out 8 bits per
// signal; the same formula is used for GenericPayload ints so one checker serves
// both. check_traces.py verifies a run from the CSVs each client writes.
//
//   Period: apply_glue_generic.py gives every BUS its own period (steps between
//   counter increments) and writes it into the produce() call, so buses tick at
//   visibly different rates; glue_wiring.json records the table. TWIN_PERIOD (env,
//   default 1) is only the fallback for the two-argument form.
#ifndef GENERIC_STIM_H
#define GENERIC_STIM_H

#include <cstdio>
#include <cstdlib>
#include <string>
#include <vector>

namespace twin {

inline long param(const char *name, long dflt) {
    std::string key = std::string("TWIN_") + name;
    const char *v = std::getenv(key.c_str());
    return (v && *v) ? std::atol(v) : dflt;
}

inline long period() { static long p = param("PERIOD", 1); return p > 0 ? p : 1; }

// the ONE definition of the produced value; check_traces.py's Python copy is
// cross-checked against this by `check_traces.py --selftest` (compiles and runs it)
inline int produce(long step, int k, long p) { return (int)((k * 16 + step / (p > 0 ? p : 1)) & 0xFF); }
inline int produce(long step, int k) { return produce(step, k, period()); }

struct Consumer {
    int last, changes, silent;
    bool seen;
    Consumer() : last(0), changes(0), silent(0), seen(false) {}
    void observe(int v) {
        if (!seen) { seen = true; last = v; return; }
        if (v != last) { changes += 1; silent = 0; last = v; } else { silent += 1; }
    }
};

class Trace {
  public:
    Trace(const std::string &component, const std::vector<std::string> &columns)
        : path_(component + "_trace.csv"), columns_(columns), header_(false) {}
    void row(long step, long long time_ns, const std::vector<long> &values) {
        FILE *fh = std::fopen(path_.c_str(), header_ ? "a" : "w");
        if (!fh) return;
        if (!header_) {
            std::fprintf(fh, "step,time_ns");
            for (size_t i = 0; i < columns_.size(); ++i) std::fprintf(fh, ",%s", columns_[i].c_str());
            std::fprintf(fh, "\n");
            header_ = true;
        }
        std::fprintf(fh, "%ld,%lld", step, time_ns);
        for (size_t i = 0; i < values.size(); ++i) std::fprintf(fh, ",%ld", values[i]);
        std::fprintf(fh, "\n");
        std::fclose(fh);
    }
  private:
    std::string path_;
    std::vector<std::string> columns_;
    bool header_;
};

}  // namespace twin
#endif
