// sol2

// The MIT License (MIT)

// Copyright (c) 2013-2022 Rapptz, ThePhD and contributors

// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#include "sol_test.hpp"

#include <catch2/catch_all.hpp>

#include <iostream>
#include <string>
#include <list>
#include <vector>
#include <memory>
#include <set>

TEST_CASE("gc/destructors", "test if destructors are fired properly through gc of unbound usertypes") {
	struct test;
	static std::vector<test*> tests_destroyed;
	struct test {
		int v = 10;
		~test() {
			tests_destroyed.push_back(this);
		}
	};
	test t;
	test* pt = nullptr;
	{
		sol::state lua;

		lua["t"] = test {};
		pt = lua["t"];
	}

	REQUIRE(tests_destroyed.size() == 2);
	REQUIRE(tests_destroyed.back() == pt);

	{
		sol::state lua;

		lua["t"] = &t;
		pt = lua["t"];
	}

	REQUIRE(tests_destroyed.size() == 2);
	REQUIRE(&t == pt);

	{
		sol::state lua;

		lua["t"] = std::ref(t);
		pt = lua["t"];
	}

	REQUIRE(tests_destroyed.size() == 2);
	REQUIRE(&t == pt);

	{
		sol::state lua;

		lua["t"] = t;
		pt = lua["t"];
	}

	REQUIRE(tests_destroyed.size() == 3);
	REQUIRE(&t != pt);
	REQUIRE(nullptr != pt);
}

TEST_CASE("gc/virtual destructors", "ensure types with virtual destroyions behave just fine") {
	class B;
	class A;
	static std::vector<B*> bs;
	static std::vector<A*> as;

	class A {
	public:
		virtual ~A() {
			as.push_back(this);
			std::cout << "~A" << std::endl;
		}
	};

	class B : public A {
	public:
		virtual ~B() {
			bs.push_back(this);
			std::cout << "~B" << std::endl;
		}
	};

	{
		sol::state lua;
		lua.open_libraries(sol::lib::base);

		lua.new_usertype<A>("A");
		lua.new_usertype<B>("B", sol::base_classes, sol::bases<A>());

		B b1;
		lua["b1"] = b1; // breaks here
	}

	REQUIRE(as.size() == 2);
	REQUIRE(bs.size() == 2);
}

TEST_CASE("gc/function argument storage", "ensure functions take references on their types, not ownership, when specified") {
	class gc_entity;
	static std::vector<gc_entity*> entities;

	class gc_entity {
	public:
		~gc_entity() {
			entities.push_back(this);
		}
	};
	SECTION("plain") {
		entities.clear();

		sol::state lua;
		lua.open_libraries();
		sol::function f = lua.safe_script(R"(
return function(e)
end
)");
		gc_entity* target = nullptr;
		{
			gc_entity e;
			target = &e;
			{
				f(e);
				lua.collect_garbage();
			}
			{
				f(&e);
				lua.collect_garbage();
			}
			{
				f(std::ref(e));
				lua.collect_garbage();
			}
		}
		REQUIRE(entities.size() == 1);
		REQUIRE(entities.back() == target);
	}
	SECTION("regular") {
		entities.clear();

		sol::state lua;
		lua.open_libraries();
		lua.new_usertype<gc_entity>("entity");
		sol::function f = lua.safe_script(R"(
return function(e)
end
)");
		gc_entity* target = nullptr;
		{
			gc_entity e;
			target = &e;
			{
				f(e);                  // same with std::ref(e)!
				lua.collect_garbage(); // destroys e for some reason
			}
			{
				f(&e);                 // same with std::ref(e)!
				lua.collect_garbage(); // destroys e for some reason
			}
			{
				f(std::ref(e));        // same with std::ref(e)!
				lua.collect_garbage(); // destroys e for some reason
			}
		}
		REQUIRE(entities.size() == 1);
		REQUIRE(entities.back() == target);
	}
}

