#pragma once

#include <library/cpp/histogram/hdr/histogram.h>

#include <util/system/spinlock.h>
#include <util/stream/output.h>

namespace NMonitoring {
    /**
     * A statistical snapshot of values recorded in histogram.
     */
    struct THistogramSnapshot {
        double Mean;
        double StdDeviation;
        i64 Min;
        i64 Max;
        i64 Percentile50;
        i64 Percentile75;
        i64 Percentile90;
        i64 Percentile95;
        i64 Percentile98;
        i64 Percentile99;
        i64 Percentile999;
        i64 TotalCount;

        void Print(IOutputStream* out) const;
    };

    /**
     * Special counter which calculates the distribution of a value.
     */
    class THdrHistogram {
    public:
        /**
         * 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.
         */
        THdrHistogram(i64 lowestDiscernibleValue, i64 highestTrackableValue,
                   i32 numberOfSignificantValueDigits)
            : Data_(lowestDiscernibleValue, highestTrackableValue,
                    numberOfSignificantValueDigits) {
        }

        /**
         * 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) {
            with_lock (Lock_) {
                return Data_.RecordValue(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) {
            with_lock (Lock_) {
                return Data_.RecordValues(value, 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) {
            with_lock (Lock_) {
                return Data_.RecordValueWithExpectedInterval(value, expectedInterval);
            }
        }

        /**
         * Record a value in the histogram count times. Applies the same correcting
         * logic as {@link THdrHistogram::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) {
            with_lock (Lock_) {
                return Data_.RecordValuesWithExpectedInterval(
                    value, count, expectedInterval);
            }
        }

        /**
         * @return The configured lowestDiscernibleValue
         */
        i64 GetLowestDiscernibleValue() const {
            with_lock (Lock_) {
                return Data_.GetLowestDiscernibleValue();
            }
        }

        /**
         * @return The configured highestTrackableValue
         */
        i64 GetHighestTrackableValue() const {
            with_lock (Lock_) {
                return Data_.GetHighestTrackableValue();
            }
        }

        /**
         * @return The configured numberOfSignificantValueDigits
         */
        i32 GetNumberOfSignificantValueDigits() const {
            with_lock (Lock_) {
                return Data_.GetNumberOfSignificantValueDigits();
            }
        }

        /**
         * @return The total count of all recorded values in the histogram
         */
        i64 GetTotalCount() const {
            with_lock (Lock_) {
                return Data_.GetTotalCount();
            }
        }

        /**
         * Place a copy of the value counts accumulated since the last snapshot
         * was taken into {@code snapshot}. Calling this member-function will
         * reset the value counts, and start accumulating value counts for the
         * next interval.
         *
         * @param snapshot the structure into which the values should be copied.
         */
        void TakeSnaphot(THistogramSnapshot* snapshot);

    private:
        mutable TSpinLock Lock_;
        NHdr::THistogram Data_;
    };

}

template <>
inline void Out<NMonitoring::THistogramSnapshot>(
    IOutputStream& out, const NMonitoring::THistogramSnapshot& snapshot) {
    snapshot.Print(&out);
}