/* +------------------------------------------------------------------------+
   |                     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          |
   +------------------------------------------------------------------------+ */
/** \example rtti_example1/test.cpp */

//! [example-define-class]
#include <mrpt/rtti/CObject.h>

#include <iostream>
#include <memory>

namespace MyNS
{
class Foo : public mrpt::rtti::CObject
{
   public:
	Foo() {}
	DEFINE_MRPT_OBJECT(Foo, MyNS)

	void printName() { std::cout << "printName: Foo" << std::endl; }
};

class BarBase : public mrpt::rtti::CObject
{
   public:
	BarBase() {}
	DEFINE_VIRTUAL_MRPT_OBJECT(BarBase)

	virtual void printName() { std::cout << "printName: BarBase" << std::endl; }
};

class Bar : public BarBase
{
   public:
	Bar() {}
	DEFINE_MRPT_OBJECT(Bar, MyNS)

	void printName() override { std::cout << "class: Bar" << std::endl; }
	void specificBarMethod()
	{
		std::cout << "specificBarMethod: reached." << std::endl;
	}
};
}  // namespace MyNS

IMPLEMENTS_MRPT_OBJECT(Foo, mrpt::rtti::CObject, MyNS)
IMPLEMENTS_VIRTUAL_MRPT_OBJECT(BarBase, mrpt::rtti::CObject, MyNS)
IMPLEMENTS_MRPT_OBJECT(Bar, MyNS::BarBase, MyNS)

//! [example-define-class]

//! [example-define-class-test]
void Test_UserTypes()
{
	using namespace MyNS;
	const auto id_foo = CLASS_ID(Foo);
	std::cout << "RTTI Foo (static): " << id_foo->className << std::endl;

	// Pointers:
	Bar::Ptr pBar = std::make_shared<Bar>();
	BarBase::Ptr pBase = mrpt::ptr_cast<BarBase>::from(pBar);
	mrpt::rtti::CObject::Ptr pObj =
		mrpt::ptr_cast<mrpt::rtti::CObject>::from(pBar);

	pBar->printName();
	pBase->printName();
	std::cout << "Is Foo?   => " << (IS_DERIVED(*pObj, Foo) ? "Yes\n" : "No\n");
	std::cout << "Is BarBase? => "
			  << (IS_DERIVED(*pObj, BarBase) ? "Yes\n" : "No\n");
	std::cout << "Is Bar?  => " << (IS_DERIVED(*pObj, Bar) ? "Yes\n" : "No\n");
	if (IS_CLASS(*pObj, Bar))
	{
		auto pBar2 = mrpt::ptr_cast<Bar>::from(pObj);
		pBar2->specificBarMethod();
	}
}

//! [example-define-class-test]

//! [example-factory]
void do_register()
{
	// Register with explicit namespace:
	mrpt::rtti::registerClass(CLASS_ID_NAMESPACE(Foo, MyNS));
	{
		// Register without explicit namespace:
		using namespace MyNS;
		mrpt::rtti::registerClass(CLASS_ID(BarBase));
		mrpt::rtti::registerClass(CLASS_ID(Bar));
		mrpt::rtti::registerClassCustomName("MyNS::Bar", CLASS_ID(Bar));
	}
}

void Test_UserTypesFactory()
{
	do_register();

	// Test register:
	{
		const auto& allClasses = mrpt::rtti::getAllRegisteredClasses();
		for (const auto& cl : allClasses)
		{
			std::cout << "Known class: " << cl->className << ", children of "
					  << (cl->getBaseClass ? cl->getBaseClass()->className
										   : "(none)")
					  << std::endl;
		}
	}

	// Test factory:
	{
		mrpt::rtti::CObject::Ptr pObj = mrpt::rtti::classFactory("MyNS::Bar");
		if (IS_CLASS(*pObj, MyNS::Bar))
		{
			auto pBar = mrpt::ptr_cast<MyNS::Bar>::from(pObj);
			pBar->specificBarMethod();
		}
	}
}

//! [example-factory]

int main(int argc, char** argv)
{
	try
	{
		Test_UserTypes();
		Test_UserTypesFactory();
		return 0;
	}
	catch (const std::exception& e)
	{
		std::cerr << "MRPT error: " << mrpt::exception_to_str(e) << std::endl;
		return -1;
	}
}
