/**
 * @file DebugServiceGTest.cpp
 * @brief Unit tests for DebugService header-only core components:
 *        TraceRingBuffer, DebugSignalInfo struct, BreakOp enum.
 *
 * These tests exercise self-contained logic from DebugCore.h without
 * requiring a running MARTe2 application or linking DebugServiceBase.cpp.
 * ProcessSignal/HandleCommand/RegisterSignal are covered by the
 * DebugService integration tests in Test/Integration/.
 */

#include "DebugCore.h"
#include "gtest/gtest.h"

using namespace MARTe;

/* ═════════════════════════════════════════════════════════════════════════
   TraceRingBuffer tests (DebugCore.h / HI-9 atomic fix validation)
   ═════════════════════════════════════════════════════════════════════════ */

class TraceRingBufferTest : public ::testing::Test {
protected:
    void SetUp() { buf = new TraceRingBuffer(); }
    void TearDown() { delete buf; }
    TraceRingBuffer *buf;
};

TEST_F(TraceRingBufferTest, Init_ValidSize) {
    ASSERT_TRUE(buf->Init(1024));
    EXPECT_EQ(buf->Count(), 0u);
}

TEST_F(TraceRingBufferTest, Init_TwiceReplacesBuffer) {
    ASSERT_TRUE(buf->Init(256));
    EXPECT_TRUE(buf->Init(512));
    EXPECT_EQ(buf->Count(), 0u);
}

TEST_F(TraceRingBufferTest, PushPop_SingleEntryRoundtrip) {
    ASSERT_TRUE(buf->Init(256));
    uint8 data[8] = {0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x33, 0x44};
    ASSERT_TRUE(buf->Push(42, 0xDEADBEEFDEADBEEFULL, data, 8));

    uint32 id = 0;
    uint64 ts = 0;
    uint8 out[8] = {};
    uint32 sz = 0;
    ASSERT_TRUE(buf->Pop(id, ts, out, sz, 16));
    EXPECT_EQ(id, 42u);
    EXPECT_EQ(ts, 0xDEADBEEFDEADBEEFULL);
    EXPECT_EQ(sz, 8u);
    EXPECT_EQ(memcmp(data, out, 8), 0);
}

TEST_F(TraceRingBufferTest, Pop_EmptyBufferReturnsFalse) {
    ASSERT_TRUE(buf->Init(256));
    uint32 id = 0; uint64 ts = 0; uint8 out[1] = {}; uint32 sz = 0;
    EXPECT_FALSE(buf->Pop(id, ts, out, sz, 16));
}

TEST_F(TraceRingBufferTest, Push_FullBufferReturnsFalse) {
    /* 32-byte buffer: entry = 4+8+4+10 = 26 bytes. Fits 1, not 2. */
    ASSERT_TRUE(buf->Init(32));
    uint8 data[10] = {};
    ASSERT_TRUE(buf->Push(1, 1, data, 10));
    EXPECT_FALSE(buf->Push(2, 2, data, 10));
}

TEST_F(TraceRingBufferTest, CountAfterMultiplePushes) {
    ASSERT_TRUE(buf->Init(256));
    uint8 data[4] = {};
    const uint32 entrySize = 4 + 8 + 4 + 4;
    for (int i = 0; i < 5; i++) {
        ASSERT_TRUE(buf->Push(static_cast<uint32>(i), 100 + i, data, 4));
    }
    EXPECT_EQ(buf->Count(), entrySize * 5u);
}

TEST_F(TraceRingBufferTest, PushPop_Wraparound) {
    /* 64-byte buffer: entry=24 bytes (4+8+4+8). Fits 2 entries. */
    ASSERT_TRUE(buf->Init(64));
    uint8 data[8] = {};
    ASSERT_TRUE(buf->Push(1, 100, data, 8));
    ASSERT_TRUE(buf->Push(2, 200, data, 8));

    uint32 id = 0; uint64 ts = 0; uint8 out[8] = {}; uint32 sz = 0;
    ASSERT_TRUE(buf->Pop(id, ts, out, sz, 16));
    EXPECT_EQ(id, 1u);

    /* Push a third entry causing wraparound */
    ASSERT_TRUE(buf->Push(3, 300, data, 8));

    ASSERT_TRUE(buf->Pop(id, ts, out, sz, 16));
    EXPECT_EQ(id, 2u);
    ASSERT_TRUE(buf->Pop(id, ts, out, sz, 16));
    EXPECT_EQ(id, 3u);
    EXPECT_FALSE(buf->Pop(id, ts, out, sz, 16));
}

TEST_F(TraceRingBufferTest, Pop_SkipOversizedEntry) {
    /* When entry size > maxSize, Pop skips it and returns false.
     * The caller must loop: the next Pop call delivers the subsequent entry. */
    ASSERT_TRUE(buf->Init(512));
    uint8 smallData[4] = {1, 2, 3, 4};
    uint8 bigData[128] = {};
    ASSERT_TRUE(buf->Push(1, 100, bigData, 128));
    ASSERT_TRUE(buf->Push(2, 200, smallData, 4));

    uint32 id = 0; uint64 ts = 0; uint8 out[4] = {}; uint32 sz = 0;
    /* First Pop: skip oversized entry */
    EXPECT_FALSE(buf->Pop(id, ts, out, sz, 16));
    /* Second Pop: deliver small entry */
    ASSERT_TRUE(buf->Pop(id, ts, out, sz, 16));
    EXPECT_EQ(id, 2u);
    EXPECT_EQ(sz, 4u);
}

