/*
 * Copyright 2022 Rive
 */

#include <rive/refcnt.hpp>
#include <catch.hpp>
#include <cstdio>

using namespace rive;

class MyRefCnt : public RefCnt<MyRefCnt>
{
public:
    MyRefCnt() {}
    MyRefCnt(int, float, bool) {}

    void require_count(int value)
    {
        REQUIRE(this->debugging_refcnt() == value);
    }
};

TEST_CASE("refcnt", "[basics]")
{
    MyRefCnt my;
    REQUIRE(my.debugging_refcnt() == 1);
    my.ref();
    REQUIRE(my.debugging_refcnt() == 2);
    my.unref();
    REQUIRE(my.debugging_refcnt() == 1);

    safe_ref(&my);
    REQUIRE(my.debugging_refcnt() == 2);
    safe_unref(&my);
    REQUIRE(my.debugging_refcnt() == 1);

    // just exercise these to be sure they don't crash
    safe_ref((MyRefCnt*)nullptr);
    safe_unref((MyRefCnt*)nullptr);
}

TEST_CASE("rcp", "[basics]")
{
    rcp<MyRefCnt> r0(nullptr);

    REQUIRE(r0.get() == nullptr);
    REQUIRE(!r0);

    auto r1 = make_rcp<MyRefCnt>();
    REQUIRE(r1.get() != nullptr);
    REQUIRE(r1);
    REQUIRE(r1 != r0);
    REQUIRE(r1->debugging_refcnt() == 1);

    auto r2 = r1;
    REQUIRE(r1.get() == r2.get());
    REQUIRE(r1 == r2);
    REQUIRE(r2->debugging_refcnt() == 2);

    auto r3 = make_rcp<MyRefCnt>(1, .5f, false);
    REQUIRE(r3.get() != nullptr);
    REQUIRE(r3);
    REQUIRE(r3 != r1);
    REQUIRE(r3->debugging_refcnt() == 1);

    auto ptr = r2.release();
    REQUIRE(r2.get() == nullptr);
    REQUIRE(r1.get() == ptr);

    // This is important, calling release() does not modify the ref count on the
    // object We have to manage that explicit since we called release()
    REQUIRE(r1->debugging_refcnt() == 2);
    ptr->unref();
    REQUIRE(r1->debugging_refcnt() == 1);

    r1.reset();
    REQUIRE(r1.get() == nullptr);

    struct A : public rive::RefCnt<A>
    {
        virtual ~A() = default;

        int x = 17;
    };
    struct B : public A
    {
        int y = 21;
    };
    auto b = make_rcp<B>();
    CHECK(b->y == 21);
    rcp<A> a = b;
    CHECK(a->x == 17);
    CHECK(static_rcp_cast<B>(a)->y == 21);
    CHECK(rcp<A>(b)->x == 17);
    CHECK(a->debugging_refcnt() == 2);
    CHECK(b->debugging_refcnt() == 2);
    CHECK(static_rcp_cast<B>(std::move(a))->y == 21);
    CHECK(a == nullptr);
    CHECK(b->debugging_refcnt() == 1);
    a = ref_rcp(b.get());
    CHECK(b == a);
    CHECK(a->debugging_refcnt() == 2);
    CHECK(a->x == 17);
    CHECK(static_rcp_cast<B>(a)->y == 21);
}
