/**
 * @file BinaryRecorderGTest.cpp
 * @brief Unit tests for the StreamHub BinaryRecorder.
 *
 * @copyright Copyright 2015 F4E | European Joint Undertaking for ITER and
 * the Development of Fusion Energy ('Fusion for Energy').
 * Licensed under the EUPL, Version 1.1 or - as soon they will be approved
 * by the European Commission - subsequent versions of the EUPL (the "Licence")
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl
 *
 * @warning Unless required by applicable law or agreed to in writing,
 * software distributed under the Licence is distributed on an "AS IS"
 * basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the Licence permissions and limitations under the Licence.
 */

#include "BinaryRecorder.h"
#include "TypeDescriptor.h"
#include "UDPSProtocol.h"

#include <gtest/gtest.h>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <string>
#include <dirent.h>
#include <unistd.h>

using StreamHub::BinaryRecorder;
using StreamHub::RecorderConfig;
using MARTe::uint8;
using MARTe::uint16;
using MARTe::uint32;
using MARTe::uint64;
using MARTe::float64;

/*---------------------------------------------------------------------------*/
/* Helpers                                                                    */
/*---------------------------------------------------------------------------*/

static MARTe::UDPSSignalDescriptor MakeDesc(const char *name, uint8 tc,
                                            uint32 nElems, uint8 quant = 0u,
                                            double rmin = 0.0, double rmax = 0.0) {
    MARTe::UDPSSignalDescriptor d;
    memset(&d, 0, sizeof(d));
    strncpy(d.name, name, MARTe::UDPS_MAX_SIGNAL_NAME - 1u);
    d.typeCode = tc;
    d.quantType = quant;
    d.numDimensions = (nElems > 1u) ? 1u : 0u;
    d.numRows = nElems;
    d.numCols = 1u;
    d.rangeMin = rmin;
    d.rangeMax = rmax;
    d.timeMode = MARTe::UDPS_TIMEMODE_PACKET;
    d.timeSignalIdx = MARTe::UDPS_NO_TIME_SIGNAL;
    return d;
}

static RecorderConfig MakeCfg(const char *dir) {
    RecorderConfig c;
    memset(&c, 0, sizeof(c));
    c.enabled = true;
    c.autoStart = true;
    strncpy(c.directory, dir, sizeof(c.directory) - 1u);
    c.maxFileBytes = 1024u * 1024u;
    c.keepFiles = 8u;
    c.stagingBytes = 1024u * 1024u;
    c.flushIntervalSec = 5u;
    c.minDiskFreeMB = 0u;
    strncpy(c.signals, "all", sizeof(c.signals) - 1u);
    return c;
}

static std::string MakeTempDir() {
    char templ[] = "/tmp/streamhub_rec_XXXXXX";
    char *p = mkdtemp(templ);
    return std::string(p != NULL ? p : "/tmp");
}

static std::vector<uint8> ReadFileBytes(const std::string &path) {
    std::vector<uint8> out;
    FILE *f = fopen(path.c_str(), "rb");
    if (f == NULL) { return out; }
    fseek(f, 0, SEEK_END);
    long n = ftell(f);
    fseek(f, 0, SEEK_SET);
    if (n > 0) {
        out.resize(static_cast<size_t>(n));
        size_t rd = fread(&out[0], 1u, static_cast<size_t>(n), f);
        out.resize(rd);
    }
    fclose(f);
    return out;
}

static std::vector<std::string> ListBinFiles(const std::string &dir) {
    std::vector<std::string> out;
    DIR *d = opendir(dir.c_str());
    if (d == NULL) { return out; }
    struct dirent *e;
    while ((e = readdir(d)) != NULL) {
        std::string nm = e->d_name;
        if (nm.size() > 4u && nm.substr(nm.size() - 4u) == ".bin") {
            out.push_back(dir + "/" + nm);
        }
    }
    closedir(d);
    return out;
}

static uint16 ReadU16(const std::vector<uint8> &b, size_t off) {
    uint16 v = 0u; memcpy(&v, &b[off], 2u); return v;
}
static uint32 ReadU32(const std::vector<uint8> &b, size_t off) {
    uint32 v = 0u; memcpy(&v, &b[off], 4u); return v;
}

