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


#include "TestSet.h"
#include "EASTLTest.h"
#include <EASTL/map.h>
#include <EASTL/set.h>
#include <EASTL/functional.h>
#include <EASTL/internal/config.h>
#include <EABase/eabase.h>


EA_DISABLE_ALL_VC_WARNINGS()
#include <stdio.h>

#ifndef EA_COMPILER_NO_STANDARD_CPP_LIBRARY
	#include <set>
	#include <map>
	#include <algorithm>
#endif
EA_RESTORE_ALL_VC_WARNINGS()

using namespace eastl;


// Template instantations.
// These tell the compiler to compile all the functions for the given class.
template class eastl::set<int>;
template class eastl::multiset<float>;
template class eastl::set<TestObject>;
template class eastl::multiset<TestObject>;


///////////////////////////////////////////////////////////////////////////////
// typedefs
//
typedef eastl::set<int> VS1;
typedef eastl::set<TestObject> VS4;
typedef eastl::multiset<int> VMS1;
typedef eastl::multiset<TestObject> VMS4;

#ifndef EA_COMPILER_NO_STANDARD_CPP_LIBRARY
	typedef std::set<int> VS3;
	typedef std::set<TestObject> VS6;
	typedef std::multiset<int> VMS3;
	typedef std::multiset<TestObject> VMS6;
#endif

///////////////////////////////////////////////////////////////////////////////


///////////////////////////////////////////////////////////////////////////////
// xvalue_test
//
// Test utility type that sets the class data to known value when its data has
// has been moved out.  This enables us to write tests that verify that the
// destruction action taken on container elements occured during move operations.
//
struct xvalue_test
{
	static const int MOVED_FROM = -1;

	int data = 42;

	xvalue_test(int in) : data(in) {}
	~xvalue_test() = default;

	xvalue_test(const xvalue_test& other) 
		: data(other.data) {}

	xvalue_test& operator=(const xvalue_test& other)
	{
		data = other.data;
		return *this;
	}

	xvalue_test(xvalue_test&& other)
	{
		data = other.data;
		other.data = MOVED_FROM;
	}

	xvalue_test& operator=(xvalue_test&& other)
	{
		data = other.data;
		other.data = MOVED_FROM;
		return *this;
	}

	friend bool operator<(const xvalue_test& rhs, const xvalue_test& lhs) 
		{ return rhs.data < lhs.data; }
};



