/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <string>
#include <type_traits>

#include <folly/Conv.h>
#include <folly/GLog.h>
#include <folly/Likely.h>
#include <folly/Range.h>
#include <folly/SharedMutex.h>
#include <folly/lang/Align.h>
#include <folly/stats/DigestBuilder.h>

namespace folly {

class PredefinedQuantiles {
 public:
  class Default {
   public:
    static constexpr std::array<double, 11> kQuantiles{
        0.0, 0.001, 0.01, 0.1, 0.25, 0.5, 0.75, 0.9, 0.99, 0.999, 1.0};
  };

  class MinAndMax {
   public:
    static constexpr std::array<double, 2> kQuantiles{0.0, 1.0};
  };

  class Median {
   public:
    static constexpr std::array<double, 3> kQuantiles{0.0, 0.5, 1.0};
  };

  class P01 {
   public:
    static constexpr std::array<double, 3> kQuantiles{0.0, 0.01, 1.0};
  };

  class P99 {
   public:
    static constexpr std::array<double, 3> kQuantiles{0.0, 0.99, 1.0};
  };

  class MedianAndHigh {
   public:
    static constexpr std::array<double, 6> kQuantiles{
        0.0, 0.5, 0.9, 0.99, 0.999, 1.0};
  };

  class Quartiles {
   public:
    static constexpr std::array<double, 5> kQuantiles{
        0.0, 0.25, 0.5, 0.75, 1.0};
  };

  class Deciles {
   public:
    static constexpr std::array<double, 11> kQuantiles{
        0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0};
  };

  class Ventiles {
   public:
    static constexpr std::array<double, 21> kQuantiles{
        0.0,  0.05, 0.1,  0.15, 0.2,  0.25, 0.3,  0.35, 0.4,  0.45, 0.5,
        0.55, 0.6,  0.65, 0.7,  0.75, 0.8,  0.85, 0.9,  0.95, 1.0};
  };
};

template <class Q = PredefinedQuantiles::Default>
class QuantileHistogram {
 public:
  QuantileHistogram() = default;
  explicit QuantileHistogram(size_t) : QuantileHistogram() {}

  static constexpr decltype(Q::kQuantiles) quantiles() { return Q::kQuantiles; }

  /*
   * Combines the given histograms into a new histogram where the locations for
   * each tracked quantile is the weighted average of the corresponding quantile
   * locations. The min and max for the new histogram will be the smallest min
   * or the largest max of all of the given histograms.
   */
  static QuantileHistogram<Q> merge(
      Range<const ::folly::QuantileHistogram<Q>*> qhists);

  QuantileHistogram<Q> merge(Range<const double*> unsortedValues) const;

  void addValue(double value);

  /*
   * Estimates the value of the given quantile.
   */
  double estimateQuantile(double q) const;

  uint64_t count() const { return count_; }

  bool empty() const { return count_ == 0; }

  double min() const { return locations_.front(); }

  double max() const { return locations_.back(); }

  std::string debugString() const;

 private:
  static_assert(quantiles().size() >= 2, "Quantiles 0.0 and 1.0 are required.");
  static_assert(quantiles().front() == 0.0, "Quantile 0.0 is required.");
  static_assert(quantiles().back() == 1.0, "Quantile 1.0 is required.");

  // locations_ tracks min and max at the two ends.
  typename std::remove_const<decltype(Q::kQuantiles)>::type locations_{};
  uint64_t count_{0};

  inline size_t addValueImpl(
      double value, const decltype(Q::kQuantiles)& oldLocations);

  void dcheckSane() const;
};

// The CPUShardedQuantileHistogram class behaves similarly to QuantileHistogram
// except that it is thread-safe. Adding values is heavily optimized while any
// kind of inference will incur a heavy cost because all cpu-local shards must
// be merged.
template <class Q = PredefinedQuantiles::Default>
class CPUShardedQuantileHistogram {
 public:
  CPUShardedQuantileHistogram()
      : histBuilder_(
            /*bufferSize=*/hardware_destructive_interference_size /
                sizeof(double),
            /*digestSize=*/0) {}

  static constexpr decltype(Q::kQuantiles) quantiles() { return Q::kQuantiles; }

  void addValue(double value);

  double estimateQuantile(double q);

  uint64_t count();

  double min();

  double max();

  std::string debugString();

 private:
  SharedMutex mtx_;
  QuantileHistogram<Q> mergedHist_;
  DigestBuilder<QuantileHistogram<Q>> histBuilder_;

  // Assumes mtx is held.
  void flush();
};

} // namespace folly

#include <folly/stats/QuantileHistogram-inl.h>
