#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <functional>
#include "core/command/Functor.h"

namespace orxonox
{
    namespace
    {
        // Fixture
        class FunctorTest : public ::testing::Test
        {
            public:
                virtual void SetUp() override
                {
                }

                virtual void TearDown() override
                {
                }
        };

        class DestroyableClass : public Destroyable
        {
            public:
                virtual void        method0() { return; }
                virtual int         method1(int arg1) { return arg1; }
                virtual float       method2(int arg1, float arg2) { return arg2; }
                virtual bool        method3(int arg1, float arg2, bool arg3) { return arg3; }
                virtual std::string method4(int arg1, float arg2, bool arg3, const std::string& arg4) { return arg4; }
                virtual Vector3     method5(int arg1, float arg2, bool arg3, const std::string& arg4, const Vector3& arg5) { return arg5; }

                static void        staticmethod0()
                    { return staticinstance->method0(); }
                static int         staticmethod1(int arg1)
                    { return staticinstance->method1(arg1); }
                static float       staticmethod2(int arg1, float arg2)
                    { return staticinstance->method2(arg1, arg2); }
                static bool        staticmethod3(int arg1, float arg2, bool arg3)
                    { return staticinstance->method3(arg1, arg2, arg3); }
                static std::string staticmethod4(int arg1, float arg2, bool arg3, const std::string& arg4)
                    { return staticinstance->method4(arg1, arg2, arg3, arg4); }
                static Vector3     staticmethod5(int arg1, float arg2, bool arg3, const std::string& arg4, const Vector3& arg5)
                    { return staticinstance->method5(arg1, arg2, arg3, arg4, arg5); }

                static DestroyableClass* staticinstance;
        };

        DestroyableClass* DestroyableClass::staticinstance = nullptr;

        class MockDestroyableClass : public DestroyableClass
        {
            public:
                MOCK_METHOD0(method0, void());
                MOCK_METHOD1(method1, int(int));
                MOCK_METHOD2(method2, float(int, float));
                MOCK_METHOD3(method3, bool(int, float, bool));
                MOCK_METHOD4(method4, std::string(int, float, bool, const std::string&));
                MOCK_METHOD5(method5, Vector3(int, float, bool, const std::string&, const Vector3&));
        };

        class NonDestroyableClass
        {
            public:
                void method() {}
        };

        void f1(int, double) {}
        void f2(int, double, double) {}
        int f3(int, double, double) { return 0; }
        int f4a(int arg) { return arg*2; }
        int f4b(int arg) { return arg*3; }
        int multiply(int a, int b) { return a*b; }

        struct CallableStruct
        {
            public:
                int operator()()
                {
                    return 1;
                }
        };
    }


    ///////////////////////////////////////////////////////////////
    ////////////////////// Functor (general) //////////////////////
    ///////////////////////////////////////////////////////////////

    TEST_F(FunctorTest, HasCorrectType)
    {
        FunctorMemberPtr<DestroyableClass> functor1 = createFunctor(&DestroyableClass::method0);
        FunctorStaticPtr functor2 = createFunctor(&DestroyableClass::staticmethod0);
        EXPECT_EQ(Functor::Type::Member, functor1->getType());
        EXPECT_EQ(Functor::Type::Static, functor2->getType());
    }

    TEST_F(FunctorTest, ReturnsCorrectParamCount)
    {
        EXPECT_EQ(0u, createFunctor(&DestroyableClass::method0)->getParamCount());
        EXPECT_EQ(1u, createFunctor(&DestroyableClass::method1)->getParamCount());
        EXPECT_EQ(2u, createFunctor(&DestroyableClass::method2)->getParamCount());
        EXPECT_EQ(3u, createFunctor(&DestroyableClass::method3)->getParamCount());
        EXPECT_EQ(4u, createFunctor(&DestroyableClass::method4)->getParamCount());
        EXPECT_EQ(5u, createFunctor(&DestroyableClass::method5)->getParamCount());
    }

    TEST_F(FunctorTest, HasReturnValue)
    {
        EXPECT_FALSE(createFunctor(&DestroyableClass::method0)->hasReturnvalue());
        EXPECT_TRUE(createFunctor(&DestroyableClass::method1)->hasReturnvalue());
        EXPECT_TRUE(createFunctor(&DestroyableClass::method2)->hasReturnvalue());
        EXPECT_TRUE(createFunctor(&DestroyableClass::method3)->hasReturnvalue());
        EXPECT_TRUE(createFunctor(&DestroyableClass::method4)->hasReturnvalue());
        EXPECT_TRUE(createFunctor(&DestroyableClass::method5)->hasReturnvalue());
    }

    TEST_F(FunctorTest, GetTypenameParam)
    {
        FunctorPtr functor = createFunctor(&DestroyableClass::method5);
        EXPECT_EQ("int",     functor->getTypenameParam(0));
        EXPECT_EQ("float",   functor->getTypenameParam(1));
        EXPECT_EQ("bool",    functor->getTypenameParam(2));
        EXPECT_EQ("string",  functor->getTypenameParam(3));
        EXPECT_EQ("Vector3", functor->getTypenameParam(4));
        EXPECT_EQ("",        functor->getTypenameParam(5)); // max 5 arguments supported (index 0-4) -> this returns empty string
    }

