#pragma once

#include "ConflictSet.h"
#include "Internal.h"

#include <assert.h>
#include <atomic>
#include <tuple>

struct Metric {
  Metric *prev;
  const char *name;
  const char *help;
  weaselab::ConflictSet::MetricsV1::Type type;
  std::atomic<int64_t> value;

protected:
  Metric(Metric *&metricList, int &metricsCount, const char *name,
         const char *help, weaselab::ConflictSet::MetricsV1::Type type)
      : prev(std::exchange(metricList, this)), name(name), help(help),
        type(type), value(0) {
    ++metricsCount;
  }
};

struct Gauge : private Metric {
  Gauge(Metric *&metricList, int &metricsCount, const char *name,
        const char *help)
      : Metric(metricList, metricsCount, name, help,
               weaselab::ConflictSet::MetricsV1::Gauge) {}

  void set(int64_t value) {
    this->value.store(value, std::memory_order_relaxed);
  }
};

struct Counter : private Metric {
  Counter(Metric *&metricList, int &metricsCount, const char *name,
          const char *help)
      : Metric(metricList, metricsCount, name, help,
               weaselab::ConflictSet::MetricsV1::Counter) {}
  // Expensive. Accumulate locally and then call add instead of repeatedly
  // calling add.
  void add(int64_t value) {
    assert(value >= 0);
    static_assert(std::atomic<int64_t>::is_always_lock_free);
    this->value.fetch_add(value, std::memory_order_relaxed);
  }
};

inline weaselab::ConflictSet::MetricsV1 *initMetrics(Metric *metricsList,
                                                     int metricsCount) {
  weaselab::ConflictSet::MetricsV1 *metrics =
      (weaselab::ConflictSet::MetricsV1 *)safe_malloc(metricsCount *
                                                      sizeof(metrics[0]));
  for (auto [i, m] = std::make_tuple(metricsCount - 1, metricsList); i >= 0;
       --i, m = m->prev) {
    metrics[i].name = m->name;
    metrics[i].help = m->help;
    metrics[i].p = m;
    metrics[i].type = m->type;
  }
  return metrics;
}