/* Copyright (C) 2025 Wildfire Games.
 * This file is part of 0 A.D.
 *
 * 0 A.D. is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * 0 A.D. is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "lib/self_test.h"

#include "lib/timer.h"
#include "simulation2/system/EntityMap.h"

#include <algorithm>
#include <cstdio>
#include <random>
#include <utility>
#include <vector>

class TestEntityMap : public CxxTest::TestSuite
{
public:
	void setUp()
	{
	}

	void tearDown()
	{
	}

	void test_insert()
	{
		EntityMap<int> test;

		TS_ASSERT(test.empty());

		test.insert(1,1);
		test.insert(2,2);
		test.insert(3,3);
		test.insert(4,4);
		test.insert(4,5);
		test.insert(4,6);

//		TS_ASSERT(test.m_Data.size() == 5);
//		TS_ASSERT(test.m_Data.back().first != INVALID_ENTITY);
		TS_ASSERT(test.size() == 4);
		TS_ASSERT(test.find(3)->second == 3);
		TS_ASSERT(test.find(4)->second == 6);

		test.insert(10,7);
//		TS_ASSERT(test.m_Data.size() == 11);
//		TS_ASSERT(test.m_Data.back().first != INVALID_ENTITY);
		TS_ASSERT(test.size() == 5);
		TS_ASSERT(test.find(4)->second == 6);
		TS_ASSERT(test.find(5) == test.end());
		TS_ASSERT(test.find(6) == test.end());
		TS_ASSERT(test.find(7) == test.end());
		TS_ASSERT(test.find(8) == test.end());
		TS_ASSERT(test.find(9) == test.end());
		TS_ASSERT(test.find(10)->second == 7);

//		EntityMap<int, 5> test2;

//		test2.insert(8,5);
//		TS_ASSERT(test2.find(8)->second == 5);
//		TS_ASSERT(test2.m_Data.size() == 5);
//		TS_ASSERT(test2.size() == 1);

	}
	void test_iterators()
	{
		EntityMap<int> test;

		test.insert(1,1);
		test.insert(2,2);
		test.insert(3,3);
		test.insert(4,4);

		EntityMap<int>::iterator it = test.begin();
		TS_ASSERT(it->first == 1);
		++it;
		TS_ASSERT(it->first == 2);
		++it; // it++ incorrectly returns a pointer in svn
		TS_ASSERT(it->first == 3);
		it = test.end();
//		TS_ASSERT(it->first == test.m_Data.back().first);

		EntityMap<int>::const_iterator cit = test.begin();
		TS_ASSERT(cit->first == 1);
		cit = test.end();
//		TS_ASSERT(cit->first == test.m_Data.back().first);

		size_t iter = 0;
		for (EntityMap<int>::value_type& v : test)
		{
			++iter;
			// Reading `second` on the end of the range is undefined behavior. Here invoking
			// `test.find` never evaluates to the past the end iterator. GCC doesn't know that and
			// issues a "maybe-uninitialized" warning.
			TS_ASSERT_DIFFERS(test.find(iter), test.end());
			TS_ASSERT(test.find(iter)->second == (int)iter);
			TS_ASSERT(test.find(iter)->second == v.second);
		}
		TS_ASSERT(iter == 4);

		test.clear();

		test.insert(10,1);
		test.insert(20,2);
		test.insert(30,3);
		test.insert(40,4);

		it = test.begin();
		TS_ASSERT(it->second == 1);
		++it; // it++ incorrectly returns a pointer in svn
		TS_ASSERT(it->second == 2);
		++it;
		TS_ASSERT(it->second == 3);
		it = test.end();
//		TS_ASSERT(it->first == test.m_Data.back().first);

	}

	void test_erase()
	{
		EntityMap<int> test;
		test.insert(1,1);
		test.insert(2,2);
		test.insert(3,3);
		test.insert(4,4);

		test.erase(2);

//		TS_ASSERT(test.m_Data.size() == 5);
		TS_ASSERT(test.size() == 3);
		TS_ASSERT(test.find(2) == test.end());

		test.erase(1);
		test.erase(3);
		test.erase(4);//		TS_ASSERT(test.erase(4) == 1);

//		TS_ASSERT(test.m_Data.size() == 5);
		TS_ASSERT(test.size() == 0);

		test.erase(5);//		TS_ASSERT(test.erase(5) == 0);

		test.insert(1,1);
		test.insert(2,2);
		test.insert(3,3);
		test.insert(4,4);

		test.erase(test.begin());
//		TS_ASSERT(test.m_Data.size() == 5);
		TS_ASSERT(test.size() == 3);
		TS_ASSERT(test.find(1) == test.end());

//		TS_ASSERT(test.erase(test.end()) == 0);
//		TS_ASSERT(test.m_Data.back().first != INVALID_ENTITY);
	}

	void test_clear()
	{
		EntityMap<int> test;
		test.insert(1,1);
		test.insert(2,2);
		test.insert(3,3);
		test.insert(4,4);

		test.clear();

//		TS_ASSERT(test.m_Data.size() == 1);
		TS_ASSERT(test.size() == 0);
	}

	void test_find()
	{
		EntityMap<int> test;
		test.insert(1,1);
		test.insert(2,2);
		test.insert(3,3);
		test.insert(40,4);

		TS_ASSERT(test.find(1)->second == 1);
		TS_ASSERT(test.find(40)->second == 4);
		TS_ASSERT(test.find(30) == test.end());
	}

	void DISABLE_test_perf()
	{
		EntityMap<int> test;
		printf("Testing performance of EntityMap\n");

		double t = timer_Time();
		for (int i = 1; i <= 200000; ++i)
			test.insert(i,i);
		double tt = timer_Time() - t;
		printf("inserting 200K elements in order: %lfs\n", tt);

		t = timer_Time();
		for (int i = 1; i <= 200000; ++i)
			test.erase(i);
		tt = timer_Time() - t;
		printf("Erasing 200K elements, by key: %lfs\n", tt);

		t = timer_Time();
		for (int i = 200000; i >= 1; --i)
			test.insert(i,i);
		tt = timer_Time() - t;
		printf("inserting 200K elements in reverse order: %lfs\n", tt);

		t = timer_Time();
		for (auto i = test.begin(); i != test.end(); ++i)
			test.erase(i);
		tt = timer_Time() - t;
		printf("Erasing 200K elements, by iterator: %lfs\n", tt);

		t = timer_Time();
		for (int i = 1; i <= 200000; ++i)
			test.erase(i);
		tt = timer_Time() - t;
		printf("Erasing 200K non-existing elements: %lfs\n", tt);

		// prep random vector
		std::vector<int> vec;
		for (int i = 1; i <= 200000; ++i)
			vec.push_back(i);

		std::shuffle(vec.begin(), vec.end(), std::mt19937{});

		for (int i = 1; i <= 200000; ++i)
			test.insert(i,i);

		t = timer_Time();
		for (int i = 1; i <= 200000; ++i)
			test.find(vec[i])->second = 3;
		tt = timer_Time() - t;
		printf("200K random lookups in random order: %lfs\n", tt);

		t = timer_Time();
		for (auto& p : test)
			p.second = 3;
		tt = timer_Time() - t;
		printf("auto iteration on 200K continuous entitymap: %lfs\n", tt);

		test.clear();

		for (int i = 1; i <= 200000; ++i)
			test.insert(i*5,i);

		t = timer_Time();
		for (EntityMap<int>::value_type& p : test)
			p.second = 3;
		tt = timer_Time() - t;
		printf("auto iteration on 200K sparse (holes of 5): %lfs\n", tt);

		test.clear();

		for (int i = 1; i <= 4000; ++i)
			test.insert(i*50,i);

		t = timer_Time();
		for (EntityMap<int>::value_type& p : test)
			p.second = 3;
		tt = timer_Time() - t;
		printf("auto iteration on 4K sparse (holes of 50): %lfs\n", tt);

		test.clear();

		for (int i = 1; i <= 200000; ++i)
			test.insert(i*50,i);

		t = timer_Time();
		for (EntityMap<int>::value_type& p : test)
			p.second = 3;
		tt = timer_Time() - t;
		printf("auto iteration on 200K sparse (holes of 50): %lfs\n", tt);

		test.clear();

		for (int i = 1; i <= 2000000; ++i)
			test.insert(i*50,i);

		t = timer_Time();
		for (EntityMap<int>::iterator i = test.begin(); i != test.end(); ++i)
			i->second = 3;
		tt = timer_Time() - t;
		printf("manual ++iteration on 2000K sparse (holes of 50) (warmup 1): %lfs\n", tt);

		t = timer_Time();
		for (EntityMap<int>::iterator i = test.begin(); i != test.end(); ++i)
			i->second = 3;
		tt = timer_Time() - t;
		printf("manual ++iteration on 2000K sparse (holes of 50) (warmup 2): %lfs\n", tt);

		t = timer_Time();
		for (EntityMap<int>::iterator i = test.begin(); i != test.end(); ++i)
			i->second = 3;
		tt = timer_Time() - t;
		printf("manual ++iteration on 2000K sparse (holes of 50): %lfs\n", tt);

		t = timer_Time();
		for (EntityMap<int>::iterator i = test.begin(); i != test.end(); ++i)
			i->second = 3;
		tt = timer_Time() - t;
		printf("manual iteration++ on 2000K sparse (holes of 50): %lfs\n", tt);
	}
};