    TEST_F(FunctorTest, TypenameParamIsAlwaysVoidForFunctionWithoutParams)
    {
        FunctorPtr functor = createFunctor(&DestroyableClass::method0);
        EXPECT_EQ("void", functor->getTypenameParam(0));
        EXPECT_EQ("void", functor->getTypenameParam(1));
        EXPECT_EQ("void", functor->getTypenameParam(2));
        EXPECT_EQ("void", functor->getTypenameParam(3));
        EXPECT_EQ("void", functor->getTypenameParam(4));
        EXPECT_EQ("",     functor->getTypenameParam(5)); // max 5 arguments supported (index 0-4) -> this returns empty string
    }

    TEST_F(FunctorTest, GetTypenameReturnvalue)
    {
        EXPECT_EQ("void",    createFunctor(&DestroyableClass::method0)->getTypenameReturnvalue());
        EXPECT_EQ("int",     createFunctor(&DestroyableClass::method1)->getTypenameReturnvalue());
        EXPECT_EQ("float",   createFunctor(&DestroyableClass::method2)->getTypenameReturnvalue());
        EXPECT_EQ("bool",    createFunctor(&DestroyableClass::method3)->getTypenameReturnvalue());
        EXPECT_EQ("string",  createFunctor(&DestroyableClass::method4)->getTypenameReturnvalue());
        EXPECT_EQ("Vector3", createFunctor(&DestroyableClass::method5)->getTypenameReturnvalue());
    }

    TEST_F(FunctorTest, GetFullIdentifier)
    {
        // static
        EXPECT_EQ(typeid(       void(*)()),                                                     createFunctor(&DestroyableClass::staticmethod0)->getFullIdentifier());
        EXPECT_EQ(typeid(        int(*)(int)),                                                  createFunctor(&DestroyableClass::staticmethod1)->getFullIdentifier());
        EXPECT_EQ(typeid(      float(*)(int, float)),                                           createFunctor(&DestroyableClass::staticmethod2)->getFullIdentifier());
        EXPECT_EQ(typeid(       bool(*)(int, float, bool)),                                     createFunctor(&DestroyableClass::staticmethod3)->getFullIdentifier());
        EXPECT_EQ(typeid(std::string(*)(int, float, bool, const std::string&)),                 createFunctor(&DestroyableClass::staticmethod4)->getFullIdentifier());
        EXPECT_EQ(typeid(    Vector3(*)(int, float, bool, const std::string&, const Vector3&)), createFunctor(&DestroyableClass::staticmethod5)->getFullIdentifier());

        // member
        EXPECT_EQ(typeid(       void(DestroyableClass::*)()),                                                     createFunctor(&DestroyableClass::method0)->getFullIdentifier());
        EXPECT_EQ(typeid(        int(DestroyableClass::*)(int)),                                                  createFunctor(&DestroyableClass::method1)->getFullIdentifier());
        EXPECT_EQ(typeid(      float(DestroyableClass::*)(int, float)),                                           createFunctor(&DestroyableClass::method2)->getFullIdentifier());
        EXPECT_EQ(typeid(       bool(DestroyableClass::*)(int, float, bool)),                                     createFunctor(&DestroyableClass::method3)->getFullIdentifier());
        EXPECT_EQ(typeid(std::string(DestroyableClass::*)(int, float, bool, const std::string&)),                 createFunctor(&DestroyableClass::method4)->getFullIdentifier());
        EXPECT_EQ(typeid(    Vector3(DestroyableClass::*)(int, float, bool, const std::string&, const Vector3&)), createFunctor(&DestroyableClass::method5)->getFullIdentifier());
    }

    TEST_F(FunctorTest, GetHeaderIdentifier)
    {
        // static
        EXPECT_EQ(typeid(       void(*)()),                                                     createFunctor(&DestroyableClass::staticmethod0)->getHeaderIdentifier());
        EXPECT_EQ(typeid(        int(*)(int)),                                                  createFunctor(&DestroyableClass::staticmethod1)->getHeaderIdentifier());
        EXPECT_EQ(typeid(      float(*)(int, float)),                                           createFunctor(&DestroyableClass::staticmethod2)->getHeaderIdentifier());
        EXPECT_EQ(typeid(       bool(*)(int, float, bool)),                                     createFunctor(&DestroyableClass::staticmethod3)->getHeaderIdentifier());
        EXPECT_EQ(typeid(std::string(*)(int, float, bool, const std::string&)),                 createFunctor(&DestroyableClass::staticmethod4)->getHeaderIdentifier());
        EXPECT_EQ(typeid(    Vector3(*)(int, float, bool, const std::string&, const Vector3&)), createFunctor(&DestroyableClass::staticmethod5)->getHeaderIdentifier());

        // member
        EXPECT_EQ(typeid(       void(*)()),                                                     createFunctor(&DestroyableClass::method0)->getHeaderIdentifier());
        EXPECT_EQ(typeid(        int(*)(int)),                                                  createFunctor(&DestroyableClass::method1)->getHeaderIdentifier());
        EXPECT_EQ(typeid(      float(*)(int, float)),                                           createFunctor(&DestroyableClass::method2)->getHeaderIdentifier());
        EXPECT_EQ(typeid(       bool(*)(int, float, bool)),                                     createFunctor(&DestroyableClass::method3)->getHeaderIdentifier());
        EXPECT_EQ(typeid(std::string(*)(int, float, bool, const std::string&)),                 createFunctor(&DestroyableClass::method4)->getHeaderIdentifier());
        EXPECT_EQ(typeid(    Vector3(*)(int, float, bool, const std::string&, const Vector3&)), createFunctor(&DestroyableClass::method5)->getHeaderIdentifier());
    }

