#include "thread/semaphore.hpp"
#include "thread/flag.hpp"
#include "tap.hpp"

#include <thread>
#include <mutex>


///////////////////////////////////////////////////////////////////////////////
void
fight (cruft::thread::semaphore &sem, const int iterations)
{
    for (int i = 0; i < iterations; ++i)
        std::lock_guard {sem};
}


///////////////////////////////////////////////////////////////////////////////
int
main ()
{
    cruft::TAP::logger tap;

    {
        cruft::thread::semaphore sem (0);
        tap.expect_eq (sem.value (), 0, "initialisation is respected");
        tap.expect_eq (sem.unlock (), 1, "bare release increments without blocking");
        tap.expect_eq (sem.lock (), 0, "bare acquire decrements without blocking");
    }

    // test that two threads can cooperate on a single semaphore.
    {
        cruft::thread::semaphore sem (1);

        // the spawned thread attempts to double acquire a semaphore with
        // only one available and so should get blocked immediately.
        std::atomic<int> test = 0;
        std::thread t ([&] () {
            sem.lock ();
            sem.lock ();
            test = 1;
        });

        // wait until we know the thread should have been blocked. it should
        // not have touched the 'test' variable at this point.
        while (sem.value () > 0)
            std::this_thread::yield ();

        tap.expect_eq (test, 0, "locking blocks in foreign thread");

        // unlock the semaphore, wait for the thread to finish, and check it
        // touched the 'test' variable.
        sem.unlock ();
        t.join ();
        tap.expect_eq (test, 1, "unlocking resumes foreign thread");
    }

    {
        const auto parallelism = std::thread::hardware_concurrency ();
        constexpr int iterations = 1 << 16;
        std::vector<std::thread> threads;

        cruft::thread::semaphore sem (0);

        for (unsigned i = 0; i < parallelism; ++i)
            threads.emplace_back (fight, std::ref (sem), iterations);

        sem.unlock ();
        sem.unlock ();

        for (auto &t: threads)
            t.join ();

        tap.expect (true, "high concurrency didn't deadlock");
    }

    return tap.status ();
}