#include "test.h"
#include <array>
#include <utility>
#include <xs/function.h>

#define TEST(name) TEST_CASE("Sub: " name, "[Sub]")

using Test = TestSv<Sub>;

void cmp_array (const Array& a, const std::initializer_list<int>& l) {
    REQUIRE(a.size() == l.size());
    auto chk = l.begin();
    for (size_t i = 0; i < l.size(); ++i) CHECK(a[i] == chk[i]);
}

template <class T, size_t N>
void cmp_array (const std::array<T,N>& a, const std::initializer_list<int>& l) {
    REQUIRE(a.size() == l.size());
    auto chk = l.begin();
    for (size_t i = 0; i < l.size(); ++i) CHECK(a[i] == chk[i]);
}

TEST("ctor") {
    perlvars vars;
    SECTION("empty") {
        Sub o;
        REQUIRE(!o);
    }
    SECTION("SV") {
        SECTION("undef SV")  { Test::ctor(vars.undef, behaviour_t::EMPTY); }
        SECTION("number SV") { Test::ctor(vars.iv, behaviour_t::THROWS); }
        SECTION("string SV") { Test::ctor(vars.pv, behaviour_t::THROWS); }
        SECTION("RV")        { Test::ctor(vars.rv, behaviour_t::THROWS); }
        SECTION("RV-OAV")    { Test::ctor(vars.oavr, behaviour_t::THROWS); }
        SECTION("RV-OHV")    { Test::ctor(vars.ohvr, behaviour_t::THROWS); }
        SECTION("RV-CV")     { Test::ctor(vars.cvr, behaviour_t::VALID, (SV*)vars.cv); }
        SECTION("AV")        { Test::ctor((SV*)vars.av, behaviour_t::THROWS); }
        SECTION("HV")        { Test::ctor((SV*)vars.hv, behaviour_t::THROWS); }
        SECTION("CV")        { Test::ctor((SV*)vars.cv, behaviour_t::VALID); }
        SECTION("GV")        { Test::ctor((SV*)vars.gv, behaviour_t::THROWS); }
        SECTION("IO")        { Test::ctor((SV*)vars.io, behaviour_t::THROWS); }
    }
    SECTION("CV") { Test::ctor(vars.cv, behaviour_t::VALID); }

    SECTION("Sub")        { Test::ctor(Sub(vars.cv), behaviour_t::VALID); }
    SECTION("valid Sv")   { Test::ctor(Sv(vars.cv), behaviour_t::VALID); }
    SECTION("invalid Sv") { Test::ctor(Sv(vars.gv), behaviour_t::THROWS); }

    SECTION("from string") {
        static auto _a = eval_pv("package MyTest::Sub::CtorFromString; sub func {}", 1); (void)_a;
        Sub c("MyTest::Sub::CtorFromString::func");
        REQUIRE(c);
        REQUIRE(c.get<CV>() == get_cv("MyTest::Sub::CtorFromString::func", 0));
        Sub c2("MyTest::Sub::CtorFromString::nonexistent");
        REQUIRE(!c2);
        Sub c3("MyTest::Sub::CtorFromString::nonexistent", GV_ADD);
        REQUIRE(c3);
        REQUIRE(c3.get<CV>() == get_cv("MyTest::Sub::CtorFromString::nonexistent", 0));
    }
}

TEST("operator=") {
    perlvars vars;
    auto o = Sub::create("1;");
    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::THROWS); }
        SECTION("RV-CV")     { Test::assign(o, vars.cvr, behaviour_t::VALID, (SV*)vars.cv); }
        SECTION("AV")        { Test::assign(o, (SV*)vars.av, behaviour_t::THROWS); }
        SECTION("HV")        { Test::assign(o, (SV*)vars.hv, behaviour_t::THROWS); }
        SECTION("CV")        { Test::assign(o, (SV*)vars.cv, behaviour_t::VALID); }
        SECTION("GV")        { Test::assign(o, (SV*)vars.gv, behaviour_t::THROWS); }
        SECTION("IO")        { Test::assign(o, (SV*)vars.io, behaviour_t::THROWS); }
    }
    SECTION("CV")         { Test::assign(o, vars.cv, behaviour_t::VALID); }
    SECTION("Sub")        { Test::assign(o, Sub(vars.cv), behaviour_t::VALID); }
    SECTION("valid Sv")   { Test::assign(o, Sv(vars.cv), behaviour_t::VALID); }
    SECTION("invalid Sv") { Test::assign(o, Sv(vars.gv), behaviour_t::THROWS); }
}