/*---------------------------------------------------------------------------*/
/* Static helper tests                                                        */
/*---------------------------------------------------------------------------*/

TEST(BinaryRecorderGTest, TypeCodeToDescriptorAll) {
    EXPECT_EQ(MARTe::UnsignedInteger32Bit.all,
              BinaryRecorder::TypeCodeToDescriptorAll(MARTe::UDPS_TYPECODE_UINT32));
    EXPECT_EQ(MARTe::Float32Bit.all,
              BinaryRecorder::TypeCodeToDescriptorAll(MARTe::UDPS_TYPECODE_FLOAT32));
    EXPECT_EQ(MARTe::Float64Bit.all,
              BinaryRecorder::TypeCodeToDescriptorAll(MARTe::UDPS_TYPECODE_FLOAT64));
    EXPECT_EQ(MARTe::UnsignedInteger64Bit.all,
              BinaryRecorder::TypeCodeToDescriptorAll(MARTe::UDPS_TYPECODE_UINT64));
    EXPECT_EQ(MARTe::SignedInteger16Bit.all,
              BinaryRecorder::TypeCodeToDescriptorAll(MARTe::UDPS_TYPECODE_INT16));
}

TEST(BinaryRecorderGTest, EncodeNativeFloat32) {
    uint8 buf[8] = {0};
    uint32 n = BinaryRecorder::EncodeNative(MARTe::UDPS_TYPECODE_FLOAT32, 1.5, buf);
    ASSERT_EQ(4u, n);
    float expect = 1.5f;
    EXPECT_EQ(0, memcmp(buf, &expect, 4u));
}

TEST(BinaryRecorderGTest, EncodeNativeUint32) {
    uint8 buf[8] = {0};
    uint32 n = BinaryRecorder::EncodeNative(MARTe::UDPS_TYPECODE_UINT32, 7.0, buf);
    ASSERT_EQ(4u, n);
    uint32 expect = 7u;
    EXPECT_EQ(0, memcmp(buf, &expect, 4u));
}

TEST(BinaryRecorderGTest, EncodeNativeInt16) {
    uint8 buf[8] = {0};
    uint32 n = BinaryRecorder::EncodeNative(MARTe::UDPS_TYPECODE_INT16, -3.0, buf);
    ASSERT_EQ(2u, n);
    MARTe::int16 expect = -3;
    EXPECT_EQ(0, memcmp(buf, &expect, 2u));
}

/*---------------------------------------------------------------------------*/
/* Header bytes                                                               */
/*---------------------------------------------------------------------------*/

TEST(BinaryRecorderGTest, HeaderBytes) {
    std::string dir = MakeTempDir();
    RecorderConfig cfg = MakeCfg(dir.c_str());
    BinaryRecorder rec;
    rec.Init(cfg, "S0");

    MARTe::UDPSSignalDescriptor descs[2] = {
        MakeDesc("Sine", MARTe::UDPS_TYPECODE_FLOAT32, 1u),
        MakeDesc("Time", MARTe::UDPS_TYPECODE_UINT32, 4u)
    };
    bool mask[2] = { true, true };
    rec.Configure(descs, 2u, mask);
    rec.FlushTick(0u);

    bool recording; char path[768]; uint64 bw, rw, dr, fm;
    rec.GetInfo(recording, path, sizeof(path), bw, rw, dr, fm);
    std::vector<uint8> b = ReadFileBytes(path);
    ASSERT_GE(b.size(), 4u + 2u * 38u);

    EXPECT_EQ(2u, ReadU32(b, 0));
    /* descriptor 0 */
    EXPECT_EQ(MARTe::Float32Bit.all, ReadU16(b, 4));
    EXPECT_STREQ("Sine", reinterpret_cast<const char *>(&b[6]));
    EXPECT_EQ(1u, ReadU32(b, 38));
    /* descriptor 1 */
    EXPECT_EQ(MARTe::UnsignedInteger32Bit.all, ReadU16(b, 42));
    EXPECT_STREQ("Time", reinterpret_cast<const char *>(&b[44]));
    EXPECT_EQ(4u, ReadU32(b, 76));
}

