#include "tile/base/thread/latch.h"

#include "tile/base/chrono.h"

#include "gtest/gtest.h"

#include <atomic>
#include <chrono>
#include <memory>
#include <thread>

namespace tile {

std::atomic<bool> exiting{false};

void
RunTest()
{
    std::size_t local_count = 0, remote_count = 0;
    while (!exiting) {
        auto called = std::make_shared<std::atomic<bool>>(false);
        std::this_thread::yield();// Wait for thread pool to start.
        Latch l(1);
        auto t = std::thread([&] {
            if (!called->exchange(true)) {
                std::this_thread::yield();// Something costly.
                l.CountDown();
                ++remote_count;
            }
        });
        std::this_thread::yield();// Something costly.
        if (!called->exchange(true)) {
            l.CountDown();
            ++local_count;
        }
        l.Wait();
        t.join();
    }
    std::cout << local_count << " " << remote_count << std::endl;
}

TEST(Latch, Torture)
{
    std::thread ts[10];
    for (auto &&t : ts) { t = std::thread(RunTest); }
    std::this_thread::sleep_for(std::chrono::seconds(1));
    exiting = true;
    for (auto &&t : ts) { t.join(); }
}

TEST(Latch, CountDownTwo)
{
    Latch l(2);
    l.ArriveAndWait(2);
    ASSERT_TRUE(1);
}

TEST(Latch, WaitFor)
{
    Latch l(1);
    ASSERT_FALSE(l.WaitFor(std::chrono::milliseconds(100)));
    l.CountDown();
    ASSERT_TRUE(l.WaitFor(std::chrono::seconds(0)));
}

TEST(Latch, WaitUntil)
{
    Latch l(1);
    ASSERT_FALSE(l.WaitUntil(ReadSteadyClock() + std::chrono::milliseconds(100)));
    l.CountDown();
    ASSERT_TRUE(l.WaitUntil(ReadSteadyClock()));
}

}// namespace tile