    TEST_F(FunctorTest, GetHeaderIdentifierFor2Params)
    {
        // static
        EXPECT_EQ(typeid(       void(*)()),           createFunctor(&DestroyableClass::staticmethod0)->getHeaderIdentifier(2));
        EXPECT_EQ(typeid(        int(*)(int)),        createFunctor(&DestroyableClass::staticmethod1)->getHeaderIdentifier(2));
        EXPECT_EQ(typeid(      float(*)(int, float)), createFunctor(&DestroyableClass::staticmethod2)->getHeaderIdentifier(2));
        EXPECT_EQ(typeid(       bool(*)(int, float)), createFunctor(&DestroyableClass::staticmethod3)->getHeaderIdentifier(2));
        EXPECT_EQ(typeid(std::string(*)(int, float)), createFunctor(&DestroyableClass::staticmethod4)->getHeaderIdentifier(2));
        EXPECT_EQ(typeid(    Vector3(*)(int, float)), createFunctor(&DestroyableClass::staticmethod5)->getHeaderIdentifier(2));

        // member
        EXPECT_EQ(typeid(       void(*)()),           createFunctor(&DestroyableClass::method0)->getHeaderIdentifier(2));
        EXPECT_EQ(typeid(        int(*)(int)),        createFunctor(&DestroyableClass::method1)->getHeaderIdentifier(2));
        EXPECT_EQ(typeid(      float(*)(int, float)), createFunctor(&DestroyableClass::method2)->getHeaderIdentifier(2));
        EXPECT_EQ(typeid(       bool(*)(int, float)), createFunctor(&DestroyableClass::method3)->getHeaderIdentifier(2));
        EXPECT_EQ(typeid(std::string(*)(int, float)), createFunctor(&DestroyableClass::method4)->getHeaderIdentifier(2));
        EXPECT_EQ(typeid(    Vector3(*)(int, float)), createFunctor(&DestroyableClass::method5)->getHeaderIdentifier(2));
    }

    TEST_F(FunctorTest, GetHeaderIdentifierForNParams)
    {
        // static
        FunctorStaticPtr functorStatic = createFunctor(&DestroyableClass::staticmethod5);
        EXPECT_EQ(typeid(Vector3(*)()),                                                     functorStatic->getHeaderIdentifier(0));
        EXPECT_EQ(typeid(Vector3(*)(int)),                                                  functorStatic->getHeaderIdentifier(1));
        EXPECT_EQ(typeid(Vector3(*)(int, float)),                                           functorStatic->getHeaderIdentifier(2));
        EXPECT_EQ(typeid(Vector3(*)(int, float, bool)),                                     functorStatic->getHeaderIdentifier(3));
        EXPECT_EQ(typeid(Vector3(*)(int, float, bool, const std::string&)),                 functorStatic->getHeaderIdentifier(4));
        EXPECT_EQ(typeid(Vector3(*)(int, float, bool, const std::string&, const Vector3&)), functorStatic->getHeaderIdentifier(5));
        EXPECT_EQ(typeid(Vector3(*)(int, float, bool, const std::string&, const Vector3&)), functorStatic->getHeaderIdentifier(6));

        // member
        FunctorMemberPtr<DestroyableClass> functorMember = createFunctor(&DestroyableClass::method5);
        EXPECT_EQ(typeid(Vector3(*)()),                                                     functorMember->getHeaderIdentifier(0));
        EXPECT_EQ(typeid(Vector3(*)(int)),                                                  functorMember->getHeaderIdentifier(1));
        EXPECT_EQ(typeid(Vector3(*)(int, float)),                                           functorMember->getHeaderIdentifier(2));
        EXPECT_EQ(typeid(Vector3(*)(int, float, bool)),                                     functorMember->getHeaderIdentifier(3));
        EXPECT_EQ(typeid(Vector3(*)(int, float, bool, const std::string&)),                 functorMember->getHeaderIdentifier(4));
        EXPECT_EQ(typeid(Vector3(*)(int, float, bool, const std::string&, const Vector3&)), functorMember->getHeaderIdentifier(5));
        EXPECT_EQ(typeid(Vector3(*)(int, float, bool, const std::string&, const Vector3&)), functorMember->getHeaderIdentifier(6));
    }

    TEST_F(FunctorTest, CanClone)
    {
        FunctorPtr functor = createFunctor(&f4a);
        FunctorPtr clone = functor->clone();
        EXPECT_NE(nullptr, clone.get());
    }

    TEST_F(FunctorTest, ClonedFunctorCanBeCalled)
    {
        FunctorPtr functor = createFunctor(&f4a);
        FunctorPtr clone = functor->clone();

        EXPECT_EQ(8, (*functor)(4).get<int>());
        EXPECT_EQ(8, (*clone)(4).get<int>());
    }

