// Copyright (c) 2020-2023, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//

//! @file
//!
//! @brief Helpers related to define interfaces and interface attributes.
#pragma once

#include <type_traits>

#ifdef OMNI_BIND

// OMNI_ATTR("enum,prefix=eState") --> __attribute__((annotate("omni_attr:enum,prefix=eState")))
#    define OMNI_ATTR(attrs_) __attribute__((annotate("omni_attr:" attrs_)))

#else

//! Provides additional contextual information to the 'omni.bind' code generation tool.  This information is used to
//! generated efficient bindings to the interface.
//!
//! For example, if an ABI method accepts a const pointer than cannot be null, the method will be bound to an API layer
//! method that accepts a C++ reference.
//!
//! See @rstdoc{../../../../docs/omni.bind/omni.bind} for an overview of `OMNI_ATTR` and the <i>omni.bind</i> tool.
#    define OMNI_ATTR(p0_)

#endif

//! Macro to access generated API of an interface.
#define OMNI_GENERATED_API(iface_) omni::core::Generated<iface_##_abi>

//! Used in cases when defined interface provides an overload for a function from generated API.
//!
//! In those situations generated API becomes unavailable and member function from generated API
//! has to be introduced explicitly:
//!
//! @code{.cpp}
//!     OMNI_DEFINE_INTERFACE_API(omni::windowing::IWindow)
//!     {
//!     public:
//!         OMNI_USE_FROM_GENERATED_API(omni::windowing::IWindow, overloadedFunction)
//!
//!         void overloadedFunction(int overloadedArg)
//!         {
//!         }
//!     };
//! @endcode
//!
#define OMNI_USE_FROM_GENERATED_API(iface_, func_) using OMNI_GENERATED_API(iface_)::func_;

//! Used to forward declare an interface.
//!
//! The given class name must not include namespaces.  Rather, this macro must be invoked within the proper namespace.
//!
//! When _defining_ an interface in a header file, either @ref OMNI_DECLARE_INTERFACE() or @ref
//! OMNI_DEFINE_INTERFACE_API() should be invoked.
#define OMNI_DECLARE_INTERFACE(iface_)                                                                                 \
    class iface_##_abi;                                                                                                \
    /**                                                                                                                \
     Typedef for API wrapper of iface_##_abi                                                                           \
     @see omni::core::Api, iface_##_abi                                                                                \
     */                                                                                                                \
    using iface_ = omni::core::Api<iface_##_abi>;

//! Used to extend the <i>omni.bind</i> generated API layer.
//!
//! This macro handles the `class` line of a C++ class.  Use this macro in the following manner:
//!
//! @code{.cpp}
//!   OMNI_DEFINE_INTERFACE_API(omni::windowing::IWindow)
//!   {
//!   public:
//!      inline ObjectPtr<input::IKeyboardOnEventConsumer> getConsumer() noexcept
//!      { /* ... */ }
//!   };
//! @endcode
#define OMNI_DEFINE_INTERFACE_API(iface_)                                                                              \
    /**                                                                                                                \
     * Implements the generated API layer for iface_                                                                   \
     */                                                                                                                \
    template <>                                                                                                        \
    class omni::core::Api<iface_##_abi> : public OMNI_GENERATED_API(iface_)

//! @def OMNI_BIND_INCLUDE_INTERFACE_DECL
//!
//!  By defining this macro before including a header generated by *omni.bind*, only the declaration of any generated
//!  boiler-plate code is included.
//!
//! @see OMNI_BIND_INCLUDE_INTERFACE_IMPL

//! @def OMNI_BIND_INCLUDE_INTERFACE_IMPL
//!
//!  By defining this macro before including a header generated by *omni.bind*, only the implementations of any
//!  generated boiler-plate code is included.
//!
//! @see OMNI_BIND_INCLUDE_INTERFACE_DECL

namespace carb
{

template <typename T>
class ObjectPtr; // forward declaration needed by ObjectParam

}

