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

#include "tile/base/thread/mutex.h"
#include "tile/base/thread/scoped_lock.h"
#include "tile/base/thread/unique_lock.h"
#include "gtest/gtest.h"

#include <thread>

namespace tile {

class CondVarTest : public ::testing::Test {
protected:
    void SetUp() override
    {
        m      = new Mutex();
        cv     = new CondVar();
        worker = nullptr;
        ResetEnv();
    }

    void TearDown() override
    {
        SetWorker(nullptr);
        delete m;
        delete cv;
    }

    void SetWorker(std::thread *new_worker)
    {
        if (worker) {
            if (worker->joinable()) { worker->join(); }
            delete worker;
        }
        worker = new_worker;
    }

    void ResetEnv()
    {
        is_started = false;
        is_set     = false;
        is_timeout = false;
    }

    void SetStarted() { is_started = true; }

    void WaitStarted()
    {
        while (!is_started) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); }
    }

protected:
    Mutex *m;
    CondVar *cv;
    std::atomic<bool> is_started;
    bool is_set;
    bool is_timeout;
    std::thread *worker;
};

TEST_F(CondVarTest, NotifyOne_Wait)
{
    // Wait
    {
        std::thread t1([&] {
            // UniqueLock<Mutex> inner_locker(*m);
            // cv->Wait(inner_locker);
            ScopedLock inner_locker(*m);
            SetStarted();
            cv->Wait(inner_locker);
            is_set = true;
            cv->NotifyOne();
        });

        WaitStarted();
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
        ASSERT_FALSE(is_set);

        {
            UniqueLock<Mutex> locker(*m);
            cv->NotifyOne();
        }
        TILE_LOG_INFO("yield the lock");
        t1.join();
        ASSERT_TRUE(is_set);
    }
}

TEST_F(CondVarTest, NotifyOne_WaitFor)
{
    // Wait
    // constexpr auto kWaitTimeoutMs = 100;
    constexpr auto kWaitTimeout = std::chrono::milliseconds(100);
    auto WaitFor                = [&] {
        UniqueLock<Mutex> inner_locker(*m);
        SetStarted();
        if (cv->WaitFor(inner_locker, kWaitTimeout)) {
            is_set = true;
            cv->NotifyOne();
        } else {
            is_timeout = true;
        }
    };

    {

        SetWorker(new std::thread(WaitFor));
        WaitStarted();
        ASSERT_FALSE(is_set);

        {
            UniqueLock<Mutex> locker(*m);
            cv->NotifyOne();
        }

        worker->join();
        ASSERT_FALSE(is_timeout);
        ASSERT_TRUE(is_set);
    }

    // timeout
    {
        ResetEnv();
        SetWorker(new std::thread(WaitFor));
        {
            UniqueLock<Mutex> locker(*m);
            ASSERT_FALSE(is_timeout);
        }
        worker->join();
        ASSERT_TRUE(is_timeout);
        ASSERT_FALSE(is_set);
    }
}

}// namespace tile