TEST_CASE("gc/function storage", "show that proper copies / destroyion happens for function storage (or not)") {
	static int created = 0;
	static int destroyed = 0;
	static void* last_call = nullptr;
	static void* static_call = reinterpret_cast<void*>(0x01);
	typedef void (*fptr)();
	struct x {
		x() {
			++created;
		}
		x(const x&) {
			++created;
		}
		x(x&&) {
			++created;
		}
		x& operator=(const x&) {
			return *this;
		}
		x& operator=(x&&) {
			return *this;
		}
		void func() {
			last_call = static_cast<void*>(this);
		};
		~x() {
			++destroyed;
		}
	};
	struct y {
		y() {
			++created;
		}
		y(const x&) {
			++created;
		}
		y(x&&) {
			++created;
		}
		y& operator=(const x&) {
			return *this;
		}
		y& operator=(x&&) {
			return *this;
		}
		static void func() {
			last_call = static_call;
		};
		void operator()() {
			func();
		}
		operator fptr() {
			return func;
		}
		~y() {
			++destroyed;
		}
	};

	// stateful functors/member functions should always copy unless specified
	{
		created = 0;
		destroyed = 0;
		last_call = nullptr;
		{
			sol::state lua;
			x x1;
			lua.set_function("x1copy", &x::func, x1);
			auto result1 = lua.safe_script("x1copy()", sol::script_pass_on_error);
			REQUIRE(result1.valid());
			REQUIRE(created == 2);
			REQUIRE(destroyed == 0);
			REQUIRE_FALSE(last_call == &x1);

			lua.set_function("x1ref", &x::func, std::ref(x1));
			auto result2 = lua.safe_script("x1ref()", sol::script_pass_on_error);
			REQUIRE(result2.valid());
			REQUIRE(created == 2);
			REQUIRE(destroyed == 0);
			REQUIRE(last_call == &x1);
		}
		REQUIRE(created == 2);
		REQUIRE(destroyed == 2);
	}

	// things convertible to a static function should _never_ be forced to make copies
	// therefore, pass through untouched
	{
		created = 0;
		destroyed = 0;
		last_call = nullptr;
		{
			sol::state lua;
			y y1;
			lua.set_function("y1copy", y1);
			auto result1 = lua.safe_script("y1copy()", sol::script_pass_on_error);
			REQUIRE(result1.valid());
			REQUIRE(created == 1);
			REQUIRE(destroyed == 0);
			REQUIRE(last_call == static_call);

			last_call = nullptr;
			lua.set_function("y1ref", std::ref(y1));
			auto result2 = lua.safe_script("y1ref()", sol::script_pass_on_error);
			REQUIRE(result2.valid());
			REQUIRE(created == 1);
			REQUIRE(destroyed == 0);
			REQUIRE(last_call == static_call);
		}
		REQUIRE(created == 1);
		REQUIRE(destroyed == 1);
	}
}

TEST_CASE("gc/same type closures", "make sure destroyions are per-object, not per-type, by destroying one type multiple times") {
	static std::set<void*> last_my_closures;
	static bool checking_closures = false;
	static bool check_failed = false;

	struct my_closure {
		int& n;

		my_closure(int& n) : n(n) {
		}
		~my_closure() noexcept(false) {
			if (!checking_closures)
				return;
			void* addr = static_cast<void*>(this);
			auto f = last_my_closures.find(addr);
			if (f != last_my_closures.cend()) {
				check_failed = true;
			}
			last_my_closures.insert(f, addr);
		}

		int operator()() {
			++n;
			return n;
		}
	};

	int n = 250;
	my_closure a(n);
	my_closure b(n);
	{
		sol::state lua;

		lua.set_function("f", a);
		lua.set_function("g", b);
		checking_closures = true;
	}
	REQUIRE_FALSE(check_failed);
	REQUIRE(last_my_closures.size() == 2);
}

