#include <chrono>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <string_view>

#include "src/main/cpp/net/eagle0/shardok/library/map/CoordsSet.hpp"

namespace shardok {
namespace {

constexpr int kColumnCount = 12;
constexpr int kRowCount = 14;
constexpr int kIndexCount = kColumnCount * kRowCount;

volatile uint64_t benchmarkSink = 0;

template<class T>
void DoNotOptimize(const T& value) {
#if defined(__GNUC__) || defined(__clang__)
    asm volatile("" : : "g"(&value) : "memory");
#else
    benchmarkSink ^= reinterpret_cast<uintptr_t>(&value);
#endif
}

struct BenchmarkResult {
    std::string_view name;
    int iterations;
    std::chrono::nanoseconds elapsed;
    uint64_t checksum;
};

void Consume(uint64_t checksum) { benchmarkSink = checksum; }

[[nodiscard]] auto MakePatternedCoordsSet(const int step, const int offset = 0) -> CoordsSet {
    CoordsSet coords(kColumnCount, kRowCount);
    for (int index = offset; index < kIndexCount; index += step) {
        coords.AddMapIndexUnchecked(static_cast<uint32_t>(index));
    }
    return coords;
}

template<class Func>
[[nodiscard]] auto RunBenchmark(std::string_view name, const int iterations, Func func)
        -> BenchmarkResult {
    const auto start = std::chrono::steady_clock::now();
    const uint64_t checksum = func(iterations);
    const auto end = std::chrono::steady_clock::now();
    Consume(checksum);
    return BenchmarkResult{
            name,
            iterations,
            std::chrono::duration_cast<std::chrono::nanoseconds>(end - start),
            checksum};
}

void PrintResult(const BenchmarkResult& result) {
    const double nsPerIteration =
            static_cast<double>(result.elapsed.count()) / static_cast<double>(result.iterations);
    std::cout << std::left << std::setw(28) << result.name << " iterations=" << std::right
              << std::setw(10) << result.iterations << " ns/op=" << std::fixed
              << std::setprecision(2) << std::setw(10) << nsPerIteration
              << " checksum=" << result.checksum << '\n';
}

[[nodiscard]] auto BenchmarkCopies(const int iterations) -> uint64_t {
    const CoordsSet source = MakePatternedCoordsSet(3);
    uint64_t checksum = 0;

    for (int i = 0; i < iterations; ++i) {
        CoordsSet copy = source;
        DoNotOptimize(copy);
        checksum += copy.size();
    }

    return checksum;
}

[[nodiscard]] auto BenchmarkUnions(const int iterations) -> uint64_t {
    const CoordsSet left = MakePatternedCoordsSet(3);
    const CoordsSet right = MakePatternedCoordsSet(5, 1);
    uint64_t checksum = 0;

    for (int i = 0; i < iterations; ++i) {
        CoordsSet combined = left;
        combined += right;
        DoNotOptimize(combined);
        checksum += combined.size();
    }

    return checksum;
}

[[nodiscard]] auto BenchmarkSubtractions(const int iterations) -> uint64_t {
    const CoordsSet full = MakePatternedCoordsSet(1);
    const CoordsSet removed = MakePatternedCoordsSet(4);
    uint64_t checksum = 0;

    for (int i = 0; i < iterations; ++i) {
        CoordsSet remaining = full;
        remaining -= removed;
        DoNotOptimize(remaining);
        checksum += remaining.size();
    }

    return checksum;
}

[[nodiscard]] auto BenchmarkContains(const int iterations) -> uint64_t {
    const CoordsSet coords = MakePatternedCoordsSet(3);
    uint64_t checksum = 0;

    for (int i = 0; i < iterations; ++i) {
        const int index = i % kIndexCount;
        checksum += coords.Contains(index / kColumnCount, index % kColumnCount) ? 1 : 0;
    }

    return checksum;
}

[[nodiscard]] auto BenchmarkCoordinateIteration(const int iterations) -> uint64_t {
    const CoordsSet coords = MakePatternedCoordsSet(3);
    uint64_t checksum = 0;

    for (int i = 0; i < iterations; ++i) {
        for (const auto& coord : coords) {
            checksum += static_cast<uint64_t>(coord.row() + coord.column());
        }
    }

    return checksum;
}

[[nodiscard]] auto BenchmarkIndexIteration(const int iterations) -> uint64_t {
    const CoordsSet coords = MakePatternedCoordsSet(3);
    uint64_t checksum = 0;

    for (int i = 0; i < iterations; ++i) {
        for (const auto index : coords.indexIterator()) { checksum += index; }
    }

    return checksum;
}

[[nodiscard]] auto BenchmarkDenseFilter(const int iterations) -> uint64_t {
    const CoordsSet source = MakePatternedCoordsSet(1);
    uint64_t checksum = 0;

    for (int i = 0; i < iterations; ++i) {
        CoordsSet filtered = source;
        filtered.Filter([](const int row, const int column) { return (row + column) % 3 == 0; });
        DoNotOptimize(filtered);
        checksum += filtered.size();
    }

    return checksum;
}

[[nodiscard]] auto BenchmarkSparseFilter(const int iterations) -> uint64_t {
    const CoordsSet source = MakePatternedCoordsSet(7);
    uint64_t checksum = 0;

    for (int i = 0; i < iterations; ++i) {
        CoordsSet filtered = source;
        filtered.Filter([](const int row, const int column) { return (row + column) % 3 == 0; });
        DoNotOptimize(filtered);
        checksum += filtered.size();
    }

    return checksum;
}

}  // namespace
}  // namespace shardok

auto main() -> int {
    using shardok::BenchmarkContains;
    using shardok::BenchmarkCoordinateIteration;
    using shardok::BenchmarkCopies;
    using shardok::BenchmarkDenseFilter;
    using shardok::BenchmarkIndexIteration;
    using shardok::BenchmarkSparseFilter;
    using shardok::BenchmarkSubtractions;
    using shardok::BenchmarkUnions;
    using shardok::CoordsSet;
    using shardok::PrintResult;
    using shardok::RunBenchmark;

    std::cout << "CoordsSet benchmark\n";
    std::cout << "sizeof(CoordsSet)=" << sizeof(CoordsSet)
              << " alignof(CoordsSet)=" << alignof(CoordsSet) << '\n';

    PrintResult(RunBenchmark("copy", 5'000'000, BenchmarkCopies));
    PrintResult(RunBenchmark("union", 3'000'000, BenchmarkUnions));
    PrintResult(RunBenchmark("subtraction", 3'000'000, BenchmarkSubtractions));
    PrintResult(RunBenchmark("contains", 20'000'000, BenchmarkContains));
    PrintResult(RunBenchmark("coord iteration", 1'000'000, BenchmarkCoordinateIteration));
    PrintResult(RunBenchmark("index iteration", 1'000'000, BenchmarkIndexIteration));
    PrintResult(RunBenchmark("dense filter", 500'000, BenchmarkDenseFilter));
    PrintResult(RunBenchmark("sparse filter", 500'000, BenchmarkSparseFilter));

    return 0;
}
