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

#include <tango/common/defines.h>

#ifdef TANGO_USE_TELEMETRY

  #include <string>
  #include <unordered_set>
  #include <variant>
  #include <vector>

namespace Tango::telemetry
{

//-----------------------------------------------------------------------------------------------------------------
//! The telemetry configuration class
//-----------------------------------------------------------------------------------------------------------------
class Configuration
{
  public:
    //-----------------------------------------------------------------------------------------------------------------
    //  CONFIGURATION
    //-----------------------------------------------------------------------------------------------------------------
    //-----------------------------------------------------------------------------------------------------------------
    //! The kinds of configuration.
    //-----------------------------------------------------------------------------------------------------------------
    enum class Kind
    {
        //- telemetry for pure Tango clients.
        Client,
        //- telemetry for Tango servers (i.e., Tango devices).
        Server
    };

    //-----------------------------------------------------------------------------------------------------------------
    //! Some specific configuration info for clients.
    //-----------------------------------------------------------------------------------------------------------------
    struct Client
    {
        // tell the world that this struct relies on default ctors and assignment operators
        Client() = default;
        Client(const Client &) = default;
        Client &operator=(const Client &) = default;
        Client &operator=(Client &&) = default;

        Client(std::string name) :
            name(std::move(name))
        {
        }

        Client(const char *name) :
            name(name)
        {
        }

        //- the pure Tango client name: used as the "service name" (opentelemetry semantic convention)
        std::string name{"undefined Tango client name"};
    };

    //-----------------------------------------------------------------------------------------------------------------
    //! Some specific configuration info for servers (i.e. Tango devices).
    //-----------------------------------------------------------------------------------------------------------------
    struct Server
    {
        // tell the world that this struct relies on default ctors and assignment operators
        Server() = default;
        Server(const Server &) = default;
        Server &operator=(const Server &) = default;
        Server &operator=(Server &&) = default;

        Server(std::string class_name, std::string device_name) :
            class_name(std::move(class_name)),
            device_name(std::move(device_name))
        {
        }

        Server(const char *class_name, const char *device_name) :
            class_name(class_name),
            device_name(device_name)
        {
        }

        //- the Tango class name: used as the "service.name" (opentelemetry semantic convention)
        std::string class_name{"undefined Tango class name"};
        //- the Tango device name: used as the "service.instance.id" (opentelemetry semantic convention)
        std::string device_name{"undefined Tango device name"};
    };

    using ServerClientDetails = std::variant<Server, Client>;

    //! Available exporter types for logs and traces
    //! \see Configuration::get_exporter_from_env
    enum class Exporter
    {
        grpc,
        http,
        console
    };

    //! Type of telemetry supported by Tango (e.g., tracing, logging, or [in the future] metrics)
    enum class TelemetryType
    {
        tracing,
        logging,
        none
    };

    //! Telemetry 'topic' to 'subscribe' to (e.g., all, database, events, polling, user)
    enum class TelemetryTopic
    {
        database,
        events,
        polling,
        user,
        all
    };

    //! Collectors are exporter/endpoint pairs, where the endpoints are strings of the form
    //! "http://addr:port/..." or "grpc://addr:port" or "cout"/"cerr"
    struct Collector
    {
        Exporter exporter_type;
        std::string endpoint;

        Collector(Exporter exp_type, std::string end) :
            exporter_type(exp_type),
            endpoint(end)
        {
        }

        bool operator==(const Collector &arg)
        {
            return (exporter_type == arg.exporter_type) && (endpoint == arg.endpoint);
        }
    };

    Configuration(std::string id, std::string name_space, ServerClientDetails details);

    // default configuration
    Configuration();

    // tell the world that this struct relies on default copy and assignment operators
    Configuration(const Configuration &) = default;
    Configuration &operator=(const Configuration &) = default;
    Configuration &operator=(Configuration &&) = default;

    //! The batch size for traces
    std::size_t traces_batch_size{Configuration::DEFAULT_TRACES_BATCH_SIZE};

    //! The batch size for logs
    std::size_t logs_batch_size{Configuration::DEFAULT_LOGS_BATCH_SIZE};

    //! The max queue size for traces and logs - threshold above witch signals are dropped
    std::size_t max_batch_queue_size{Configuration::DEFAULT_MAX_BATCH_QUEUE_SIZE};

    //! The delay (in ms) after which a batch processing is scheduled whatever is the number of pending signals in the
    //! queue
    std::size_t batch_schedule_delay_in_milliseconds{Configuration::DEFAULT_BATCH_SCHEDULE_DELAY};

    //! Get the 'kind' of the configuration.
    //! \see Configuration::Kind.
    Configuration::Kind get_kind() const noexcept;

    //! Check the 'kind' of the configuration.
    //! Returns true if the Configuration is of the specified Configuration::Kind, returns false otherwise.
    //!
    //! @param kind The Configuration::Kind to be compared to the actual kind of the configuration.
    //!
    //! \see Configuration::Kind.
    bool is_a(const Configuration::Kind &kind) const noexcept;

