#include "test.h"

using Test = TestSv<Glob>;

template <class T> SV* _get_xv (const char* name);
template <> SV* _get_xv<Sv>     (const char* name) { return get_sv(name, 0); }
template <> SV* _get_xv<Scalar> (const char* name) { return get_sv(name, 0); }
template <> SV* _get_xv<Array>  (const char* name) { return (SV*)get_av(name, 0); }
template <> SV* _get_xv<Hash>   (const char* name) { return (SV*)get_hv(name, 0); }
template <> SV* _get_xv<Sub>    (const char* name) { return (SV*)get_cv(name, 0); }
template <> SV* _get_xv<Io>     (const char* name) {
    panda::string code = "*";
    code += name;
    code += "{IO}";
    auto ret = eval(code);
    return ret;
}

template <class SlotClass, class T>
void test_set_slot (Glob& o, T* sv) {
    SV* initial = o.slot<SlotClass>();
    U32 icnt = 0;
    if (initial) {
        SvREFCNT_inc(initial);
        sv_2mortal(initial);
        icnt = SvREFCNT(initial);
        REQUIRE(initial == _get_xv<SlotClass>("M1::gv2set"));
    }
    auto cnt = sv ? SvREFCNT(sv) : 0;
    o.slot(sv);
    if (initial) REQUIRE(SvREFCNT(initial) == icnt-1);
    if (sv) REQUIRE(SvREFCNT(sv) == cnt+1);
    REQUIRE(o.slot<SlotClass>() != initial);
    REQUIRE(o.slot<SlotClass>() == _get_xv<SlotClass>("M1::gv2set"));
}

template <class SlotClass, class T>
void test_set_slot (Glob& o, const T& sv) {
    SV* initial = o.slot<SlotClass>();
    U32 icnt = 0;
    if (initial) {
        SvREFCNT_inc(initial);
        sv_2mortal(initial);
        icnt = SvREFCNT(initial);
        REQUIRE(initial == _get_xv<SlotClass>("M1::gv2set"));
    }
    auto cnt = sv ? SvREFCNT(sv) : 0;
    o.slot(sv);
    if (initial) REQUIRE(SvREFCNT(initial) == icnt-1);
    if (sv) REQUIRE(SvREFCNT(sv) == cnt+1);
    REQUIRE(o.slot<SlotClass>() != initial);
    REQUIRE(o.slot<SlotClass>() == _get_xv<SlotClass>("M1::gv2set"));
}

