/*
 * SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES
 * Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 * SPDX-License-Identifier: LicenseRef-NvidiaProprietary
 *
 * NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
 * property and proprietary rights in and to this material, related
 * documentation and any modifications thereto. Any use, reproduction,
 * disclosure or distribution of this material and related documentation
 * without an express license agreement from NVIDIA CORPORATION or
 * its affiliates is strictly prohibited.
 */

#pragma once

// std
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <string>
#include <vector>
// 3rd party
#include "nlohmann/json.hpp"
// sharp_am
#include "common/json_utils.h"

/**
 * Histogram, literately
 */
template <typename BucketType = std::int64_t, typename CounterType = BucketType>
struct Histogram
{
    using Bucket = BucketType;
    using Counter = CounterType;

    using Buckets = std::vector<Bucket>;
    using Counters = std::vector<Counter>;

    // when we want the histogram to take into account all possible counters,
    // we put this value as the last backet, it is a big-enough-value for our histograms
    static BucketType bucket_infinity;

    Histogram() = default;

    //! ctor.
    explicit Histogram(Buckets a_buckets) : buckets{std::move(a_buckets)}
    {
        // create as much counters as we have buckets
        counters.resize(buckets.size());
    }

    //! Add N times a value, to the histogram
    void Add(Bucket value, Counter n = 1) { Update(value, n); }

    //! Remove N times a value, from the histogram
    void Remove(Bucket value, Counter n = 1) { Update(value, -n); }

    //! Update histogram with N times a value (could be negative times)
    void Update(Bucket value, Counter n)
    {
        auto bucket_it = std::lower_bound(std::begin(buckets), std::end(buckets), value);
        if (bucket_it != std::end(buckets)) {
            auto bucket_index = std::distance(std::begin(buckets), bucket_it);
            counters[bucket_index] += n;
        }
    }

    Buckets buckets;
    Counters counters;
};

// define bucket_infinity (static member)
template <typename BucketType, typename CounterType>
BucketType Histogram<BucketType, CounterType>::bucket_infinity{1000000000};   // over 30 years worth of seconds

// we will work with these histograms
using IntegralHistogram = Histogram<std::int64_t, std::int64_t>;
using HoursHistogram = Histogram<std::chrono::duration<float, std::ratio<3600>>, std::int64_t>;

// supprt JSON serialization
template <typename BucketType, typename CounterType>
void to_json(nlohmann::ordered_json& j, const Histogram<BucketType, CounterType>& h);
template <typename BucketType, typename CounterType>
void from_json(const nlohmann::ordered_json& j, Histogram<BucketType, CounterType>& h);

// NOTE:
//   uncomment this code, and remove the defintions above,
//   when we serialize histograms as buckets + counters arrays
//
// template <typename BucketType, typename CounterType>
// void to_json(nlohmann::json& j, const Histogram<BucketType, CounterType>& h)
// {
//     j = nlohmann::json{{"buckets", h.buckets}, {"counters", h.counters}};
// }
// template <typename BucketType, typename CounterType>
// void from_json(const nlohmann::json& j, Histogram<BucketType, CounterType>& h)
// {
//     j.at("buckets").get_to(h.buckets);
//     j.at("counters").get_to(h.counters);
// }
