#pragma once

#include <util/generic/ptr.h>
#include <util/generic/noncopyable.h>
#include <util/memory/alloc.h>

struct hdr_histogram;

namespace NHdr {
    /**
     * A High Dynamic Range (HDR) Histogram
     *
     * THdrHistogram supports the recording and analyzing sampled data value counts
     * across a configurable integer value range with configurable value precision
     * within the range. Value precision is expressed as the number of significant
     * digits in the value recording, and provides control over value quantization
     * behavior across the value range and the subsequent value resolution at any
     * given level.
     */
    class THistogram: public TMoveOnly {
    public:
        /**
         * Construct a histogram given the Highest value to be tracked and a number
         * of significant decimal digits. The histogram will be constructed to
         * implicitly track (distinguish from 0) values as low as 1. Default
         * allocator will be used to allocate underlying memory.
         *
         * @param highestTrackableValue The highest value to be tracked by the
         *        histogram. Must be a positive integer that is literal >= 2.
         *
         * @param numberOfSignificantValueDigits Specifies the precision to use.
         *        This is the number of significant decimal digits to which the
         *        histogram will maintain value resolution and separation. Must be
         *        a non-negative integer between 0 and 5.
         */
        THistogram(i64 highestTrackableValue, i32 numberOfSignificantValueDigits)
            : THistogram(1, highestTrackableValue, numberOfSignificantValueDigits)
        {
        }

        /**
         * Construct a histogram given the Lowest and Highest values to be tracked
         * and a number of significant decimal digits. Providing a
         * lowestDiscernibleValue is useful in situations where the units used for
         * the histogram's values are much smaller that the minimal accuracy
         * required. E.g. when tracking time values stated in nanosecond units,
         * where the minimal accuracy required is a microsecond, the proper value
         * for lowestDiscernibleValue would be 1000.
         *
         * @param lowestDiscernibleValue The lowest value that can be discerned
         *        (distinguished from 0) by the histogram. Must be a positive
         *        integer that is >= 1. May be internally rounded down to nearest
         *        power of 2.
         *
         * @param highestTrackableValue The highest value to be tracked by the
         *        histogram. Must be a positive integer that is
         *        >= (2 * lowestDiscernibleValue).
         *
         * @param numberOfSignificantValueDigits Specifies the precision to use.
         *        This is the number of significant decimal digits to which the
         *        histogram will maintain value resolution and separation. Must be
         *        a non-negative integer between 0 and 5.
         *
         * @param allocator Specifies allocator which will be used to allocate
         *        memory for histogram.
         */
        THistogram(i64 lowestDiscernibleValue, i64 highestTrackableValue,
                   i32 numberOfSignificantValueDigits,
                   IAllocator* allocator = TDefaultAllocator::Instance());

        ~THistogram();

        // Histogram structure querying support -----------------------------------

        /**
         * @return The configured lowestDiscernibleValue
         */
        i64 GetLowestDiscernibleValue() const;

        /**
         * @return The configured highestTrackableValue
         */
        i64 GetHighestTrackableValue() const;

        /**
         * @return The configured numberOfSignificantValueDigits
         */
        i32 GetNumberOfSignificantValueDigits() const;

        /**
         * @return The size of allocated memory for histogram
         */
        size_t GetMemorySize() const;

        /**
         * @return The number of created counters
         */
        i32 GetCountsLen() const;

        /**
         * @return The total count of all recorded values in the histogram
         */
        i64 GetTotalCount() const;

        // Value recording support ------------------------------------------------

        /**
         * Records a value in the histogram, will round this value of to a
         * precision at or better than the NumberOfSignificantValueDigits specified
         * at construction time.
         *
         * @param value Value to add to the histogram
         * @return false if the value is larger than the HighestTrackableValue
         *         and can't be recorded, true otherwise.
         */
        bool RecordValue(i64 value);

        /**
         * Records count values in the histogram, will round this value of to a
         * precision at or better than the NumberOfSignificantValueDigits specified
         * at construction time.
         *
         * @param value Value to add to the histogram
         * @param count Number of values to add to the histogram
         * @return false if the value is larger than the HighestTrackableValue
         *         and can't be recorded, true otherwise.
         */
        bool RecordValues(i64 value, i64 count);

        /**
         * Records a value in the histogram and backfill based on an expected
         * interval. Value will be rounded this to a precision at or better
         * than the NumberOfSignificantValueDigits specified at contruction time.
         * This is specifically used for recording latency. If the value is larger
         * than the expectedInterval then the latency recording system has
         * experienced co-ordinated omission. This method fills in the values that
         *  would have occured had the client providing the load not been blocked.
         *
         * @param value Value to add to the histogram
         * @param expectedInterval The delay between recording values
         * @return false if the value is larger than the HighestTrackableValue
         *         and can't be recorded, true otherwise.
         */
        bool RecordValueWithExpectedInterval(i64 value, i64 expectedInterval);