TEST("set") {
    perlvars vars;
    Sub o;
    o.set(vars.iv); // no checks
    REQUIRE(o);
    REQUIRE(SvREFCNT(vars.iv) == 2);
    REQUIRE(o.get() == vars.iv);
}

TEST("cast") {
    perlvars vars;
    Sub o(vars.cv);
    auto rcnt = SvREFCNT(vars.cv);
    SECTION("to SV") {
        SV* sv = o;
        REQUIRE(sv == (SV*)vars.cv);
        REQUIRE(SvREFCNT(vars.cv) == rcnt);
    }
    SECTION("to CV") {
        CV* sv = o;
        REQUIRE(sv == vars.cv);
        REQUIRE(SvREFCNT(vars.cv) == rcnt);
    }
}

TEST("get") {
    perlvars vars;
    Sub o(vars.cv);
    auto rcnt = SvREFCNT(vars.cv);
    REQUIRE(o.get<>() == (SV*)vars.cv);
    REQUIRE(o.get<SV>() == (SV*)vars.cv);
    REQUIRE(o.get<CV>() == vars.cv);
    REQUIRE(SvREFCNT(vars.cv) == rcnt);
}

TEST("stash") {
    static auto _a = eval_pv("package MyTest::Sub::Stash; sub func {}", 1); (void)_a;
    Sub o("MyTest::Sub::Stash::func");
    REQUIRE(o.stash());
    REQUIRE(o.stash() == gv_stashpvs("MyTest::Sub::Stash", 0));
}

TEST("glob") {
    static auto _a = eval_pv("package MyTest::Sub::Glob; sub func {}", 1); (void)_a;
    Sub o("MyTest::Sub::Glob::func");
    REQUIRE(o.glob());
    REQUIRE(o.glob() == Stash("MyTest::Sub::Glob")["func"]);
}

TEST("name") {
    static auto _a = eval_pv("package MyTest::Sub::Name; sub func {}", 1); (void)_a;
    Sub o("MyTest::Sub::Name::func");
    REQUIRE(o.name() == "func");
}

TEST("named") {
    static auto _a = eval_pv("package MyTest::Sub::Named; sub func {}", 1); (void)_a;
    Sub o("MyTest::Sub::Named::func");
    REQUIRE(!o.named());
}

