#include "thread/flag.hpp"

#include "tap.hpp"

#include <thread>


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

    cruft::thread::flag f;
    std::atomic<int> value = 0;

    std::thread t1 ([&] () {
        f.wait ();
        value = 1;
    });

    std::this_thread::sleep_for (std::chrono::milliseconds (100));

    tap.expect_eq (value, 0, "value hasn't been set during wait");
    f.notify_all ();

    t1.join ();
    tap.expect_eq (value, 1, "value has been changed after wait");

    std::thread t2 ([&] () {
        f.wait ();
        value = 2;
    });

    std::this_thread::sleep_for (std::chrono::milliseconds (100));
    tap.expect_eq (value, 2, "second wait didn't appear to block");
    t2.join ();

    {
        // perform a stress test to (hopefully) discover deadlocks
        //
        // * create a large matrix of flag variables
        // * create a bank of threads which:
        //     * wait on each flag of each row, or
        //     * notify if the flag index matches the thread index
        constexpr int iterations = 1024;
        constexpr int parallelism = 16;

        std::vector<
            std::array<cruft::thread::flag,parallelism>
        > flags (iterations);

        const auto func = [&flags] (const int idx) {
            for (auto &row: flags) {
                for (int i = 0; i < parallelism; ++i) {
                    if (i == idx)
                        row[i].notify_all ();
                    else
                        row[i].wait ();
                }
            }
        };

        std::vector<std::thread> workers;
        std::generate_n (
            std::back_inserter (workers),
            parallelism,
            [&, i = 0] () mutable
        {
               return std::thread { func, i++ };
        });

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

        tap.expect (true, "flag sequence did not block");
    }

    return tap.status ();
}