        /**
         * Record a value in the histogram count times. Applies the same correcting
         * logic as {@link THistogram::RecordValueWithExpectedInterval}.
         *
         * @param value Value to add to the histogram
         * @param count Number of values to add to the histogram
         * @param expectedInterval The delay between recording values.
         * @return false if the value is larger than the HighestTrackableValue
         *         and can't be recorded, true otherwise.
         */
        bool RecordValuesWithExpectedInterval(
            i64 value, i64 count, i64 expectedInterval);

        /**
         * Adds all of the values from rhs to this histogram. Will return the
         * number of values that are dropped when copying. Values will be dropped
         * if they around outside of [LowestDiscernibleValue, GetHighestTrackableValue].
         *
         * @param rhs Histogram to copy values from.
         * @return The number of values dropped when copying.
         */
        i64 Add(const THistogram& rhs);

        /**
         * Adds all of the values from rhs to this histogram. Will return the
         * number of values that are dropped when copying. Values will be dropped
         * if they around outside of [LowestDiscernibleValue, GetHighestTrackableValue].
         * Applies the same correcting logic as
         * {@link THistogram::RecordValueWithExpectedInterval}.
         *
         * @param rhs Histogram to copy values from.
         * @return The number of values dropped when copying.
         */
        i64 AddWithExpectedInterval(const THistogram& rhs, i64 expectedInterval);

        // Histogram Data access support ------------------------------------------

        /**
         * Get the lowest recorded value level in the histogram. If the histogram
         * has no recorded values, the value returned is undefined.
         *
         * @return the Min value recorded in the histogram
         */
        i64 GetMin() const;

        /**
         * Get the highest recorded value level in the histogram. If the histogram
         * has no recorded values, the value returned is undefined.
         *
         * @return the Max value recorded in the histogram
         */
        i64 GetMax() const;

        /**
         * Get the computed mean value of all recorded values in the histogram
         *
         * @return the mean value (in value units) of the histogram data
         */
        double GetMean() const;

        /**
         * Get the computed standard deviation of all recorded values in the histogram
         *
         * @return the standard deviation (in value units) of the histogram data
         */
        double GetStdDeviation() const;

        /**
         * Get the value at a given percentile.
         * Note that two values are "equivalent" in this statement if
         * {@link THistogram::ValuesAreEquivalent} would return true.
         *
         * @param percentile  The percentile for which to return the associated
         *        value
         * @return The value that the given percentage of the overall recorded
         *         value entries in the histogram are either smaller than or
         *         equivalent to. When the percentile is 0.0, returns the value
         *         that all value entries in the histogram are either larger than
         *         or equivalent to.
         */
        i64 GetValueAtPercentile(double percentile) const;

        /**
         * Get the count of recorded values at a specific value (to within the
         * histogram resolution at the value level).
         *
         * @param value The value for which to provide the recorded count
         * @return The total count of values recorded in the histogram within the
         *         value range that is >= GetLowestEquivalentValue(value) and
         *         <= GetHighestEquivalentValue(value)
         */
        i64 GetCountAtValue(i64 value) const;

        /**
         * Determine if two values are equivalent with the histogram's resolution.
         * Where "equivalent" means that value samples recorded for any two
         * equivalent values are counted in a common total count.
         *
         * @param v1 first value to compare
         * @param v2 second value to compare
         * @return True if values are equivalent with the histogram's resolution.
         */
        bool ValuesAreEqual(i64 v1, i64 v2) const;

        /**
         * Get the lowest value that is equivalent to the given value within the
         * histogram's resolution. Where "equivalent" means that value samples
         * recorded for any two equivalent values are counted in a common total
         * count.
         *
         * @param value The given value
         * @return The lowest value that is equivalent to the given value within
         *         the histogram's resolution.
         */
        i64 GetLowestEquivalentValue(i64 value) const;

        /**
         * Get the highest value that is equivalent to the given value within the
         * histogram's resolution. Where "equivalent" means that value samples
         * recorded for any two equivalent values are counted in a common total
         * count.
         *
         * @param value The given value
         * @return The highest value that is equivalent to the given value within
         *         the histogram's resolution.
         */
        i64 GetHighestEquivalentValue(i64 value) const;

        /**
         * Get a value that lies in the middle (rounded up) of the range of values
         * equivalent the given value. Where "equivalent" means that value samples
         * recorded for any two equivalent values are counted in a common total
         * count.
         *
         * @param value The given value
         * @return The value lies in the middle (rounded up) of the range of values
         *         equivalent the given value.
         */
        i64 GetMedianEquivalentValue(i64 value) const;

        // misc functions ---------------------------------------------------------

        /**
         * Reset a histogram to zero - empty out a histogram and re-initialise it.
         * If you want to re-use an existing histogram, but reset everything back
         * to zero, this is the routine to use.
         */
        void Reset();

        const hdr_histogram* GetHdrHistogramImpl() const {
            return Data_.Get();
        }

    private:
        THolder<hdr_histogram> Data_;
        IAllocator* Allocator_;
    };
}