#include <utility>
#include <gtest/gtest.h>
#include "core/object/StrongPtr.h"

namespace orxonox
{
    namespace
    {
        class DestroyableTest : public Destroyable
        {
            public:
                DestroyableTest(bool& destroyed) : destroyed_(destroyed) { destroyed_ = false; }
                virtual ~DestroyableTest() { destroyed_ = true; }

            private:
                bool& destroyed_;
        };
    }

    TEST(StrongPtrTest, CanReferenceObject)
    {
        bool bla;
        DestroyableTest* test = new DestroyableTest(bla);
        StrongPtr<DestroyableTest> strongPtr = test;
        EXPECT_EQ(test, strongPtr.get());
        EXPECT_EQ(test, &(*strongPtr));
        EXPECT_EQ(static_cast<Destroyable*>(test), strongPtr.getBase());
        test->destroy();
    }

    TEST(StrongPtrTest, CanHaveMultiplePointers)
    {
        bool bla;
        DestroyableTest* test = new DestroyableTest(bla);
        StrongPtr<DestroyableTest> strongPtr1 = test;
        StrongPtr<DestroyableTest> strongPtr2 = test;
        StrongPtr<DestroyableTest> strongPtr3 = test;
        EXPECT_EQ(test, strongPtr1.get());
        EXPECT_EQ(test, strongPtr2.get());
        EXPECT_EQ(test, strongPtr3.get());
        EXPECT_EQ(3u, test->getReferenceCount());
        test->destroy();
    }

    TEST(StrongPtrTest, IncreasesReferenceCount)
    {
        bool bla;
        DestroyableTest* test = new DestroyableTest(bla);
        EXPECT_EQ(0u, test->getReferenceCount());
        {
            StrongPtr<DestroyableTest> strongPtr = test;
            EXPECT_EQ(1u, test->getReferenceCount());
        }
        EXPECT_EQ(0u, test->getReferenceCount());
        test->destroy();
    }

    TEST(StrongPtrTest, DestroyDeletesInstance)
    {
        bool destroyed = false;
        DestroyableTest* test = new DestroyableTest(destroyed);
        EXPECT_FALSE(destroyed);
        test->destroy();
        EXPECT_TRUE(destroyed);
    }

    TEST(StrongPtrTest, PreventsDestruction)
    {
        bool destroyed = false;
        DestroyableTest* test = new DestroyableTest(destroyed);
        EXPECT_FALSE(destroyed);
        StrongPtr<DestroyableTest> strongPtr = test;
        test->destroy();
        EXPECT_FALSE(destroyed);
    }

    TEST(StrongPtrTest, DestroysIfStrongPtrRemoved)
    {
        bool destroyed = false;
        DestroyableTest* test = new DestroyableTest(destroyed);
        EXPECT_FALSE(destroyed);
        {
            StrongPtr<DestroyableTest> strongPtr = test;
            test->destroy();
            EXPECT_FALSE(destroyed);
        }
        EXPECT_TRUE(destroyed);
    }

    TEST(StrongPtrTest, DestroysIfAllStrongPtrsRemoved)
    {
        bool destroyed = false;
        DestroyableTest* test = new DestroyableTest(destroyed);
        EXPECT_FALSE(destroyed);
        {
            StrongPtr<DestroyableTest> strongPtr1 = test;
            {
                StrongPtr<DestroyableTest> strongPtr2 = test;
                {
                    StrongPtr<DestroyableTest> strongPtr3 = test;
                    test->destroy();
                    EXPECT_FALSE(destroyed);
                }
                EXPECT_FALSE(destroyed);
            }
            EXPECT_FALSE(destroyed);
        }
        EXPECT_TRUE(destroyed);
    }

    void isNull(const StrongPtr<DestroyableTest> strongPtr)
    {
        EXPECT_TRUE(strongPtr == nullptr);
        EXPECT_TRUE(strongPtr == nullptr);
        EXPECT_TRUE(!strongPtr);
        EXPECT_FALSE(strongPtr != nullptr);
        EXPECT_FALSE(strongPtr != nullptr);
        EXPECT_FALSE(strongPtr);
    }

    TEST(StrongPtrTest, IsNull)
    {
        {
            StrongPtr<DestroyableTest> strongPtr;
            isNull(strongPtr);
        }
        {
            StrongPtr<DestroyableTest> strongPtr = nullptr;
            isNull(strongPtr);
        }
        {
            StrongPtr<DestroyableTest> strongPtr;
            strongPtr = nullptr;
            isNull(strongPtr);
        }
        {
            StrongPtr<DestroyableTest> strongPtr = nullptr;
            isNull(strongPtr);
        }
        {
            StrongPtr<DestroyableTest> strongPtr;
            strongPtr = nullptr;
            isNull(strongPtr);
        }
    }

    TEST(StrongPtrTest, IsNotNull)
    {
        bool destroyed = false;
        DestroyableTest* test = new DestroyableTest(destroyed);
        StrongPtr<DestroyableTest> strongPtr = test;
        EXPECT_FALSE(strongPtr == nullptr);
        EXPECT_FALSE(strongPtr == nullptr);
        EXPECT_FALSE(!strongPtr);
        EXPECT_TRUE(strongPtr != nullptr);
        EXPECT_TRUE(strongPtr != nullptr);
        EXPECT_TRUE(strongPtr);
        test->destroy();
    }