/*---------------------------------------------------------------------------*/
/* Row serialization                                                          */
/*---------------------------------------------------------------------------*/

TEST(BinaryRecorderGTest, RowStrictUnquantized) {
    std::string dir = MakeTempDir();
    BinaryRecorder rec;
    rec.Init(MakeCfg(dir.c_str()), "S0");

    MARTe::UDPSSignalDescriptor descs[2] = {
        MakeDesc("A", MARTe::UDPS_TYPECODE_FLOAT32, 1u),
        MakeDesc("B", MARTe::UDPS_TYPECODE_UINT32, 2u)
    };
    bool mask[2] = { true, true };
    rec.Configure(descs, 2u, mask);

    uint8 payload[12];
    float a = 2.5f; uint32 b0 = 10u, b1 = 20u;
    memcpy(payload + 0, &a, 4u);
    memcpy(payload + 4, &b0, 4u);
    memcpy(payload + 8, &b1, 4u);
    uint32 sigOff[2] = { 0u, 4u };
    uint32 sigElems[2] = { 1u, 2u };
    rec.CapturePacket(payload, sigOff, sigElems, MARTe::UDPS_PUBLISH_STRICT, 1u);
    rec.FlushTick(0u);

    bool recording; char path[768]; uint64 bw, rw, dr, fm;
    rec.GetInfo(recording, path, sizeof(path), bw, rw, dr, fm);
    std::vector<uint8> b = ReadFileBytes(path);
    const size_t hdr = 4u + 2u * 38u;
    ASSERT_EQ(hdr + 12u, b.size());
    float ra; uint32 rb0, rb1;
    memcpy(&ra, &b[hdr + 0], 4u);
    memcpy(&rb0, &b[hdr + 4], 4u);
    memcpy(&rb1, &b[hdr + 8], 4u);
    EXPECT_FLOAT_EQ(2.5f, ra);
    EXPECT_EQ(10u, rb0);
    EXPECT_EQ(20u, rb1);
    EXPECT_EQ(1u, rw);
}

TEST(BinaryRecorderGTest, RowAccumulateExpansion) {
    std::string dir = MakeTempDir();
    BinaryRecorder rec;
    rec.Init(MakeCfg(dir.c_str()), "S0");

    MARTe::UDPSSignalDescriptor descs[2] = {
        MakeDesc("S", MARTe::UDPS_TYPECODE_FLOAT32, 1u),
        MakeDesc("C", MARTe::UDPS_TYPECODE_UINT32, 1u)
    };
    bool mask[2] = { true, true };
    rec.Configure(descs, 2u, mask);

    /* Wire: S accumulated x3 (3 floats), C scalar (1 uint32). */
    uint8 payload[16];
    float s[3] = { 1.0f, 2.0f, 3.0f };
    uint32 c = 9u;
    memcpy(payload + 0, s, 12u);
    memcpy(payload + 12, &c, 4u);
    uint32 sigOff[2] = { 0u, 12u };
    uint32 sigElems[2] = { 3u, 1u };
    rec.CapturePacket(payload, sigOff, sigElems, MARTe::UDPS_PUBLISH_ACCUMULATE, 3u);
    rec.FlushTick(0u);

    bool recording; char path[768]; uint64 bw, rw, dr, fm;
    rec.GetInfo(recording, path, sizeof(path), bw, rw, dr, fm);
    std::vector<uint8> b = ReadFileBytes(path);
    const size_t hdr = 4u + 2u * 38u;
    const size_t rowBytes = 8u;
    ASSERT_EQ(hdr + 3u * rowBytes, b.size());
    for (uint32 r = 0u; r < 3u; r++) {
        float sr; uint32 cr;
        memcpy(&sr, &b[hdr + r * rowBytes + 0], 4u);
        memcpy(&cr, &b[hdr + r * rowBytes + 4], 4u);
        EXPECT_FLOAT_EQ(static_cast<float>(r + 1u), sr);
        EXPECT_EQ(9u, cr);
    }
    EXPECT_EQ(3u, rw);
}

