/*
 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see .
 */
#ifndef METRIC_H__
#define METRIC_H__
#include "Define.h"
#include "Duration.h"
#include "MPSCQueue.h"
#include "Optional.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
namespace Trinity
{
    namespace Asio
    {
        class IoContext;
        class DeadlineTimer;
    }
}
enum MetricDataType
{
    METRIC_DATA_VALUE,
    METRIC_DATA_EVENT
};
using MetricTag = std::pair;
using MetricTagsVector = boost::container::small_vector;
struct MetricData
{
    std::string Category;
    SystemTimePoint Timestamp;
    MetricDataType Type;
    // LogValue-specific fields
    MetricTagsVector Tags;
    // LogEvent-specific fields
    std::string Title;
    std::string ValueOrEventText;
    // intrusive queue link
    std::atomic QueueLink;
};
class TC_COMMON_API Metric
{
private:
    std::iostream& GetDataStream() { return *_dataStream; }
    std::unique_ptr _dataStream;
    MPSCQueue _queuedData;
    std::unique_ptr _batchTimer;
    std::unique_ptr _overallStatusTimer;
    int32 _updateInterval = 0;
    int32 _overallStatusTimerInterval = 0;
    bool _enabled = false;
    bool _overallStatusTimerTriggered = false;
    std::string _hostname;
    std::string _port;
    std::string _databaseName;
    std::function _overallStatusLogger;
    std::string _realmName;
    std::unordered_map _thresholds;
    bool Connect();
    void SendBatch();
    void ScheduleSend();
    void ScheduleOverallStatusLog();
    static std::string FormatInfluxDBValue(bool value);
    template 
    static std::string FormatInfluxDBValue(T value);
    static std::string FormatInfluxDBValue(std::string const& value);
    static std::string FormatInfluxDBValue(char const* value);
    static std::string FormatInfluxDBValue(double value);
    static std::string FormatInfluxDBValue(float value);
    static std::string FormatInfluxDBValue(std::chrono::nanoseconds value);
    static std::string FormatInfluxDBTagValue(std::string const& value);
    // ToDo: should format TagKey and FieldKey too in the same way as TagValue
public:
    Metric();
    ~Metric();
    static Metric* instance();
    void Initialize(std::string const& realmName, Trinity::Asio::IoContext& ioContext, std::function overallStatusLogger);
    void LoadFromConfigs();
    void Update();
    bool ShouldLog(std::string const& category, int64 value) const;
    template
    void LogValue(std::string category, T value, Tags&&... tags)
    {
        using namespace std::chrono;
        MetricData* data = new MetricData;
        data->Category = std::move(category);
        data->Timestamp = system_clock::now();
        data->Type = METRIC_DATA_VALUE;
        data->ValueOrEventText = FormatInfluxDBValue(value);
        if constexpr (sizeof...(tags) > 0)
            (data->Tags.emplace_back(std::move(tags)), ...);
        _queuedData.Enqueue(data);
    }
    void LogEvent(std::string category, std::string title, std::string description);
    void Unload();
    bool IsEnabled() const { return _enabled; }
};
#define sMetric Metric::instance()
template
class MetricStopWatch
{
public:
    MetricStopWatch(LoggerType&& loggerFunc) :
        _logger(std::forward(loggerFunc)),
        _startTime(std::chrono::steady_clock::now())
    {
    }
    ~MetricStopWatch()
    {
        _logger(_startTime);
    }
private:
    LoggerType _logger;
    TimePoint _startTime;
};
template
Optional> MakeMetricStopWatch(LoggerType&& loggerFunc)
{
    if (!sMetric->IsEnabled())
        return {};
    return Optional>(std::in_place, std::forward(loggerFunc));
}
#define TC_METRIC_TAG(name, value) MetricTag(name, value)
#define TC_METRIC_DO_CONCAT(a, b) a ## b
#define TC_METRIC_CONCAT(a, b) TC_METRIC_DO_CONCAT(a, b)
#define TC_METRIC_UNIQUE_NAME(name) TC_METRIC_CONCAT(name, __LINE__)
#if defined PERFORMANCE_PROFILING || defined WITHOUT_METRICS
#define TC_METRIC_EVENT(category, title, description) ((void)0)
#define TC_METRIC_VALUE(category, value, ...) ((void)0)
#define TC_METRIC_TIMER(category, ...) ((void)0)
#define TC_METRIC_DETAILED_EVENT(category, title, description) ((void)0)
#define TC_METRIC_DETAILED_TIMER(category, ...) ((void)0)
#define TC_METRIC_DETAILED_NO_THRESHOLD_TIMER(category, ...) ((void)0)
#else
#  if TRINITY_PLATFORM != TRINITY_PLATFORM_WINDOWS
#define TC_METRIC_EVENT(category, title, description)                  \
        do {                                                           \
            if (sMetric->IsEnabled())                                  \
                sMetric->LogEvent(category, title, description);       \
        } while (0)
#define TC_METRIC_VALUE(category, value, ...)                          \
        do {                                                           \
            if (sMetric->IsEnabled())                                  \
                sMetric->LogValue(category, value, ##__VA_ARGS__);     \
        } while (0)
#  else
#define TC_METRIC_EVENT(category, title, description)                  \
        __pragma(warning(push))                                        \
        __pragma(warning(disable:4127))                                \
        do {                                                           \
            if (sMetric->IsEnabled())                                  \
                sMetric->LogEvent(category, title, description);       \
        } while (0)                                                    \
        __pragma(warning(pop))
#define TC_METRIC_VALUE(category, value, ...)                          \
        __pragma(warning(push))                                        \
        __pragma(warning(disable:4127))                                \
        do {                                                           \
            if (sMetric->IsEnabled())                                  \
                sMetric->LogValue(category, value, ##__VA_ARGS__);     \
        } while (0)                                                    \
        __pragma(warning(pop))
#  endif
#define TC_METRIC_TIMER(category, ...)                                                                           \
        auto TC_METRIC_UNIQUE_NAME(__tc_metric_stop_watch) = MakeMetricStopWatch([&](TimePoint start)            \
        {                                                                                                        \
            sMetric->LogValue(category, std::chrono::steady_clock::now() - start, ##__VA_ARGS__);                \
        });
#  if defined WITH_DETAILED_METRICS
#define TC_METRIC_DETAILED_TIMER(category, ...)                                                                  \
        auto TC_METRIC_UNIQUE_NAME(__tc_metric_stop_watch) = MakeMetricStopWatch([&](TimePoint start)            \
        {                                                                                                        \
            int64 duration = int64(std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count()); \
            std::string category2 = category;                                                                    \
            if (sMetric->ShouldLog(category2, duration))                                                         \
                sMetric->LogValue(std::move(category2), duration, ##__VA_ARGS__);                                \
        });
#define TC_METRIC_DETAILED_NO_THRESHOLD_TIMER(category, ...) TC_METRIC_TIMER(category, ##__VA_ARGS__)
#define TC_METRIC_DETAILED_EVENT(category, title, description) TC_METRIC_EVENT(category, title, description)
#  else
#define TC_METRIC_DETAILED_EVENT(category, title, description) ((void)0)
#define TC_METRIC_DETAILED_TIMER(category, ...) ((void)0)
#define TC_METRIC_DETAILED_NO_THRESHOLD_TIMER(category, ...) ((void)0)
#  endif
#endif
#endif // METRIC_H__