#include "../test/Test.hpp"
#include "core/data/HashMap.hpp"
#include "core/data/ProbingHashMap.hpp"
#include "core/utils/Clock.hpp"
#include "core/utils/Random.hpp"

using Nanos = Core::Clock::Nanos;
namespace Logger = Core::Logger;

struct Timer {
    Nanos nanos;

    Timer() : nanos(0) {
        CORE_TEST_ERROR(Core::Clock::getNanos(nanos));
    }

    Nanos get() const {
        Nanos nanos2 = 0;
        CORE_TEST_ERROR(Core::Clock::getNanos(nanos2));
        return nanos2 - nanos;
    }
};

template<typename Map>
static Nanos testSearch(const Map& m) {
    Timer t;
    volatile int sum = 0;
    for(int i = 0; i < 10000; i++) {
        for(int k = -5000; k < 5000; k++) {
            const int* s = m.search(i + k);
            if(s != nullptr) {
                sum = sum + *s;
            }
        }
    }
    return t.get();
}

template<typename Map>
static Nanos testEmptySearch(const Map& m) {
    Timer t;
    volatile int sum = 0;
    for(int i = 0; i < 100'000'000; i++) {
        const int* s = m.search(-i);
        if(s != nullptr) {
            sum = sum + *s;
        }
    }
    return t.get();
}

template<typename Map>
static void fillOrder(Map& m) {
    for(int i = 0; i < 10000; i++) {
        m.add(i, i * i);
    }
}

template<typename Map>
static void fillChaos(Map& m) {
    Core::Random random(0);
    for(int i = 0; i < 10000; i++) {
        int r = random.nextI32(0, 9999);
        m.add(r, r * r);
    }
}

template<typename Map>
static int average(Map& m, Nanos (*f)(const Map&), int n) {
    Nanos sum = 0;
    for(int i = 0; i < n; i++) {
        sum += f(m);
    }
    return static_cast<int>(sum / (n * 1'000'000));
}

static void order(int n) {
    Core::HashMap<int, int> m;
    Core::ProbingHashMap<int, int> m2;
    fillOrder(m);
    fillOrder(m2);
    Logger::log(Logger::COLOR_GRAY, "Order Chaining | Order Probing");
    Logger::log(Logger::COLOR_GRAY, "Search | # ms | # ms",
                average(m, testSearch, n), average(m2, testSearch, n));
    Logger::log(Logger::COLOR_GRAY, "EmptySearch | # ms | # ms",
                average(m, testEmptySearch, n),
                average(m2, testEmptySearch, n));
}

static void chaos(int n) {
    Core::HashMap<int, int> m;
    Core::ProbingHashMap<int, int> m2;
    fillChaos(m);
    fillChaos(m2);
    Logger::log(Logger::COLOR_GRAY, "Chaos Chaining | Chaos Probing");
    Logger::log(Logger::COLOR_GRAY, "Search | # ms | # ms",
                average(m, testSearch, n), average(m2, testSearch, n));
    Logger::log(Logger::COLOR_GRAY, "EmptySearch | # ms | # ms",
                average(m, testEmptySearch, n),
                average(m2, testEmptySearch, n));
}

static void testProbing(int n) {
    Core::ProbingHashMap<int, int> m;
    Core::ProbingHashMap<int, int> m2;
    fillOrder(m);
    fillChaos(m2);
    Logger::log(Logger::COLOR_GRAY, "Order | Chaos");
    Logger::log(Logger::COLOR_GRAY, "Search | # ms | # ms",
                average(m, testSearch, n), average(m2, testSearch, n));
    Logger::log(Logger::COLOR_GRAY, "EmptySearch | # ms | # ms",
                average(m, testEmptySearch, n),
                average(m2, testEmptySearch, n));
}

int main() {
    (void)order;
    (void)chaos;
    (void)testProbing;
    order(3);
    chaos(3);
    // testProbing(3);
    Core::Test::finalize();
    return 0;
}