TEST(BinaryRecorderGTest, SubsetSelection) {
    std::string dir = MakeTempDir();
    BinaryRecorder rec;
    rec.Init(MakeCfg(dir.c_str()), "S0");

    MARTe::UDPSSignalDescriptor descs[3] = {
        MakeDesc("A", MARTe::UDPS_TYPECODE_FLOAT32, 1u),
        MakeDesc("B", MARTe::UDPS_TYPECODE_FLOAT32, 1u),
        MakeDesc("C", MARTe::UDPS_TYPECODE_UINT32, 1u)
    };
    bool mask[3] = { true, false, true };
    rec.Configure(descs, 3u, mask);

    uint8 payload[12];
    float a = 1.0f, bb = 2.0f; uint32 c = 3u;
    memcpy(payload + 0, &a, 4u);
    memcpy(payload + 4, &bb, 4u);
    memcpy(payload + 8, &c, 4u);
    uint32 sigOff[3] = { 0u, 4u, 8u };
    uint32 sigElems[3] = { 1u, 1u, 1u };
    rec.CapturePacket(payload, sigOff, sigElems, MARTe::UDPS_PUBLISH_STRICT, 1u);
    rec.FlushTick(0u);

    bool recording; char path[768]; uint64 bw, rw, dr, fm;
    rec.GetInfo(recording, path, sizeof(path), bw, rw, dr, fm);
    std::vector<uint8> b = ReadFileBytes(path);
    /* header has 2 signals, A and C */
    EXPECT_EQ(2u, ReadU32(b, 0));
    EXPECT_STREQ("A", reinterpret_cast<const char *>(&b[6]));
    EXPECT_STREQ("C", reinterpret_cast<const char *>(&b[44]));
    const size_t hdr = 4u + 2u * 38u;
    ASSERT_EQ(hdr + 8u, b.size());
    float ra; uint32 rc;
    memcpy(&ra, &b[hdr + 0], 4u);
    memcpy(&rc, &b[hdr + 4], 4u);
    EXPECT_FLOAT_EQ(1.0f, ra);
    EXPECT_EQ(3u, rc);
}

TEST(BinaryRecorderGTest, QuantizedReconstruction) {
    std::string dir = MakeTempDir();
    BinaryRecorder rec;
    rec.Init(MakeCfg(dir.c_str()), "S0");

    /* float32 signal quantized to uint16 over [0,10]. */
    MARTe::UDPSSignalDescriptor descs[1] = {
        MakeDesc("Q", MARTe::UDPS_TYPECODE_FLOAT32, 1u,
                 MARTe::UDPS_QUANT_UINT16, 0.0, 10.0)
    };
    bool mask[1] = { true };
    rec.Configure(descs, 1u, mask);

    uint8 payload[2];
    uint16 q = 32767u;
    memcpy(payload, &q, 2u);
    uint32 sigOff[1] = { 0u };
    uint32 sigElems[1] = { 1u };
    rec.CapturePacket(payload, sigOff, sigElems, MARTe::UDPS_PUBLISH_STRICT, 1u);
    rec.FlushTick(0u);

    bool recording; char path[768]; uint64 bw, rw, dr, fm;
    rec.GetInfo(recording, path, sizeof(path), bw, rw, dr, fm);
    std::vector<uint8> b = ReadFileBytes(path);
    const size_t hdr = 4u + 38u;
    ASSERT_EQ(hdr + 4u, b.size());
    float rv;
    memcpy(&rv, &b[hdr], 4u);
    double expect = 0.0 + (32767.0 / 65535.0) * 10.0;
    EXPECT_NEAR(expect, static_cast<double>(rv), 1e-3);
}

/*---------------------------------------------------------------------------*/
/* Flush / rotation / guards                                                  */
/*---------------------------------------------------------------------------*/

