#include <ConflictSet.h>

#include <cerrno>
#include <chrono>
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <span>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <vector>

using namespace weaselab;

double now() {
  return std::chrono::duration_cast<std::chrono::nanoseconds>(
             std::chrono::steady_clock::now().time_since_epoch())
             .count() *
         1e-9;
}

inline size_t getPageSize() {
  static size_t kPageSize = sysconf(_SC_PAGESIZE);
  return kPageSize;
}

/// Helper for rounding up to page size (or some other alignment)
constexpr inline size_t rightAlign(size_t offset, size_t alignment) {
  return offset % alignment == 0 ? offset
                                 : ((offset / alignment) + 1) * alignment;
}

int main(int argc, const char **argv) {
  // Use with this dataset https://snap.stanford.edu/data/memetracker9.html
  // Preprocess the files with `sed -i'' '/^Q/d'`

  double checkTime = 0;
  double addTime = 0;
  double gcTime = 0;
  double checkBytes = 0;
  double addBytes = 0;

  ConflictSet cs{0};
  int64_t version = 0;
  double timer = 0;

  int64_t peakMemory = 0;

  for (int i = 1; i < argc; ++i) {
    int fd = open(argv[i], O_RDONLY);
    struct stat st;
    if (fstat(fd, &st) == -1) {
      int err = errno;
      fprintf(stderr, "stat error %s - %s\n", argv[i], strerror(err));
      fflush(stderr);
      abort();
    }

    int64_t size = rightAlign(st.st_size, getPageSize());
    const uint8_t *begin =
        (uint8_t *)mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
    madvise((void *)begin, size, MADV_SEQUENTIAL);
    auto *const mapOriginal = begin;
    const auto sizeOriginal = size;

    using StringView = std::span<const uint8_t>;

    StringView write;
    std::vector<StringView> reads;
    std::vector<ConflictSet::ReadRange> readRanges;
    std::vector<ConflictSet::Result> results;

    for (uint8_t *end = (uint8_t *)memchr(begin, '\n', size); end != nullptr;) {
      StringView line{begin, static_cast<size_t>(end - begin)};
      size -= end - begin + 1;
      begin = end + 1;
      end = (uint8_t *)memchr(begin, '\n', size);

      if (line.size() > 0 && line[0] == 'P') {
        write = line.subspan(2, line.size());
      } else if (line.size() > 0 && line[0] == 'L') {
        reads.push_back(line.subspan(2, line.size()));
      } else if (line.empty()) {
        {
          readRanges.resize(reads.size());
          auto iter = readRanges.begin();
          for (const auto &read : reads) {
            iter->begin.p = (const uint8_t *)read.data();
            iter->begin.len = read.size();
            checkBytes += read.size();
            iter->end.len = 0;
            iter->readVersion = version - 100;
            ++iter;
          }
        }
        results.resize(readRanges.size());

        timer = now();
        cs.check(readRanges.data(), results.data(), readRanges.size());
        checkTime += now() - timer;

        // Add unconditionally so that the load doesn't actually depend on the
        // conflict rate
        ConflictSet::WriteRange w;
        w.begin.p = (const uint8_t *)write.data();
        w.begin.len = write.size();
        w.end.len = 0;

        addBytes += write.size();

        timer = now();
        cs.addWrites(&w, 1, ++version);
        addTime += now() - timer;

        write = {};
        reads.clear();

        if (cs.getBytes() > peakMemory) {
          peakMemory = cs.getBytes();
        }

        timer = now();
        cs.setOldestVersion(version - 10000);
        gcTime += now() - timer;
      }
    }
    munmap((void *)mapOriginal, sizeOriginal);
    close(fd);
  }

  ConflictSet::MetricsV1 *metrics;
  int metricsCount;
  cs.getMetricsV1(&metrics, &metricsCount);
  for (int i = 0; i < metricsCount; ++i) {
    fprintf(stderr, "# HELP %s %s\n", metrics[i].name, metrics[i].help);
    fprintf(stderr, "# TYPE %s %s\n", metrics[i].name,
            metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
    fprintf(stderr, "%s %g\n", metrics[i].name, metrics[i].getValue());
  }

  printf("Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: "
         "%g%%, Peak idle memory: %g\n",
         checkTime, checkBytes / checkTime * 1e-6, addTime,
         addBytes / addTime * 1e-6, gcTime / (gcTime + addTime) * 1e2,
         double(peakMemory));
}
