#include <atomic>
#include <barrier>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <thread>

#if 0
constexpr uint64_t THRESHOLD = 1'000'000;
constexpr size_t ITERATIONS = 500'000;
std::barrier<> trial_barrier{5};
std::atomic<bool> start{false};

alignas(256) std::atomic<uint64_t> x1{0};
alignas(256) std::atomic<uint64_t> x2{0};
alignas(256) std::atomic<uint64_t> z1{0};
alignas(256) std::atomic<uint64_t> z2{0};

uint64_t observed_by_1 = 0;
uint64_t observed_by_2 = 0;

void wait_for_start() {
    while (!start.load(std::memory_order_acquire)) {
        std::this_thread::yield();
    }
}

void producer1() {
    for (size_t iteration = 0; iteration < ITERATIONS; ++iteration) {
        trial_barrier.arrive_and_wait();
        for (size_t i = 0; i < THRESHOLD; ++i) {
            x1.fetch_add(1, std::memory_order_relaxed);
            z1.fetch_add(1, std::memory_order_release);
        }
        trial_barrier.arrive_and_wait();
    }
}

void producer2() {
    for (size_t iteration = 0; iteration < ITERATIONS; ++iteration) {
        trial_barrier.arrive_and_wait();
        for (size_t i = 0; i < THRESHOLD; ++i) {
            x2.fetch_add(1, std::memory_order_relaxed);
            z2.fetch_add(1, std::memory_order_release);
        }
        trial_barrier.arrive_and_wait();
    }
}

void consumer1() {
    wait_for_start();
    for (size_t i = 0; i < THRESHOLD; ++i) {
        uint64_t zz = z1.load(std::memory_order_acquire);
        uint64_t xx = x1.load(std::memory_order_relaxed);

        /* assert((xx - zz) < 4000); */
        assert(xx >= zz);
    }
}

void consumer2() {
    wait_for_start();
    for (size_t i = 0; i < THRESHOLD; ++i) {
        uint64_t zz = z2.load(std::memory_order_acquire);
        uint64_t xx = x2.load(std::memory_order_relaxed);

        /* assert((xx - zz) < 4000); */
        assert(xx >= zz);
    }
}

void observer1() {
    for (size_t iteration = 0; iteration < ITERATIONS; ++iteration) {
        trial_barrier.arrive_and_wait();
        for (;;) {
            uint64_t r1 = z1.load(std::memory_order_acquire);

            if (r1 >= THRESHOLD) {
                observed_by_1 = z2.load(std::memory_order_acquire);
                break;
            }
        }
        trial_barrier.arrive_and_wait();
    }
}

void observer2() {
    for (size_t iteration = 0; iteration < ITERATIONS; ++iteration) {
        trial_barrier.arrive_and_wait();
        for (;;) {
            uint64_t r2 = z2.load(std::memory_order_acquire);

            if (r2 >= THRESHOLD) {
                observed_by_2 = z1.load(std::memory_order_acquire);
                break;
            }
        }
        trial_barrier.arrive_and_wait();
    }
}


int main() {
    std::cout << "&x1: " << (void*) &x1 << '\n';
    std::cout << "&x2: " << (void*) &x2 << '\n';
    std::cout << "&z1: " << (void*) &z1 << '\n';
    std::cout << "&z2: " << (void*) &z2 << '\n';
    std::thread t1(producer1);
    std::thread t2(producer2);
    std::thread t3(observer1);
    std::thread t4(observer2);

    for (size_t iteration = 0; iteration < ITERATIONS; ++iteration) {
        x1.store(0, std::memory_order_relaxed);
        x2.store(0, std::memory_order_relaxed);
        z1.store(0, std::memory_order_relaxed);
        z2.store(0, std::memory_order_relaxed);

        trial_barrier.arrive_and_wait();
        trial_barrier.arrive_and_wait();

        assert(observed_by_1 == THRESHOLD || observed_by_2 == THRESHOLD);
    }

    t1.join();
    t2.join();
    t3.join();
    t4.join();
}
#endif

constexpr size_t ITERATIONS = 1'000'000;
std::barrier trial_barrier{3};
alignas(256) std::atomic<int> x{0};
alignas(256) std::atomic<int> y{0};
int r1 = -1;
int r2 = -1;

void precreated_thread1()
{
    for (size_t i = 0; i < ITERATIONS; ++i) {
        trial_barrier.arrive_and_wait();
        x.store(1, std::memory_order_relaxed);
        r1 = y.load(std::memory_order_relaxed);
        trial_barrier.arrive_and_wait();
    }
}

void precreated_thread2()
{
    for (size_t i = 0; i < ITERATIONS; ++i) {
        trial_barrier.arrive_and_wait();
        y.store(1, std::memory_order_relaxed);
        r2 = x.load(std::memory_order_relaxed);
        trial_barrier.arrive_and_wait();
    }
}