TEST(BinaryRecorderGTest, RotationKeepN) {
    std::string dir = MakeTempDir();
    RecorderConfig cfg = MakeCfg(dir.c_str());
    cfg.maxFileBytes = 200u;
    cfg.keepFiles = 2u;
    BinaryRecorder rec;
    rec.Init(cfg, "S0");

    MARTe::UDPSSignalDescriptor descs[2] = {
        MakeDesc("A", MARTe::UDPS_TYPECODE_FLOAT32, 1u),
        MakeDesc("B", MARTe::UDPS_TYPECODE_UINT32, 2u)
    };
    bool mask[2] = { true, true };
    rec.Configure(descs, 2u, mask);

    uint8 payload[12];
    float a = 1.0f; uint32 b0 = 1u, b1 = 2u;
    memcpy(payload + 0, &a, 4u);
    memcpy(payload + 4, &b0, 4u);
    memcpy(payload + 8, &b1, 4u);
    uint32 sigOff[2] = { 0u, 4u };
    uint32 sigElems[2] = { 1u, 2u };

    for (uint32 i = 0u; i < 50u; i++) {
        rec.CapturePacket(payload, sigOff, sigElems, MARTe::UDPS_PUBLISH_STRICT, 1u);
        rec.FlushTick(0u);
    }

    std::vector<std::string> files = ListBinFiles(dir);
    EXPECT_LE(files.size(), 2u);
    EXPECT_GE(files.size(), 1u);
    for (size_t i = 0u; i < files.size(); i++) {
        std::vector<uint8> b = ReadFileBytes(files[i]);
        ASSERT_GE(b.size(), 4u);
        EXPECT_EQ(2u, ReadU32(b, 0));
    }
}

TEST(BinaryRecorderGTest, DiskGuardStops) {
    std::string dir = MakeTempDir();
    RecorderConfig cfg = MakeCfg(dir.c_str());
    cfg.minDiskFreeMB = 0xFFFFFFFFu; /* impossibly large → always low */
    BinaryRecorder rec;
    rec.Init(cfg, "S0");

    MARTe::UDPSSignalDescriptor descs[1] = {
        MakeDesc("A", MARTe::UDPS_TYPECODE_FLOAT32, 1u)
    };
    bool mask[1] = { true };
    rec.Configure(descs, 1u, mask);
    rec.FlushTick(0u);

    bool recording; char path[768]; uint64 bw, rw, dr, fm;
    rec.GetInfo(recording, path, sizeof(path), bw, rw, dr, fm);
    EXPECT_FALSE(recording);
    EXPECT_EQ(0u, ListBinFiles(dir).size());
}

TEST(BinaryRecorderGTest, OverflowDropsRows) {
    std::string dir = MakeTempDir();
    RecorderConfig cfg = MakeCfg(dir.c_str());
    cfg.stagingBytes = 4096u; /* small staging, no flush → overflow */
    BinaryRecorder rec;
    rec.Init(cfg, "S0");

    MARTe::UDPSSignalDescriptor descs[2] = {
        MakeDesc("A", MARTe::UDPS_TYPECODE_FLOAT32, 1u),
        MakeDesc("B", MARTe::UDPS_TYPECODE_UINT32, 2u)
    };
    bool mask[2] = { true, true };
    rec.Configure(descs, 2u, mask);

    uint8 payload[12];
    float a = 1.0f; uint32 b0 = 1u, b1 = 2u;
    memcpy(payload + 0, &a, 4u);
    memcpy(payload + 4, &b0, 4u);
    memcpy(payload + 8, &b1, 4u);
    uint32 sigOff[2] = { 0u, 4u };
    uint32 sigElems[2] = { 1u, 2u };

    /* Capture far more rows than the 4096-byte buffer can hold (no flush). */
    for (uint32 i = 0u; i < 500u; i++) {
        rec.CapturePacket(payload, sigOff, sigElems, MARTe::UDPS_PUBLISH_STRICT, 1u);
    }

    bool recording; char path[768]; uint64 bw, rw, dr, fm;
    rec.GetInfo(recording, path, sizeof(path), bw, rw, dr, fm);
    EXPECT_GT(dr, 0u);
}