    //! Extract "host:port" from specified grpc endpoint.
    //! An empty string is returned if the specified endpoint is invalid.
    //! A valid telemetry grpc data collector endpoint is of the form: "grpc://addr:port".
    //!
    //! @param endpoint The telemetry endpoint from which the 'host:port' substring is to extracted.
    std::string extract_grpc_host_port(const std::string &endpoint) const noexcept;

    //! Set to true (false is the default) to enable the interface.
    //! A disabled interface bypasses the telemetry system
    bool enabled{false};

    //! The logging type is enabled by default; disabled logging provides a no-op logger.
    bool logging_enabled{true};
    //! The tracing type is enabled by default; disabled tracing provides a no-op tracer.
    bool tracing_enabled{true};

    //! Enable telemetry for any supplied types, disable otherwise
    void enable_telemetry_types(const std::string &type_str);

    //! An optional telemetry interface name (identifier)
    std::string id;

    //! The "name space" or "subsystem name" to which the Interface owner belongs to (for logical organisation of
    //! the telemetry signals). Things like "diagnostics" or "power-supplies" are examples of name spaces.
    std::string name_space;

    //! The client or server details.
    //! \see Configuration::Client and Configuration::Server.
    ServerClientDetails details;

    //! The telemetry data collectors for traces/logs
    using CollectorSet = std::vector<Collector>;

    //! Set the logging/tracing collectors from strings or collector sets; the source is for logging purposes
    void set_tracing_collectors(const std::string &exporters_str,
                                const std::string &endpoints_str,
                                const std::string &source);
    void set_logging_collectors(const std::string &exporters_str,
                                const std::string &endpoints_str,
                                const std::string &source);
    void set_tracing_collectors(CollectorSet collectors);
    void set_logging_collectors(CollectorSet collectors);

    //! Get logging/tracing collectors
    CollectorSet get_tracing_collectors() const;
    CollectorSet get_logging_collectors() const;

    //! Add/remove collectors from logging/tracing
    bool add_tracing_collector(Collector collector);
    bool add_logging_collector(Collector collector);
    bool remove_tracing_collector(const Collector &collector);
    bool remove_logging_collector(const Collector &collector);

    //! Helper methods for creating collectors
    static Collector create_collector_from_endpoint(const std::string &exporter, std::string endpoint);
    static Collector create_collector_from_endpoint(Exporter exporter, std::string endpoint);

    //! Set the topics either from a vector or comma-separated string
    void set_topics(const std::string &input_str);
    void set_topics(const std::vector<std::string> &input_topics);

    //! Get topics as a vector of strings
    std::vector<std::string> get_topics() const;

    //! Check for the existence of (possibly many) topics in the set topics
    bool check_for_topic(const std::string &topic_str) const;
    bool check_for_topic(TelemetryTopic topic) const;

    //! Check if all topics are allowed
    bool accept_all_spans()
    {
        return topics.find(TelemetryTopic::all) != topics.end();
    }

    //! Helper method to convert string to topic
    static TelemetryTopic to_topic(const std::string &str);

  private:
    //! Hold the configuration collectors and topics
    CollectorSet tracing_collectors, logging_collectors;
    std::unordered_set<TelemetryTopic> topics;

    //! Get the enabled telemetry types (logging, tracing, ...) from the dedicated environment variable
    //! If the variable is not set then the types default to true. If 'none' is among the types then all
    //! types are disabled.
    void enable_telemetry_types_from_env(const char *type_env_var);

    //! Set the list of topics from the dedicated environment variable. If the variable is not set then
    //! then default to 'all'
    void set_topics_from_env(const char *topic_env_var);

    //! Set the tracing_collectors vector from the dedicated environment variables for tracing exporters
    //! and endpoints. If the exporter variable is not set then the default is used. If any endpoints are
    //! set then the number of endpoints must match the number of exporters. If no endpoints are set then
    //! the defaults are used.
    void set_tracing_collectors_from_env(const char *exporter_env_var, const char *endpoint_env_var);

    //! Set the logging_collectors vector from the dedicated environment variables for logging exporters
    //! and endpoints. If the exporter variable is not set then the default is used. If any endpoints are
    //! set then the number of endpoints must match the number of exporters. If no endpoints are set then
    //! the defaults are used.
    void set_logging_collectors_from_env(const char *exporter_env_var, const char *endpoint_env_var);

    //! Parse the dedicated environment variables for exporters and endpoints. If the exporter variable is
    //! not set then the default is used. If any endpoints are set then the number of endpoints must match
    //! the number of exporters. If no endpoints are set then the defaults are used.
    void convert_exporter_and_endpoint_strings(const std::string &exporter_str,
                                               const std::string &endpoint_str,
                                               const std::string &source,
                                               std::vector<Exporter> &exporters,
                                               std::vector<std::string> &endpoints) const;

    //! Add a Collector to the given CollectorSet if not already present; return true if added
    bool add_collector(CollectorSet &collectorSet, Collector collector);

