#include <gtest/gtest.h>
#include <gmock/gmock.h>

#include "core/object/Iterator.h"
#include "core/class/OrxonoxClass.h"
#include "core/class/OrxonoxInterface.h"
#include "core/CoreIncludes.h"
#include "core/module/ModuleInstance.h"

namespace orxonox
{
    namespace
    {
        class TestInterface : virtual public OrxonoxInterface
        {
            public:
                TestInterface() { RegisterObject(TestInterface); }
        };

        class TestClass : public OrxonoxClass, public TestInterface
        {
            public:
                TestClass() { RegisterObject(TestClass); }
                MOCK_METHOD0(test, void());
        };

        RegisterClassNoArgs(TestInterface);
        RegisterClassNoArgs(TestClass);

        // Fixture
        class IteratorTest : public ::testing::Test
        {
            public:
                virtual void SetUp() override
                {
                    new IdentifierManager();
                    ModuleInstance::getCurrentModuleInstance()->loadAllStaticallyInitializedInstances(StaticInitialization::IDENTIFIER);
                    Context::setRootContext(new Context(nullptr));
                }

                virtual void TearDown() override
                {
                    Context::destroyRootContext();
                    ModuleInstance::getCurrentModuleInstance()->unloadAllStaticallyInitializedInstances(StaticInitialization::IDENTIFIER);
                    delete &IdentifierManager::getInstance();
                }
        };
    }

    TEST_F(IteratorTest, CanCreateIterator)
    {
        Iterator<TestInterface> it;
    }

    TEST_F(IteratorTest, CanAssignIterator)
    {
        ObjectList<TestInterface> list;
        Iterator<TestInterface> it = list.begin();
    }

    TEST_F(IteratorTest, CanIterateOverEmptyList)
    {
        size_t i = 0;
        ObjectList<TestInterface> list;
        for (Iterator<TestInterface> it = list.begin(); it != list.end(); ++it)
            ++i;
        EXPECT_EQ(0u, i);
    }

    TEST_F(IteratorTest, CanCallObjects)
    {
        TestClass test1;
        TestClass test2;
        TestClass test3;

        EXPECT_CALL(test1, test());
        EXPECT_CALL(test2, test());
        EXPECT_CALL(test3, test());

        // iterate over interfaces but use a TestClass iterator - now we can call TestClass::test()
        ObjectList<TestInterface> list;
        for (Iterator<TestClass> it = list.begin(); it != list.end(); ++it)
            it->test();
    }

    TEST_F(IteratorTest, CanIterateOverInterfaceListWithInterfaceIterator)
    {
        TestClass testClass;
        TestInterface testInterface;

        size_t i = 0;
        ObjectList<TestInterface> list;
        for (Iterator<TestInterface> it = list.begin(); it != list.end(); ++it)
        {
            ++i;
            if (i == 1u) EXPECT_EQ(&testClass, *it);
            if (i == 2u) EXPECT_EQ(&testInterface, *it);
        }
        EXPECT_EQ(2u, i);
    }

    TEST_F(IteratorTest, CanIterateOverClassListWithClassIterator)
    {
        TestClass testClass;
        TestInterface testInterface;

        size_t i = 0;
        ObjectList<TestClass> list;
        for (Iterator<TestClass> it = list.begin(); it != list.end(); ++it)
        {
            ++i;
            if (i == 1u) EXPECT_EQ(&testClass, *it);
        }
        EXPECT_EQ(1u, i);
    }

    TEST_F(IteratorTest, CanIterateOverInterfaceListWithClassIterator)
    {
        TestClass testClass;
        TestInterface testInterface;

        size_t i = 0;
        ObjectList<TestInterface> list;
        for (Iterator<TestClass> it = list.begin(); it != list.end(); ++it)
        {
            ++i;
            if (i == 1u) EXPECT_EQ(&testClass, *it);
            if (i == 2u) EXPECT_EQ(nullptr, *it);
        }
        EXPECT_EQ(2u, i);
    }

    TEST_F(IteratorTest, CanIterateOverClassListWithInterfaceIterator)
    {
        TestClass testClass;
        TestInterface testInterface;

        size_t i = 0;
        ObjectList<TestClass> list;
        for (Iterator<TestInterface> it = list.begin(); it != list.end(); ++it)
        {
            ++i;
            if (i == 1u) EXPECT_EQ(&testClass, *it);
        }
        EXPECT_EQ(1u, i);
    }
}
