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

#include <mrpt/core/aligned_allocator.h>
#include <mrpt/core/safe_pointers.h>
#include <mrpt/typemeta/static_string.h>  // literal()

#include <memory>
#include <vector>

namespace mrpt
{
namespace rtti
{
/** @name RTTI classes and functions for polymorphic hierarchies
  @{ */
class CObject;

/** A structure that holds runtime class type information. Use
 * CLASS_ID(<class_name>) to get a reference to the class_name's TRuntimeClassId
 * descriptor.
 * \ingroup mrpt_rtti_grp
 */
struct TRuntimeClassId
{
  using Ptr = safe_ptr<TRuntimeClassId>;
  const char* className = nullptr;
  /** Create an object of the related class, or nullptr if it is virtual. */
  std::shared_ptr<CObject> (*ptrCreateObject)() = nullptr;

  /** Gets the base class runtime id. */
  const TRuntimeClassId* (*getBaseClass)() = nullptr;

  // Operations
  std::shared_ptr<CObject> createObject() const;
  bool derivedFrom(const TRuntimeClassId* pBaseClass) const;
  bool derivedFrom(const char* pBaseClass_name) const;
};

/** Register a class into the MRPT internal list of "CObject" descendents.
 *  Used internally in the macros DEFINE_SERIALIZABLE, etc...
 * \sa getAllRegisteredClasses
 */
void registerClass(const mrpt::rtti::TRuntimeClassId* pNewClass);

/** Mostly for internal use within mrpt sources, to handle exceptional cases
 * with multiple serialization names for backward compatibility
 * (CMultiMetricMaps, CImage,...)
 */
void registerClassCustomName(const char* customName, const TRuntimeClassId* pNewClass);

/** Returns a list with all the classes registered in the system through
 * mrpt::rtti::registerClass.
 * \sa registerClass, findRegisteredClass
 */
std::vector<const mrpt::rtti::TRuntimeClassId*> getAllRegisteredClasses();

/** Like getAllRegisteredClasses(), but filters the list to only include
 * children clases of a given base one.
 * \sa getAllRegisteredClasses(), getAllRegisteredClassesChildrenOf()  */
std::vector<const TRuntimeClassId*> getAllRegisteredClassesChildrenOf(
    const TRuntimeClassId* parent_id);

/** Return info about a given class by its name, or nullptr if the class is not
 * registered.
 *
 * The list of registered "namespaces::class_name" will be looked up first. If
 * no match is found, **and** `allow_ignore_namespace=true`, then a second
 * search will be performed looking for a match of the class name without the
 * namespace part. Note that this is enabled by default since namespaces were
 * not used while serializing classes in MRPT older than v2.0, so this option
 * allows reading from older datasets transparently. It could be set to false if
 * it is ensured that only mrpt2 datasets will be read.
 *
 * \param[in] className The name of the class to look up
 * \param[i] allow_ignore_namespace See discussion above
 *
 * \sa registerClass, getAllRegisteredClasses
 */
const TRuntimeClassId* findRegisteredClass(
    const std::string& className, const bool allow_ignore_namespace = true);

template <typename T>
struct CLASS_ID_impl
{
  static constexpr const mrpt::rtti::TRuntimeClassId* get()
  {
    return &T::GetRuntimeClassIdStatic();
  }
};
//(A specialization for variant's monostate is provided in CArchive.h)

/** Access to runtime class ID for a defined class name.
 */
#define CLASS_ID(T) mrpt::rtti::CLASS_ID_impl<T>::get()
// Convert these
#define CLASS_ID_TEMPLATE(class_name, T) mrpt::rtti::CLASS_ID_impl<T>::get()
#define CLASS_ID_NAMESPACE(class_name, namespaceName) \
  mrpt::rtti::CLASS_ID_impl<namespaceName::class_name>::get()

template <typename T>
struct IS_CLASS_impl
{
  template <typename REF>
  static bool check(const REF& p)
  {
    return p.GetRuntimeClass() == CLASS_ID_impl<T>::get();
  }
};

namespace internal
{
template <bool is_copy_ctrtible>
struct CopyCtor;
template <>
struct CopyCtor<true>
{
  template <typename T>
  static T* clone(const T& o)
  {
    return new T(o);
  }
};
template <>
struct CopyCtor<false>
{
  template <typename T>
  static T* clone(const T& o)
  {
    throw std::runtime_error(
        "clone(): Attempt to call copy ctor of non copy-constructible "
        "class.");
  }
};
}  // namespace internal

/** True if the given reference to object (derived from mrpt::rtti::CObject) is
 * of the given class. */
#define IS_CLASS(obj, class_name) mrpt::rtti::IS_CLASS_impl<class_name>::check(obj)

/** True if the given reference to object (derived from mrpt::rtti::CObject) is
 * an instance of the given class OR any of its derived classes. */
#define IS_DERIVED(obj, class_name) ((obj).GetRuntimeClass()->derivedFrom(CLASS_ID(class_name)))

/** Virtual base to provide a compiler-independent RTTI system.
 *
 * Each class named `Foo` will have associated smart pointer types:
 * - `Foo::Ptr` => `std::shared_ptr<Foo>` (the most commonly-used one)
 * - `Foo::ConstPtr` => `std::shared_ptr<const Foo>`
 * - `Foo::UniquePtr` => `std::unique_ptr<Foo>`
 * - `Foo::ConstUniquePtr` => `std::unique_ptr<const Foo>`
 *
 * It is recommended to use MRPT-defined `std::make_shared<>` instead
 * of `std::make_shared<>` to create objects, to avoid memory alignment
 * problems caused by classes containing Eigen vectors or matrices. Example:
 * \code
 * Foo::Ptr o = std::make_shared<Foo>();
 * \endcode
 * Or using the shorter auxiliary static method `::Create()` for conciseness or
 * to keep compatibility with MRPT 1.5.* code bases:
 * \code
 * Foo::Ptr o = Foo::Create();
 * \endcode
 * If a special memory allocator is needed, use `Foo::CreateAlloc(alloc,...);`.
 * \sa  mrpt::rtti::CObject
 * \ingroup mrpt_rtti_grp
 */
class CObject
{
 protected:
  static mrpt::rtti::TRuntimeClassId* _GetBaseClass();
  static constexpr mrpt::rtti::TRuntimeClassId runtimeClassId = {"CObject", nullptr, nullptr};