    TEST_F(FunctorTest, ClonedFunctorKeepsFunction)
    {
        FunctorPtr functor = createFunctor(&f4a);
        FunctorPointer<int(*)(int)>* pointer = static_cast<FunctorPointer<int(*)(int)>*>(functor.get());

        FunctorPtr clone = functor->clone();
        FunctorPointer<int(*)(int)>* clonePointer = static_cast<FunctorPointer<int(*)(int)>*>(clone.get());

        EXPECT_EQ(&f4a, pointer->getFunction());
        EXPECT_EQ(&f4a, clonePointer->getFunction());
    }

    TEST_F(FunctorTest, ClonedFunctorKeepsObject)
    {
        DestroyableClass object;
        FunctorPtr functor = createFunctor(&DestroyableClass::method0, &object);
        FunctorPtr clone = functor->clone();

        EXPECT_EQ(&object, functor->getRawObjectPointer());
        EXPECT_EQ(&object, clone->getRawObjectPointer());
    }

    // TODO: test fails... fix or delete clone()
//    TEST_F(FunctorTest, ClonedFunctorKeepsSafeMode)
//    {
//        DestroyableClass* object = new DestroyableClass();
//        FunctorPtr functor = createFunctor(&DestroyableClass::method0, object);
//        functor->setSafeMode(true);
//        FunctorPtr clone = functor->clone();
//
//        EXPECT_EQ(object, functor->getRawObjectPointer());
//        EXPECT_EQ(object, clone->getRawObjectPointer());
//        object->destroy();
//        EXPECT_EQ(nullptr, functor->getRawObjectPointer());
//        EXPECT_EQ(nullptr, clone->getRawObjectPointer());
//
//    }

    TEST_F(FunctorTest, CanEvaluateArgument)
    {
        FunctorPtr functor = createFunctor(&f4a);
        MultiType value("5");
        EXPECT_FALSE(value.isType<int>());
        EXPECT_TRUE(value.isType<std::string>());
        functor->evaluateArgument(0, value);
        EXPECT_TRUE(value.isType<int>());
        EXPECT_FALSE(value.isType<std::string>());
    }

    TEST_F(FunctorTest, CanGetAndSetFunctionPointer)
    {
        FunctorPtr functor = createFunctor(&f4a);
        FunctorPointer<int(*)(int)>* pointer = static_cast<FunctorPointer<int(*)(int)>*>(functor.get());

        EXPECT_EQ(6, (*functor)(3).get<int>()); // 3*2 = 6
        EXPECT_EQ(&f4a, pointer->getFunction());

        pointer->setFunction(&f4b);

        EXPECT_EQ(9, (*functor)(3).get<int>()); // 3*3 = 9
        EXPECT_EQ(&f4b, pointer->getFunction());
    }

