// Copyright (c) 2024, Eugene Gershnik
// SPDX-License-Identifier: BSD-3-Clause

#include "test_util.h"

#include <modern-uuid/ulid.h>

#include "persistence.h"

#if MUUID_MULTITHREADED

#include <thread>
#include <signal.h>

using namespace muuid;
using namespace std::literals;

class ulid_per_thread : public per_thread_file_clock_persistence<ulid_persistence_data> {
    using super = per_thread_file_clock_persistence<ulid_persistence_data>;
public:
    using data = ulid_persistence_data;
public:
    ulid_per_thread(const std::filesystem::path & path):
        super(path)
    {}

    bool load(data & d) override {
        using when_type = data::time_point_t::rep;

        uint8_t buf[sizeof(when_type) + sizeof(d.adjustment) + sizeof(d.random)];
        if (!super::read(buf))
            return false;
        
        uint8_t * current = buf;
        
        when_type count;
        memcpy(&count, current, sizeof(count));
        d.when = data::time_point_t(data::time_point_t::duration(count));
        current += sizeof(count);
        memcpy(&d.adjustment, current, sizeof(d.adjustment));
        current += sizeof(d.adjustment);
        memcpy(&d.random, current, sizeof(d.random));
        
        return true;
    }

    void store(const data & d) override {
        using when_type = data::time_point_t::rep;
        
        uint8_t buf[sizeof(when_type) + sizeof(d.adjustment) + sizeof(d.random)];
        uint8_t * current = buf;

        when_type count = d.when.time_since_epoch().count();
        memcpy(current, &count, sizeof(count));
        current += sizeof(count);
        memcpy(current, &d.adjustment, sizeof(d.adjustment));
        current += sizeof(d.adjustment);
        memcpy(current, &d.random, sizeof(d.random));
        
        super::write(buf);
    }
};


static auto g_path = std::filesystem::path("pers" + std::to_string(sys_getpid()) + ".bin");
static file_clock_persistence<ulid_per_thread> pers(g_path);

TEST_SUITE("ulid persistence") {

TEST_CASE("basic") {

    {
        struct restore_pers {
            ~restore_pers() {
                set_ulid_persistence(nullptr);
            }
        } restore_pers;

        remove(g_path);
        set_ulid_persistence(&pers);

        CHECK(pers.ref_count() != 0);

        ulid::generate();

        CHECK(is_regular_file(g_path));

        int check_count = 0;
        for(int i = 0; i < 50; ++i) {

            ulid u1,u2;

            volatile sig_atomic_t start = 1;

            std::thread t1([&]() {
                while (start != 0);
                u1 = ulid::generate();
                std::atomic_thread_fence(std::memory_order_release);
            });
            std::thread t2([&]() {
                while (start != 0);
                u2 = ulid::generate();
                std::atomic_thread_fence(std::memory_order_release);
            });

            start = 0;

            t1.join();
            t2.join();

            std::atomic_thread_fence(std::memory_order_acquire);

            auto time1 = std::span(u1.bytes).subspan(0, 6);
            auto time2 = std::span(u2.bytes).subspan(0, 6);

            if (std::equal(time1.begin(), time1.end(), time2.begin(), time2.end())) {
                ++check_count;

                CHECK(u1 != u2);
                if (u1 == u2)
                    break;
                uint64_t ran1_high = (uint64_t(u1.bytes[6 ]) << 56) | (uint64_t(u1.bytes[7 ]) << 48) |
                                     (uint64_t(u1.bytes[8 ]) << 40) | (uint64_t(u1.bytes[9 ]) << 32) |
                                     (uint64_t(u1.bytes[10]) << 24) | (uint64_t(u1.bytes[11]) << 16) |
                                     (uint64_t(u1.bytes[12]) << 8 ) |           u1.bytes[13];
                uint16_t ran1_low = (u1.bytes[14] << 8) | u1.bytes[15];

                uint64_t ran2_high = (uint64_t(u2.bytes[6 ]) << 56) | (uint64_t(u2.bytes[7 ]) << 48) |
                                     (uint64_t(u2.bytes[8 ]) << 40) | (uint64_t(u2.bytes[9 ]) << 32) |
                                     (uint64_t(u2.bytes[10]) << 24) | (uint64_t(u2.bytes[11]) << 16) |
                                     (uint64_t(u2.bytes[12]) << 8 ) |           u2.bytes[13];
                uint16_t ran2_low = (u2.bytes[14] << 8) | u2.bytes[15];

                CAPTURE(u1);
                CAPTURE(u2);
                CAPTURE(ran1_high);
                CAPTURE(ran1_low);
                CAPTURE(ran2_high);
                CAPTURE(ran2_low);

                if (ran1_high != ran2_high) {
                    if (abs(int64_t(ran1_high - ran2_high)) != 1) {
                        CHECK(false);
                        break;
                    }
                }
                if (abs(int16_t(ran1_low - ran2_low)) != 1) {
                    CHECK(false);
                    break;
                }
            }

        }
        WARN_MESSAGE(check_count, "No ulid persistence checks made - generation too slow");
        
        set_ulid_persistence(nullptr);
    }
    ulid::generate();
    CHECK(pers.ref_count() == 0);
}

}

#endif
