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

namespace orxonox
{
    namespace
    {
        class DestroyableTest : public Destroyable
        {
        };

        class Callback
        {
            public:
                virtual ~Callback() {}
                MOCK_METHOD0(callback, void());
        };
    }

    TEST(WeakPtrTest, CanReferenceObject)
    {
        DestroyableTest* test = new DestroyableTest();
        WeakPtr<DestroyableTest> weakPtr = test;
        EXPECT_EQ(test, weakPtr.get());
        EXPECT_EQ(test, &(*weakPtr));
        EXPECT_EQ(static_cast<Destroyable*>(test), weakPtr.getBase());
        test->destroy();
    }

    TEST(WeakPtrTest, DestroyRemovesReference)
    {
        DestroyableTest* test = new DestroyableTest();
        WeakPtr<DestroyableTest> weakPtr = test;
        EXPECT_EQ(test, weakPtr.get());
        test->destroy();
        EXPECT_EQ(nullptr, weakPtr.get());
    }

    TEST(WeakPtrTest, CanHaveMultiplePointers)
    {
        DestroyableTest* test = new DestroyableTest();
        WeakPtr<DestroyableTest> weakPtr1 = test;
        WeakPtr<DestroyableTest> weakPtr2 = test;
        WeakPtr<DestroyableTest> weakPtr3 = test;
        EXPECT_EQ(test, weakPtr1.get());
        EXPECT_EQ(test, weakPtr2.get());
        EXPECT_EQ(test, weakPtr3.get());
        test->destroy();
        EXPECT_EQ(nullptr, weakPtr1.get());
        EXPECT_EQ(nullptr, weakPtr2.get());
        EXPECT_EQ(nullptr, weakPtr3.get());
    }

    TEST(WeakPtrTest, DestroyCallsCallback)
    {
        DestroyableTest* test = new DestroyableTest();
        WeakPtr<DestroyableTest> weakPtr = test;
        Callback callback;
        weakPtr.setCallback(createFunctor(&Callback::callback, &callback));
        EXPECT_CALL(callback, callback());
        test->destroy();
    }

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

    TEST(WeakPtrTest, IsNull)
    {
        {
            WeakPtr<DestroyableTest> weakPtr;
            isNull(weakPtr);
        }
        {
            WeakPtr<DestroyableTest> weakPtr = nullptr;
            isNull(weakPtr);
        }
        {
            WeakPtr<DestroyableTest> weakPtr;
            weakPtr = nullptr;
            isNull(weakPtr);
        }
        {
            WeakPtr<DestroyableTest> weakPtr = nullptr;
            isNull(weakPtr);
        }
        {
            WeakPtr<DestroyableTest> weakPtr;
            weakPtr = nullptr;
            isNull(weakPtr);
        }
    }

    TEST(WeakPtrTest, IsNotNull)
    {
        DestroyableTest* test = new DestroyableTest();
        WeakPtr<DestroyableTest> weakPtr = test;
        EXPECT_FALSE(weakPtr == nullptr);
        EXPECT_FALSE(weakPtr == nullptr);
        EXPECT_FALSE(!weakPtr);
        EXPECT_TRUE(weakPtr != nullptr);
        EXPECT_TRUE(weakPtr != nullptr);
        EXPECT_TRUE(weakPtr);
        test->destroy();
    }

    TEST(WeakPtrTest, TestConstructors)
    {
        DestroyableTest* test = new DestroyableTest();

        // default
        WeakPtr<DestroyableTest> weakPtr1;
        EXPECT_EQ(nullptr, weakPtr1.get());

        // pointer
        WeakPtr<DestroyableTest> weakPtr2a = test;
        WeakPtr<DestroyableTest> weakPtr2b(test);
        EXPECT_EQ(test, weakPtr2a.get());
        EXPECT_EQ(test, weakPtr2b.get());

        // copy
        WeakPtr<DestroyableTest> weakPtr3a = weakPtr2a;
        WeakPtr<DestroyableTest> weakPtr3b(weakPtr2b);
        EXPECT_EQ(test, weakPtr3a.get());
        EXPECT_EQ(test, weakPtr3b.get());

        // move
        WeakPtr<DestroyableTest> weakPtr4a = std::move(weakPtr3a);
        WeakPtr<DestroyableTest> weakPtr4b(std::move(weakPtr3b));
        EXPECT_EQ(test, weakPtr4a.get());
        EXPECT_EQ(test, weakPtr4b.get());

        // other
        WeakPtr<Destroyable> weakPtr5a = weakPtr4a;
        WeakPtr<Destroyable> weakPtr5b(weakPtr4b);
        EXPECT_EQ(test, weakPtr5a.get());
        EXPECT_EQ(test, weakPtr5b.get());

        test->destroy();
    }

    TEST(WeakPtrTest, TestAssignments)
    {
        DestroyableTest* test = new DestroyableTest();

        // pointer
        WeakPtr<DestroyableTest> weakPtr1;
        weakPtr1 = test;
        EXPECT_EQ(test, weakPtr1.get());

        // copy
        WeakPtr<DestroyableTest> weakPtr2;
        weakPtr2 = weakPtr1;
        EXPECT_EQ(test, weakPtr2.get());

        // move
        WeakPtr<DestroyableTest> weakPtr3;
        weakPtr3 = std::move(weakPtr2);
        EXPECT_EQ(test, weakPtr3.get());

        // other
        WeakPtr<Destroyable> weakPtr4;
        weakPtr4 = weakPtr3;
        EXPECT_EQ(test, weakPtr4.get());

        test->destroy();
    }

    TEST(WeakPtrTest, TestWeakPtrInVector)
    {
        DestroyableTest* test = new DestroyableTest();

        std::vector<WeakPtr<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());

        test->destroy();

        EXPECT_EQ(nullptr, vector[0].get());
        EXPECT_EQ(nullptr, vector[1].get());
        EXPECT_EQ(nullptr, vector[2].get());
    }

    TEST(WeakPtrTest, TestReset)
    {
        DestroyableTest* test = new DestroyableTest();
        WeakPtr<DestroyableTest> weakPtr(test);
        EXPECT_EQ(test, weakPtr.get());
        weakPtr.reset();
        EXPECT_EQ(nullptr, weakPtr.get());

        test->destroy();
    }

    TEST(WeakPtrTest, TestSwap)
    {
        DestroyableTest* test1 = new DestroyableTest();
        DestroyableTest* test2 = new DestroyableTest();

        WeakPtr<DestroyableTest> weakPtr1(test1);
        WeakPtr<DestroyableTest> weakPtr2(test2);
        EXPECT_EQ(test1, weakPtr1.get());
        EXPECT_EQ(test2, weakPtr2.get());

        weakPtr1.swap(weakPtr2);

        EXPECT_EQ(test2, weakPtr1.get());
        EXPECT_EQ(test1, weakPtr2.get());

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