/*
Minetest
Copyright (C) 2024 sfan5

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "test.h"

#include "util/container.h"

class TestDataStructures : public TestBase
{
public:
	TestDataStructures() { TestManager::registerTestModule(this); }
	const char *getName() { return "TestDataStructures"; }

	void runTests(IGameDef *gamedef);

	void testMap1();
	void testMap2();
	void testMap3();
	void testMap4();
	void testMap5();
};

static TestDataStructures g_test_instance;

void TestDataStructures::runTests(IGameDef *gamedef)
{
	rawstream << "-------- ModifySafeMap" << std::endl;
	TEST(testMap1);
	TEST(testMap2);
	TEST(testMap3);
	TEST(testMap4);
	TEST(testMap5);
}

namespace {

struct TrackerState {
	bool copied = false;
	bool deleted = false;
};

class Tracker {
	TrackerState *res = nullptr;

	inline void trackDeletion() { res && (res->deleted = true); }

public:
	Tracker() {}
	Tracker(TrackerState &res) : res(&res) {}

	operator bool() const { return !!res; }

	Tracker(const Tracker &other) { *this = other; }
	Tracker &operator=(const Tracker &other) {
		trackDeletion();
		res = other.res;
		res && (res->copied = true);
		return *this;
	}
	Tracker(Tracker &&other) { *this = std::move(other); }
	Tracker &operator=(Tracker &&other) {
		trackDeletion();
		res = other.res;
		other.res = nullptr;
		return *this;
	}

	~Tracker() { trackDeletion(); }
};

}

void TestDataStructures::testMap1()
{
	ModifySafeMap<int, Tracker> map;
	TrackerState t0, t1;

	// no-copy put
	map.put(1, Tracker(t0));
	UASSERT(!t0.copied);
	UASSERT(!t0.deleted);

	// overwrite during iter
	bool once = false;
	for (auto &it : map.iter()) {
		(void)it;
		map.put(1, Tracker(t1));
		UASSERT(t0.deleted);
		UASSERT(!t1.copied);
		UASSERT(!t1.deleted);
		if ((once |= 1))
			break;
	}
	UASSERT(once);

	map.clear(); // ASan complains about stack-use-after-scope otherwise
}

void TestDataStructures::testMap2()
{
	ModifySafeMap<int, Tracker> map;
	TrackerState t0, t1;

	// overwrite
	map.put(1, Tracker(t0));
	map.put(1, Tracker(t1));
	UASSERT(t0.deleted);
	UASSERT(!t1.copied);
	UASSERT(!t1.deleted);

	map.clear();
}

void TestDataStructures::testMap3()
{
	ModifySafeMap<int, Tracker> map;
	TrackerState t0, t1;

	// take
	map.put(1, Tracker(t0));
	{
		auto v = map.take(1);
		UASSERT(!t0.copied);
		UASSERT(!t0.deleted);
	}
	UASSERT(t0.deleted);

	// remove during iter
	map.put(1, Tracker(t1));
	for (auto &it : map.iter()) {
		(void)it;
		map.remove(1);
		UASSERT(t1.deleted);
		break;
	}
}

void TestDataStructures::testMap4()
{
	ModifySafeMap<int, u32> map;

	// overwrite + take during iter
	map.put(1, 100);
	for (auto &it : map.iter()) {
		(void)it;
		map.put(1, 200);
		u32 taken = map.take(1);
		UASSERTEQ(u32, taken, 200);
		break;
	}

	UASSERT(map.get(1) == u32());
	UASSERTEQ(size_t, map.size(), 0);
}

void TestDataStructures::testMap5()
{
	ModifySafeMap<int, u32> map;

	// overwrite 2x during iter
	map.put(9001, 9001);
	for (auto &it : map.iter()) {
		(void)it;
		map.put(1, 100);
		map.put(1, 200);
		UASSERTEQ(u32, map.get(1), 200);
		break;
	}
}