TEST_F(TraceRingBufferTest, Pop_SkipOversized_EmptyAfterSkip) {
    /* If the last entry is oversized and skipped, Pop returns false. */
    ASSERT_TRUE(buf->Init(256));
    uint8 bigData[128] = {};
    ASSERT_TRUE(buf->Push(1, 100, bigData, 128));

    uint32 id = 0; uint64 ts = 0; uint8 out[1] = {}; uint32 sz = 0;
    EXPECT_FALSE(buf->Pop(id, ts, out, sz, 16));
}

TEST_F(TraceRingBufferTest, Push_LargePayloadFits) {
    /* Buffer just big enough for one entry with 200 data bytes. */
    ASSERT_TRUE(buf->Init(256));
    uint8 bigData[200] = {};
    uint32 entryBytes = 4 + 8 + 4 + 200; // 216
    ASSERT_LE(entryBytes, 255u); /* fits with 1-byte ring gap */
    ASSERT_TRUE(buf->Push(1, 100, bigData, 200));

    uint32 id = 0; uint64 ts = 0;
    uint8 out[200] = {};
    uint32 sz = 0;
    ASSERT_TRUE(buf->Pop(id, ts, out, sz, 256));
    EXPECT_EQ(id, 1u);
    EXPECT_EQ(sz, 200u);
}

TEST_F(TraceRingBufferTest, Push_ZeroSizeData) {
    ASSERT_TRUE(buf->Init(64));
    ASSERT_TRUE(buf->Push(1, 100, NULL_PTR(void *), 0));

    uint32 id = 0; uint64 ts = 0; uint32 sz = 0;
    ASSERT_TRUE(buf->Pop(id, ts, NULL_PTR(void *), sz, 16));
    EXPECT_EQ(sz, 0u);
}

TEST_F(TraceRingBufferTest, DestructorDoesNotLeak) {
    TraceRingBuffer *b = new TraceRingBuffer();
    b->Init(1024);
    delete b;
    SUCCEED();
}

TEST_F(TraceRingBufferTest, ThreadSafety_SingleThreadSPSC) {
    /* Single-threaded SPSC: Push then Pop in same thread. The HI-9 atomic
     * fix uses __atomic_load_n/__atomic_store_n with acquire/release ordering.
     * In a single thread these are equivalent to regular loads/stores. */
    ASSERT_TRUE(buf->Init(2048));  /* room for 50 entries of 20 bytes each */
    for (int i = 0; i < 50; i++) {
        uint8 data[4] = {};
        ASSERT_TRUE(buf->Push(static_cast<uint32>(i), 1000 + i, data, 4));
    }
    for (int i = 0; i < 50; i++) {
        uint32 id = 0; uint64 ts = 0; uint8 out[4] = {}; uint32 sz = 0;
        ASSERT_TRUE(buf->Pop(id, ts, out, sz, 16));
        EXPECT_EQ(id, static_cast<uint32>(i));
    }
    /* Buffer should now be empty */
    uint32 id = 0; uint64 ts = 0; uint32 sz = 0;
    EXPECT_FALSE(buf->Pop(id, ts, NULL_PTR(void *), sz, 16));
}

/* ═════════════════════════════════════════════════════════════════════════
   DebugSignalInfo struct layout tests
   ═════════════════════════════════════════════════════════════════════════ */

TEST(DebugSignalInfoTest, ForcedValueSizeMatchesSpec) {
    EXPECT_EQ(sizeof(((DebugSignalInfo *)0)->forcedValue), 1024u);
}

TEST(DebugSignalInfoTest, ForcedMaskSupports256Elements) {
    /* forcedMask is 32 bytes = 256 bits, supporting up to 256 elements. */
    EXPECT_EQ(sizeof(((DebugSignalInfo *)0)->forcedMask), 32u);
    EXPECT_EQ(sizeof(((DebugSignalInfo *)0)->forcedMask) * 8u, 256u);
}

TEST(DebugSignalInfoTest, DefaultBreakOpIsOff) {
    /* The struct default-initialises with breakOp cleared to BREAK_OFF (0).
     * We cannot memset because StreamString is non-POD; verify with sizeof.
     * The RegisterSignal code in DebugServiceBase.cpp explicit sets breakOp=0. */
    EXPECT_EQ(BREAK_OFF, 0);
}

/* ═════════════════════════════════════════════════════════════════════════
   BreakOp enum tests
   ═════════════════════════════════════════════════════════════════════════ */

TEST(BreakOpTest, ValuesMatchSpec) {
    EXPECT_EQ(BREAK_OFF, 0);
    EXPECT_EQ(BREAK_GT,  1);
    EXPECT_EQ(BREAK_LT,  2);
    EXPECT_EQ(BREAK_EQ,  3);
    EXPECT_EQ(BREAK_GEQ, 4);
    EXPECT_EQ(BREAK_LEQ, 5);
    EXPECT_EQ(BREAK_NEQ, 6);
}