TEST_CASE("gc/usertypes", "show that proper copies / destroyion happens for usertypes") {
	static int created = 0;
	static int destroyed = 0;
	struct x {
		x() {
			++created;
		}
		x(const x&) {
			++created;
		}
		x(x&&) {
			++created;
		}
		x& operator=(const x&) {
			return *this;
		}
		x& operator=(x&&) {
			return *this;
		}
		~x() {
			++destroyed;
		}
	};
	SECTION("plain") {
		created = 0;
		destroyed = 0;
		{
			sol::state lua;
			x x1;
			x x2;
			lua.set("x1copy", x1, "x2copy", x2, "x1ref", std::ref(x1));
			x& x1copyref = lua["x1copy"];
			x& x2copyref = lua["x2copy"];
			x& x1ref = lua["x1ref"];
			REQUIRE(created == 4);
			REQUIRE(destroyed == 0);
			REQUIRE(std::addressof(x1) == std::addressof(x1ref));
			REQUIRE(std::addressof(x1copyref) != std::addressof(x1));
			REQUIRE(std::addressof(x2copyref) != std::addressof(x2));
		}
		REQUIRE(created == 4);
		REQUIRE(destroyed == 4);
	}
	SECTION("regular") {
		created = 0;
		destroyed = 0;
		{
			sol::state lua;
			lua.new_usertype<x>("x");
			x x1;
			x x2;
			lua.set("x1copy", x1, "x2copy", x2, "x1ref", std::ref(x1));
			x& x1copyref = lua["x1copy"];
			x& x2copyref = lua["x2copy"];
			x& x1ref = lua["x1ref"];
			REQUIRE(created == 4);
			REQUIRE(destroyed == 0);
			REQUIRE(std::addressof(x1) == std::addressof(x1ref));
			REQUIRE(std::addressof(x1copyref) != std::addressof(x1));
			REQUIRE(std::addressof(x2copyref) != std::addressof(x2));
		}
		REQUIRE(created == 4);
		REQUIRE(destroyed == 4);
	}
}

TEST_CASE("gc/double-deletion tests", "make sure usertypes are properly destroyed and don't double-delete memory or segfault") {
	class crash_class {
	public:
		crash_class() {
		}
		~crash_class() {
			a = 10;
		}

	private:
		int a;
	};

	sol::state lua;

	SECTION("regular") {
		lua.new_usertype<crash_class>("CrashClass", sol::call_constructor, sol::constructors<sol::types<>>());

		auto result1 = lua.safe_script(R"(
		function testCrash()
			local x = CrashClass()
		end
		)",
		     sol::script_pass_on_error);
		REQUIRE(result1.valid());

		for (int i = 0; i < 1000; ++i) {
			lua["testCrash"]();
		}
	}
}

TEST_CASE("gc/shared_ptr regression", "metatables should not screw over unique usertype metatables") {
	static int created = 0;
	static int destroyed = 0;
	struct test {
		test() {
			++created;
		}

		~test() {
			++destroyed;
		}
	};

	SECTION("regular") {
		created = 0;
		destroyed = 0;
		{
			std::list<std::shared_ptr<test>> tests;
			sol::state lua;
			lua.open_libraries();

			lua.new_usertype<test>("test", "create", [&]() -> std::shared_ptr<test> {
				tests.push_back(std::make_shared<test>());
				return tests.back();
			});
			REQUIRE(created == 0);
			REQUIRE(destroyed == 0);
			auto result1 = lua.safe_script("x = test.create()", sol::script_pass_on_error);
			REQUIRE(result1.valid());
			REQUIRE(created == 1);
			REQUIRE(destroyed == 0);
			REQUIRE_FALSE(tests.empty());
			std::shared_ptr<test>& x = lua["x"];
			std::size_t xuse = static_cast<std::size_t>(x.use_count());
			std::size_t tuse = static_cast<std::size_t>(tests.back().use_count());
			REQUIRE(xuse == tuse);
		}
		REQUIRE(created == 1);
		REQUIRE(destroyed == 1);
	}
}

