#pragma once

#include <library/cpp/json/json_value.h>
#include <library/cpp/json/writer/json.h>
#include <library/cpp/threading/future/async.h>

#include <util/generic/algorithm.h>
#include <util/generic/hash.h>
#include <util/generic/utility.h>
#include <util/generic/vector.h>
#include <util/generic/yexception.h>
#include <util/stream/format.h>
#include <util/string/builder.h>
#include <util/thread/pool.h>
#include <util/system/mutex.h>

namespace NSimpleHistogram {
    template <typename T>
    class THistogram {
    public:
        explicit THistogram(TVector<T>&& values)
            : Values_(std::move(values))
        {
        }

        size_t TotalCount() const {
            return Values_.size();
        }

        T ValueAtPercentile(double percentile) const {
            Y_ASSERT(!Values_.empty());
            Y_ASSERT(percentile >= 0.0 && percentile <= 1.0);

            const size_t index = static_cast<size_t>(percentile * Values_.size());
            return Values_[Min(Values_.size() - 1, index)];
        }

    private:
        TVector<T> Values_;
    };

    template <typename T>
    class THistogramCalcer {
    public:
        size_t TotalCount() const {
            return Values_.size();
        }

        void Reserve(size_t cnt) {
            Values_.reserve(cnt);
        }

        void RecordValue(T value) {
            Values_.push_back(value);
        }

        THistogram<T> Calc() {
            if (!IsSorted(Values_.begin(), Values_.end())) {
                Sort(Values_.begin(), Values_.end());
            }
            return THistogram<T>(std::move(Values_));
        }

    private:
        TVector<T> Values_;
    };

    template <typename T>
    class TMultiHistogramCalcer {
    public:
        void RecordValue(TStringBuf name, T value) {
            Calcers_[name].RecordValue(value);
        }

        THashMap<TString, THistogram<T>> Calc() {
            THashMap<TString, THistogram<T>> result;

            for (auto& calcer : Calcers_) {
                result.emplace(calcer.first, calcer.second.Calc());
            }

            return result;
        }

    private:
        THashMap<TString, THistogramCalcer<T>> Calcers_;
    };

    template <typename T>
    class TThreadSafeMultiHistogramCalcer {
    public:
        void RecordValue(TStringBuf name, T value) {
            TGuard<TMutex> guard(Mutex_);
            Calcer_.RecordValue(name, value);
        }

        THashMap<TString, THistogram<T>> Calc() {
            return Calcer_.Calc();
        }

    private:
        TMutex Mutex_;
        TMultiHistogramCalcer<T> Calcer_;
    };

    template <typename T>
    NJson::TJsonValue ToJson(const THistogram<T>& hist, const TVector<double>& percentiles) {
        NJson::TJsonValue json;

        for (double percentile : percentiles) {
            TStringBuilder name;
            name << "Q" << Prec(percentile * 100, PREC_POINT_DIGITS_STRIP_ZEROES, 2);
            json[name] = hist.ValueAtPercentile(percentile);
        }

        json["RecordCount"] = hist.TotalCount();

        return json;
    }

    template <typename T>
    NJson::TJsonValue ToJson(const THashMap<TString, THistogram<T>>& hists, const TVector<double>& percentiles) {
        NJson::TJsonValue json;

        for (const auto& p : hists) {
            json[p.first] = ToJson(p.second, percentiles);
        }

        return json;
    }

    template <typename T>
    TString ToJsonStr(const THashMap<TString, THistogram<T>>& hists, const TVector<double>& percentiles, bool format = true) {
        NJson::TJsonValue json = ToJson(hists, percentiles);

        NJsonWriter::TBuf buf;
        if (format) {
            buf.SetIndentSpaces(4);
        }

        return buf.WriteJsonValue(&json, true).Str();
    }

}