#include "tile/base/internal/move_on_copy.h"

#include "tile/base/make_unique.h"
#include "tile/base/thread/cond_var.h"
#include "tile/base/thread/mutex.h"
#include "tile/base/thread/scoped_lock.h"

#include "gtest/gtest.h"

#include <functional>
#include <memory>
#include <thread>

using MoveOnlyType = std::unique_ptr<int>;

namespace tile {

TEST(MoveOnCopy, Construct)
{
    // from lvalue
    MoveOnlyType x = make_unique<int>(1);
    auto moved_x   = MakeMoveOnCopy(x);
    EXPECT_EQ(*moved_x.Ref(), 1);
}

TEST(MoveOnCopy, Copy)
{
    // from rvalue
    MoveOnlyType x = make_unique<int>(1);
    auto moved_x   = MakeMoveOnCopy(std::move(x));
    auto moved_x2  = moved_x;
    EXPECT_EQ(*moved_x2.Ref(), 1);
}

TEST(MoveOnCopy, Lambda)
{
    MoveOnlyType x = make_unique<int>(1);
    auto moved_x   = MakeMoveOnCopy(x);
    auto l1        = [moved_x]() mutable { return *moved_x.Ref(); };
    auto l2        = l1;
    auto l3        = std::move(l2);
    ASSERT_DEATH(l1(), ".*");
    ASSERT_DEATH(l2(), ".*");
    ASSERT_EQ(l3(), 1);
}

TEST(MoveOnCopy, LambdaWithRef)
{
    auto x       = make_unique<int>(1);
    auto moved_x = MakeMoveOnCopy(x);
    int v        = 0;

    Mutex m;
    CondVar cv;

    auto l1 = [&]() {
        ScopedLock l(m);
        v = *moved_x.Ref();
        cv.NotifyOne();
    };

    std::thread(l1).detach();
    ScopedLock l(m);
    cv.Wait(l, [&] { return v != 0; });
    ASSERT_EQ(v, 1);
}

}// namespace tile
