#pragma once

#include "metric_consumer.h"

#include <util/datetime/base.h>
#include <util/generic/ptr.h>

namespace NMonitoring {
    ///////////////////////////////////////////////////////////////////////////////
    // IMetric
    ///////////////////////////////////////////////////////////////////////////////
    class IMetric : public TThrRefBase {
    public:
        virtual ~IMetric() = default;

        virtual EMetricType Type() const noexcept = 0;
        virtual void Accept(TInstant time, IMetricConsumer* consumer) const = 0;
    };

    using IMetricPtr = TIntrusivePtr<IMetric>;

    class IGauge: public IMetric {
    public:
        EMetricType Type() const noexcept final {
            return  EMetricType::GAUGE;
        }

        virtual double Add(double n) noexcept = 0;
        virtual void Set(double n) noexcept = 0;
        virtual double Get() const noexcept = 0;
        virtual void Reset() noexcept {
            Set(0);
        }
    };

    class ILazyGauge: public IMetric {
    public:
        EMetricType Type() const noexcept final {
            return  EMetricType::GAUGE;
        }
        virtual double Get() const noexcept = 0;
    };

    class IIntGauge: public IMetric {
    public:
        EMetricType Type() const noexcept final {
            return EMetricType::IGAUGE;
        }

        virtual i64 Add(i64 n) noexcept = 0;
        virtual i64 Inc() noexcept {
            return Add(1);
        }

        virtual i64 Dec() noexcept {
            return Add(-1);
        }

        virtual void Set(i64 value) noexcept = 0;
        virtual i64 Get() const noexcept = 0;
        virtual void Reset() noexcept {
            Set(0);
        }
    };

    class ILazyIntGauge: public IMetric {
    public:
        EMetricType Type() const noexcept final {
            return EMetricType::IGAUGE;
        }

        virtual i64 Get() const noexcept = 0;
    };

    class ICounter: public IMetric {
    public:
        EMetricType Type() const noexcept final {
            return EMetricType::COUNTER;
        }

        virtual ui64 Inc() noexcept {
            return Add(1);
        }

        virtual ui64 Add(ui64 n) noexcept = 0;
        virtual ui64 Get() const noexcept = 0;
        virtual void Reset() noexcept = 0;
    };

    class ILazyCounter: public IMetric {
    public:
        EMetricType Type() const noexcept final {
            return EMetricType::COUNTER;
        }

        virtual ui64 Get() const noexcept = 0;
    };

    class IRate: public IMetric {
    public:
        EMetricType Type() const noexcept final {
            return EMetricType::RATE;
        }

        virtual ui64 Inc() noexcept {
            return Add(1);
        }

        virtual ui64 Add(ui64 n) noexcept = 0;
        virtual ui64 Get() const noexcept = 0;
        virtual void Reset() noexcept = 0;
    };

    class ILazyRate: public IMetric {
    public:
        EMetricType Type() const noexcept final {
            return EMetricType::RATE;
        }

        virtual ui64 Get() const noexcept = 0;
    };

    class IHistogram: public IMetric {
    public:
        explicit IHistogram(bool isRate)
            : IsRate_{isRate}
        {
        }

        EMetricType Type() const noexcept final {
            return IsRate_ ? EMetricType::HIST_RATE : EMetricType::HIST;
        }

        virtual void Record(double value) = 0;
        virtual void Record(double value, ui32 count) = 0;
        virtual IHistogramSnapshotPtr TakeSnapshot() const = 0;
        virtual void Reset() = 0;

    protected:
        const bool IsRate_;
    };

    ///////////////////////////////////////////////////////////////////////////////
    // TGauge
    ///////////////////////////////////////////////////////////////////////////////
    class TGauge final: public IGauge {
    public:
        explicit TGauge(double value = 0.0) {
            Set(value);
        }

        double Add(double n) noexcept override {
            double newValue;
            double oldValue = Get();

            do {
                newValue = oldValue + n;
            } while (!Value_.compare_exchange_weak(oldValue, newValue, std::memory_order_release, std::memory_order_consume));

            return newValue;
        }

        void Set(double n) noexcept override {
            Value_.store(n, std::memory_order_relaxed);
        }

        double Get() const noexcept override {
            return Value_.load(std::memory_order_relaxed);
        }

        void Accept(TInstant time, IMetricConsumer* consumer) const override {
            consumer->OnDouble(time, Get());
        }

    private:
        std::atomic<double> Value_;
    };

    ///////////////////////////////////////////////////////////////////////////////
    // TLazyGauge
    ///////////////////////////////////////////////////////////////////////////////
    class TLazyGauge final: public ILazyGauge {
    public:
        explicit TLazyGauge(std::function<double()> supplier)
            : Supplier_(std::move(supplier))
        {
        }

        double Get() const noexcept override {
            return Supplier_();
        }

        void Accept(TInstant time, IMetricConsumer* consumer) const override {
            consumer->OnDouble(time, Get());
        }

    private:
        std::function<double()> Supplier_;
    };