    TEST(StrongPtrTest, TestConstructors)
    {
        bool bla;
        DestroyableTest* test = new DestroyableTest(bla);

        // default
        StrongPtr<DestroyableTest> strongPtr1;
        EXPECT_EQ(nullptr, strongPtr1.get());

        // pointer
        StrongPtr<DestroyableTest> strongPtr2a = test;
        StrongPtr<DestroyableTest> strongPtr2b(test);
        EXPECT_EQ(test, strongPtr2a.get());
        EXPECT_EQ(test, strongPtr2b.get());

        // copy
        StrongPtr<DestroyableTest> strongPtr3a = strongPtr2a;
        StrongPtr<DestroyableTest> strongPtr3b(strongPtr2b);
        EXPECT_EQ(test, strongPtr3a.get());
        EXPECT_EQ(test, strongPtr3b.get());

        // move
        StrongPtr<DestroyableTest> strongPtr4a = std::move(strongPtr3a);
        StrongPtr<DestroyableTest> strongPtr4b(std::move(strongPtr3b));
        EXPECT_EQ(test, strongPtr4a.get());
        EXPECT_EQ(test, strongPtr4b.get());

        // other
        StrongPtr<Destroyable> strongPtr5a = strongPtr4a;
        StrongPtr<Destroyable> strongPtr5b(strongPtr4b);
        EXPECT_EQ(test, strongPtr5a.get());
        EXPECT_EQ(test, strongPtr5b.get());

        // weakPtr
        WeakPtr<DestroyableTest> weakPtr = test;
        StrongPtr<DestroyableTest> strongPtr6a = weakPtr;
        StrongPtr<DestroyableTest> strongPtr6b(weakPtr);
        EXPECT_EQ(test, strongPtr6a.get());
        EXPECT_EQ(test, strongPtr6b.get());

        // weakPtr other
        StrongPtr<Destroyable> strongPtr7a = weakPtr;
        StrongPtr<Destroyable> strongPtr7b(weakPtr);
        EXPECT_EQ(test, strongPtr7a.get());
        EXPECT_EQ(test, strongPtr7b.get());

        test->destroy();
    }

    TEST(StrongPtrTest, TestAssignments)
    {
        bool bla;
        DestroyableTest* test = new DestroyableTest(bla);

        // pointer
        StrongPtr<DestroyableTest> strongPtr1;
        strongPtr1 = test;
        EXPECT_EQ(test, strongPtr1.get());

        // copy
        StrongPtr<DestroyableTest> strongPtr2;
        strongPtr2 = strongPtr1;
        EXPECT_EQ(test, strongPtr2.get());

        // move
        StrongPtr<DestroyableTest> strongPtr3;
        strongPtr3 = std::move(strongPtr2);
        EXPECT_EQ(test, strongPtr3.get());

        // other
        StrongPtr<Destroyable> strongPtr4;
        strongPtr4 = strongPtr3;
        EXPECT_EQ(test, strongPtr4.get());

        // weakPtr
        WeakPtr<DestroyableTest> weakPtr = test;
        StrongPtr<DestroyableTest> strongPtr5;
        strongPtr5 = weakPtr;
        EXPECT_EQ(test, strongPtr5.get());

        // weakPtr other
        StrongPtr<Destroyable> strongPtr6;
        strongPtr6 = weakPtr;
        EXPECT_EQ(test, strongPtr6.get());

        test->destroy();
    }

    TEST(StrongPtrTest, TestStrongPtrInVector)
    {
        bool bla;
        DestroyableTest* test = new DestroyableTest(bla);

        std::vector<StrongPtr<DestroyableTest>> vector;
        vector.push_back(test);
        vector.push_back(test);
        vector.push_back(test);

        ASSERT_EQ(3u, vector.size());
        EXPECT_EQ(test, vector[0].get());
        EXPECT_EQ(test, vector[1].get());
        EXPECT_EQ(test, vector[2].get());
        EXPECT_EQ(3u, test->getReferenceCount());

        vector.clear();
        EXPECT_EQ(0u, test->getReferenceCount());

        test->destroy();
    }

    TEST(StrongPtrTest, TestReset)
    {
        bool bla;
        DestroyableTest* test = new DestroyableTest(bla);
        StrongPtr<DestroyableTest> strongPtr(test);
        EXPECT_EQ(test, strongPtr.get());
        EXPECT_EQ(1u, test->getReferenceCount());
        strongPtr.reset();
        EXPECT_EQ(nullptr, strongPtr.get());
        EXPECT_EQ(0u, test->getReferenceCount());

        test->destroy();
    }

    TEST(StrongPtrTest, TestSwap)
    {
        bool bla;
        DestroyableTest* test1 = new DestroyableTest(bla);
        DestroyableTest* test2 = new DestroyableTest(bla);

        StrongPtr<DestroyableTest> strongPtr1(test1);
        StrongPtr<DestroyableTest> strongPtr2(test2);
        EXPECT_EQ(test1, strongPtr1.get());
        EXPECT_EQ(test2, strongPtr2.get());
        EXPECT_EQ(1u, test1->getReferenceCount());
        EXPECT_EQ(1u, test2->getReferenceCount());

        strongPtr1.swap(strongPtr2);

        EXPECT_EQ(test2, strongPtr1.get());
        EXPECT_EQ(test1, strongPtr2.get());
        EXPECT_EQ(1u, test1->getReferenceCount());
        EXPECT_EQ(1u, test2->getReferenceCount());

        test1->destroy();
        test2->destroy();
    }
}