    //! Remove a Collector from the given CollectorSet if present; return true if removed
    bool remove_collector(CollectorSet &collectorSet, const Collector &collector);

    //! Return the default endpoint for a tracing exporter
    static std::string get_tracing_default_endpoint(Exporter exporter);

    //! Return the default endpoint for a logging exporter
    static std::string get_logging_default_endpoint(Exporter exporter);

    //! Throw if Tango is compiled without exporter support or if the endpoint is invalid for the exporter
    static void validate_exporter_and_endpoint(Exporter exporter, const std::string &endpoint);

    //! Check the syntactic validity of the specified endpoint.
    //! Returns true if the specified string contains a syntactically valid http endpoint, returns false otherwise.
    //! A valid telemetry http data collector endpoint is of the form: "http://addr:port/...".
    //!
    //! @param endpoint The telemetry endpoint to be checked.
    static bool is_valid_http_endpoint(const std::string &endpoint) noexcept;

    //! Check the syntactic validity of the specified endpoint.
    //! Returns true if the specified string contains a syntactically grpc endpoint, returns false otherwise.
    //! A valid telemetry grpc data collector endpoint is of the form: "grpc://addr:port".
    //!
    //! @param endpoint The telemetry endpoint to be checked.
    static bool is_valid_grpc_endpoint(const std::string &endpoint) noexcept;

    //! Check the syntactic validity of the specified endpoint.
    //! Returns true if the specified string contains a syntactically console endpoint, returns false otherwise.
    //! A valid telemetry console data collector endpoint is of the form "cout" or "cerr"
    //!
    //! @param endpoint The telemetry endpoint to be checked.
    static bool is_valid_console_endpoint(const std::string &endpoint) noexcept;
    inline static const Exporter kDefaultExporter{Exporter::console};

    //! Parse the given string as Exporter, throws on error
    static Exporter to_exporter(std::string_view str);

    //! Parse the string as a telemetry type, throw if not
    static TelemetryType to_type(std::string_view str);

    //-----------------------------------------------------------------------------------------------------------------
    //! The default gRPC endpoint to which the telemetry data is exported: grpc://localhost:4317
    //-----------------------------------------------------------------------------------------------------------------
    static const std::string DEFAULT_GRPC_TRACES_ENDPOINT;

    //-----------------------------------------------------------------------------------------------------------------
    //! The default HTTP endpoint to which the telemetry data is exported: http://localhost:4318/v1/traces
    //-----------------------------------------------------------------------------------------------------------------
    static const std::string DEFAULT_HTTP_TRACES_ENDPOINT;

    //-----------------------------------------------------------------------------------------------------------------
    //! The default console endpoint to which the telemetry data is exported: cout
    //-----------------------------------------------------------------------------------------------------------------
    static const std::string DEFAULT_CONSOLE_TRACES_ENDPOINT;

    //-------------------------------------------------------------------------------------------------
    //! The default endpoint to which logs are exported
    //-------------------------------------------------------------------------------------------------
    static const std::string DEFAULT_GRPC_LOGS_ENDPOINT;

    //-------------------------------------------------------------------------------------------------
    //! The default endpoint to which logs are exported
    //-------------------------------------------------------------------------------------------------
    static const std::string DEFAULT_HTTP_LOGS_ENDPOINT;

    //-----------------------------------------------------------------------------------------------------------------
    //! The default console endpoint to which the telemetry data is exported
    //-----------------------------------------------------------------------------------------------------------------
    static const std::string DEFAULT_CONSOLE_LOGS_ENDPOINT;

    //-----------------------------------------------------------------------------------------------------------------
    //! The default batch size for traces
    //-----------------------------------------------------------------------------------------------------------------
    static const std::size_t DEFAULT_TRACES_BATCH_SIZE;

    //-----------------------------------------------------------------------------------------------------------------
    //! The default batch size for logs
    //-----------------------------------------------------------------------------------------------------------------
    static const std::size_t DEFAULT_LOGS_BATCH_SIZE;

    //--------------------------------------------------------s---------------------------------------------------------
    //! The default max batch queue size (threshold above which signals are dropped - common to traces and logs)
    //-----------------------------------------------------------------------------------------------------------------
    static const std::size_t DEFAULT_MAX_BATCH_QUEUE_SIZE;

    //-----------------------------------------------------------------------------------------------------------------
    //! The default delay (in ms) after which a batch processing is scheduled whatever is the number of pending signals
    //! in the queue (common to traces and logs)
    //-----------------------------------------------------------------------------------------------------------------
    static const std::size_t DEFAULT_BATCH_SCHEDULE_DELAY;
};

std::string to_string(Configuration::Exporter exporter_type);
std::string to_string(Configuration::TelemetryType telemetry_type);
std::string to_string(Configuration::TelemetryTopic topic);

} // namespace Tango::telemetry

#endif // TANGO_USE_TELEMETRY

#endif // TANGO_COMMON_TELEMETRY_CONFIGURATION_H
