/////////////////////////////////////////////////////////////////////////////
// Copyright (c) Electronic Arts Inc. All rights reserved.
/////////////////////////////////////////////////////////////////////////////


#include "EASTLTest.h"
#include <EASTL/allocator.h>
#include <EASTL/allocator_malloc.h>
#include <EASTL/fixed_allocator.h>
#include <EASTL/core_allocator_adapter.h>
#include <EASTL/list.h>
#include <EAStdC/EAString.h>
#include <EAStdC/EAAlignment.h>



///////////////////////////////////////////////////////////////////////////////
// fixed_pool_reference
//
struct fixed_pool_reference
{
public:
	fixed_pool_reference(const char* = NULL)
	{
		mpFixedPool = NULL;
	}

	fixed_pool_reference(eastl::fixed_pool& fixedPool)
	{
		mpFixedPool = &fixedPool;
	}

	fixed_pool_reference(const fixed_pool_reference& x)
	{
		mpFixedPool = x.mpFixedPool;
	}

	fixed_pool_reference& operator=(const fixed_pool_reference& x)
	{
		mpFixedPool = x.mpFixedPool;
		return *this;
	}

	void* allocate(size_t /*n*/, int /*flags*/ = 0)
	{
		return mpFixedPool->allocate();
	}

	void* allocate(size_t /*n*/, size_t /*alignment*/, size_t /*offset*/, int /*flags*/ = 0)
	{
		return mpFixedPool->allocate();
	}

	void deallocate(void* p, size_t /*n*/)
	{
		return mpFixedPool->deallocate(p);
	}

	const char* get_name() const
	{
		return "fixed_pool_reference";
	}

	void set_name(const char* /*pName*/)
	{
	}

protected:
	friend bool operator==(const fixed_pool_reference& a, const fixed_pool_reference& b);
	friend bool operator!=(const fixed_pool_reference& a, const fixed_pool_reference& b);

	eastl::fixed_pool* mpFixedPool;
};


inline bool operator==(const fixed_pool_reference& a, const fixed_pool_reference& b)
{
	return (a.mpFixedPool == b.mpFixedPool);
}

inline bool operator!=(const fixed_pool_reference& a, const fixed_pool_reference& b)
{
	return (a.mpFixedPool != b.mpFixedPool);
}