TEST("call args") {
    static auto _a = eval_pv(R"EOF(
        package MyTest::Sub::CallArgs;
        our $call_cnt = 0;

        sub check_args {
            $call_cnt++;
            return [@_]
        }
    )EOF", 1); (void)_a;

    Stash s("MyTest::Sub::CallArgs");
    auto sub        = s.sub("check_args");
    Simple call_cnt = s.scalar("call_cnt");
    call_cnt = 0;

    SECTION("empty") {
        cmp_array(sub.call(), {});
        CHECK(call_cnt == 1);
        cmp_array(sub(), {});
        CHECK(call_cnt == 2);
    }
    SECTION("SV*") {
        cmp_array(sub.call(Simple(999).get()), {999});
    }
    SECTION("SV**") {
        Simple arg1(100), arg2(200);
        SV* args[] = {arg1, arg2};
        cmp_array(sub.call(args, 2), {100, 200});
    }
    SECTION("SV* + SV**") {
        Simple arg1(100), arg2(200), arg3(300);
        SV* args[] = {arg2, arg3};
        cmp_array(sub.call(Simple(100).get(), args, 2), {100, 200, 300});
    }
    SECTION("const Scalar*") {
        Scalar args[] = {Simple(111), Simple(222)};
        cmp_array(sub.call(args, 2), {111, 222});
    }
    SECTION("SV* + const Scalar*") {
        Scalar args[] = {Simple(111), Simple(222)};
        cmp_array(sub.call(Simple(666).get(), args, 2), {666, 111, 222});
    }
    SECTION("ilist") {
        std::initializer_list<Scalar> l = {Simple(123), Simple(321)};
        cmp_array(sub.call(l), {123, 321});
    }
    SECTION("SV* + ilist") {
        std::initializer_list<Scalar> l = {Simple(300), Simple(400)};
        cmp_array(sub.call(Simple(7).get(), l), {7, 300, 400});
    }
    SECTION("variadic-1") {
        Simple arg(10);
        cmp_array(sub.call(arg), {10});
        CHECK(arg.use_count() == 1); // check for argument leaks
    }
    SECTION("variadic-2") {
        cmp_array(sub.call(Simple(10), Simple(20)), {10, 20});
    }
    SECTION("variadic-3") {
        cmp_array(sub.call(Simple(10), Simple(20), Scalar(Simple(100))), {10, 20, 100});
    }
    SECTION("variadic-4") {
        cmp_array(sub.call(Simple(10), Simple(20), Scalar(Simple(100)), Sv(Simple(200))), {10, 20, 100, 200});
    }
    SECTION("empty/nullptr -> undef") {
        Array ret = sub.call(Simple(10), nullptr, Simple());
        CHECK(ret.use_count() == 1); // check for retval leaks
        REQUIRE(ret.size() == 3);
        CHECK(ret[0] == 10);
        CHECK(!ret[1].defined());
        CHECK(!ret[2].defined());
    }
}

TEST("call context") {
    static auto _a = eval_pv(R"EOF(
        package MyTest::Sub::CallContext;
        our $call_cnt = 0;
        our $call_ret;

        sub check_context {
            $call_cnt++;
            return @_ if wantarray();
            return $_[0] if defined wantarray();
            $call_ret = $_[0];
        }
    )EOF", 1); (void)_a;

    Stash s("MyTest::Sub::CallContext");
    auto sub        = s.sub("check_context");
    Simple call_cnt = s.scalar("call_cnt");
    Simple call_ret = s.scalar("call_ret");
    call_cnt = 0;

    SECTION("void") {
        static_assert(std::is_same<decltype(sub.call<void>()),void>::value, "wrong signature");
        sub.call<void>(Simple(333));
        CHECK(call_cnt == 1);
        CHECK(call_ret == 333);
    }
    SECTION("scalar") {
        static_assert(std::is_same<decltype(sub.call()),Scalar>::value, "wrong signature");
        static_assert(std::is_same<decltype(sub.call<Scalar>()),Scalar>::value, "wrong signature");
        static_assert(std::is_same<decltype(sub.call<Simple>()),Simple>::value, "wrong signature");
        CHECK(sub.call(Simple(999)) == 999);
        CHECK(sub.call(Simple(999), Simple(111)) == 999);
        CHECK(!sub.call().defined());
    }
    SECTION("fixed-list array") {
        static_assert(std::is_same<decltype(sub.call<std::array<Simple,3>>()),std::array<Simple,3>>::value, "wrong signature");
        cmp_array(sub.call<std::array<Simple,3>>(Simple(1), Simple(2), Simple(3)), {1,2,3});
        cmp_array(sub.call<std::array<Simple,2>>(Simple(4), Simple(5), Simple(6)), {4,5});
        cmp_array(sub.call<std::array<Simple,4>>(Simple(7), Simple(8), Simple(9)), {7,8,9,0});
    }
    SECTION("fixed-list tuple") {
        using Tuple = std::tuple<Sv,Simple,Simple,Ref>;
        static_assert(std::is_same<decltype(sub.call<Tuple>()),Tuple>::value, "wrong signature");
        static_assert(std::is_same<decltype(sub.call<Sv,Simple,Simple,Ref>()),Tuple>::value, "wrong signature");
        SECTION("explicit") {
            Tuple res = sub.call<Tuple>(Simple(10), Simple(20), Simple(30));
            CHECK(Simple(std::get<0>(res)) == 10);
            CHECK(std::get<1>(res) == 20);
            CHECK(std::get<2>(res) == 30);
            CHECK(!std::get<3>(res));
        }
        SECTION("implicit") {
            Tuple res = sub.call<Sv,Simple,Simple,Ref>(Simple(50), Simple(60), Simple(70), Ref::create(Simple(111)), Simple(999));
            CHECK(Simple(std::get<0>(res)) == 50);
            CHECK(std::get<1>(res) == 60);
            CHECK(std::get<2>(res) == 70);
            auto ref = std::get<3>(res);
            CHECK(ref);
            CHECK(ref.value<Scalar>() == 111);
        }
    }
    SECTION("unlimited-list") {
        static_assert(std::is_same<decltype(sub.call<List>()),List>::value, "wrong signature");
        cmp_array(sub.call<List>(Simple(10), Simple(20), Simple(30)), {10, 20, 30});
    }
    SECTION("panda::string") {
        static_assert(std::is_same<decltype(sub.call<panda::string>()),panda::string>::value, "wrong signature");
        auto str = sub.call<panda::string>(Simple("suka"));
        CHECK(str == "suka");
    }
    SECTION("numeric") {
        static_assert(std::is_same<decltype(sub.call<int>()),int>::value, "wrong signature");
        static_assert(std::is_same<decltype(sub.call<double>()),double>::value, "wrong signature");
        CHECK(sub.call<int>(Simple(200)) == 200);
        CHECK(sub.call<double>(Simple(1.234)) == 1.234);
        CHECK(sub.call<long long>(Simple(1.234)) == 1);
    }
}