void run_precreated_threads()
{
    std::thread t1(precreated_thread1);
    std::thread t2(precreated_thread2);

    for (size_t i = 0; i < ITERATIONS; ++i) {
        x.store(0, std::memory_order_relaxed);
        y.store(0, std::memory_order_relaxed);
        r1 = r2 = -1;
        trial_barrier.arrive_and_wait();
        trial_barrier.arrive_and_wait();
        assert(r1 != -1 && r2 != -1);
        assert(r1 != 0 || r2 != 0);
    }

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

// ----------------------------------------

void run_create_and_join_each_iteration()
{
    std::atomic<int> local_x{0};
    std::atomic<int> local_y{0};

    for (size_t i = 0; i < ITERATIONS; ++i) {
        int local_r1 = -1;
        int local_r2 = -1;

        local_x.store(0, std::memory_order_relaxed);
        local_y.store(0, std::memory_order_relaxed);

        std::thread t1([&] {
            local_x.store(1, std::memory_order_release);
            local_r1 = local_y.load(std::memory_order_acquire);
        });
        std::thread t2([&] {
            local_y.store(1, std::memory_order_release);
            local_r2 = local_x.load(std::memory_order_acquire);
        });

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

        assert(local_r1 != 0 || local_r2 != 0);
    }
}

void run_seq_cst_store_buffering()
{
    for (size_t i = 0; i < ITERATIONS; ++i) {
        alignas(256) std::atomic<bool> local_x{false};
        alignas(256) std::atomic<bool> local_y{false};
        alignas(256) std::atomic<int> local_z{0};

        std::thread a([&] {
            local_x.store(true, std::memory_order_relaxed);
        });
        std::thread b([&] {
            local_y.store(true, std::memory_order_relaxed);
        });
        std::thread c([&] {
            while (!local_x.load(std::memory_order_relaxed))
                ;
            if (local_y.load(std::memory_order_relaxed))
                ++local_z;
        });
        std::thread d([&] {
            while (!local_y.load(std::memory_order_relaxed))
                ;
            if (local_x.load(std::memory_order_relaxed))
                ++local_z;
        });

        a.join();
        b.join();
        c.join();
        d.join();
        assert(local_z.load(std::memory_order_seq_cst) != 0);
    }
}

void run_seq_cst_store_buffering_precreated()
{
    alignas(256) std::atomic<bool> local_x{false};
    alignas(256) std::atomic<bool> local_y{false};
    alignas(256) std::atomic<int> local_z{0};
    std::barrier sync{5};

    std::thread a([&] {
        for (size_t i = 0; i < ITERATIONS; ++i) {
            sync.arrive_and_wait();
            local_x.store(true, std::memory_order_release);
            sync.arrive_and_wait();
        }
    });
    std::thread b([&] {
        for (size_t i = 0; i < ITERATIONS; ++i) {
            sync.arrive_and_wait();
            local_y.store(true, std::memory_order_release);
            sync.arrive_and_wait();
        }
    });
    std::thread c([&] {
        for (size_t i = 0; i < ITERATIONS; ++i) {
            sync.arrive_and_wait();
            while (!local_x.load(std::memory_order_acquire))
                ;
            if (local_y.load(std::memory_order_acquire))
                ++local_z;
            sync.arrive_and_wait();
        }
    });
    std::thread d([&] {
        for (size_t i = 0; i < ITERATIONS; ++i) {
            sync.arrive_and_wait();
            while (!local_y.load(std::memory_order_acquire))
                ;
            if (local_x.load(std::memory_order_acquire))
                ++local_z;
            sync.arrive_and_wait();
        }
    });

    for (size_t i = 0; i < ITERATIONS; ++i) {
        local_x = false;
        local_y = false;
        local_z = 0; 
        sync.arrive_and_wait();
        // std::this_thread::sleep_for(std::chrono::milliseconds(500));
        sync.arrive_and_wait();
        // std::cout << "z: " << local_z.load(std::memory_order_seq_cst) << '\n';
        assert(local_z.load(std::memory_order_seq_cst) != 0);
    }

    a.join();
    b.join();
    c.join();
    d.join();
}

int main()
{
    // run_precreated_threads();
    // run_create_and_join_each_iteration();
    // run_seq_cst_store_buffering();
    run_seq_cst_store_buffering_precreated();
}


/*

~EXPERIMENTS~

1. Dekker -- no barrier, thread creation in loop
   run_create_and_join_each_iteration()
    * relaxed: trips on O0 & O1
    * rel_acq: passes on O0 & trips on O1 (even with no alignment)
        * according to gemini O0 produces LDAR and and O1 produces LDAPR. they
        * map to seq_cst and acquire in C++.
    * seq_cst: passes on O0 & O1 (even O3, even with no alignment)


2. IRIW (cppreference) -- no barrier, thread creation in loop
   run_seq_cst_store_buffering()
    * relaxed: Passes on O3 (even with alignment)

3. IRIW (cppreference) -- barriers & pre-created threads
   run_seq_cst_store_buffering_precreated()
    * relaxed: trips consistently at O0, but inconsistently at O1,O2,O3.
        * chatgpt notes that O0 calls go through library, O1 is inline
    * rel_acq: passes on O0 & 03 (even with alignment)


4. Increment Threshold (variant of IRIW)

*/