///////////////////////////////////////////////////////////////////////////////
// TestFixedAllocator
//
EA_DISABLE_VC_WARNING(6262)
static int TestFixedAllocator()
{
	using namespace eastl;

	int nErrorCount = 0;

	{  // fixed_allocator
		typedef eastl::list<int, fixed_allocator> IntList;
		typedef IntList::node_type                IntListNode;

		const size_t  kBufferCount = 200;
		IntListNode   buffer1[kBufferCount];
		IntList       intList1;
		const size_t  kAlignOfIntListNode = EA_ALIGN_OF(IntListNode);

		intList1.get_allocator().init(buffer1, sizeof(buffer1), sizeof(IntListNode), kAlignOfIntListNode);

		for(size_t i = 0; i < kBufferCount; i++)
			intList1.push_back(0);

		EATEST_VERIFY(intList1.size() == kBufferCount);

		// Try making a copy.
		IntListNode buffer2[kBufferCount];
		IntList intList2;
		intList2.get_allocator().init(buffer2, sizeof(buffer2), sizeof(IntListNode), kAlignOfIntListNode);
		intList2 = intList1;
		EATEST_VERIFY(intList2.size() == kBufferCount);
	}

	// fixed_allocator_with_overflow, ensure allocations are coming from fixed buffer. This is to
	// prevent a reported user regression where all allocations were being routed to the overflow
	// allocator.
	{
		const int DEFAULT_VALUE = 0xbaadf00d;
		const int TEST_VALUE = 0x12345689;
		const size_t kBufferCount = 10;

		typedef eastl::list<int, fixed_allocator_with_overflow> IntList;
		typedef IntList::node_type IntListNode;

		IntList intList1;
		const size_t kAlignOfIntListNode = EA_ALIGN_OF(IntListNode);

		// ensure the fixed buffer contains the default value that will be replaced
		IntListNode buffer1[kBufferCount];
		for (int i = 0; i < kBufferCount; i++)
		{
			buffer1[i].mValue = DEFAULT_VALUE;
			EATEST_VERIFY(buffer1[i].mValue == DEFAULT_VALUE);
		}

		// replace all the values in the local buffer with the test value
		intList1.get_allocator().init(buffer1, sizeof(buffer1), sizeof(IntListNode), kAlignOfIntListNode);
		for (size_t i = 0; i < kBufferCount; i++)
			intList1.push_back(TEST_VALUE);

		// ensure the local buffer has been altered with the contents of the list::push_back
		for (int i = 0; i < kBufferCount; i++)
		{
			EATEST_VERIFY(buffer1[i].mValue == TEST_VALUE);
		}
	}

	{  // fixed_allocator_with_overflow
		typedef eastl::list<int, fixed_allocator_with_overflow> IntList;
		typedef IntList::node_type                              IntListNode;

		const size_t  kBufferCount = 200;
		IntListNode   buffer1[kBufferCount];
		IntList       intList1;
		const size_t  kAlignOfIntListNode = EA_ALIGN_OF(IntListNode);

		intList1.get_allocator().init(buffer1, sizeof(buffer1), sizeof(IntListNode), kAlignOfIntListNode);

		for(size_t i = 0; i < kBufferCount * 2; i++)
			intList1.push_back(0);

		EATEST_VERIFY(intList1.size() == (kBufferCount * 2));

		// Try making a copy.
		IntListNode buffer2[kBufferCount];
		IntList intList2;
		intList2.get_allocator().init(buffer2, sizeof(buffer2), sizeof(IntListNode), kAlignOfIntListNode);
		intList2 = intList1;
		EATEST_VERIFY(intList2.size() == (kBufferCount * 2));
	}

	{
		// fixed_pool_reference
		typedef eastl::list<int, fixed_pool_reference> WidgetList;
	 
		WidgetList::node_type buffer[16];
		eastl::fixed_pool myPool(buffer, sizeof(buffer), sizeof(WidgetList::node_type), 16);

		WidgetList myList1(myPool);
		WidgetList myList2(myPool);
	 
		myList1.push_back(1);
		myList2.push_back(1);
		EATEST_VERIFY(myList1 == myList2);

		myList1.push_back(2);
		myList1.sort();
		myList2.push_front(2);
		myList2.sort();
		EATEST_VERIFY(myList1 == myList2);
	}

	return nErrorCount;
}
EA_RESTORE_VC_WARNING()


///////////////////////////////////////////////////////////////////////////////
// TestAllocatorMalloc
//
static int TestAllocatorMalloc()
{
	int nErrorCount = 0;

	{
		typedef eastl::list<int, eastl::allocator_malloc> WidgetList;
	 
		WidgetList myList1;
		WidgetList myList2;
	 
		myList1.push_back(1);
		myList2.push_back(1);
		EATEST_VERIFY(myList1 == myList2);

		myList1.push_back(2);
		myList1.sort();
		myList2.push_front(2);
		myList2.sort();
		EATEST_VERIFY(myList1 == myList2);

		#if EASTL_ALIGNED_MALLOC_AVAILABLE
			
		#endif
	}

	return nErrorCount;
}



#if EASTL_DLL
	void* operator new[](size_t size, const char* pName, int flags, unsigned debugFlags, const char* file, int line);
	void* operator new[](size_t size, size_t alignment, size_t alignmentOffset, const char* pName, int flags, unsigned debugFlags, const char* file, int line);
#endif


struct EASTLTestCoreAllocator
{
public:
	void* Alloc(size_t size, const char* name, unsigned int flags)
	{
		return ::operator new[](size, name, flags, 0, __FILE__, __LINE__);
	}

	void* Alloc(size_t size, const char* name, unsigned int flags,
				 unsigned int alignment, unsigned int alignOffset = 0)
	{
		return ::operator new[](size, alignment, alignOffset, name, flags, 0, __FILE__, __LINE__);
	}

	void Free(void* p, size_t /*size*/ = 0)
	{
		::operator delete((char*)p);
	}