    ///////////////////////////////////////////////////////////////////////////////
    // TIntGauge
    ///////////////////////////////////////////////////////////////////////////////
    class TIntGauge final: public IIntGauge {
    public:
        explicit TIntGauge(i64 value = 0) {
            Set(value);
        }

        i64 Add(i64 n) noexcept override {
            return Value_.fetch_add(n, std::memory_order_relaxed) + n;
        }

        void Set(i64 value) noexcept override {
            Value_.store(value, std::memory_order_relaxed);
        }

        i64 Get() const noexcept override {
            return Value_.load(std::memory_order_relaxed);
        }

        void Accept(TInstant time, IMetricConsumer* consumer) const override {
            consumer->OnInt64(time, Get());
        }

    private:
        std::atomic_int64_t Value_;
    };

    ///////////////////////////////////////////////////////////////////////////////
    // TLazyIntGauge
    ///////////////////////////////////////////////////////////////////////////////
    class TLazyIntGauge final: public ILazyIntGauge {
    public:
        explicit TLazyIntGauge(std::function<i64()> supplier)
            : Supplier_(std::move(supplier))
        {
        }

        i64 Get() const noexcept override {
            return Supplier_();
        }

        void Accept(TInstant time, IMetricConsumer* consumer) const override {
            consumer->OnInt64(time, Get());
        }

    private:
        std::function<i64()> Supplier_;
    };

    ///////////////////////////////////////////////////////////////////////////////
    // TCounter
    ///////////////////////////////////////////////////////////////////////////////
    class TCounter final: public ICounter {
    public:
        explicit TCounter(ui64 value = 0) {
            Value_.store(value, std::memory_order_relaxed);
        }

        ui64 Add(ui64 n) noexcept override {
            return Value_.fetch_add(n, std::memory_order_relaxed) + n;
        }

        ui64 Get() const noexcept override {
            return Value_.load(std::memory_order_relaxed);
        }

        void Reset() noexcept override {
            Value_.store(0, std::memory_order_relaxed);
        }

        void Accept(TInstant time, IMetricConsumer* consumer) const override {
            consumer->OnUint64(time, Get());
        }

    private:
        std::atomic_uint64_t Value_;
    };

    ///////////////////////////////////////////////////////////////////////////////
    // TLazyCounter
    ///////////////////////////////////////////////////////////////////////////////
    class TLazyCounter final: public ILazyCounter {
    public:
        explicit TLazyCounter(std::function<ui64()> supplier)
            : Supplier_(std::move(supplier))
        {
        }

        ui64 Get() const noexcept override {
            return Supplier_();
        }

        void Accept(TInstant time, IMetricConsumer* consumer) const override {
            consumer->OnUint64(time, Get());
        }

    private:
        std::function<ui64()> Supplier_;
    };

    ///////////////////////////////////////////////////////////////////////////////
    // TRate
    ///////////////////////////////////////////////////////////////////////////////
    class TRate final: public IRate {
    public:
        explicit TRate(ui64 value = 0) {
            Value_.store(value, std::memory_order_relaxed);
        }

        ui64 Add(ui64 n) noexcept override {
            return Value_.fetch_add(n, std::memory_order_relaxed) + n;
        }

        ui64 Get() const noexcept override {
            return Value_.load(std::memory_order_relaxed);
        }

        void Reset() noexcept override {
            Value_.store(0, std::memory_order_relaxed);
        }

        void Accept(TInstant time, IMetricConsumer* consumer) const override {
            consumer->OnUint64(time, Get());
        }

    private:
        std::atomic_uint64_t Value_;
    };

    ///////////////////////////////////////////////////////////////////////////////
    // TLazyRate
    ///////////////////////////////////////////////////////////////////////////////
    class TLazyRate final: public ILazyRate {
    public:
        explicit TLazyRate(std::function<ui64()> supplier)
            : Supplier_(std::move(supplier))
        {
        }

        ui64 Get() const noexcept override {
            return Supplier_();
        }

        void Accept(TInstant time, IMetricConsumer* consumer) const override {
            consumer->OnUint64(time, Get());
        }

    private:
        std::function<ui64()> Supplier_;
    };

    ///////////////////////////////////////////////////////////////////////////////
    // THistogram
    ///////////////////////////////////////////////////////////////////////////////
    class THistogram final: public IHistogram {
    public:
        THistogram(IHistogramCollectorPtr collector, bool isRate)
            : IHistogram(isRate)
            , Collector_(std::move(collector))
        {
        }

        THistogram(std::function<IHistogramCollectorPtr()> makeHistogramCollector, bool isRate)
            : IHistogram(isRate)
            , Collector_(makeHistogramCollector())
        {
        }

        void Record(double value) override {
            Collector_->Collect(value);
        }

        void Record(double value, ui32 count) override {
            Collector_->Collect(value, count);
        }

        void Accept(TInstant time, IMetricConsumer* consumer) const override {
            consumer->OnHistogram(time, TakeSnapshot());
        }

        IHistogramSnapshotPtr TakeSnapshot() const override {
            return Collector_->Snapshot();
        }

        void Reset() override {
            Collector_->Reset();
        }

    private:
        IHistogramCollectorPtr Collector_;
    };
}