#ifndef TEST_UTILS_H
#define TEST_UTILS_H

#include <random>
#include <cassert>
#include <climits>
#include <sstream>
#include <iomanip>
#include <string>

class RandomHelper {
public:
    RandomHelper()
        : randomEngine(std::random_device{}()),
          randomDist(0, ULLONG_MAX) {}

    explicit RandomHelper(unsigned long long seed)
        : randomEngine(seed),
          randomDist(0, ULLONG_MAX) {}

    void setSeed(unsigned long long seed) {
        randomEngine.seed(seed);
    }

    long long inRange(long long minimum, long long maximum) {
        assert(minimum <= maximum);
        unsigned long long n = randomDist(randomEngine);
        return n % (maximum - minimum + 1) + minimum;
    }

    long long inRangeInFirstTenPercent(long long minimum, long long maximum) {
        long long total = maximum - minimum + 1;
        long long tenPercentSize = total / 10 + 1;
        return inRange(minimum, minimum + tenPercentSize - 1);
    }

    long long inRangeInLastTenPercent(long long minimum, long long maximum) {
        long long total = maximum - minimum + 1;
        long long tenPercentSize = total / 10 + 1;
        return inRange(maximum - tenPercentSize + 1, maximum);
    }

    long long primeInRangeInLastTenPercent(long long minimum, long long maximum) {
        long long total = maximum-minimum+1;
        long long tenPercentSize = total/10+1;
        long long n = inRangeInLastTenPercent(minimum, maximum);
        while (n <= maximum) n++;
        if (n <= maximum) return n;
        while (n > maximum) n--;
        while (n >= minimum) n--;
        assert(minimum <= n && n <= maximum);
        return n;
    }

private:
    std::mt19937_64 randomEngine;
    std::uniform_int_distribution<unsigned long long> randomDist;
};

int generateSeed(const std::string& s, std::initializer_list<int> nums) {
    std::vector<int> data(s.begin(), s.end());
    data.insert(data.end(), nums.begin(), nums.end());

    std::seed_seq seq(data.begin(), data.end());
    std::vector<std::uint32_t> seeds(1);
    seq.generate(seeds.begin(), seeds.end());

    return static_cast<int>(seeds[0]);
}

int generateSeed(std::initializer_list<int> nums) {
    std::vector<int> data(nums.begin(), nums.end());

    std::seed_seq seq(data.begin(), data.end());
    std::vector<std::uint32_t> seeds(1);
    seq.generate(seeds.begin(), seeds.end());

    return static_cast<int>(seeds[0]);
}

size_t hash_vector_of_strings(const std::vector<std::string>& vec) {
    std::hash<std::string> string_hasher;
    size_t result = 0;

    for (const auto& s : vec) {
        size_t h = string_hasher(s);
        result ^= h + 0x9e3779b9 + (result << 6) + (result >> 2);
    }

    return result;
}

std::string generateFilename(const std::string& base, int number, int width) {
    std::ostringstream oss;
    oss << base << '.' << std::setw(width) << std::setfill('0') << number << ".in";
    return oss.str();
}

#if defined(_WIN32)
    #include <direct.h>
    #define MKDIR(path) _mkdir(std::string(path).c_str())
#else
    #include <sys/stat.h>
    #define MKDIR(path) mkdir(std::string(path).c_str(), 0755)
#endif

/** C++20
std::string generateFilename(const std::string& base, int number, int width) {
    return std::format("{}.{:0{}}.in", base, number, width);
}*/

static bool isPrime(long long n) {
    if (n < 2) return false;
    for (long long i = 2; i * i <= n; ++i) {
        if (n % i == 0) return false;
    }
    return true;
}

struct GroupLimit {
    long long minN, maxN;
};

#endif
