#include "ConflictSet.h"
#include "Internal.h"
#include <functional>
#include <string>
#include <unordered_map>

// This implementation isn't correct for range queries :). It's just intended as
// a reference for performance comparison with point queries.

// struct is from "https://www.cppstories.com/2021/heterogeneous-access-cpp20/"
struct string_hash {
  using is_transparent = void;
  [[nodiscard]] size_t operator()(std::string_view txt) const {
    return std::hash<std::string_view>{}(txt);
  }
  [[nodiscard]] size_t operator()(const std::string &txt) const {
    return std::hash<std::string>{}(txt);
  }
};

struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
  Impl(int64_t oldestVersion) : oldestVersion(oldestVersion) {}
  void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
             int count) const {
    for (int i = 0; i < count; ++i) {
      auto key =
          std::string_view((const char *)reads[i].begin.p, reads[i].begin.len);
      auto version = reads[i].readVersion;
      if (version < oldestVersion) {
        results[i] = TooOld;
        continue;
      }
      auto iter = map.find(key);
      results[i] =
          iter == map.end() || iter->second <= version ? Commit : Conflict;
    }
  }

  void addWrites(const ConflictSet::WriteRange *writes, int count,
                 int64_t writeVersion) {
    for (int i = 0; i < count; ++i) {
      auto &max = map[std::string((const char *)writes[i].begin.p,
                                  writes[i].begin.len)];
      assert(writeVersion >= max);
      max = writeVersion;
      keyUpdates += 2;
    }
  }

  void setOldestVersion(int64_t oldestVersion) {
    if (oldestVersion <= this->oldestVersion) {
      return;
    }
    this->oldestVersion = oldestVersion;
    if (keyUpdates < 100) {
      return;
    }
    auto iter = map.find(removalKey);
    while (keyUpdates > 0) {
      if (iter == map.end()) {
        iter = map.begin();
      }
      for (; iter != map.end(); --keyUpdates) {
        if (iter->second <= oldestVersion) {
          iter = map.erase(iter);
        } else {
          ++iter;
        }
      }
    }
    if (iter == map.end()) {
      removalKey.clear();
    } else {
      removalKey = iter->first;
    }
  }

private:
  int64_t keyUpdates = 0;
  int64_t oldestVersion;
  std::unordered_map<std::string, int64_t, string_hash, std::equal_to<>> map;
  std::string removalKey;
};

void ConflictSet::check(const ReadRange *reads, Result *results,
                        int count) const {
  return impl->check(reads, results, count);
}

void ConflictSet::addWrites(const WriteRange *writes, int count,
                            int64_t writeVersion) {
  return impl->addWrites(writes, count, writeVersion);
}

void ConflictSet::setOldestVersion(int64_t oldestVersion) {
  return impl->setOldestVersion(oldestVersion);
}

int64_t ConflictSet::getBytes() const { return -1; }

void ConflictSet::getMetricsV1(MetricsV1 **metrics, int *count) const {
  *metrics = nullptr;
  *count = 0;
}

double ConflictSet::MetricsV1::getValue() const { return 0; }

ConflictSet::ConflictSet(int64_t oldestVersion)
    : impl(new(safe_malloc(sizeof(Impl))) Impl{oldestVersion}) {}

ConflictSet::~ConflictSet() {
  if (impl) {
    impl->~Impl();
    safe_free(impl, sizeof(Impl));
  }
}

ConflictSet::ConflictSet(ConflictSet &&other) noexcept
    : impl(std::exchange(other.impl, nullptr)) {}

ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept {
  impl = std::exchange(other.impl, nullptr);
  return *this;
}

using ConflictSet_Result = ConflictSet::Result;
using ConflictSet_Key = ConflictSet::Key;
using ConflictSet_ReadRange = ConflictSet::ReadRange;
using ConflictSet_WriteRange = ConflictSet::WriteRange;

extern "C" {
__attribute__((__visibility__("default"))) void
ConflictSet_check(void *cs, const ConflictSet_ReadRange *reads,
                  ConflictSet_Result *results, int count) {
  ((ConflictSet::Impl *)cs)->check(reads, results, count);
}
__attribute__((__visibility__("default"))) void
ConflictSet_addWrites(void *cs, const ConflictSet_WriteRange *writes, int count,
                      int64_t writeVersion) {
  ((ConflictSet::Impl *)cs)->addWrites(writes, count, writeVersion);
}
__attribute__((__visibility__("default"))) void
ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) {
  ((ConflictSet::Impl *)cs)->setOldestVersion(oldestVersion);
}
__attribute__((__visibility__("default"))) void *
ConflictSet_create(int64_t oldestVersion) {
  return new (safe_malloc(sizeof(ConflictSet::Impl)))
      ConflictSet::Impl{oldestVersion};
}
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
  using Impl = ConflictSet::Impl;
  ((Impl *)cs)->~Impl();
  safe_free(cs, sizeof(Impl));
}
__attribute__((__visibility__("default"))) int64_t
ConflictSet_getBytes(void *cs) {
  using Impl = ConflictSet::Impl;
  return -1;
}
}