	static EASTLTestCoreAllocator* GetDefaultAllocator();
};

EASTLTestCoreAllocator gEASTLTestCoreAllocator;

EASTLTestCoreAllocator* EASTLTestCoreAllocator::GetDefaultAllocator()
{
	return &gEASTLTestCoreAllocator;
}



struct TestClass
{
	mutable int mX;

	TestClass() : mX(37) { }

	void Increment()
		{ mX++; }

	void IncrementConst() const
		{ mX++; }

	int MultiplyBy(int x)
		{ return mX * x; }

	int MultiplyByConst(int x) const
		{ return mX * x; }
};

///////////////////////////////////////////////////////////////////////////////
// TestCoreAllocatorAdapter
//
static int TestCoreAllocatorAdapter()
{
	int nErrorCount = 0;
	
#if EASTL_CORE_ALLOCATOR_ENABLED
	typedef EA::Allocator::CoreAllocatorAdapter<EASTLTestCoreAllocator> Adapter;

	eastl::list<TestClass, Adapter> widgetList(Adapter("UI/WidgetList", &gEASTLTestCoreAllocator));
	widgetList.push_back(TestClass());
	EATEST_VERIFY(widgetList.size() == 1);

	eastl::vector<TestClass, Adapter> widgetVector(100, Adapter("UI/WidgetVector", &gEASTLTestCoreAllocator));
	widgetVector.push_back(TestClass());
	EATEST_VERIFY(widgetVector.size() == 101);

	eastl::vector<TestClass, Adapter> widgetVector2(widgetVector);
	widgetVector2.resize(400);
	EATEST_VERIFY(widgetVector2.size() == 400);
#endif

	return nErrorCount;
}


///////////////////////////////////////////////////////////////////////////////
// TestSwapAllocator
//
static int TestSwapAllocator()
{
	int nErrorCount = 0;

	{
		InstanceAllocator a(nullptr, (uint8_t)111), b(nullptr, (uint8_t)222);
		eastl::swap(a, b);
		
		EATEST_VERIFY(a.mInstanceId == 222);
		EATEST_VERIFY(b.mInstanceId == 111);

		EATEST_VERIFY(EA::StdC::Strcmp(a.get_name(), "InstanceAllocator 222") == 0);
		EATEST_VERIFY(EA::StdC::Strcmp(b.get_name(), "InstanceAllocator 111") == 0);
	}

	return nErrorCount;
}

static int TestAllocationOffsetAndAlignment()
{
	int nErrorCount = 0;

	auto testAllocatorAlignment = [&nErrorCount](int requestedSize, int requestedAlignment, int requestedOffset)
	{
		CountingAllocator::resetCount();
		CountingAllocator a;

		void* p = allocate_memory(a, requestedSize, requestedAlignment, requestedOffset);

		EATEST_VERIFY(p != nullptr);
		EATEST_VERIFY(EA::StdC::IsAligned(p, requestedAlignment));

		a.deallocate(p, requestedSize);
		EATEST_VERIFY(CountingAllocator::getActiveAllocationSize() == 0);
	};

	testAllocatorAlignment(100, 1, 0);
	testAllocatorAlignment(100, 2, 0);
	testAllocatorAlignment(100, 4, 0);
	testAllocatorAlignment(100, 8, 0);
	testAllocatorAlignment(100, 16, 0);

	testAllocatorAlignment(100, 1, 16);
	testAllocatorAlignment(100, 2, 16);
	testAllocatorAlignment(100, 4, 16);
	testAllocatorAlignment(100, 8, 16);
	testAllocatorAlignment(100, 16, 16);

	return nErrorCount;
}


///////////////////////////////////////////////////////////////////////////////
// TestAllocator
//
int TestAllocator()
{
	int nErrorCount = 0;
	
	nErrorCount += TestAllocationOffsetAndAlignment();
	nErrorCount += TestFixedAllocator();
	nErrorCount += TestAllocatorMalloc();
	nErrorCount += TestCoreAllocatorAdapter();
	nErrorCount += TestSwapAllocator();

	return nErrorCount;
}