#include "tile/base/make_unique.h"
#include "tile/base/maybe_owning.h"
#include "gtest/gtest.h"

namespace tile {
int dtor_called = 0;

struct C {
    ~C() { ++dtor_called; }
};

struct Base {
    virtual ~Base() = default;
};

struct Drived : Base {};

void
AcceptMaybeOwningArgument(MaybeOwningArgument<int> ptr)
{}

void
AcceptMaybeOwningArgumentBase(MaybeOwningArgument<Base> ptr)
{}

TEST(MaybeOwning, Owning)
{
    dtor_called = 0;
    C *ptr      = new C();
    {
        MaybeOwning<C> holder(ptr, true);
        ASSERT_EQ(0, dtor_called);
    }
    ASSERT_EQ(1, dtor_called);
}

TEST(MaybeOwning, OwningTag)
{
    dtor_called = 0;
    C *ptr      = new C();
    {
        MaybeOwning<C> unholder(owning_t{}, ptr);
        ASSERT_EQ(0, dtor_called);
    }
    ASSERT_EQ(1, dtor_called);
}

TEST(MaybeOwning, NonOwning)
{
    dtor_called = 0;
    C *ptr      = new C();
    {
        MaybeOwning<C> unholder(ptr, false);
        ASSERT_EQ(0, dtor_called);
    }
    ASSERT_EQ(0, dtor_called);
    delete ptr;
    ASSERT_EQ(1, dtor_called);
}

TEST(MaybeOwning, NonOwningTag)
{
    dtor_called = 0;
    C *ptr      = new C();
    {
        MaybeOwning<C> holder(non_owning_t{}, ptr);
        ASSERT_EQ(0, dtor_called);
    }
    ASSERT_EQ(0, dtor_called);
    delete ptr;
    ASSERT_EQ(1, dtor_called);
}

TEST(MaybeOwning, FromUniquePtr)
{
    dtor_called = 0;
    std::unique_ptr<C> p;
    {
        MaybeOwning<C> holder(std::move(p));
        ASSERT_EQ(0, dtor_called);
    }
}

TEST(MaybeOwning, Move)
{
    dtor_called = 0;
    {
        MaybeOwning<C> holder(new C(), true);
        ASSERT_EQ(0, dtor_called);

        // move constructor 1
        MaybeOwning<C> holder2(std::move(holder));
        ASSERT_FALSE(!!holder);
        ASSERT_TRUE(!!holder2);
        ASSERT_EQ(0, dtor_called);

        // move assign 1
        MaybeOwning<C> holder3;
        ASSERT_FALSE(!!holder3);

        // move from non-owning
        holder3 = std::move(holder);
        ASSERT_FALSE(!!holder3);

        // move from owning
        holder3 = std::move(holder2);
        ASSERT_FALSE(!!holder2);
        ASSERT_TRUE(!!holder3);
        ASSERT_EQ(0, dtor_called);
    }
    ASSERT_EQ(1, dtor_called);
}

TEST(MaybeOwning, Reset)
{
    dtor_called = 0;
    MaybeOwning<C> holder(make_unique<C>());
    ASSERT_EQ(0, dtor_called);
    holder.Reset();
    ASSERT_EQ(1, dtor_called);
}

TEST(MaybeOwning, MoveIntoNonNull)
{
    dtor_called = 0;
    {
        MaybeOwning<C> holder(make_unique<C>());
        MaybeOwning<C> holder2(make_unique<C>());
        ASSERT_EQ(0, dtor_called);
        holder = std::move(holder2);
        ASSERT_EQ(1, dtor_called);
    }
    ASSERT_EQ(2, dtor_called);
}

TEST(MaybeOwning, SelfMove)
{
    dtor_called = 0;
    {
        MaybeOwning<C> holder(make_unique<C>());
        ASSERT_EQ(0, dtor_called);
        holder = std::move(holder);
        ASSERT_EQ(0, dtor_called);
    }
    ASSERT_EQ(1, dtor_called);
}

TEST(MaybeOwning, ConversionToConst)
{
    MaybeOwning<C> holder(new C(), true);
    MaybeOwning<const C> holder2(std::move(holder));

    ASSERT_FALSE(!!holder);
    ASSERT_TRUE(!!holder2);
}

TEST(MaybeOwning, ConversionToConst2)
{
    MaybeOwning<C> holder(new C(), true);
    MaybeOwning<const C> holder2;
    holder2 = std::move(holder);
    ASSERT_FALSE(!!holder);
    ASSERT_TRUE(!!holder2);
}

TEST(MaybeOwning, ConversionToUniquePtr)
{
    auto uptr = make_unique<C>();
    MaybeOwning<C> holder(std::move(uptr));
    ASSERT_FALSE(!!uptr);
    ASSERT_TRUE(!!holder);
}

TEST(MaybeOwning, ConversionToUniquePtr2)
{
    auto uptr = make_unique<C>();
    MaybeOwning<const C> holder;
    holder = std::move(uptr);
    ASSERT_FALSE(!!uptr);
    ASSERT_TRUE(!!holder);
}

TEST(MaybeOwning, DeductionGuides)
{
    MaybeOwning<C> holder(new C(), true);
    ASSERT_TRUE(!!holder);
}

TEST(MaybeOwning, Argument)
{
    int x = 0;
    AcceptMaybeOwningArgument(&x);
    AcceptMaybeOwningArgument(make_unique<int>());
    AcceptMaybeOwningArgument(nullptr);

    Drived drived;
    AcceptMaybeOwningArgumentBase(&drived);

    static_assert(std::is_convertible<Drived *, MaybeOwningArgument<Base>>::value, "");
}

}// namespace tile