TEST("call result as argument") {
    auto sub = Sub::create("[@_]");
    Array ret = sub.call( sub.call(Simple(999)), sub.call(Simple(888), Simple(777)) );
    cmp_array(ret[0], {999});
    cmp_array(ret[1], {888, 777});
}

TEST("super/super_strict") {
    static auto _a = eval_pv(R"EOF(
        package MyTest::Sub::Super::Parent;
        sub func {}
        package MyTest::Sub::Super::Child;
        our @ISA = 'MyTest::Sub::Super::Parent';
        sub func {}
    )EOF", 1); (void)_a;

    auto psub = Sub("MyTest::Sub::Super::Child::func");
    auto sub = psub.SUPER();
    REQUIRE(sub == Sub("MyTest::Sub::Super::Parent::func"));
    REQUIRE(sub == psub.SUPER_strict());
    REQUIRE(!sub.SUPER());
    REQUIRE_THROWS(sub.SUPER_strict());
}

TEST("create from code") {
    auto sub = Sub::create("return shift() + 10");
    Simple res = sub.call(Simple(3));
    CHECK(res == 13);
}

TEST("want") {
    Sub::Want ret;
    panda::function<void()> f = [&ret]{ ret = Sub::want(); };
    auto sub = xs::out(f);
    SECTION("void") {
        Sub::create("$_[0]->(); return").call(sub);
        CHECK(ret == Sub::Want::Void);
    }
    SECTION("scalar") {
        Sub::create("my $a = $_[0]->()").call(sub);
        CHECK(ret == Sub::Want::Scalar);
    }
    SECTION("array") {
        Sub::create("my @a = $_[0]->()").call(sub);
        CHECK(ret == Sub::Want::Array);
    }
}

