#pragma once

#include <algorithm>
#include <cstdint>

namespace tw::metrics {

/**
 * Aggregate of every value that fell into one bucket.
 *
 * Answers sum, average, minimum and maximum without keeping the individual
 * values. A sample nothing was added to reports zero for all of them, so gaps
 * read as zero rather than as an unset extreme.
 */
struct MetricSample {
    uint32_t count = 0;
    double   sum   = 0.0;
    double   min   = 0.0;
    double   max   = 0.0;

    void add(double value) {
        if(count == 0) {
            min = value;
            max = value;
        } else {
            min = std::min(min, value);
            max = std::max(max, value);
        }

        sum += value;
        count++;
    }

    /** Folds `other` in, as if its values had been added to this sample. */
    void merge(const MetricSample& other) {
        if(other.count == 0) {
            return;
        }

        if(count == 0) {
            *this = other;
            return;
        }

        min = std::min(min, other.min);
        max = std::max(max, other.max);

        sum += other.sum;
        count += other.count;
    }

    double avg() const {
        return count == 0 ? 0.0 : sum / count;
    }

    bool is_empty() const {
        return count == 0;
    }

    void reset() {
        *this = {};
    }
};

/** Statistic to read out of a sample. */
enum class MetricField {
    Avg,
    Min,
    Max,
    Sum,
    Count
};

inline double value_of(const MetricSample& sample, MetricField field) {
    switch(field) {
        case MetricField::Avg:   return sample.avg();
        case MetricField::Min:   return sample.min;
        case MetricField::Max:   return sample.max;
        case MetricField::Sum:   return sample.sum;
        case MetricField::Count: return (double)sample.count;
    }

    return 0.0;
}

}
