/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2023, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#include <gtest/gtest.h>
#include <mrpt/config.h>
#include <mrpt/core/exceptions.h>

#include <algorithm>  // count()
#include <array>
#include <sstream>

TEST(exception, stackedExceptionBasic)
{
	EXPECT_THROW({ THROW_EXCEPTION("wtf"); }, mrpt::ExceptionWithCallBackBase);
}

template <typename T>
void test_except_3rd_lvl(T val)
{
	MRPT_START
	THROW_EXCEPTION("Aw!");
	val++;
	MRPT_END
}

void test_except_2nd_lvl()
{
	MRPT_START
	test_except_3rd_lvl<int>(10);
	MRPT_END
}

void test_except_toplevel()
{
	MRPT_START
	test_except_2nd_lvl();
	MRPT_END
}

#if !MRPT_IN_EMSCRIPTEN
TEST(exception, stackedExceptionComplex)
{
	try
	{
		test_except_toplevel();
		GTEST_FAIL() << "Shouldn't reach here.";
	}
	catch (const std::exception& e)
	{
		const auto sExc = mrpt::exception_to_str(e);

#if !defined(MRPT_EXCEPTIONS_WITH_CALL_STACK)
		EXPECT_TRUE(sExc.find("Aw!") != std::string::npos) << sExc;
#else
		const auto num_lines = std::count(sExc.begin(), sExc.end(), '\n');
		EXPECT_GT(num_lines, 10) << sExc;
		EXPECT_TRUE(sExc.find("Message:  Aw!") != std::string::npos) << sExc;
#endif
// This test doesn't pass in Windows if building w/o debug symbols:
#if defined(MRPT_EXCEPTIONS_WITH_CALL_STACK) &&                                \
	(!defined(_WIN32) || defined(_DEBUG))
		EXPECT_TRUE(sExc.find("test_except") != std::string::npos) << sExc;
#endif
	}
}
#endif

TEST(exception, assertException)
{
	bool trueValue = true;
	bool falseValue = false;
	EXPECT_THROW(
		{ ASSERT_EQUAL_(trueValue, falseValue); },
		mrpt::ExceptionWithCallBackBase);
}

namespace
{
std::string testFoo()
{
	try
	{
		std::vector<int> dummy;
		ASSERTMSG_(!dummy.empty(), "Check it");
		return {};
	}
	catch (std::exception& e)
	{
		const auto err = mrpt::exception_to_str(e);
		return err;
	}
}
}  // namespace

TEST(exception, infiniteRecurseBug)
{
	// Should not crash:
	const auto s = testFoo();
	EXPECT_TRUE(s.find("Check it") != std::string::npos);
}

TEST(exception, assertMacros)
{
	// ASSERT_EQUAL_
	EXPECT_NO_THROW(ASSERT_EQUAL_(1, 1));
	EXPECT_NO_THROW(ASSERT_EQUAL_(1 + 3, 1 + 1 + 1 + 1));
	EXPECT_ANY_THROW(ASSERT_EQUAL_(1, 2));
	EXPECT_ANY_THROW(ASSERT_EQUAL_(1 + 3, 3 + 2));

	// ASSERT_NOT_EQUAL_
	EXPECT_ANY_THROW(ASSERT_NOT_EQUAL_(1, 1));
	EXPECT_ANY_THROW(ASSERT_NOT_EQUAL_(1 + 3, 1 + 1 + 1 + 1));
	EXPECT_NO_THROW(ASSERT_NOT_EQUAL_(1, 2));
	EXPECT_NO_THROW(ASSERT_NOT_EQUAL_(1 + 3, 3 + 2));

	// ASSERT_NEAR_
	EXPECT_NO_THROW(ASSERT_NEAR_(10.0, 10.01, 0.2));
	EXPECT_NO_THROW(ASSERT_NEAR_(10.0 + 1.0, 10.0 + 1.01, 0.2));
	EXPECT_NO_THROW(ASSERT_NEAR_(1, 1, 0));
	EXPECT_ANY_THROW(ASSERT_NEAR_(1, 2, 0));
	EXPECT_ANY_THROW(ASSERT_NEAR_(10.0, 10.01, 0.001));
	EXPECT_ANY_THROW(ASSERT_NEAR_(10.0, 10.01f, 0.001));
	EXPECT_ANY_THROW(ASSERT_NEAR_(10.0, 10.01f, 0.001f));
	EXPECT_ANY_THROW(ASSERT_NEAR_(10.0f, 10.01, 0.001f));
	EXPECT_NO_THROW(ASSERT_NEAR_(10.0, 10.01f, 0.1));
	EXPECT_NO_THROW(ASSERT_NEAR_(10.0f, 10.01f, 0.1));
	EXPECT_NO_THROW(ASSERT_NEAR_(10.0f, 10.01, 0.1));
	EXPECT_NO_THROW(ASSERT_NEAR_(10.0, 10.01f, 0.1f));

	const std::array<double, 2> arr_dc = {10.0, 10.01};
	std::array<double, 2> arr_dnc = {10.0, 10.01};
	EXPECT_NO_THROW(ASSERT_NEAR_(arr_dc[0], arr_dc[1], 0.1));
	EXPECT_NO_THROW(ASSERT_NEAR_(arr_dc[0], arr_dnc[1], 0.1));
	EXPECT_NO_THROW(ASSERT_NEAR_(arr_dnc[0], arr_dc[1], 0.1));
	EXPECT_NO_THROW(ASSERT_NEAR_(arr_dnc[0], arr_dnc[1], 0.1));

	// ASSERT_LT_
	EXPECT_NO_THROW(ASSERT_LT_(1.0, 2.0));
	EXPECT_ANY_THROW(ASSERT_LT_(3, 2));
	EXPECT_ANY_THROW(ASSERT_LT_(-1, -3));

	// ASSERT_LE_
	EXPECT_NO_THROW(ASSERT_LE_(1.0, 2.0));
	EXPECT_NO_THROW(ASSERT_LE_(1.0, 1.0));

	// ASSERT_GT_
	EXPECT_ANY_THROW(ASSERT_GT_(1.0, 2.0));
	EXPECT_NO_THROW(ASSERT_GT_(3, 2));
	EXPECT_NO_THROW(ASSERT_GT_(-1, -3));

	// ASSERT_GE_
	EXPECT_NO_THROW(ASSERT_GE_(2.0, 1.0));
	EXPECT_NO_THROW(ASSERT_GE_(1.0, 1.0));
}