TEST("want_count") {
    int ret;
    panda::function<void()> f = [&ret]{ ret = Sub::want_count(); };
    auto sub = xs::out(f);
    SECTION("void") {
        Sub::create("$_[0]->(); return").call(sub);
        CHECK(ret == 0);
    }
    SECTION("scalar") {
        Sub::create("my $a = $_[0]->()").call(sub);
        CHECK(ret == 1);
    }
    SECTION("2-list") {
        Sub::create("my ($a,$b) = $_[0]->()").call(sub);
        CHECK(ret == 2);
        Sub::create("($a,$b) = $_[0]->()").call(sub);
        CHECK(ret == 2);
    }
    SECTION("2-list with junk") {
        Sub::create("my ($a,$b) = ($_[0]->(), 1)").call(sub);
        CHECK(ret == 2);
        Sub::create("my (undef,$b) = ($_[0]->(), 1)").call(sub);
        CHECK(ret == 2);
    }
    SECTION("3-list") {
        Sub::create("my ($a,$b,$c) = $_[0]->()").call(sub);
        CHECK(ret == 3);
    }
    SECTION("array slice list") {
        Sub::create("my @a; @a[0,1,2] = $_[0]->()").call(sub);
        CHECK(ret == 3);
        Sub::create("my @a; my $i1 = 1; my $i2 = 2; @a[0,$i1,$i2] = $_[0]->()").call(sub);
        CHECK(ret == 3);
    }
    SECTION("arrayref slice list") {
        Sub::create("my $a = []; @$a[0,1,2] = $_[0]->()").call(sub);
        CHECK(ret == 3);
    }
    SECTION("array slice dia") {
        Sub::create("my @a; @a[2..4] = $_[0]->()").call(sub);
        CHECK(ret == 3);
    }
    SECTION("array elem") {
        Sub::create("my @a; ($a[0], $a[1]) = $_[0]->()").call(sub);
        CHECK(ret == 2);
        Sub::create("my @a; my ($b,$c) = (0,1); ($a[$b], $a[$c]) = $_[0]->()").call(sub);
        CHECK(ret == 2);
    }
    SECTION("hash elem") {
        Sub::create("my %a; ($a{0}, $a{1}) = $_[0]->()").call(sub);
        CHECK(ret == 2);
        Sub::create("my %a; my ($b,$c) = (0,1); ($a{$b}, $a{$c}) = $_[0]->()").call(sub);
        CHECK(ret == 2);
    }
    SECTION("hash slice list") {
        Sub::create("my %a; @a{qw/a b c/} = $_[0]->()").call(sub);
        CHECK(ret == 3);
    }
    SECTION("hashref slice list") {
        Sub::create("my $a = {}; @$a{qw/a b c/} = $_[0]->()").call(sub);
        CHECK(ret == 3);
    }
    SECTION("hash slice dia") {
        Sub::create("my %a; @a{2..4} = $_[0]->()").call(sub);
        CHECK(ret == 3);
    }
    SECTION("complex") {
        Sub::create("my ($a, $b, @arr, %hash); ($a, @arr[0,1], @arr[2..4], @hash{qw/a b/}, @hash{0..2}, $b, $arr[5], $hash{10}) = $_[0]->()").call(sub);
        CHECK(ret == 14);
    }
    SECTION("infinite") {
        Sub::create("my @a = $_[0]->()").call(sub); CHECK(ret == -1);
        Sub::create("my ($a, @a) = $_[0]->()").call(sub); CHECK(ret == -1);
        Sub::create("my ($a, @a, $b) = $_[0]->()").call(sub); CHECK(ret == -1);
        Sub::create("my @a; my $i = 1; my $j = 3; @a[$i..$j] = $_[0]->()").call(sub); CHECK(ret == -1);
        Sub::create("my @a; my @b; @a[@b] = $_[0]->()").call(sub); CHECK(ret == -1);
        Sub::create("($a, substr($a,0,1)) = $_[0]->()").call(sub); CHECK(ret == -1);
        Sub::create("my ($a) = [ 1, $_[0]->() ]").call(sub); CHECK(ret == -1);
        Sub::create("my ($a) = { key => 'val', $_[0]->() }").call(sub); CHECK(ret == -1);
        Sub::create("my $a = sub {}; my ($b) = $a->($_[0]->())").call(sub); CHECK(ret == -1);
    }
    //SECTION("benchmark") {
    //    f = []{ for (int i = 0; i < 1000; ++i) Sub::want_count(); };
    //    auto sub = xs::out(f);
    //    Sub::create("use Benchmark; my $sub = shift; Benchmark::timethis(-1, sub { my ($a,$b) = $sub->()})").call(sub);
    //}
}