namespace omni
{
namespace core
{

//! Templated class to store generated code from the <i>omni.bind</i> code generator.
//!
//! See @ref omni::core::Api for how this class is used.
template <typename T>
class Generated : public T
{
};

//! The API layer of an Omniverse interface.
//!
//! This template is the main construct used by users to interact with Omniverse interfaces.
//!
//! This template inherits from the @ref omni::core::Generated template which in turn inherits from another @ref
//! omni::core::Api template instantiation. Eventually, we'll find that the root base class is @ref IObject_abi.  For
//! example, @ref omni::log::ILog has the following inheritance chain:
//!
//!     Api<omni::log::ILog_abi>                    // interface author bindings for ILog
//!       Generated<omni::log::ILog_abi>            // omni.bind (generated) bindings for ILog
//!         omni::log::ILog_abi                     // raw ABI (defined by author)
//!           Api<omni::core::IObject_abi>          // hand-written bindings to IObject
//!             Generated<omni::core::IObject_abi>  // omni.bind (generated) bindings for IObject
//!               omni::core::IObject_abi           // raw ABI
//!
//! where:
//!
//! @code{.cpp}
//!   namespace omni::log
//!   {
//!       using ILog = Api<omni::log::ILog_abi>;
//!   }
//! @endcode
//!
//! Each point in the inheritance chain serves a different component in the system:
//!
//! - The `_abi` layer defines the raw ABI. The `_abi` object's methods define how the interface can be used across DLL
//!   boundaries.  Defining a stable ABI is the primary goal of Omniverse interfaces.  In practice, methods in the ABI
//!   have many restrictions.  See @oni_overview for details.
//!
//! - The @ref Generated template gives the <i>omni.bind</i> code generation tool a place to create a template
//!   specialization that defines boiler-plate methods that make using the ABI easier.  Again, see @oni_overview for
//!   details.
//!
//! - The @ref Api template gives the interface author a place to create wrappers (via specialization) to make using the
//!   interface easier. Since the @ref Api layer inherits from the @ref Generated layer, specializing the the @ref Api
//!   layer allows interface authors to augment the boiler-plate code generated by <i>omni.bind</i>.  Interfaces authors
//!   should use the @ref OMNI_DEFINE_INTERFACE_API() macro to specialize the @ref Api layer.
//!
//! Expected usage is:
//!
//! @code{.cpp}
//!   // forward declare the interface.  this sets up the Api<> typedef.
//!   OMNI_DECLARE_INTERFACE(foo::IMyInteface);
//!
//!   namespace foo {
//!   class IMyInterface_abi : public omni::core::Inherits<omni::core::IObject, OMNI_TYPE_ID("foo.IMyInterface")>
//!   {
//!   protected:
//!       void myMethod_abi(uint32_t x) noexcept = 0;
//!       // ...
//!   };
//!   } // namespace foo
//!
//!   // include code generated by the omni.bind tool.
//!   // this include should be outside of any namespace {} blocks
//!   #include "IMyInterface.gen.h"
//!
//!   // use OMNI_DEFINE_INTERFACE_API() to add hand-written API wrappers
//!   // this macro should be invoked outside of any namespace {} blocks
//!   OMNI_DEFINE_INTERFACE(foo::IMyInterface_abi)
//!   {
//!       void myReallyCoolMethod() { /* inline code here */ }
//!       // ...
//!   };
//! @endcode
template <typename T>
class Api : public Generated<T>
{
};

template <typename T>
class ObjectPtr; // forward declaration needed by ObjectParam

//! Helper object used by <i>omni.bind</i> to ease, at zero cost, the acceptance of raw and smart pointers to methods
//! that wish to accept a raw pointer.
//!
//! This object should never be used outside of <i>omni.bind</i>.
template <typename T>
class ObjectParam
{
public:
    //! Accept a smart pointer of different type.
    template <typename Y>
    ObjectParam(const ObjectPtr<Y>& o, typename std::enable_if_t<std::is_base_of<T, Y>::value>* = nullptr) noexcept
        : m_ptr{ o.get() }
    {
    }

    //! Accept a Carbonite smart pointer of different type.
    template <typename Y>
    ObjectParam(const carb::ObjectPtr<Y>& o) noexcept : m_ptr{ o.get() }
    {
    }

    //! Accept a raw pointer.
    ObjectParam(T* o) noexcept : m_ptr{ o }
    {
    }

    //! Arrow operator.
    T* operator->() const noexcept
    {
        return m_ptr;
    }

    //! Access raw pointer.
    T* get() const noexcept
    {
        return m_ptr;
    }

    //! Returns true if the wrapped pointer is not `nullptr`.
    explicit operator bool() const noexcept
    {
        return m_ptr != nullptr;
    }

private:
    T* m_ptr;
};

} // namespace core
} // namespace omni
