#include "RootedObject.h"


#if WITH_DEV_AUTOMATION_TESTS

template<typename AssigneeType, typename ValueType>
struct TIsAssignableFrom
{
	struct TrueType {};

	template<typename T>
	static T DeclVal(); // intentionally not implemented

	enum
	{
		Value = TIsSame<decltype(DeclVal<AssigneeType>() = DeclVal<ValueType>(), DeclVal<TrueType>()), TrueType>::Value
	};
};

static_assert(TIsConstructible<TRootedObject<UObject>>::Value, "TRootedObject should be default constructible");

static_assert(TIsConstructible<TRootedObject<UObject>, TRootedObject<UObject>&&>::Value, "TRootedObject should be move constructible");
static_assert(TIsAssignableFrom<TRootedObject<UObject>, TRootedObject<UObject>&&>::Value, "TRootedObject should be move assignable");

static_assert(TIsAssignableFrom<UObject*&, TRootedObject<UObject>>::Value, "TRootedObject should implicitly castable to its inner type");

BEGIN_DEFINE_SPEC(TRootedObjectSpec, "Workshop.RootedObject", EAutomationTestFlags::ProductFilter | EAutomationTestFlags::ApplicationContextMask)
	UObject* TheInnerObject;
END_DEFINE_SPEC(TRootedObjectSpec)

void TRootedObjectSpec::Define()
{
	BeforeEach([this]()
	{
		TheInnerObject = NewObject<USceneComponent>();
	});

	Describe("Construct", [this]()
	{
		It("Should add the rooted object to the root", [this]()
		{
			TRootedObject<UObject> RootedObject(TheInnerObject);
			TestTrue("TheInnerObject->IsRooted()", TheInnerObject->IsRooted());
		});

		It("Should move correctly", [this]()
		{
			TRootedObject<UObject> RootedObject(TheInnerObject);
			TRootedObject<UObject> OtherRootedObject;

			OtherRootedObject = MoveTemp(RootedObject);

			TestTrue("TheInnerObject->IsRooted()", TheInnerObject->IsRooted());
			TestFalse("RootedObject.IsValid()", RootedObject.IsValid());
			TestTrue("OtherRootedObject.IsValid()", OtherRootedObject.IsValid());
		});
	});

	Describe("Destruct", [this]()
	{
		It("Should remove the rooted object from the root", [this]()
		{
			{
				TRootedObject<UObject> RootedObject(TheInnerObject);
				TestTrue("TheInnerObject->IsRooted()", TheInnerObject->IsRooted());
			}
			TestFalse("TheInnerObject->IsRooted()", TheInnerObject->IsRooted());
		});
	});

	Describe("RetrieveObject", [this]()
	{
		It("Should invalidate the rooted object", [this]()
		{
			TRootedObject<UObject> RootedObject(TheInnerObject);
			RootedObject.RetrieveObject();
			TestFalse("RootedObject.IsValid()", RootedObject.IsValid());
		});

		It("Should return the inner object", [this]()
		{
			TRootedObject<UObject> RootedObject(TheInnerObject);
			UObject* RetrievedObject = RootedObject.RetrieveObject();
			TestEqual("RootedObject.IsValid()", TheInnerObject, RetrievedObject);
		});

		It("Should remove the inner object from root", [this]()
		{
			TRootedObject<UObject> RootedObject(TheInnerObject);
			UObject* RetrievedObject = RootedObject.RetrieveObject();
			TestFalse("RetrievedObject->IsRooted()", RetrievedObject->IsRooted());
		});

		It("Implicit Retrieve is same as explicit retrieve", [this]()
		{
			TRootedObject<UObject> RootedObject(TheInnerObject);
			UObject* RetrievedObject = RootedObject;
			TestEqual("RootedObject.IsValid()", TheInnerObject, RetrievedObject);
			TestFalse("RetrievedObject->IsRooted()", RetrievedObject->IsRooted());
		});
	});
}

#endif