#include "test.h"

using Test = TestSv<Hash>;

TEST_CASE("Hash", "[Hash]") {
    perlvars vars;
    Hash my(vars.hv);
    Sv oth_valid(vars.ohv), oth_invalid(vars.av);

    SECTION("ctor") {
        SECTION("empty") {
            Hash o;
            REQUIRE(!o);
        }
        SECTION("from SV") {
            SECTION("from undef SV")  { Test::ctor(vars.undef, behaviour_t::EMPTY); }
            SECTION("from number SV") { Test::ctor(vars.iv, behaviour_t::THROWS); }
            SECTION("from string SV") { Test::ctor(vars.pv, behaviour_t::THROWS); }
            SECTION("from RV")        { Test::ctor(vars.rv, behaviour_t::THROWS); }
            SECTION("from RV-OAV")    { Test::ctor(vars.oavr, behaviour_t::THROWS); }
            SECTION("from RV-OHV")    { Test::ctor(vars.ohvr, behaviour_t::VALID, (SV*)vars.ohv); }
            SECTION("from AV")        { Test::ctor((SV*)vars.av, behaviour_t::THROWS); }
            SECTION("from HV")        { Test::ctor((SV*)vars.hv, behaviour_t::VALID); }
            SECTION("from SHV")       { Test::ctor((SV*)vars.stash, behaviour_t::VALID); }
            SECTION("from OHV")       { Test::ctor((SV*)vars.ohv, behaviour_t::VALID); }
            SECTION("from CV")        { Test::ctor((SV*)vars.cv, behaviour_t::THROWS); }
            SECTION("from IO")        { Test::ctor((SV*)vars.io, behaviour_t::THROWS); }
        }
        SECTION("from HV") { Test::ctor(vars.hv, behaviour_t::VALID); }

        SECTION("from Hash")       { Test::ctor(my, behaviour_t::VALID); }
        SECTION("from valid Sv")   { Test::ctor(oth_valid, behaviour_t::VALID); }
        SECTION("from invalid Sv") { Test::ctor(oth_invalid, behaviour_t::THROWS); }
        SECTION("from ilist")      {
            Hash o({
                {"key1", Simple(1)},
                {"key2", Simple("val2")},
            });
            REQUIRE(o.size() == 2);
            REQUIRE(Simple(o["key1"]) == 1);
            REQUIRE(Simple(o["key2"]) == "val2");
        }
    }

    SECTION("create") {
        SECTION("empty") {
            auto o = Hash::create();
            REQUIRE(o);
            REQUIRE(o.get());
        }
        SECTION("ilist") {
            auto o = Hash::create({
                {"key1", Simple(1)},
                {"key2", Simple("val2")},
            });
            REQUIRE(o.size() == 2);
            REQUIRE(Simple(o["key1"]) == 1);
            REQUIRE(Simple(o["key2"]) == "val2");
        }
        SECTION("with capacity") {
            auto o = Hash::create(50);
            REQUIRE(o.capacity() >= 50);
        }
    }

    SECTION("operator=") {
        auto o = Hash::create();
        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("RV-OAV")    { Test::assign(o, vars.oavr, behaviour_t::THROWS); }
            SECTION("RV-OHV")    { Test::assign(o, vars.ohvr, behaviour_t::VALID, (SV*)vars.ohv); }
            SECTION("AV")        { Test::assign(o, (SV*)vars.av, behaviour_t::THROWS); }
            SECTION("HV")        { Test::assign(o, (SV*)vars.hv, behaviour_t::VALID); }
            SECTION("SHV")       { Test::assign(o, (SV*)vars.stash, behaviour_t::VALID); }
            SECTION("OHV")       { Test::assign(o, (SV*)vars.ohv, behaviour_t::VALID); }
            SECTION("CV")        { Test::assign(o, (SV*)vars.cv, behaviour_t::THROWS); }
            SECTION("IO")        { Test::assign(o, (SV*)vars.io, behaviour_t::THROWS); }
        }
        SECTION("HV")         { Test::assign(o, vars.hv, behaviour_t::VALID); }
        SECTION("Hash")       { 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") {
        Hash o;
        o.set(vars.iv); // no checks
        REQUIRE(o);
        REQUIRE(SvREFCNT(vars.iv) == 2);
        REQUIRE(o.get() == vars.iv);
    }

    SECTION("cast") {
        Hash o(vars.hv);
        auto rcnt = SvREFCNT(vars.hv);
        SECTION("to SV") {
            SV* sv = o;
            REQUIRE(sv == (SV*)vars.hv);
            REQUIRE(SvREFCNT(vars.hv) == rcnt);
        }
        SECTION("to HV") {
            HV* sv = o;
            REQUIRE(sv == vars.hv);
            REQUIRE(SvREFCNT(vars.hv) == rcnt);
        }
    }

    SECTION("get") {
        Hash o(vars.hv);
        auto rcnt = SvREFCNT(vars.hv);
        REQUIRE(o.get<>() == (SV*)vars.hv);
        REQUIRE(o.get<SV>() == (SV*)vars.hv);
        REQUIRE(o.get<HV>() == vars.hv);
        REQUIRE(SvREFCNT(vars.hv) == rcnt);
    }

    SECTION("fetch/[]const/[]/at") {
        Hash o;
        const Hash& co = o;
        REQUIRE(!o.fetch("key")); // fetch return empty object when empty
        REQUIRE(!co["key"]);
        REQUIRE(!o.fetch("key"));
        REQUIRE_THROWS(o.at("key"));

        o = Hash::create();
        REQUIRE(!o.fetch("key"));
        REQUIRE(!co["key"]);
        REQUIRE(o["key"]);
        REQUIRE(o.fetch("key"));
        REQUIRE(o.at("key"));
        REQUIRE_THROWS(o.at("nokey"));

        hv_stores(o, "key", newSViv(10));
        hv_stores(o, "name", newSVpvs("vasya"));

        REQUIRE(o.fetch("key"));
        REQUIRE(o.at("key"));
        REQUIRE(Simple(o.fetch("key")).get<int>() == 10);
        REQUIRE(Simple(o.at("key")).get<int>() == 10);
        REQUIRE(Simple(co["name"]) == "vasya");
        REQUIRE(Simple(o["name"]) == "vasya");
        REQUIRE(!o.fetch("nokey"));
        REQUIRE(!co["nokey"]);
        REQUIRE_THROWS(o.at("nokey"));
        REQUIRE(o["nokey"]);
        REQUIRE(co["nokey"]);
        REQUIRE(o.fetch("nokey"));
    }

    SECTION("store/[]=") {
        Hash o;
        REQUIRE_THROWS(o.store("key", Sv()));
        REQUIRE(!o.fetch("key"));

        o = Hash::create();
        auto pcnt = SvREFCNT(vars.pv);
        o.store("key", vars.pv);
        REQUIRE(Simple(o.fetch("key")) == "hello");
        REQUIRE(SvREFCNT(vars.pv) == pcnt+1);

        auto icnt = SvREFCNT(vars.iv);
        o["age"] = vars.iv;
        REQUIRE(o.fetch("age").get() == vars.iv);
        REQUIRE(SvREFCNT(vars.iv) == icnt+1);

        auto rcnt = SvREFCNT(vars.rv);
        o.store("key", vars.rv);
        REQUIRE(o.fetch("key").get() == vars.rv);
        REQUIRE(SvREFCNT(vars.pv) == pcnt);
        REQUIRE(SvREFCNT(vars.rv) == rcnt+1);

        o["age"] = vars.rv;
        REQUIRE(o.fetch("age").get() == vars.rv);
        REQUIRE(SvREFCNT(vars.iv) == icnt);
        REQUIRE(SvREFCNT(vars.rv) == rcnt+2);

        o.store("undef", nullptr);
        REQUIRE(o.fetch("undef"));
        REQUIRE(!o.fetch("undef").defined());
        REQUIRE(o.fetch("undef").use_count() == 2);

        o["u"] = nullptr;
        REQUIRE(o.fetch("u"));
        REQUIRE(!o.fetch("u").defined());
        REQUIRE(o.fetch("u").use_count() == 2);

        o["a"] = Simple(100);
        o["b"] = o["a"];
        REQUIRE(o.fetch("b"));
        REQUIRE(Simple(o.fetch("b")) == 100);

        o.store("c", o["b"]);
        REQUIRE(Simple(o.fetch("c")) == 100);
    }

    SECTION("erase") {
        auto o = Hash::create();
        REQUIRE(!o.erase("key"));
        o["key"] = vars.pv;
        Sv elem = o.erase("key");
        REQUIRE(elem);
        REQUIRE(elem.get() == vars.pv);
        REQUIRE(!o.fetch("key"));
        elem.reset();
    }

    SECTION("exists") {
        Hash o;
        REQUIRE(!o.exists("key"));
        o = Hash::create();
        REQUIRE(!o.exists("key"));
        o["key"] = nullptr;
        REQUIRE(o.exists("key"));
        o.erase("key");
        REQUIRE(!o.contains("key"));
    }

    SECTION("size") {
        Hash o;
        REQUIRE(o.size() == 0);
        o = Hash::create();
        REQUIRE(o.size() == 0);
        o["key"] = nullptr;
        REQUIRE(o.size() == 1);
        o.store("key2", vars.pv);
        REQUIRE(o.size() == 2);
        o["key2"] = vars.iv;
        REQUIRE(o.size() == 2);
    }

    SECTION("clear/undef") {
        Hash o;
        o.clear();
        o.undef();
        REQUIRE(o.size() == 0);
        o = Hash::create();
        o.clear();
        o.undef();
        REQUIRE(o.size() == 0);

        o["key"] = vars.iv;
        REQUIRE(o.size() == 1);
        o.clear();
        REQUIRE(o.size() == 0);
        REQUIRE(!o.exists("key"));

        o["key"] = vars.pv;
        REQUIRE(o.size() == 1);
        o.undef();
        REQUIRE(o.size() == 0);
        REQUIRE(!o.exists("key"));
    }

    SECTION("iterate") {
        Hash o;
        REQUIRE(o.begin() == o.end());
        o = Hash::create();
        REQUIRE(o.begin() == o.end());
        auto icnt = SvREFCNT(vars.iv);
        auto pcnt = SvREFCNT(vars.pv);
        o["key"] = vars.iv;
        o["name"] = vars.pv;
        o["ref"] = vars.rv;
        REQUIRE(SvREFCNT(vars.iv) == icnt+1);

        Hash check = Hash::create();
        int cnt = 0;
        SECTION("iterator") {
            for (auto it = o.begin(); it != o.end(); ++it) {
                cnt++;
                REQUIRE(it->key().length());
                REQUIRE(it->value());
                REQUIRE(it->hash());
                check[it->key()] = it->value();
            }
        }
#if (__cplusplus >= 201703L)
        SECTION("range based") {
            for (auto [key, value] : o) {
                cnt++;
                REQUIRE(key.length());
                REQUIRE(value);
                check[key] = value;
            }
        }
#endif
        REQUIRE(cnt == 3);
        REQUIRE(check.size() == 3);
        REQUIRE(check["key"].get() == vars.iv);
        REQUIRE(check["name"].get() == vars.pv);
        REQUIRE(check["ref"].get() == vars.rv);
        REQUIRE(SvREFCNT(vars.iv) == icnt+2);
        check.clear();
        REQUIRE(SvREFCNT(vars.iv) == icnt+1);

        for (auto it = o.begin(); it != o.end(); ++it) {
            it->value(vars.pv);
        }
        REQUIRE(SvREFCNT(vars.iv) == icnt);
        REQUIRE(SvREFCNT(vars.pv) == pcnt+3);
        REQUIRE(o["key"].get() == vars.pv);
        REQUIRE(o["name"].get() == vars.pv);
        REQUIRE(o["ref"].get() == vars.pv);
    }

    SECTION("const iterate") {
        Hash src;
        const Hash& o = src;
        REQUIRE(o.begin() == o.end());
        src = Hash::create();
        REQUIRE(o.cbegin() == o.cend());
        src["key"] = vars.iv;
        src["name"] = vars.pv;
        src["ref"] = vars.rv;

        Hash check = Hash::create();
        int cnt = 0;
        for (auto it = o.cbegin(); it != o.cend(); ++it) {
            cnt++;
            REQUIRE(it->key().length());
            REQUIRE(it->value());
            REQUIRE(it->hash());
            check[it->key()] = it->value();
        }
        REQUIRE(cnt == 3);
        REQUIRE(check.size() == 3);
        REQUIRE(check["key"].get() == vars.iv);
        REQUIRE(check["name"].get() == vars.pv);
        REQUIRE(check["ref"].get() == vars.rv);
    }

    SECTION("multi-deref") {
        auto o = Hash::create();
        o["key1"] = Ref::create(Array::create({Simple(100)}));
        o["key2"] = Simple(1);
        Simple res = o["key1"][0];
        REQUIRE(res);
        REQUIRE(res == 100);
        o["key1"][0] = Simple(200);
        REQUIRE(o["key1"][0]);
        REQUIRE(Simple(o["key1"][0]) == 200);
        REQUIRE_THROWS(o["key2"][0]);

        auto h = Hash::create();
        h["key"] = Simple(100);
        o.store("key3", Ref::create(h));
        res = o["key3"]["key"];
        REQUIRE(res);
        REQUIRE(res == 100);
        o["key3"]["key"] = Simple(200);
        REQUIRE(o["key3"]["key"]);
        REQUIRE(Simple(o["key3"]["key"]) == 200);
        REQUIRE_THROWS(o["key2"]["key"]);
    }

    SECTION("capacity") {
        Hash o;
        REQUIRE(o.capacity() == 0);
        o = Hash::create();
        o["a"] = Simple(1);
        o["b"] = Simple(1);
        REQUIRE(o.capacity() >= 2);
    }

    SECTION("reserve") {
        auto o = Hash::create();
        o.reserve(100);
        REQUIRE(o.capacity() >= 100);
    }
}