// Channel byte accounting for the pipeline message types.
//
// TRACES: AR-004 | SR-002
//
// kpn::ChannelDataSize<T> is what a channel reports as bytes pushed, and its
// primary template returns sizeof(T). Every message type here is a handful of
// vectors and a cv::Mat header owning megabytes on the heap, so unspecialised
// the diagnostics reported ~200 bytes for a message carrying a full decoded
// frame — off by four orders of magnitude at 1080p.
//
// That is the instrument for choosing channel capacities against a memory
// ceiling, which is the open half of AR-004. These cases assert it measures the
// payload rather than the header, because a stat that is quietly wrong is worse
// than no stat: it was read as evidence.
#include <catch2/catch_test_macros.hpp>

#include <kpn/channel.hpp>

#include "types.hpp"

namespace {

Frame frame_with_image(int w, int h) {
    Frame f;
    f.image = cv::Mat(h, w, CV_8UC3, cv::Scalar(0, 0, 0));
    f.timestamp_sec = 1.0;
    return f;
}

}  // namespace

TEST_CASE("frame bytes count the decoded image, not the header", "[channel_bytes]") {
    const Frame f = frame_with_image(1920, 1080);
    const std::size_t got = kpn::ChannelDataSize<Frame>::bytes(f);

    // 1920 * 1080 * 3 = 6,220,800 payload bytes.
    REQUIRE(got >= 1920u * 1080u * 3u);
    // The header is a rounding error next to it; this is the assertion that
    // fails on the unspecialised default.
    CHECK(got > 100u * sizeof(Frame));
}

TEST_CASE("an empty frame costs only its header", "[channel_bytes]") {
    // The eof sentinel carries no image, and must not be charged for one.
    Frame eof;
    eof.eof = true;
    CHECK(kpn::ChannelDataSize<Frame>::bytes(eof) == sizeof(Frame));
}

TEST_CASE("crops and embeddings are counted on top of the frame", "[channel_bytes]") {
    // The case AR-003 created: a crowd frame occupies one slot exactly as an
    // empty one does, and only the byte figure distinguishes them.
    EmbeddedSceneFrame v;
    v.source = frame_with_image(640, 360);
    const std::size_t bare = kpn::ChannelDataSize<EmbeddedSceneFrame>::bytes(v);

    constexpr int kFaces = 60;
    for (int i = 0; i < kFaces; ++i) {
        v.faces.push_back({});
        v.crops.emplace_back(112, 112, CV_8UC3, cv::Scalar(0, 0, 0));
        v.embeddings.emplace_back();
    }
    const std::size_t crowded = kpn::ChannelDataSize<EmbeddedSceneFrame>::bytes(v);

    // 60 crops at 112*112*3 = 2,257,920 bytes, plus 60 * 2 KiB of embeddings.
    CHECK(crowded - bare >= kFaces * (112u * 112u * 3u + sizeof(Embedding)));
    // And the crowd frame really is the multiple of the empty one that the
    // item-count capacity cannot see: 640x360x3 is ~691 KB, the crops ~2.26 MB.
    CHECK(crowded > 3 * bare);
}

TEST_CASE("every message type on a channel measures its payload", "[channel_bytes]") {
    // A specialisation missing for any one of these silently reverts that
    // channel to sizeof(T), which is exactly how this went unnoticed.
    const Frame f = frame_with_image(320, 240);
    const std::size_t img = 320u * 240u * 3u;

    SceneFrame sf;        sf.source = f;
    AlignedSceneFrame af; af.source = f;
    EmbeddedSceneFrame ef; ef.source = f;
    TrackedSceneFrame tf; tf.source = f;
    MatchedSceneFrame mf; mf.source = f;

    CHECK(kpn::ChannelDataSize<SceneFrame>::bytes(sf)         >= img);
    CHECK(kpn::ChannelDataSize<AlignedSceneFrame>::bytes(af)  >= img);
    CHECK(kpn::ChannelDataSize<EmbeddedSceneFrame>::bytes(ef) >= img);
    CHECK(kpn::ChannelDataSize<TrackedSceneFrame>::bytes(tf)  >= img);
    CHECK(kpn::ChannelDataSize<MatchedSceneFrame>::bytes(mf)  >= img);

    // SceneAnnotation carries no source frame — only the actors it identified,
    // each with its own crop.
    SceneAnnotation sa;
    sa.visible_actors.push_back({});
    sa.visible_actors.back().crop = cv::Mat(112, 112, CV_8UC3, cv::Scalar(0, 0, 0));
    CHECK(kpn::ChannelDataSize<SceneAnnotation>::bytes(sa) >= 112u * 112u * 3u);
}

TEST_CASE("a shared image is charged to each message holding it", "[channel_bytes]") {
    // cv::Mat is reference-counted, so a frame referenced from several messages
    // is counted once per reference. The sum is an upper bound on distinct
    // bytes, and the right bound for "what would this channel keep alive if
    // nothing else held it" — which is the question a capacity answers.
    const Frame f = frame_with_image(320, 240);
    SceneFrame a; a.source = f;
    SceneFrame b; b.source = f;   // shares the same pixel buffer

    CHECK(kpn::ChannelDataSize<SceneFrame>::bytes(a)
          == kpn::ChannelDataSize<SceneFrame>::bytes(b));
}
