#include "test.h"

using Test = TestSv<Object>;

TEST_CASE("Object", "[Object]") {
    perlvars vars;
    Object my(vars.ov);
    Sv oth_valid(vars.oavr), oth_invalid(vars.av);

    SECTION("ctor") {
        SECTION("empty") {
            Object r;
            REQUIRE(!r);
        }
        SECTION("SV") {
            SECTION("undef")  { Test::ctor(vars.undef, behaviour_t::EMPTY); }
            SECTION("number") { Test::ctor(vars.iv, behaviour_t::THROWS); }
            SECTION("string") { Test::ctor(vars.pv, behaviour_t::THROWS); }
            SECTION("RV")     { Test::ctor(vars.rv, behaviour_t::THROWS); }
            SECTION("RV-OSV") { Test::ctor(vars.ovr, behaviour_t::VALID, vars.ov);  }
            SECTION("RV-OAV") { Test::ctor(vars.oavr, behaviour_t::VALID, (SV*)vars.oav); }
            SECTION("RV-OHV") { Test::ctor(vars.ohvr, behaviour_t::VALID, (SV*)vars.ohv); }
            SECTION("RV-OIO") { Test::ctor(vars.ior, behaviour_t::VALID, (SV*)vars.io); }
            SECTION("AV")     { Test::ctor((SV*)vars.av, behaviour_t::THROWS); }
            SECTION("OAV")    { Test::ctor((SV*)vars.oav, behaviour_t::VALID); }
            SECTION("HV")     { Test::ctor((SV*)vars.hv, behaviour_t::THROWS); }
            SECTION("OHV")    { Test::ctor((SV*)vars.ohv, behaviour_t::VALID); }
            SECTION("CV")     { Test::ctor((SV*)vars.cv, behaviour_t::THROWS); }
            SECTION("GV")     { Test::ctor((SV*)vars.gv, behaviour_t::THROWS); }
            SECTION("OIO")    { Test::ctor((SV*)vars.io, behaviour_t::VALID); }
        }
        SECTION("AV") {
            SECTION("AV")     { Test::ctor(vars.av, behaviour_t::THROWS); }
            SECTION("OAV")    { Test::ctor(vars.oav, behaviour_t::VALID); }
        }
        SECTION("HV") {
            SECTION("HV")     { Test::ctor(vars.hv, behaviour_t::THROWS); }
            SECTION("OHV")    { Test::ctor(vars.ohv, behaviour_t::VALID); }
        }
        SECTION("CV")         { Test::ctor(vars.cv, behaviour_t::THROWS); }
        SECTION("GV")         { Test::ctor(vars.gv, behaviour_t::THROWS); }
        SECTION("OIO")        { Test::ctor(vars.io, behaviour_t::VALID); }

        SECTION("Object")     { Test::ctor(my, behaviour_t::VALID); }
        SECTION("valid Sv")   { Test::ctor(oth_valid, behaviour_t::VALID); }
        SECTION("invalid Sv") { Test::ctor(oth_invalid, behaviour_t::THROWS); }

        SECTION("check refcnt") {
            auto ocnt = SvREFCNT(vars.ov);
            auto rcnt = SvREFCNT(vars.ovr);
            {
                Object o(vars.ovr);
                REQUIRE(SvREFCNT(vars.ov) == ocnt+1);
                REQUIRE(SvREFCNT(vars.ovr) == rcnt+1);
            }
            REQUIRE(SvREFCNT(vars.ov) == ocnt);
            REQUIRE(SvREFCNT(vars.ovr) == rcnt);
        }
    }

    SECTION("operator=") {
        Object o = Stash("MyTest").bless(Simple(100));

        SECTION("SV") {
            SECTION("undef")  { Test::assign(o, vars.undef, behaviour_t::EMPTY); }
            SECTION("number") { Test::assign(o, vars.iv, behaviour_t::THROWS); }
            SECTION("string") { Test::assign(o, vars.pv, behaviour_t::THROWS); }
            SECTION("RV")     { Test::assign(o, vars.rv, behaviour_t::THROWS); }
            SECTION("RV-OSV") { Test::assign(o, vars.ovr, behaviour_t::VALID, vars.ov); }
            SECTION("RV-OAV") { Test::assign(o, vars.oavr, behaviour_t::VALID, (SV*)vars.oav); }
            SECTION("RV-OHV") { Test::assign(o, vars.ohvr, behaviour_t::VALID, (SV*)vars.ohv); }
            SECTION("RV-OIO") { Test::assign(o, vars.ior, behaviour_t::VALID, (SV*)vars.io); }
            SECTION("AV")     { Test::assign(o, (SV*)vars.av, behaviour_t::THROWS); }
            SECTION("OAV")    { Test::assign(o, (SV*)vars.oav, behaviour_t::VALID); }
            SECTION("HV")     { Test::assign(o, (SV*)vars.hv, behaviour_t::THROWS); }
            SECTION("OHV")    { Test::assign(o, (SV*)vars.ohv, behaviour_t::VALID); }
            SECTION("CV")     { Test::assign(o, (SV*)vars.cv, behaviour_t::THROWS); }
            SECTION("GV")     { Test::assign(o, (SV*)vars.gv, behaviour_t::THROWS); }
            SECTION("OIO")    { Test::assign(o, (SV*)vars.io, behaviour_t::VALID); }
        }
        SECTION("AV") {
            SECTION("AV")     { Test::assign(o, vars.av, behaviour_t::THROWS); }
            SECTION("OAV")    { Test::assign(o, vars.oav, behaviour_t::VALID); }
        }
        SECTION("HV") {
            SECTION("HV")     { Test::assign(o, vars.hv, behaviour_t::THROWS); }
            SECTION("OHV")    { Test::assign(o, vars.ohv, behaviour_t::VALID); }
        }
        SECTION("CV")         { Test::assign(o, vars.cv, behaviour_t::THROWS); }
        SECTION("GV")         { Test::assign(o, vars.gv, behaviour_t::THROWS); }
        SECTION("OIO")        { Test::assign(o, vars.io, behaviour_t::VALID); }

        SECTION("Object")     { Test::assign(o, my, behaviour_t::VALID); }
        SECTION("valid Sv")   { Test::assign(o, oth_valid, behaviour_t::VALID); }
        SECTION("invalid Sv") { Test::assign(o, oth_invalid, behaviour_t::THROWS); }

        SECTION("check refcnt") {
            auto obase = Simple(100);
            auto r = Ref::create(obase);
            Object o = Stash("MyTest").bless(r);
            auto ocnt = SvREFCNT(obase);
            auto rcnt = SvREFCNT(r);
            auto ocnt2 = SvREFCNT(vars.ov);
            auto rcnt2 = SvREFCNT(vars.ovr);
            o = vars.ovr;
            REQUIRE(SvREFCNT(r) == rcnt-1);
            REQUIRE(SvREFCNT(obase) == ocnt-1);
            REQUIRE(SvREFCNT(vars.ovr) == rcnt2+1);
            REQUIRE(SvREFCNT(vars.ov) == ocnt2+1);
        }
    }

    SECTION("set") {
        Object o;
        o.set(vars.iv); // no checks
        REQUIRE(o);
        REQUIRE(SvREFCNT(vars.iv) == 2);
        REQUIRE(o.get() == vars.iv);
    }

    SECTION("stash") {
        Object o(vars.ov);
        REQUIRE(o.stash());
        REQUIRE(o.stash() == vars.stash);

        Stash st("MyTest");
        o.stash(st);
        REQUIRE(o.stash() == gv_stashpvs("MyTest", 0));
        o.stash(vars.stash);
        REQUIRE(o.stash() == vars.stash);
    }

    SECTION("rebless") {
        Stash st("M1");
        auto o = st.bless(Simple(100));
        REQUIRE(o.stash() == st);
        Stash st2("M2");
        o.rebless(st2);
        REQUIRE(o.stash() == st2);
    }

    SECTION("ref") {
        Ref r;
        {
            auto o = Stash("M1").bless(Simple(100));
            r = o.ref();
            REQUIRE(r);
        }
        REQUIRE(r.use_count() == 1);
        Object o = r;
        REQUIRE(o.ref() == r);
        REQUIRE(r.use_count() == 2);
        o.reset();
        REQUIRE(r.use_count() == 1);
        o = r;
        o = vars.ov;
        REQUIRE(r.use_count() == 1);
        REQUIRE(o.ref() != r);
    }

    SECTION("method/method_strict") {
        auto o = Stash("M2").bless(Simple(333));
        o.stash().sub("child_method", vars.cv);
        REQUIRE(o.method("child_method"));
        REQUIRE(o.method("child_method") == get_cv("M2::child_method", 0));
        REQUIRE(o.method("child_method") == o.method_strict("child_method"));
        REQUIRE(o.method("method"));
        REQUIRE(o.method("method") == get_cv("M1::method", 0));
        REQUIRE(o.method("method") == o.method_strict("method"));
        REQUIRE(!o.method("nomethod"));
        REQUIRE_THROWS(o.method_strict("nomethod"));
    }

    SECTION("isa") {
        auto o = Stash("M2").bless(Simple(333));
        REQUIRE(o.isa("M1"));
        REQUIRE(o.isa(Stash("M1")));
        REQUIRE(!o.isa("M111"));
        REQUIRE(!o.isa(Stash("M222", GV_ADD)));
    }

    SECTION("call") {
        auto o = Stash("M2").bless(Simple(333));
        Simple ret = o.call("method");
        REQUIRE(ret == 343);
        o = vars.ov;
        ret = Simple(o.call("method", Simple(100)));
        REQUIRE(ret == 233);
    }

    SECTION("call_next/super/SUPER") {
        auto o = Stash("M4").bless(Simple(333));
        Sub sub = o.method("meth");
        Simple res;

        SECTION("SUPER") {
            res = o.call_SUPER(sub);
            REQUIRE(res == "M4(OBJ)-2");
            sub = sub.SUPER_strict();
            REQUIRE(sub == get_cv("M2::meth", 0));

            res = o.call_SUPER(sub);
            REQUIRE(res == "M4(OBJ)-1");
            sub = sub.SUPER_strict();
            REQUIRE(sub == get_cv("M1::meth", 0));

            REQUIRE(!sub.SUPER());
            REQUIRE_THROWS(sub.SUPER_strict());
            REQUIRE_THROWS(o.call_SUPER<void>(sub));
        }

        SECTION("next") {
            res = o.call_next(sub);
            REQUIRE(res == "M4(OBJ)-2");
            sub = o.next_method_strict(sub);
            REQUIRE(sub == get_cv("M2::meth", 0));

            res = o.call_next(sub);
            REQUIRE(res == "M4(OBJ)-3");
            sub = o.next_method_strict(sub);
            REQUIRE(sub == get_cv("M3::meth", 0));

            res = o.call_next(sub);
            REQUIRE(res == "M4(OBJ)-1");
            sub = o.next_method_strict(sub);
            REQUIRE(sub == get_cv("M1::meth", 0));

            REQUIRE(!o.next_method(sub));
            REQUIRE_THROWS(o.next_method_strict(sub));
            REQUIRE_THROWS(o.call_next<void>(sub));
        }

        SECTION("next_maybe") {
            res = o.call_next_maybe(sub);
            REQUIRE(res == "M4(OBJ)-2");
            sub = o.next_method(sub);

            res = o.call_next_maybe(sub);
            REQUIRE(res == "M4(OBJ)-3");
            sub = o.next_method(sub);

            res = o.call_next_maybe(sub);
            REQUIRE(res == "M4(OBJ)-1");
            sub = o.next_method(sub);

            res = o.call_next_maybe(sub);
            REQUIRE(!res);
        }

        SECTION("super/dfs") {
            res = o.call_super(sub);
            REQUIRE(res == "M4(OBJ)-2");
            sub = o.super_method_strict(sub);
            REQUIRE(sub == get_cv("M2::meth", 0));

            res = o.call_super(sub);
            REQUIRE(res == "M4(OBJ)-1");
            sub = o.super_method_strict(sub);
            REQUIRE(sub == get_cv("M1::meth", 0));

            REQUIRE(!o.super_method(sub));
            REQUIRE_THROWS(o.super_method_strict(sub));
            REQUIRE_THROWS(o.call_super<void>(sub));
        }

        SECTION("super/c3") {
            o.call<void>("enable_c3");

            res = o.call_super(sub);
            REQUIRE(res == "M4(OBJ)-2");
            sub = o.super_method_strict(sub);
            REQUIRE(sub == get_cv("M2::meth", 0));

            res = o.call_super(sub);
            REQUIRE(res == "M4(OBJ)-3");
            sub = o.super_method_strict(sub);
            REQUIRE(sub == get_cv("M3::meth", 0));

            res = o.call_super(sub);
            REQUIRE(res == "M4(OBJ)-1");
            sub = o.super_method_strict(sub);
            REQUIRE(sub == get_cv("M1::meth", 0));

            REQUIRE(!o.super_method(sub));
            REQUIRE_THROWS(o.super_method_strict(sub));
            REQUIRE_THROWS(o.call_super<void>(sub));

            o.call<void>("disable_c3");
        }

        SECTION("super_maybe") {
            res = o.call_super_maybe(sub);
            REQUIRE(res == "M4(OBJ)-2");
            sub = o.super_method(sub);

            res = o.call_super_maybe(sub);
            REQUIRE(res == "M4(OBJ)-1");
            sub = o.super_method(sub);

            res = o.call_super_maybe(sub);
            REQUIRE(!res);
        }
    }

    SECTION("detach") {
        auto obase = Simple(100);
        auto r = Ref::create(obase);
        Object o = Stash("MyTest").bless(r);
        REQUIRE(obase.use_count() == 3);
        REQUIRE(r.use_count() == 2);
        SV* osv = o.detach();
        REQUIRE(r.use_count() == 1);
        REQUIRE(obase.use_count() == 3);
        SvREFCNT_dec(osv);
    }
}