TEST_CASE("Glob", "[Glob]") {
    perlvars vars;
    Glob my(vars.gv);
    Sv oth_valid(vars.gv), oth_invalid(vars.hv);

    SECTION("ctor") {
        SECTION("empty") {
            Glob o;
            REQUIRE(!o);
        }
        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("AV")     { Test::ctor((SV*)vars.av, behaviour_t::THROWS); }
            SECTION("HV")     { Test::ctor((SV*)vars.hv, behaviour_t::THROWS); }
            SECTION("OHV")    { Test::ctor((SV*)vars.ohv, behaviour_t::THROWS); }
            SECTION("SHV")    { Test::ctor((SV*)vars.stash, behaviour_t::THROWS); }
            SECTION("CV")     { Test::ctor((SV*)vars.cv, behaviour_t::THROWS); }
            SECTION("GV")     { Test::ctor((SV*)vars.gv, behaviour_t::VALID); }
            SECTION("IO")     { Test::ctor((SV*)vars.io, behaviour_t::THROWS); }
        }
        SECTION("GV") { Test::ctor(vars.gv, behaviour_t::VALID); }

        SECTION("Glob")       { 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("operator=") {
        Glob o((GV*)(*hv_fetchs(vars.stash, "_dummy", GV_ADD)));
        SECTION("SV") {
            SECTION("undef SV")  { Test::assign(o, vars.undef, behaviour_t::EMPTY); }
            SECTION("number SV") { Test::assign(o, vars.iv, behaviour_t::THROWS); }
            SECTION("string SV") { Test::assign(o, vars.pv, behaviour_t::THROWS); }
            SECTION("RV")        { Test::assign(o, vars.rv, behaviour_t::THROWS); }
            SECTION("AV")        { Test::assign(o, (SV*)vars.av, behaviour_t::THROWS); }
            SECTION("HV")        { Test::assign(o, (SV*)vars.hv, behaviour_t::THROWS); }
            SECTION("OHV")       { Test::assign(o, (SV*)vars.ohv, behaviour_t::THROWS); }
            SECTION("SHV")       { Test::assign(o, (SV*)vars.stash, behaviour_t::THROWS); }
            SECTION("CV")        { Test::assign(o, (SV*)vars.cv, behaviour_t::THROWS); }
            SECTION("GV")        { Test::assign(o, (SV*)vars.gv, behaviour_t::VALID); }
            SECTION("IO")        { Test::assign(o, (SV*)vars.io, behaviour_t::THROWS); }
        }
        SECTION("GV")         { Test::assign(o, vars.gv, behaviour_t::VALID); }
        SECTION("Glob")       { 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("set") {
        Glob o;
        auto cnt = SvREFCNT(vars.iv);
        o.set(vars.iv); // no checks
        REQUIRE(o);
        REQUIRE(SvREFCNT(vars.iv) == cnt+1);
        REQUIRE(o.get() == vars.iv);
    }

    SECTION("cast") {
        Glob o(vars.gv);
        auto rcnt = SvREFCNT(vars.gv);
        SECTION("to SV") {
            SV* sv = o;
            REQUIRE(sv == (SV*)vars.gv);
            REQUIRE(SvREFCNT(vars.gv) == rcnt);
        }
        SECTION("to GV") {
            GV* sv = o;
            REQUIRE(sv == vars.gv);
            REQUIRE(SvREFCNT(vars.gv) == rcnt);
        }
    }

    SECTION("get") {
        Glob o(vars.gv);
        auto rcnt = SvREFCNT(vars.gv);
        REQUIRE(o.get<>() == (SV*)vars.gv);
        REQUIRE(o.get<SV>() == (SV*)vars.gv);
        REQUIRE(o.get<GV>() == vars.gv);
        REQUIRE(SvREFCNT(vars.gv) == rcnt);
    }

    SECTION("name/effective_name") {
        REQUIRE(my.name() == "class_method");
        REQUIRE(my.effective_name() == "class_method");
    }

    SECTION("stash/effective_stash") {
        REQUIRE(my.stash() == vars.stash);
        REQUIRE(my.effective_stash() == vars.stash);
    }

    SECTION("get slot") {
        Glob o = Stash(vars.stash)["allgv"];
        REQUIRE(o.slot<Scalar>());
        REQUIRE(Simple(o.slot<Scalar>()) == "scalar");

        REQUIRE(o.slot<Array>());
        REQUIRE(Simple(o.slot<Array>()[0]) == "array");

        REQUIRE(o.slot<Hash>());
        REQUIRE(Simple(o.slot<Hash>()["key"]) == "hash");

        REQUIRE(o.slot<Sub>());
        REQUIRE(o.slot<Sub>() == get_cv("M1::allgv", 0));
    }

    SECTION("set slot") {
        Glob o = Stash(vars.stash)["gv2set"];
        SECTION("SV") {
            SECTION("simple")  { test_set_slot<Scalar>(o, Simple(100).get()); }
            SECTION("nullify") { test_set_slot<Scalar>(o, (SV*)NULL); }
            SECTION("AV")      { test_set_slot<Array>(o, Array::create().get()); }
            SECTION("HV")      { test_set_slot<Hash>(o, Hash::create().get()); }
            SECTION("CV")      { test_set_slot<Sub>(o, Sub("M1::dummy").get()); }
        }
        SECTION("AV")         { test_set_slot<Array>(o, Array::create().get<AV>()); }
        SECTION("AV-nullify") { test_set_slot<Array>(o, (AV*)NULL); }
        SECTION("HV")         { test_set_slot<Hash>(o, Hash::create().get<HV>()); }
        SECTION("HV-nullify") { test_set_slot<Hash>(o, (HV*)NULL); }
        SECTION("CV")         { test_set_slot<Sub>(o, Sub("M1::dummy2").get<CV>()); }
        SECTION("CV-nullify") { test_set_slot<Sub>(o, (CV*)NULL); }
        SECTION("Sv") {
            SECTION("simple")  { test_set_slot<Scalar>(o, static_cast<Sv>(Simple(100))); }
            SECTION("nullify") { test_set_slot<Scalar>(o, Sv()); }
            SECTION("AV")      { test_set_slot<Array>(o, static_cast<Sv>(Array::create())); }
            SECTION("HV")      { test_set_slot<Hash>(o, static_cast<Sv>(Hash::create())); }
            SECTION("CV")      { test_set_slot<Sub>(o, static_cast<Sv>(Sub("M1::dummy"))); }
        }
        SECTION("Scalar")         { test_set_slot<Scalar>(o, Scalar(Simple(200))); }
        SECTION("Scalar-nullify") { test_set_slot<Scalar>(o, Scalar()); }
        SECTION("Simple")         { test_set_slot<Scalar>(o, Simple(200)); }
        SECTION("Simple-nullify") { test_set_slot<Scalar>(o, Simple()); }
        SECTION("Ref")            { test_set_slot<Scalar>(o, Ref::create(Simple(200))); }
        SECTION("Ref-nullify")    { test_set_slot<Scalar>(o, Ref()); }
        SECTION("Array")          { test_set_slot<Array>(o, Array::create()); }
        SECTION("Array-nullify")  { test_set_slot<Array>(o, Array()); }
        SECTION("Hash")           { test_set_slot<Hash>(o, Hash::create()); }
        SECTION("Hash-nullify")   { test_set_slot<Hash>(o, Hash()); }
        SECTION("Stash")          { test_set_slot<Hash>(o, Stash(vars.stash)); }
        SECTION("Stash-nullify")  { test_set_slot<Hash>(o, Stash()); }
        SECTION("Sub")            { test_set_slot<Sub>(o, Sub("M1::dummy2")); }
        SECTION("Sub-nullify")    { test_set_slot<Sub>(o, Sub()); }
        SECTION("Object-Scalar")  { test_set_slot<Scalar>(o, Object(vars.ov)); }
        SECTION("Object-Nullify") { test_set_slot<Scalar>(o, Object()); }
        SECTION("Object-Array")   { test_set_slot<Array>(o, Object(vars.oav)); o.slot(Array()); }
    }

    SECTION("create") {
        Stash stash(vars.stash);
        auto glob = Glob::create(stash, "autogen");
        REQUIRE(glob);
        REQUIRE(glob.stash() == vars.stash);
        REQUIRE(glob.name() == "autogen");
        glob.slot(Sub("M1::dummy"));
        stash["aliased"] = glob;
        REQUIRE(get_cv("M1::dummy", 0) == get_cv("M1::aliased", 0));
    }

    SECTION("scalar") {
        Glob o;
        REQUIRE(!o.scalar());
        o = Stash(vars.stash)["gv2set"];
        auto v = Simple(200);
        o.scalar(v);
        REQUIRE(o.slot<Scalar>() == v);
        REQUIRE(o.scalar() == v);
    }

    SECTION("array") {
        Glob o;
        REQUIRE(!o.array());
        o = Stash(vars.stash)["gv2set"];
        auto v = Array::create();
        o.array(v);
        REQUIRE(o.slot<Array>() == v);
        REQUIRE(o.array() == v);
    }

    SECTION("hash") {
        Glob o;
        REQUIRE(!o.hash());
        o = Stash(vars.stash)["gv2set"];
        auto v = Hash::create();
        o.hash(v);
        REQUIRE(o.slot<Hash>() == v);
        REQUIRE(o.hash() == v);
    }

    SECTION("sub") {
        Glob o;
        REQUIRE(!o.sub());
        o = Stash(vars.stash)["gv2set"];
        auto v = Sub("M1::dummy");
        o.sub(v);
        REQUIRE(o.slot<Sub>() == v);
        REQUIRE(o.sub() == v);
    }
}