    TEST_F(FunctorTest, ReturnsValue)
    {
        DestroyableClass object;
        {
            FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method1);
            int result = (*functor)(&object, 13);
            EXPECT_EQ(13, result);
        }
        {
            FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method2);
            float result = (*functor)(&object, 0, 111.11f);
            EXPECT_FLOAT_EQ(111.11f, result);
        }
        {
            FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method3);
            bool result = (*functor)(&object, 0, 0, true);
            EXPECT_EQ(true, result);
        }
        {
            FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method4);
            std::string result = (*functor)(&object, 0, 0, false, "test");
            EXPECT_EQ("test", result);
        }
        {
            FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method5);
            Vector3 result = (*functor)(&object, 0, 0, false, "", Vector3(3, 2, 1));
            EXPECT_EQ(Vector3(3, 2, 1), result);
        }
    }


    ///////////////////////////////////////////////////////////
    ////////////////////// FunctorMember //////////////////////
    ///////////////////////////////////////////////////////////

    TEST_F(FunctorTest, FunctorMember_CanSetObject)
    {
        DestroyableClass object;

        FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method0);
        EXPECT_EQ(nullptr, functor->getObject());
        functor->setObject(&object);
        EXPECT_EQ(&object, functor->getObject());
        functor->setObject(nullptr);
        EXPECT_EQ(nullptr, functor->getObject());
    }

    TEST_F(FunctorTest, FunctorMember_CanSetObjectInConstructor)
    {
        DestroyableClass object;

        FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method0, &object);
        EXPECT_EQ(&object, functor->getObject());
        functor->setObject(nullptr);
        EXPECT_EQ(nullptr, functor->getObject());
    }

    TEST_F(FunctorTest, FunctorMember_CanSetRawObjectPointer)
    {
        DestroyableClass object;

        FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method0);
        EXPECT_EQ(nullptr, functor->getRawObjectPointer());
        functor->setRawObjectPointer(&object);
        EXPECT_EQ(&object, functor->getRawObjectPointer());
        functor->setRawObjectPointer(nullptr);
        EXPECT_EQ(nullptr, functor->getRawObjectPointer());
    }

    TEST_F(FunctorTest, FunctorMember_RawObjectPointerAndObjectAreEqual)
    {
        DestroyableClass object1;
        DestroyableClass object2;

        FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method0);
        EXPECT_EQ(nullptr, functor->getRawObjectPointer());
        EXPECT_EQ(nullptr, functor->getObject());
        functor->setObject(&object1);
        EXPECT_EQ(&object1, functor->getRawObjectPointer());
        EXPECT_EQ(&object1, functor->getObject());
        functor->setObject(&object2);
        EXPECT_EQ(&object2, functor->getRawObjectPointer());
        EXPECT_EQ(&object2, functor->getObject());
    }

    TEST_F(FunctorTest, FunctorMember_SafeModeWorks_SetObjectThenActivateSafeMode)
    {
        FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method0);
        {
            DestroyableClass object;
            functor->setObject(&object);
            functor->setSafeMode(true);
            EXPECT_EQ(&object, functor->getObject());
        }
        EXPECT_EQ(nullptr, functor->getObject());
    }

    TEST_F(FunctorTest, FunctorMember_SafeModeWorks_ActivateSafeModeThenSetObject)
    {
        FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method0);
        {
            DestroyableClass object;
            functor->setSafeMode(true);
            functor->setObject(&object);
            EXPECT_EQ(&object, functor->getObject());
        }
        EXPECT_EQ(nullptr, functor->getObject());
    }

    TEST_F(FunctorTest, FunctorMember_SafeModeCanBeDisabledAfterObjectWasSet)
    {
        DestroyableClass* ptr;
        FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method0);
        {
            DestroyableClass object;
            functor->setSafeMode(true);
            functor->setObject(&object);
            functor->setSafeMode(false);
            ptr = &object; // remember the pointer
            EXPECT_EQ(ptr, functor->getObject());
        }
        EXPECT_EQ(ptr, functor->getObject());
    }

    TEST_F(FunctorTest, FunctorMember_SafeModeWorks_CanChangeObject)
    {
        FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method0);
        DestroyableClass* object1 = new DestroyableClass();
        DestroyableClass* object2 = new DestroyableClass();
        functor->setSafeMode(true);

        functor->setObject(object1);
        EXPECT_EQ(object1, functor->getObject());
        functor->setObject(object2);
        EXPECT_EQ(object2, functor->getObject());
        object1->destroy();
        EXPECT_EQ(object2, functor->getObject());
        object2->destroy();
        EXPECT_EQ(nullptr, functor->getObject());
    }

    TEST_F(FunctorTest, FunctorMember_CanDestroyFunctorWhenObjectInSafeModeStillExists)
    {
        DestroyableClass object;
        {
            FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method0);
            functor->setSafeMode(true);
            functor->setObject(&object);
            EXPECT_EQ(&object, functor->getObject());
        } // functor is destroyed here
    }

    TEST_F(FunctorTest, FunctorMember_SafeModeDoesntDoAnythingWithObjectsNotInheritedFromDestroyable)
    {
        NonDestroyableClass* ptr;
        FunctorMemberPtr<NonDestroyableClass> functor = createFunctor(&NonDestroyableClass::method);
        {
            NonDestroyableClass object;
            functor->setSafeMode(true);
            functor->setObject(&object);
            ptr = &object; // remember the pointer
            EXPECT_EQ(ptr, functor->getObject());
        }
        EXPECT_EQ(ptr, functor->getObject()); // pointer is still set because safe mode doesn't work on NonDestroyableClass
    }

    TEST_F(FunctorTest, FunctorMember_CanCall_WithObjectInCall)
    {
        MockDestroyableClass mock;
        {
            EXPECT_CALL(mock, method0());
            FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method0);
            (*functor)(&mock);
        }
        {
            EXPECT_CALL(mock, method1(13)).WillOnce(::testing::Return(0));
            FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method1);
            (*functor)(&mock, 13);
        }
        {
            EXPECT_CALL(mock, method2(13, 111.11f)).WillOnce(::testing::Return(0.0f));
            FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method2);
            (*functor)(&mock, 13, 111.11f);
        }
        {
            EXPECT_CALL(mock, method3(13, 111.11f, true)).WillOnce(::testing::Return(false));
            FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method3);
            (*functor)(&mock, 13, 111.11f, true);
        }
        {
            EXPECT_CALL(mock, method4(13, 111.11f, true, "test")).WillOnce(::testing::Return(""));
            FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method4);
            (*functor)(&mock, 13, 111.11f, true, "test");
        }
        {
            EXPECT_CALL(mock, method5(13, 111.11f, true, "test", Vector3(3, 2, 1))).WillOnce(::testing::Return(Vector3()));
            FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method5);
            (*functor)(&mock, 13, 111.11f, true, "test", Vector3(3, 2, 1));
        }
    }

    TEST_F(FunctorTest, FunctorMember_CanCall_WithObjectInConstructor)
    {
        MockDestroyableClass mock;
        {
            EXPECT_CALL(mock, method0());
            FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method0, &mock);
            (*functor)();
        }
        {
            EXPECT_CALL(mock, method1(13)).WillOnce(::testing::Return(0));
            FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method1, &mock);
            (*functor)(13);
        }
        {
            EXPECT_CALL(mock, method2(13, 111.11f)).WillOnce(::testing::Return(0.0f));
            FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method2, &mock);
            (*functor)(13, 111.11f);
        }
        {
            EXPECT_CALL(mock, method3(13, 111.11f, true)).WillOnce(::testing::Return(false));
            FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method3, &mock);
            (*functor)(13, 111.11f, true);
        }
        {
            EXPECT_CALL(mock, method4(13, 111.11f, true, "test")).WillOnce(::testing::Return(""));
            FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method4, &mock);
            (*functor)(13, 111.11f, true, "test");
        }
        {
            EXPECT_CALL(mock, method5(13, 111.11f, true, "test", Vector3(3, 2, 1))).WillOnce(::testing::Return(Vector3()));
            FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method5, &mock);
            (*functor)(13, 111.11f, true, "test", Vector3(3, 2, 1));
        }
    }

    TEST_F(FunctorTest, FunctorMember_ReturnsZeroIfCalledWithoutObject)
    {
        DestroyableClass object;
        {
            FunctorMemberPtr<DestroyableClass> functor = createFunctor(&DestroyableClass::method1);
            int resultWithObject = (*functor)(&object, 13);
            int resultWithoutObject = (*functor)(13);
            EXPECT_EQ(13, resultWithObject);
            EXPECT_EQ(0, resultWithoutObject);
        }
    }


    ///////////////////////////////////////////////////////////
    ////////////////////// FunctorStatic //////////////////////
    ///////////////////////////////////////////////////////////

    TEST_F(FunctorTest, FunctorStatic_CanCall)
    {
        MockDestroyableClass mock;
        DestroyableClass::staticinstance = &mock;

        {
            EXPECT_CALL(mock, method0());
            FunctorStaticPtr functor = createFunctor(&DestroyableClass::staticmethod0);
            (*functor)();
        }
        {
            EXPECT_CALL(mock, method1(13)).WillOnce(::testing::Return(0));
            FunctorStaticPtr functor = createFunctor(&DestroyableClass::staticmethod1);
            (*functor)(13);
        }
        {
            EXPECT_CALL(mock, method2(13, 111.11f)).WillOnce(::testing::Return(0.0f));
            FunctorStaticPtr functor = createFunctor(&DestroyableClass::staticmethod2);
            (*functor)(13, 111.11f);
        }
        {
            EXPECT_CALL(mock, method3(13, 111.11f, true)).WillOnce(::testing::Return(false));
            FunctorStaticPtr functor = createFunctor(&DestroyableClass::staticmethod3);
            (*functor)(13, 111.11f, true);
        }
        {
            EXPECT_CALL(mock, method4(13, 111.11f, true, "test")).WillOnce(::testing::Return(""));
            FunctorStaticPtr functor = createFunctor(&DestroyableClass::staticmethod4);
            (*functor)(13, 111.11f, true, "test");
        }
        {
            EXPECT_CALL(mock, method5(13, 111.11f, true, "test", Vector3(3, 2, 1))).WillOnce(::testing::Return(Vector3()));
            FunctorStaticPtr functor = createFunctor(&DestroyableClass::staticmethod5);
            (*functor)(13, 111.11f, true, "test", Vector3(3, 2, 1));
        }
    }

    TEST_F(FunctorTest, FunctorStatic_CanCallWithObject)
    {
        MockDestroyableClass mock;
        DestroyableClass::staticinstance = &mock;

        {
            EXPECT_CALL(mock, method0());
            FunctorStaticPtr functor = createFunctor(&DestroyableClass::staticmethod0);
            (*functor)(&mock); // object is ignored
        }
    }

    TEST_F(FunctorTest, FunctorStatic_CanCallWithNull)
    {
        MockDestroyableClass mock;
        DestroyableClass::staticinstance = &mock;

        {
            EXPECT_CALL(mock, method0());
            FunctorStaticPtr functor = createFunctor(&DestroyableClass::staticmethod0);
            (*functor)(nullptr); // pointer is ignored
        }
    }

    TEST_F(FunctorTest, FunctorStatic_RawObjectPointerIsAlwaysNull)
    {
        DestroyableClass object;

        FunctorStaticPtr functor = createFunctor(&DestroyableClass::staticmethod0);
        EXPECT_EQ(nullptr, functor->getRawObjectPointer());
        functor->setRawObjectPointer(&object);
        EXPECT_EQ(nullptr, functor->getRawObjectPointer());
        functor->setRawObjectPointer(nullptr);
        EXPECT_EQ(nullptr, functor->getRawObjectPointer());
    }

    ////////////////////////////// Various tests //////////////////////////////

    TEST_F(FunctorTest, CreateFunctorWithVariousFunctionTypes)
    {
        FunctorPtr c1 = createFunctor(&f4a);
        FunctorPtr c2 = createFunctor(&f4b);
        FunctorPtr c3 = createFunctor([] (int arg) { return arg*4; });
        auto lambda = [] (int arg1, int arg2) { return arg1*arg2; };
        FunctorPtr c4 = createFunctor(lambda);

        std::function<int()> bind0 = std::bind(multiply, 4, 6);
        std::function<int(int)> bind1 = std::bind(multiply, std::placeholders::_1, 7);
        std::function<int(int, int)> bind2 = std::bind(multiply, std::placeholders::_1, std::placeholders::_2);
        std::function<int(int)> function = f4a;

        FunctorPtr c5 = createFunctor(bind0);
        FunctorPtr c6 = createFunctor(bind1);
        FunctorPtr c7 = createFunctor(bind2);
        FunctorPtr c8 = createFunctor(function);


        EXPECT_EQ(8,  (*c1)(4).get<int>());
        EXPECT_EQ(12, (*c2)(4).get<int>());
        EXPECT_EQ(16, (*c3)(4).get<int>());
        EXPECT_EQ(20, (*c4)(4, 5).get<int>());
        EXPECT_EQ(24, (*c5)(4).get<int>());
        EXPECT_EQ(28, (*c6)(4).get<int>());
        EXPECT_EQ(32, (*c7)(4, 8).get<int>());
        EXPECT_EQ(8,  (*c8)(4).get<int>());

        EXPECT_EQ(1u, c1->getParamCount());
        EXPECT_EQ(1u, c2->getParamCount());
        EXPECT_EQ(1u, c3->getParamCount());
        EXPECT_EQ(2u, c4->getParamCount());
        EXPECT_EQ(0u, c5->getParamCount());
        EXPECT_EQ(1u, c6->getParamCount());
        EXPECT_EQ(2u, c7->getParamCount());
        EXPECT_EQ(1u, c8->getParamCount());

        EXPECT_EQ(typeid(int(*)(int)), c1->getFullIdentifier());
        EXPECT_EQ(typeid(int(*)(int)), c2->getFullIdentifier());
        EXPECT_EQ(typeid(int(*)(int)), c3->getFullIdentifier());
        EXPECT_EQ(typeid(int(*)(int, int)), c4->getFullIdentifier());
        EXPECT_EQ(typeid(int(*)()), c5->getFullIdentifier());
        EXPECT_EQ(typeid(int(*)(int)), c6->getFullIdentifier());
        EXPECT_EQ(typeid(int(*)(int, int)), c7->getFullIdentifier());
        EXPECT_EQ(typeid(int(*)(int)), c8->getFullIdentifier());

        EXPECT_EQ(typeid(int(*)(int)), c1->getHeaderIdentifier());
        EXPECT_EQ(typeid(int(*)(int)), c2->getHeaderIdentifier());
        EXPECT_EQ(typeid(int(*)(int)), c3->getHeaderIdentifier());
        EXPECT_EQ(typeid(int(*)(int, int)), c4->getHeaderIdentifier());
        EXPECT_EQ(typeid(int(*)()), c5->getHeaderIdentifier());
        EXPECT_EQ(typeid(int(*)(int)), c6->getHeaderIdentifier());
        EXPECT_EQ(typeid(int(*)(int, int)), c7->getHeaderIdentifier());
        EXPECT_EQ(typeid(int(*)(int)), c8->getHeaderIdentifier());

        EXPECT_EQ(typeid(int(*)(int)), c1->getHeaderIdentifier(1));
        EXPECT_EQ(typeid(int(*)(int)), c2->getHeaderIdentifier(1));
        EXPECT_EQ(typeid(int(*)(int)), c3->getHeaderIdentifier(1));
        EXPECT_EQ(typeid(int(*)(int)), c4->getHeaderIdentifier(1));
        EXPECT_EQ(typeid(int(*)()), c5->getHeaderIdentifier(1));
        EXPECT_EQ(typeid(int(*)(int)), c6->getHeaderIdentifier(1));
        EXPECT_EQ(typeid(int(*)(int)), c7->getHeaderIdentifier(1));
        EXPECT_EQ(typeid(int(*)(int)), c8->getHeaderIdentifier(1));

        EXPECT_EQ(typeid(int(*)()), c1->getHeaderIdentifier(0));
        EXPECT_EQ(typeid(int(*)()), c2->getHeaderIdentifier(0));
        EXPECT_EQ(typeid(int(*)()), c3->getHeaderIdentifier(0));
        EXPECT_EQ(typeid(int(*)()), c4->getHeaderIdentifier(0));
        EXPECT_EQ(typeid(int(*)()), c5->getHeaderIdentifier(0));
        EXPECT_EQ(typeid(int(*)()), c6->getHeaderIdentifier(0));
        EXPECT_EQ(typeid(int(*)()), c7->getHeaderIdentifier(0));
        EXPECT_EQ(typeid(int(*)()), c8->getHeaderIdentifier(0));
    }

    TEST_F(FunctorTest, CanCallFunctorWithLambdaWichIsAlreadyOutOfScope)
    {
        int var1 = 1;
        FunctorPtr fp1a = createFunctor([&var1] () { return var1++; });
        FunctorPtr fp1b = createFunctor([&var1] () { return var1; });
        FunctorPtr fp2;
        FunctorPtr fp3;

        {
            int var2 = 2;
            fp2 = createFunctor([var2] () { return var2; });
        }
        {
            int var3 = 3;
            fp3 = createFunctor([var3] () { return var3; });
        }

        EXPECT_EQ(1, var1);
        EXPECT_EQ(1, (*fp1a)().get<int>());
        EXPECT_EQ(2, var1);
        EXPECT_EQ(2, (*fp1b)().get<int>());

        EXPECT_EQ(2, (*fp2)().get<int>());
        EXPECT_EQ(3, (*fp3)().get<int>());
    }

    TEST_F(FunctorTest, canCompareHeaderIdentifiers)
    {
        FunctorPtr fp1 = createFunctor(&f1);
        FunctorPtr fp2 = createFunctor(&f2);
        FunctorPtr fp3 = createFunctor(&f3);
        ASSERT_STREQ(fp1->getHeaderIdentifier(1).name(), fp2->getHeaderIdentifier(1).name());
        ASSERT_STREQ(fp1->getHeaderIdentifier(2).name(), fp2->getHeaderIdentifier(2).name());
        ASSERT_STRNE(fp1->getHeaderIdentifier(1).name(), fp2->getHeaderIdentifier(2).name());
        ASSERT_STRNE(fp1->getHeaderIdentifier(10).name(), fp2->getHeaderIdentifier(10).name());
        ASSERT_STRNE(fp2->getHeaderIdentifier(2).name(), fp3->getHeaderIdentifier(2).name());
        ASSERT_STRNE(fp1->getHeaderIdentifier().name(), fp2->getHeaderIdentifier().name());
        ASSERT_STRNE(fp2->getHeaderIdentifier().name(), fp3->getHeaderIdentifier().name());
    }

    TEST_F(FunctorTest, canCompareTypenames)
    {
        FunctorPtr fp1 = createFunctor(&f1);
        FunctorPtr fp2 = createFunctor(&f2);
        FunctorPtr fp3 = createFunctor(&f3);
        ASSERT_EQ(fp1->getTypenameReturnvalue(), fp2->getTypenameReturnvalue());
        ASSERT_EQ(fp1->getTypenameParam(0), fp3->getTypenameParam(0));
        ASSERT_EQ("void", fp1->getTypenameReturnvalue());
        ASSERT_EQ("int", fp3->getTypenameReturnvalue());
        ASSERT_EQ("int", fp2->getTypenameParam(0));
        ASSERT_EQ("double", fp3->getTypenameParam(2));
        ASSERT_EQ("", fp3->getTypenameParam(6));
    }

    TEST_F(FunctorTest, testGetParamCountAndHasReturnValue)
    {
        FunctorPtr fp1 = createFunctor(&f1);
        FunctorPtr fp2 = createFunctor(&f2);
        FunctorPtr fp3 = createFunctor(&f3);
        ASSERT_EQ(3u, fp2->getParamCount());
        ASSERT_NE(fp1->getParamCount(), fp3->getParamCount());
        ASSERT_FALSE(fp2->hasReturnvalue());
        ASSERT_TRUE(fp3->hasReturnvalue());
    }

    TEST_F(FunctorTest, canEvaluateArgument)
    {
        FunctorPtr fp1 = createFunctor(&f1);
        MultiType mttype = "2";
        fp1->evaluateArgument(0, mttype);
        ASSERT_TRUE(mttype.isType<int>());
        ASSERT_EQ(2, mttype.get<int>());
        fp1->evaluateArgument(1, mttype);
        ASSERT_TRUE(mttype.isType<double>());
        ASSERT_EQ(2.0, mttype.get<double>());
        mttype.reset();
        fp1->evaluateArgument(5, mttype);
        ASSERT_TRUE(mttype.null());
    }

    TEST_F(FunctorTest, canUseCallables)
    {
        int a = 0;
        FunctorPtr fp1 = createFunctor(CallableStruct{});
        FunctorPtr fp2 = createFunctor([](bool val) { return val; });
        FunctorPtr fp3 = createFunctor([&a]() {return a++; });
        FunctorPtr fp4 = createFunctor([a]() {return a; });
        ASSERT_EQ(1, (*fp1)().get<int>());
        ASSERT_EQ(true, (*fp2)(true).get<bool>());
        ASSERT_EQ(0, (*fp3)().get<int>());
        ASSERT_EQ(1, a);
        ASSERT_EQ(0, (*fp4)().get<int>());
        ASSERT_STREQ(fp1->getHeaderIdentifier().name(), fp3->getHeaderIdentifier().name());
    }

    TEST_F(FunctorTest, SafeModeWorks)
    {
        DestroyableClass* testclass = new DestroyableClass();
        DestroyableClass* testclass2 = new DestroyableClass();
        FunctorPtr fp1 = createFunctor(&DestroyableClass::method0, testclass);
        fp1->setSafeMode(true);
        FunctorPtr fp2 = createFunctor(&DestroyableClass::method0, testclass);
        fp2->setSafeMode(true);
        FunctorPtr fp3 = createFunctor(&DestroyableClass::method0, testclass);
        fp2->setRawObjectPointer(testclass2);

        ASSERT_NE(nullptr, fp1->getRawObjectPointer());
        ASSERT_NE(nullptr, fp2->getRawObjectPointer());
        ASSERT_NE(nullptr, fp3->getRawObjectPointer());
        testclass->destroy();
        ASSERT_EQ(nullptr, fp1->getRawObjectPointer());
        ASSERT_NE(nullptr, fp2->getRawObjectPointer());
        ASSERT_NE(nullptr, fp3->getRawObjectPointer());

        fp3->setRawObjectPointer(testclass2);
        fp3->setSafeMode(true);
        fp2->setSafeMode(false);
        testclass2->destroy();
        ASSERT_NE(nullptr, fp2->getRawObjectPointer());
        ASSERT_EQ(nullptr, fp3->getRawObjectPointer());
    }
}
