/*-
 * Copyright 2021 Vsevolod Stakhov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef RSPAMD_RSPAMD_CXX_LOCAL_PTR_HXX
#define RSPAMD_RSPAMD_CXX_LOCAL_PTR_HXX

#define DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL
#include "doctest/doctest.h"

#include "libutil/cxx/local_shared_ptr.hxx"

namespace test_internal {
struct deleter_test {
	bool *pv;

	deleter_test(bool &v)
	{
		v = false;
		pv = &v;
	}

	~deleter_test()
	{
		*pv = true;
	}
};
}// namespace test_internal

namespace std {
template<>
struct hash<test_internal::deleter_test> {
	inline auto operator()(const test_internal::deleter_test &) const noexcept -> auto
	{
		return 42;
	}
};
}// namespace std

TEST_SUITE("local_ptr")
{
	using namespace test_internal;

	TEST_CASE("shared_ptr from nullptr")
	{
		rspamd::local_shared_ptr<int const> pi(static_cast<int *>(nullptr));
		CHECK((!!pi ? false : true));
		CHECK(!pi);
		CHECK(pi.get() == nullptr);
		CHECK(pi.use_count() == 1);
		CHECK(pi.unique());
	}
	TEST_CASE("shared_ptr from ptr")
	{
		int *p = new int(7);
		rspamd::local_shared_ptr<int> pi(p);
		CHECK((pi ? true : false));
		CHECK(!!pi);
		CHECK(pi.get() == p);
		CHECK(pi.use_count() == 1);
		CHECK(pi.unique());
		CHECK(*pi == 7);
	}

	TEST_CASE("shared_ptr copy")
	{
		rspamd::local_shared_ptr<int> pi;

		rspamd::local_shared_ptr<int> pi2(pi);
		CHECK(pi2 == pi);
		CHECK((pi2 ? false : true));
		CHECK(!pi2);
		CHECK(pi2.get() == nullptr);
		CHECK(pi2.use_count() == pi.use_count());

		rspamd::local_shared_ptr<int> pi3(pi);
		CHECK(pi3 == pi);
		CHECK((pi3 ? false : true));
		CHECK(!pi3);
		CHECK(pi3.get() == nullptr);
		CHECK(pi3.use_count() == pi.use_count());

		rspamd::local_shared_ptr<int> pi4(pi3);
		CHECK(pi4 == pi3);
		CHECK((pi4 ? false : true));
		CHECK(!pi4);
		CHECK(pi4.get() == nullptr);
		CHECK(pi4.use_count() == pi3.use_count());

		int *p = new int(7);
		rspamd::local_shared_ptr<int> pi5(p);

		rspamd::local_shared_ptr<int> pi6(pi5);
		CHECK(pi5 == pi6);
		CHECK((pi6 ? true : false));
		CHECK(!!pi6);
		CHECK(pi6.get() == p);
		CHECK(pi6.use_count() == 2);
		CHECK(!pi6.unique());
		CHECK(*pi6 == 7);
		CHECK(pi6.use_count() == pi6.use_count());
		CHECK(!(pi5 < pi6 || pi5 < pi6));// shared ownership test

		auto pi7 = pi6;
		CHECK(pi5 == pi7);
		CHECK((pi7 ? true : false));
		CHECK(!!pi7);
		CHECK(pi7.get() == p);
		CHECK(pi7.use_count() == 3);
		CHECK(!pi7.unique());
		CHECK(*pi7 == 7);
		CHECK(pi7.use_count() == pi7.use_count());
		CHECK(!(pi5 < pi7 || pi5 < pi7));// shared ownership test
	}

	TEST_CASE("shared_ptr move")
	{
		rspamd::local_shared_ptr<int> pi(new int);

		rspamd::local_shared_ptr<int> pi2(std::move(pi));
		CHECK(!(pi2 == pi));
		CHECK((!pi2 ? false : true));
		CHECK(!pi);
		CHECK(pi.get() == nullptr);
		CHECK(pi2.get() != nullptr);
		CHECK(pi.use_count() != pi2.use_count());

		std::swap(pi, pi2);
		CHECK(!(pi2 == pi));
		CHECK((!pi ? false : true));
		CHECK(!pi2);
		CHECK(pi.get() != nullptr);
		CHECK(pi2.get() == nullptr);
		CHECK(pi.use_count() != pi2.use_count());
	}

	TEST_CASE("shared_ptr dtor")
	{
		bool t;

		{
			rspamd::local_shared_ptr<deleter_test> pi(new deleter_test{t});

			CHECK((!pi ? false : true));
			CHECK(!!pi);
			CHECK(pi.get() != nullptr);
			CHECK(pi.use_count() == 1);
			CHECK(pi.unique());
			CHECK(t == false);
		}

		CHECK(t == true);

		{
			rspamd::local_shared_ptr<deleter_test> pi(new deleter_test{t});

			CHECK((!pi ? false : true));
			CHECK(!!pi);
			CHECK(pi.get() != nullptr);
			CHECK(pi.use_count() == 1);
			CHECK(pi.unique());
			CHECK(t == false);

			rspamd::local_shared_ptr<deleter_test> pi2(pi);
			CHECK(pi2 == pi);
			CHECK(pi.use_count() == 2);
			pi.reset();
			CHECK(!(pi2 == pi));
			CHECK(pi2.use_count() == 1);
			CHECK(t == false);

			pi = pi2;
			CHECK(pi2 == pi);
			CHECK(pi.use_count() == 2);
			CHECK(t == false);
		}

		CHECK(t == true);
	}

	TEST_CASE("make_shared dtor")
	{
		bool t;

		{
			auto pi = rspamd::local_make_shared<deleter_test>(t);

			CHECK((!pi ? false : true));
			CHECK(!!pi);
			CHECK(pi.get() != nullptr);
			CHECK(pi.use_count() == 1);
			CHECK(pi.unique());
			CHECK(t == false);

			rspamd::local_shared_ptr<deleter_test> pi2(pi);
			CHECK(pi2 == pi);
			CHECK(pi.use_count() == 2);
			pi.reset();
			CHECK(!(pi2 == pi));
			CHECK(pi2.use_count() == 1);
			CHECK(t == false);

			pi = pi2;
			CHECK(pi2 == pi);
			CHECK(pi.use_count() == 2);
			CHECK(t == false);
		}

		CHECK(t == true);
	}

	TEST_CASE("shared_ptr dtor")
	{
		bool t;

		{
			rspamd::local_shared_ptr<deleter_test> pi(new deleter_test{t});

			CHECK((!pi ? false : true));
			CHECK(!!pi);
			CHECK(pi.get() != nullptr);
			CHECK(pi.use_count() == 1);
			CHECK(pi.unique());
			CHECK(t == false);
		}

		CHECK(t == true);

		{
			rspamd::local_shared_ptr<deleter_test> pi(new deleter_test{t});

			CHECK((!pi ? false : true));
			CHECK(!!pi);
			CHECK(pi.get() != nullptr);
			CHECK(pi.use_count() == 1);
			CHECK(pi.unique());
			CHECK(t == false);

			rspamd::local_shared_ptr<deleter_test> pi2(pi);
			CHECK(pi2 == pi);
			CHECK(pi.use_count() == 2);
			pi.reset();
			CHECK(!(pi2 == pi));
			CHECK(pi2.use_count() == 1);
			CHECK(t == false);

			pi = pi2;
			CHECK(pi2 == pi);
			CHECK(pi.use_count() == 2);
			CHECK(t == false);
		}

		CHECK(t == true);
	}

	TEST_CASE("weak_ptr")
	{
		bool t;

		{
			rspamd::local_shared_ptr<deleter_test> pi(new deleter_test{t});

			CHECK((!pi ? false : true));
			CHECK(!!pi);
			CHECK(pi.get() != nullptr);
			CHECK(pi.use_count() == 1);
			CHECK(pi.unique());
			CHECK(t == false);

			rspamd::local_weak_ptr<deleter_test> wp(pi);
			CHECK(wp.lock().get() != nullptr);
			CHECK(pi.use_count() == 1);
			CHECK(wp.use_count() == 1);
			pi.reset();
			CHECK(pi.use_count() == 0);
			CHECK(wp.use_count() == 0);
		}

		CHECK(t == true);

		rspamd::local_weak_ptr<deleter_test> wp;
		{
			rspamd::local_shared_ptr<deleter_test> pi(new deleter_test{t});
			wp = pi;
			CHECK(!wp.expired());
			CHECK(wp.lock().get() != nullptr);
		}

		CHECK(t == true);
		CHECK(wp.expired());
	}

	TEST_CASE("std::swap")
	{
		bool t;

		{
			rspamd::local_shared_ptr<deleter_test> pi(new deleter_test{t});
			CHECK(pi.use_count() == 1);
			CHECK(pi.unique());
			CHECK(t == false);

			rspamd::local_shared_ptr<deleter_test> pi1;
			CHECK(pi1.get() == nullptr);
			CHECK(pi1.use_count() == 0);
			std::swap(pi1, pi);
			CHECK(pi.use_count() == 0);
			CHECK(pi.get() == nullptr);
			CHECK(pi1.get() != nullptr);
			std::swap(pi, pi1);
			CHECK(pi.use_count() != 0);
			CHECK(pi.get() != nullptr);
			CHECK(pi1.get() == nullptr);
		}

		CHECK(t == true);
	}

	TEST_CASE("std::hash")
	{
		bool v;
		deleter_test dt(v);
		CHECK(std::hash<deleter_test>()(dt) == 42);
		auto pi = rspamd::local_make_shared<deleter_test>(v);
		rspamd::local_shared_ptr<deleter_test> pi1;
		CHECK(std::hash<decltype(pi)>()(pi) == 42);
		// No hash for nullptr, different from std::smart_pointers!
		CHECK_THROWS(std::hash<decltype(pi)>()(pi1));
	}
}

#endif//RSPAMD_RSPAMD_CXX_LOCAL_PTR_HXX