TEST_CASE("gc/double deleter guards", "usertype metatables internally must not rely on C++ state") {
	SECTION("regular") {
		struct c_a {
			int xv;
		};
		struct c_b {
			int yv;
		};
		auto routine = []() {
			sol::state lua;
			lua.new_usertype<c_a>("c_a", "x", &c_a::xv);
			lua.new_usertype<c_b>("c_b", "y", &c_b::yv);
			lua = sol::state();
			lua.new_usertype<c_a>("c_a", "x", &c_a::xv);
			lua.new_usertype<c_b>("c_b", "y", &c_b::yv);
			lua = sol::state();
		};
		REQUIRE_NOTHROW(routine());
	}
}

TEST_CASE("gc/alignment", "test that allocation is always on aligned boundaries, no matter the wrapper / type") {
	struct test {
		std::function<void()> callback = []() { std::cout << "Hello world!" << std::endl; };

		void check_alignment() {
			std::uintptr_t p = reinterpret_cast<std::uintptr_t>(this);
			std::uintptr_t offset = p % std::alignment_of<test>::value;
			REQUIRE(offset == 0);
		}
	};

	sol::state lua;
	lua.new_usertype<test>("test", "callback", &test::callback);

	test obj {};
	lua["obj"] = &obj;
	INFO("obj");
	{
		auto r = lua.safe_script("obj.callback()", sol::script_pass_on_error);
		REQUIRE(r.valid());
	}
	{
		// Do not check for stack-created object
		// test& lobj = lua["obj"];
		// lobj.check_alignment();
	}

	lua["obj0"] = std::ref(obj);
	INFO("obj0");
	{
		auto r = lua.safe_script("obj0.callback()", sol::script_pass_on_error);
		REQUIRE(r.valid());
	}
	{
		// Do not check for stack-created object
		// test& lobj = lua["obj0"];
		// lobj.check_alignment();
	}

	lua["obj1"] = obj;
	INFO("obj1");
	{
		auto r = lua.safe_script("obj1.callback()", sol::script_pass_on_error);
		REQUIRE(r.valid());
	}
	{
		test& lobj = lua["obj1"];
		lobj.check_alignment();
	}

	lua["obj2"] = test {};
	INFO("obj2");
	{
		auto r = lua.safe_script("obj2.callback()", sol::script_pass_on_error);
		REQUIRE(r.valid());
	}
	{
		test& lobj = lua["obj2"];
		lobj.check_alignment();
	}

	lua["obj3"] = std::make_unique<test>();
	INFO("obj3");
	{
		auto r = lua.safe_script("obj3.callback()", sol::script_pass_on_error);
		REQUIRE(r.valid());
	}
	{
		test& lobj = lua["obj3"];
		lobj.check_alignment();
	}

	lua["obj4"] = std::make_shared<test>();
	INFO("obj4");
	{
		auto r = lua.safe_script("obj4.callback()", sol::script_pass_on_error);
		REQUIRE(r.valid());
	}
	{
		test& lobj = lua["obj4"];
		lobj.check_alignment();
	}
}

TEST_CASE("gc/multi-argument destructors", "make sure transparent arguments come along for the ride") {
	static int transparent_foos_destroyed = 0;

	struct transparent_foo {
		~transparent_foo() {
			++transparent_foos_destroyed;
		}
	};

	lua_State* lua_state = nullptr;
	lua_State* call_state = nullptr;
	auto call_des = [&call_state](transparent_foo* f, sol::this_state s) {
		call_state = s;
		return f->~transparent_foo();
	};
	{
		sol::state lua;
		lua_state = lua;
		lua.new_usertype<transparent_foo>("foo", sol::meta_function::garbage_collect, sol::destructor(std::move(call_des)));

		lua.script("foo.new()");
	}
	REQUIRE(transparent_foos_destroyed == 1);
	REQUIRE(call_state == lua_state);
}
