#include "catch2_common.h"
#include <catch2/matchers/catch_matchers.hpp>

namespace testNS
{

class TestClass
{
  protected:
    std::string function_name;

  public:
    const double *pointerTest([[maybe_unused]] int dummy)
    {
        function_name = TELEMETRY_CURRENT_FUNCTION;
        const Tango::DevDouble *outptr = nullptr;
        return outptr;
    }

    virtual long &referenceTest(long &dummy)
    {
        function_name = TELEMETRY_CURRENT_FUNCTION;
        return dummy;
    }

    template <typename T, typename U>
    U templateTest(T)
    {
        function_name = TELEMETRY_CURRENT_FUNCTION;
        U dummy{};
        return dummy;
    }

    template <class T, class U, typename V>
    void templateClassTest(long, V)
    {
        function_name = TELEMETRY_CURRENT_FUNCTION;
        [[maybe_unused]] T dummy1{};
        [[maybe_unused]] U dummy2{};
    }

    void functionInputTest(void func())
    {
        function_name = TELEMETRY_CURRENT_FUNCTION;
        func();
    }

    template <typename T, typename U, typename V>
    V (*functionReturnTest(T))(U)
    {
        function_name = TELEMETRY_CURRENT_FUNCTION;
        return [](U) -> V { return 0; };
    }

    std::string getFuncName()
    {
        return function_name;
    }
};

class Fake1
{
  public:
    void noop() { }
};

class Fake2
{
  public:
    void noop() { }
};

template <class T, class U>
class TemplatedTestClass : public TestClass
{
  public:
    T myClassT;
    U myClassU;

    template <typename Y, class Z>
    void doSomething(long)
    {
        [[maybe_unused]] Y dummy1;
        [[maybe_unused]] Z dummy2;
        function_name = TELEMETRY_CURRENT_FUNCTION;
    }
};
} // namespace testNS

SCENARIO("Method signature formatting")
{
    using namespace Catch::Matchers;

    GIVEN("a class with multiple method signatures")
    {
        testNS::TestClass TC;
        long dummy = 0;

        WHEN("a method returns a pointer")
        {
            auto *dummyptr = TC.pointerTest(dummy);
            if(dummyptr)
            {
                dummyptr = nullptr;
            }; // Suppress compiler warnings
            REQUIRE_THAT(TC.getFuncName(), Equals("testNS::TestClass::pointerTest"));
        }

        WHEN("a method returns a reference")
        {
            dummy = TC.referenceTest(dummy);
            REQUIRE_THAT(TC.getFuncName(), Equals("testNS::TestClass::referenceTest"));
        }

        WHEN("a method is templated with types")
        {
            TC.templateTest<long, int *>(dummy);
            REQUIRE_THAT(TC.getFuncName(), Equals("testNS::TestClass::templateTest"));
        }

        WHEN("a method is templated with a class")
        {
            TC.templateClassTest<testNS::Fake1, double>(dummy, 3.14);
            REQUIRE_THAT(TC.getFuncName(), Equals("testNS::TestClass::templateClassTest"));
        }

        WHEN("a method accepts a function as an argument")
        {
            TC.functionInputTest([]() { });
            REQUIRE_THAT(TC.getFuncName(), Equals("testNS::TestClass::functionInputTest"));
        }

        WHEN("a method templated method returns a function pointer")
        {
            TC.functionReturnTest<double, long, int>(0);
            REQUIRE_THAT(TC.getFuncName(), Equals("testNS::TestClass::functionReturnTest"));
        }
    }

    GIVEN("a templated class with templated methods")
    {
        testNS::TemplatedTestClass<testNS::Fake1, testNS::Fake2> TTC;
        long dummy = 0;

        WHEN("a templated method")
        {
            TTC.doSomething<int, long>(dummy);
            REQUIRE_THAT(TTC.getFuncName(), StartsWith("testNS::TemplatedTestClass<") && EndsWith(">::doSomething"));
        }
    }
}