 public:
  using Ptr = std::shared_ptr<CObject>;
  using ConstPtr = std::shared_ptr<const CObject>;
  using UniquePtr = std::unique_ptr<CObject>;
  using ConstUniquePtr = std::unique_ptr<const CObject>;
  static const mrpt::rtti::TRuntimeClassId& GetRuntimeClassIdStatic();
  /** Returns information about the class of an object in runtime. */
  virtual const mrpt::rtti::TRuntimeClassId* GetRuntimeClass() const { return CLASS_ID(CObject); }

  /** Makes a deep copy of the object and returns a smart pointer to it */
  inline mrpt::rtti::CObject::Ptr duplicateGetSmartPtr() const;

  /** Returns a deep copy (clone) of the object, indepently of its class. */
  virtual CObject* clone() const = 0;

  virtual ~CObject() = default;
};  // End of class def.

inline mrpt::rtti::CObject::Ptr CObject::duplicateGetSmartPtr() const
{
  return mrpt::rtti::CObject::Ptr(this->clone());
}

/** This declaration must be inserted in all CObject classes definition, within
 * the class declaration. */
#define DEFINE_MRPT_OBJECT(class_name, NameSpace)                                \
  /*! @name RTTI stuff  */                                                       \
  /*! @{  */                                                                     \
 public:                                                                         \
  /*! A type for the associated smart pointer */                                 \
  using Ptr = std::shared_ptr<NameSpace::class_name>;                            \
  using ConstPtr = std::shared_ptr<const NameSpace::class_name>;                 \
  using UniquePtr = std::unique_ptr<NameSpace::class_name>;                      \
  using ConstUniquePtr = std::unique_ptr<const NameSpace::class_name>;           \
  static constexpr const char* className = #NameSpace "::" #class_name;          \
  static constexpr auto getClassName()                                           \
  {                                                                              \
    return mrpt::typemeta::literal(#NameSpace "::" #class_name);                 \
  }                                                                              \
  static const mrpt::rtti::TRuntimeClassId& GetRuntimeClassIdStatic();           \
  virtual const mrpt::rtti::TRuntimeClassId* GetRuntimeClass() const override;   \
  virtual mrpt::rtti::CObject* clone() const override;                           \
  static std::shared_ptr<CObject> CreateObject();                                \
  template <typename... Args>                                                    \
  static Ptr Create(Args&&... args)                                              \
  {                                                                              \
    return std::make_shared<class_name>(std::forward<Args>(args)...);            \
  }                                                                              \
  template <typename Alloc, typename... Args>                                    \
  static Ptr CreateAlloc(const Alloc& alloc, Args&&... args)                     \
  {                                                                              \
    return std::allocate_shared<class_name>(alloc, std::forward<Args>(args)...); \
  }                                                                              \
  template <typename... Args>                                                    \
  static UniquePtr CreateUnique(Args&&... args)                                  \
  {                                                                              \
    return std::make_unique<class_name>(std::forward<Args>(args)...);            \
  }                                                                              \
                                                                                 \
 protected:                                                                      \
  static const mrpt::rtti::TRuntimeClassId* _GetBaseClass();                     \
  static constexpr mrpt::rtti::TRuntimeClassId runtimeClassId = {                \
      #NameSpace "::" #class_name, NameSpace::class_name::CreateObject,          \
      &class_name::_GetBaseClass};                                               \
                                                                                 \
  /*! @} */                                                                      \
 public:

#define INTERNAL_IMPLEMENTS_MRPT_OBJECT(class_name, base, NameSpace)                     \
  mrpt::rtti::CObject::Ptr NameSpace::class_name::CreateObject()                         \
  {                                                                                      \
    return std::static_pointer_cast<CObject>(std::make_shared<NameSpace::class_name>()); \
  }                                                                                      \
  const mrpt::rtti::TRuntimeClassId* NameSpace::class_name::_GetBaseClass()              \
  {                                                                                      \
    return CLASS_ID(base);                                                               \
  }                                                                                      \
  const mrpt::rtti::TRuntimeClassId& NameSpace::class_name::GetRuntimeClassIdStatic()    \
  {                                                                                      \
    return NameSpace::class_name::runtimeClassId;                                        \
  }                                                                                      \
  const mrpt::rtti::TRuntimeClassId* NameSpace::class_name::GetRuntimeClass() const      \
  {                                                                                      \
    return CLASS_ID_NAMESPACE(class_name, NameSpace);                                    \
  }                                                                                      \
  mrpt::rtti::CObject* NameSpace::class_name::clone() const                              \
  {                                                                                      \
    return mrpt::rtti::internal::CopyCtor<                                               \
        std::is_copy_constructible<NameSpace::class_name>::value>::clone(*this);         \
  }

/** Must be added to all CObject-derived classes implementation file.
 * This registers class ns1::Foo as "ns1::Foo".
 */
#define IMPLEMENTS_MRPT_OBJECT(class_name, base, NameSpace) \
  INTERNAL_IMPLEMENTS_MRPT_OBJECT(class_name, base, NameSpace)

/** This declaration must be inserted in virtual CObject classes
 * definition:
 */
#define DEFINE_VIRTUAL_MRPT_OBJECT(class_name, NameSpace)                      \
  /*! @name RTTI stuff  */                                                     \
  /*! @{  */                                                                   \
 protected:                                                                    \
  static const mrpt::rtti::TRuntimeClassId* _GetBaseClass();                   \
  static constexpr mrpt::rtti::TRuntimeClassId runtimeClassId = {              \
      #NameSpace "::" #class_name, nullptr, &class_name::_GetBaseClass};       \
                                                                               \
 public:                                                                       \
  using Ptr = std::shared_ptr<class_name>;                                     \
  using ConstPtr = std::shared_ptr<const class_name>;                          \
  virtual const mrpt::rtti::TRuntimeClassId* GetRuntimeClass() const override; \
  static const mrpt::rtti::TRuntimeClassId& GetRuntimeClassIdStatic();         \
  /*! @}  */

/** This must be inserted as implementation of some required members for
 *  virtual CObject classes:
 */
#define INTERNAL_IMPLEMENTS_VIRTUAL_MRPT_OBJECT(class_name, base_name, NS)     \
  const mrpt::rtti::TRuntimeClassId* NS::class_name::_GetBaseClass()           \
  {                                                                            \
    return CLASS_ID(base_name);                                                \
  }                                                                            \
  const mrpt::rtti::TRuntimeClassId* NS::class_name::GetRuntimeClass() const   \
  {                                                                            \
    return CLASS_ID(class_name);                                               \
  }                                                                            \
  const mrpt::rtti::TRuntimeClassId& NS::class_name::GetRuntimeClassIdStatic() \
  {                                                                            \
    return NS::class_name::runtimeClassId;                                     \
  }

#define IMPLEMENTS_VIRTUAL_MRPT_OBJECT(class_name, base, NS) \
  INTERNAL_IMPLEMENTS_VIRTUAL_MRPT_OBJECT(class_name, base, NS)

/** Register all pending classes - to be called just before
 * de-serializing an object, for example. After calling this method,
 * pending_class_registers_modified is set to false until
 * pending_class_registers() is invoked.
 */
void registerAllPendingClasses();

/** Creates an object given by its registered name.
 * \sa findRegisteredClass(), registerClass() */
mrpt::rtti::CObject::Ptr classFactory(const std::string& className);

/** @}  */  // end of RTTI

}  // namespace rtti

/** Converts a polymorphic smart pointer Base::Ptr to Derived::Ptr, in a
 * way compatible with MRPT >=1.5.4 and MRPT 2.x series.
 * \ingroup mrpt_rtti_grp
 */
template <typename CAST_TO>
struct ptr_cast
{
  template <typename CAST_FROM_PTR>
  static typename CAST_TO::Ptr from(const CAST_FROM_PTR& ptr)
  {
    return std::dynamic_pointer_cast<CAST_TO>(ptr);
  }
};

}  // namespace mrpt