int TestSet()
{
	int nErrorCount = 0;

	#ifndef EA_COMPILER_NO_STANDARD_CPP_LIBRARY
		{   // Test construction
			nErrorCount += TestSetConstruction<VS1, VS3, false>();
			nErrorCount += TestSetConstruction<VS4, VS6, false>();

			nErrorCount += TestSetConstruction<VMS1, VMS3, true>();
			nErrorCount += TestSetConstruction<VMS4, VMS6, true>();
		}


		{   // Test mutating functionality.
			nErrorCount += TestSetMutation<VS1, VS3, false>();
			nErrorCount += TestSetMutation<VS4, VS6, false>();

			nErrorCount += TestSetMutation<VMS1, VMS3, true>();
			nErrorCount += TestSetMutation<VMS4, VMS6, true>();
		}
	#endif // EA_COMPILER_NO_STANDARD_CPP_LIBRARY


	{   // Test searching functionality.
		nErrorCount += TestSetSearch<VS1, false>();
		nErrorCount += TestSetSearch<VS4, false>();

		nErrorCount += TestSetSearch<VMS1, true>();
		nErrorCount += TestSetSearch<VMS4, true>();
	}


	{
		// C++11 emplace and related functionality
		nErrorCount += TestSetCpp11<eastl::set<TestObject> >();

		nErrorCount += TestMultisetCpp11<eastl::multiset<TestObject> >();
	}


	{ // Misc tests

		// const key_compare& key_comp() const;
		// key_compare&       key_comp();
		VS1       vs;
		const VS1 vsc;

		const VS1::key_compare& kc = vsc.key_comp();
		vs.key_comp() = kc;
	}

	{ // non-const comparator test
		struct my_less
		{
			bool operator()(int a, int b) { return a < b; }
		};

		{
			set<int, my_less> a = {0, 1, 2, 3, 4};
			auto i = a.find(42);
			VERIFY(i == a.end());
		}
	}

	{ // set erase_if tests
		set<int> s = {0, 1, 2, 3, 4};
		auto numErased = eastl::erase_if(s, [](auto i) { return i % 2 == 0;});
		VERIFY((s == set<int>{1,3}));
		VERIFY(numErased == 3);
	}

	{ // multiset erase_if tests
		multiset<int> s = {0, 0, 0, 0, 0, 1, 1, 1, 2, 3, 3, 3, 4};
		auto numErased = eastl::erase_if(s, [](auto i) { return i % 2 == 0;});
		VERIFY((s == multiset<int>{1, 1, 1, 3, 3, 3}));
		VERIFY(numErased == 7);
	}

#if defined(EA_COMPILER_HAS_THREE_WAY_COMPARISON)
	{ // Test set <=>
	    set<int> s1 = {0, 1, 2, 3, 4};
	    set<int> s2 = {4, 3, 2, 1, 0};
	    set<int> s3 = {1, 2, 3, 4, 5, 6, 7, 8, 9};
	    set<int> s4 = {1, 2, 3, 4, 5, 6};
	    set<int> s5 = {9};

	    VERIFY(s1 == s2);
	    VERIFY(s1 != s3);
	    VERIFY(s3 > s4);
	    VERIFY(s5 > s4);
	    VERIFY(s5 > s3);

	    VERIFY((s1 <=> s2) == 0);
	    VERIFY((s1 <=> s3) != 0);
	    VERIFY((s3 <=> s4) > 0);
	    VERIFY((s5 <=> s4) > 0);
	    VERIFY((s5 <=> s3) > 0);
	}

	{ // Test multiset <=>
	    multiset<int> s1 = {0, 0, 0, 1, 1, 2, 3, 3, 4};
	    multiset<int> s2 = {4, 3, 3, 2, 1, 1, 0, 0, 0};
	    multiset<int> s3 = {1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 9};
	    multiset<int> s4 = {1, 1, 2, 2, 3, 4, 5, 5, 6};
	    multiset<int> s5 = {9};

	    VERIFY(s1 == s2);
	    VERIFY(s1 != s3);
	    VERIFY(s3 > s4);
	    VERIFY(s5 > s4);
	    VERIFY(s5 > s3);

	    VERIFY((s1 <=> s2) == 0);
	    VERIFY((s1 <=> s3) != 0);
	    VERIFY((s3 <=> s4) > 0);
	    VERIFY((s5 <=> s4) > 0);
	    VERIFY((s5 <=> s3) > 0);
	}
#endif

	{
		// user reported regression: ensure container elements are NOT 
		// moved from during the eastl::set construction process.
		eastl::vector<xvalue_test> m1 = {{0}, {1}, {2}, {3}, {4}, {5}};
		eastl::set<xvalue_test> m2{m1.begin(), m1.end()};

		bool result = eastl::all_of(m1.begin(), m1.end(), 
				[&](auto& e) { return e.data != xvalue_test::MOVED_FROM; });

		VERIFY(result);
	}

	{
		// user reported regression: ensure container elements are moved from during the
		// eastl::set construction process when using an eastl::move_iterator.
		eastl::vector<xvalue_test> m1 = {{0}, {1}, {2}, {3}, {4}, {5}};
		eastl::set<xvalue_test> m2{eastl::make_move_iterator(m1.begin()), eastl::make_move_iterator(m1.end())};

		bool result = eastl::all_of(m1.begin(), m1.end(), 
				[&](auto& e) { return e.data == xvalue_test::MOVED_FROM; });

		VERIFY(result);
	}

	return nErrorCount;
}












