/*
 * SPDX-FileCopyrightText: 2024 Copyright contributors to the cppTango project
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */
#ifndef TANGO_COMMON_TELEMETRY_MACROS_H
#define TANGO_COMMON_TELEMETRY_MACROS_H

#include <tango/common/defines.h>

#ifdef TANGO_USE_TELEMETRY

  #include <tango/common/telemetry/Interface.h>
  #include <tango/common/telemetry/InterfaceFactory.h>
  #include <tango/common/telemetry/InterfaceScope.h>
  #include <tango/common/telemetry/Scope.h>
  #include <tango/common/telemetry/Span.h>
  #include <tango/common/telemetry/TelemetryAppender.h>

  #include <string_view>

//
// telemetry_current_function
//
// Parse TANGO_CURRENT_FUNCTION to remove the return type and argument list from the
// function signature, i.e., to only provide the fully qualified function name
//
constexpr std::string_view telemetry_current_function(const char *tango_current_function, const char *function_name)
{
    std::string_view fullname{tango_current_function};
    std::string_view shortname{function_name};
    // Find the function name from the right; everything to the right of the function
    // name should only be types
    size_t right_index = fullname.rfind(shortname);
    if(right_index == std::string_view::npos || right_index < 1)
    {
        return fullname;
    }

    // The fully qualified method name will have a space before it, but class template
    // descriptions may also contain spaces so we parse those too
    size_t left_index = fullname.rfind(' ', right_index);
    if(fullname.find('>', left_index) != std::string_view::npos)
    {
        size_t template_start = fullname.rfind('<', right_index);
        if(template_start < left_index)
        {
            left_index = fullname.rfind(' ', template_start);
        }
    }
    left_index = left_index == std::string_view::npos ? 0 : left_index + 1;

    // Test for clang-like formatting of returned *function* pointers
    if(fullname[left_index] == '(')
    {
        ++left_index;
    }
    // Test for clang-like formatting of returned pointers/references
    if((fullname[left_index] == '*') || (fullname[left_index] == '&'))
    {
        ++left_index;
    }
    return fullname.substr(left_index, right_index - left_index + shortname.size());
}

  #define TELEMETRY_CURRENT_FUNCTION                                   \
      std::string                                                      \
      {                                                                \
          telemetry_current_function(TANGO_CURRENT_FUNCTION, __func__) \
      }

    //---------------------------------------------------------------------------------------------------------------------
    // Implementation note
    //
    // We decided to totally hide the underlying telemetry dependency (opentelemetry) using the "PIMPL" the idiom. The
    // latter makes use of opaque implementation classes to which the actual 'actions' are delegated - the aim is to
    // provide a stable ABI by allowing to change the underlying implementation without impacting users' code.
    //
    // See the iso-cpp guidelines on PIMPL:
    // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#i27-for-stable-library-abi-consider-the-pimpl-idiom
    //---------------------------------------------------------------------------------------------------------------------

    //! @defgroup TelMacros Telemetry macros
    //!
    //! The macros are the only supported way of interacting with the cppTango
    //! telemetry infrastructure for device client/server authors. The underlying C++
    //! interface can change without notice.
    //!
    //! Usage example:
    //!
    //! \code
    //!
    //! void MyDeviceClass::read_some_attribute(Tango::Attribute &attr)
    //!  {
    //!     //-----------------------------------------------------------------------------------
    //!     // The telemetry interface of the device has been activated by the kernel upon
    //!     // reception of a "remote" call. We can consequently generate traces on behalf of
    //!     // our device in this context and contribute to the "end-to-end" observability.
    //!     // ----------------------------------------------------------------------------------
    //!     // Let's create a span then make it the active one using a scope.
    //!     // The scope makes use of teh RAII paradigm to control how long the span remains
    //!     // active. The active span will be the parent of any span created downstream.
    //!     //-----------------------------------------------------------------------------------
    //!     auto span = TANGO_TELEMETRY_SPAN(TELEMETRY_CURRENT_FUNCTION);
    //!     auto scope = TANGO_TELEMETRY_SCOPE(span);
    //!
    //!     // -----------------------------------------------------------------------------------
    //!     // Now, let's add an attribute (as a key/value pair) to the span. An attribute is
    //!     // purely user defined and there is no semantic convention to respect. This a contextual
    //!     // information that will be propagated together with the span and that will available
    //!     // as a meta-data in the backend (e.g., as a search criteria). We can do so using the
    //!     // span object or the TANGO_TELEMETRY_ADD_ATTRIBUTE macro. The key must be a std::string while
    //!     // the value must be one of the types supported by Tango::telemetry::AttributeValue.
    //!     //-----------------------------------------------------------------------------------
    //!     TANGO_TELEMETRY_ADD_ATTRIBUTE("data_update_counter", get_data_update_counter());
    //!
    //!     try
    //!     {
    //!          ...
    //!          // add an annotations to the span (an event in the OpenTelemetry jargon)
    //!          TANGO_TELEMETRY_ADD_EVENT("so far, so good...");
    //!          ...
    //!          // end the span for precise profiling
    //!          // will be ended automatically otherwise when it goes out of scope
    //!          span->end();
    //!      }
    //!      catch(...)
    //!      {
    //!          // tell the world that an error occurred in the span
    //!          auto err_msg = Tango::telemetry::extract_exception_info();
    //!          TANGO_TELEMETRY_SET_ERROR_STATUS(err_msg);
    //!          throw(...);
    //!      }
    //!
    //!      //------------------------------------------------------------------------
    //!      // the span and the scope goes out of (function) scope
    //!      //------------------------------------------------------------------------
    //!      // when released, the scope restores the previously active span
    //!      // when released, the span is automatically ended (if not already ended)
    //!      //------------------------------------------------------------------------
    //!  }
    //!
    //! \endcode
    //!
    //! @ingroup TelMacros

    //! Snapshots which interface is currently attached to the current thread then attaches
    //! the specified one. Uses the magic provide by the C++11 "thread_local" keyword.
    //! The interface that was initially active is restored when the underlying InterfaceScope
    //! goes out of scope (RAII paradigm).
    //!
    //! Usage example:
    //!
    //! \code
    //!
    //!    void myClient()
    //!    {
    //!        Tango::telemetry::Configuration cfg{
    //!                                 true,
    //!                                 false,
    //!                                 "Some Diagnostics Application",
    //!                                 "diagnostics",
    //!                                 Tango::telemetry::Configuration::Client{"myDiagnosticsClientApp"},
    //!                                 Tango::telemetry::Configuration::DEFAULT_COLLECTOR_ENDPOINT};
    //!
    //!        Tango::telemetry::InterfacePtr tti = Tango::telemetry::InterfaceFactory(cfg);
    //!
    //!        // activate the interface (make it the "current" one for the thread executing this code)
    //!        auto interface_scope = TANGO_TELEMETRY_ACTIVE_INTERFACE(tti);
    //!
    //!        // create the client root span (i.e., the parent of any span created in this thread)
    //!        auto span = TANGO_TELEMETRY_SPAN(TELEMETRY_CURRENT_FUNCTION);
    //!        auto scope = TANGO_TELEMETRY_SCOPE(span);
    //!
    //!        while (go_on)
    //!        {
    //!            // generate traces on behalf of the client
    //!            ...
    //!        }
    //!    }
    //!
    //! \endcode
    //!
    //! @param [in] TI -- Interface pointer
    //!
    //! @ingroup TelMacros
  #define TANGO_TELEMETRY_ACTIVE_INTERFACE(TI) Tango::telemetry::InterfaceScope(TI)

    //! Start a new span.
    //!
    //! Usage:
    //!
    //! \code
    //!
    //!   auto span = TANGO_TELEMETRY_SPAN(TELEMETRY_CURRENT_FUNCTION);
    //!   auto span = TANGO_TELEMETRY_SPAN(TELEMETRY_CURRENT_FUNCTION, {{"myKey", "myValue"}});
    //!
    //! \endcode
    //!
    //!@ingroup TelMacros
  #define TANGO_TELEMETRY_SPAN(...) Tango::telemetry::Interface::get_current()->start_span(__VA_ARGS__)

    //! Start a new scope (i.e., makes the specified span the active one).
    //!
    //! The "location" (i.e., the file name and the line number) where the scope has been created is automatically
    //! added to the span attributes.
    //!
    //! Usage:
    //!
    //! \code
    //!
    //!    auto span = TANGO_TELEMETRY_SPAN(TELEMETRY_CURRENT_FUNCTION);
    //!    auto scope = TANGO_TELEMETRY_SCOPE(span);
    //!
    //! \endcode
    //!
    //! @param [in] SPAN -- telemetry span
    //! @ingroup TelMacros
  #define TANGO_TELEMETRY_SCOPE(SPAN) Tango::telemetry::Interface::get_current()->scope(SPAN, __FILE__, __LINE__)

    //! Add an event to the current span.
    //!
    //! Usage:
    //!
    //! \code
    //!
    //!    TANGO_TELEMETRY_ADD_EVENT("this annotation will be attached to the current span");
    //!
    //! \endcode
    //!
    //! @param [in] MSG -- annotation to attach to the current span
    //! @ingroup TelMacros
  #define TANGO_TELEMETRY_ADD_EVENT(MSG)                                                      \
      {                                                                                       \
          auto current_span = Tango::telemetry::Interface::get_current()->get_current_span(); \
          if(current_span)                                                                    \
          {                                                                                   \
              current_span->add_event(MSG);                                                   \
          }                                                                                   \
      }

    //! Add an attribute to the current span.
    //!
    //! The key must be a std::string while the value must be one of the types supported by
    //! Tango::telemetry::AttributeValue.
    //!
    //! Usage:
    //!
    //! \code
    //!
    //!    TANGO_TELEMETRY_ADD_ATTRIBUTE("somekey", someValue);
    //!
    //! \endcode
    //!
    //! @param [in] KEY -- telemetry attribute key
    //! @param [in] VALUE -- telemetry attribute value
    //! @ingroup TelMacros
  #define TANGO_TELEMETRY_ADD_ATTRIBUTE(KEY, VALUE)                                           \
      {                                                                                       \
          auto current_span = Tango::telemetry::Interface::get_current()->get_current_span(); \
          if(current_span)                                                                    \
          {                                                                                   \
              current_span->set_attribute(KEY, VALUE);                                        \
          }                                                                                   \
      }

    //! Set the error status of the current span.
    //!
    //! This is typically used in error/exception context.
    //!
    //! Usage:
    //!
    //! \code
    //!
    //!    try
    //!    {
    //!       do_some_job();
    //!    }
    //!    catch (...)
    //!    {
    //!      TANGO_TELEMETRY_SET_ERROR_STATUS("oops, an error occurred!");
    //!    }
    //!
    //! \endcode
    //!
    //! @param [in] DESC -- description of the error
    //! @ingroup TelMacros
  #define TANGO_TELEMETRY_SET_ERROR_STATUS(DESC)                                              \
      {                                                                                       \
          auto current_span = Tango::telemetry::Interface::get_current()->get_current_span(); \
          if(current_span)                                                                    \
          {                                                                                   \
              if(current_span->get_status() != Tango::telemetry::Span::Status::kError)        \
              {                                                                               \
                  current_span->set_status(Tango::telemetry::Span::Status::kError, DESC);     \
              }                                                                               \
          }                                                                                   \
      }

#endif // TANGO_USE_TELEMETRY

#endif // TANGO_COMMON_TELEMETRY_MACROS_H
