#pragma once
// Copyright 2020 TooYoungTooSimp <lyc@xuming.studio>, All rights reserved.
#define _CRT_SECURE_NO_WARNINGS
#ifndef _UTILITIES_H_
#define _UTILITIES_H_
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <map>
#include <mutex>
#include <random>
#include <string>

static void __strconcat_impl(std::string &res) {}
template <typename T, typename... TArgs>
static void __strconcat_impl(std::string &res, const T &arg, const TArgs &... args)
{
    res.append(arg);
    __strconcat_impl(res, args...);
}
template <typename... TArgs>
static std::string strconcat(TArgs... args)
{
    std::string res;
    __strconcat_impl(res, args...);
    return res;
}

class spin_lock
{
private:
    std::atomic<bool> flag = false;

public:
    spin_lock() = default;
    spin_lock(const spin_lock &) = delete;
    spin_lock &operator=(const spin_lock &) = delete;
    void lock()
    {
        bool target = false;
        while (!flag.compare_exchange_strong(target, true))
            target = false;
    }
    void unlock() { flag.store(false); }
};

#ifdef _WIN32
// clang-format off
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <wincrypt.h>
#include <Psapi.h>
#pragma comment(lib, "Advapi32.lib")
#pragma comment(lib, "user32.lib")
// clang-format on
static size_t __get_working_set_size()
{
    PROCESS_MEMORY_COUNTERS ProcessMemoryCounters;
    GetProcessMemoryInfo(GetCurrentProcess(), &ProcessMemoryCounters, sizeof(PROCESS_MEMORY_COUNTERS));
    return ProcessMemoryCounters.WorkingSetSize;
}
class MemoryMonitor
{
private:
    static spin_lock lock;
    static int64_t global_counter;
    static std::map<MemoryMonitor *, int64_t *> M;
    int64_t max_usage;
    int64_t counter;
    _CRT_ALLOC_HOOK _orig_alloc_hook;
    static size_t __get_block_size(void *ptr) { return *((size_t *)ptr - 4 + (sizeof(size_t) >> 2)); }
    static int __crt_alloc_hook(int allocType, void *ptr, size_t size,
                                int blockType, long requestNumber,
                                const unsigned char *filename, int lineNumber);

public:
    MemoryMonitor()
    {
        std::lock_guard<spin_lock> guard(lock);
        if (M.empty())
        {
            _orig_alloc_hook = _CrtGetAllocHook();
            _CrtSetAllocHook(__crt_alloc_hook);
        }
        M[this] = &max_usage;
        reset();
    }
    ~MemoryMonitor()
    {
        std::lock_guard<spin_lock> guard(lock);
        M.erase(this);
        if (M.empty())
            _CrtSetAllocHook(_orig_alloc_hook);
    }
    void reset() { max_usage = counter = global_counter; }
    int64_t save() { return max_usage - counter; }
};

static unsigned int __get_random_seed()
{
    unsigned int r = 0;
    HCRYPTPROV hCryptProv;
    CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, 0);
    CryptGenRandom(hCryptProv, sizeof(unsigned int), (BYTE *)&r);
    CryptReleaseContext(hCryptProv, 0);
    return r;
}
static void __error_then_exit(const char *msg)
{
    MessageBoxA(NULL, msg, "Error", MB_OK | MB_ICONERROR);
    exit(1);
}
#else
unsigned int __get_random_seed()
{
    unsigned int r = 0;
    FILE *fd = fopen("/dev/random", "rb");
    fread(&r, sizeof(unsigned int), 1, fd);
    fclose(fd);
    return r;
}
void __error_then_exit(const char *msg)
{
    fputs(msg, stderr);
    getchar(), getchar();
    exit(1);
}
#endif

class Random
{
private:
    std::mt19937 *rd;

public:
    Random() { rd = new std::mt19937(__get_random_seed()); }
    ~Random() { delete rd; }
    unsigned int next() { return (*rd)(); }
    unsigned int next(unsigned int mn, unsigned int mx) { return next() % (mx - mn) + mn; }
};

class FileHandleExitOnFail
{
private:
    FILE *fp;

public:
    FileHandleExitOnFail(FILE *_fp) : fp(_fp) {}
    FileHandleExitOnFail(const char *filename, const char *mode)
    {
        fp = fopen(filename, mode);
        if (!fp)
        {
            auto msg = strconcat("Failed to open ", filename);
            __error_then_exit(msg.c_str());
        }
    }
    ~FileHandleExitOnFail() { fclose(fp); }
    operator FILE *() const { return fp; }
};

class Timer
{
public:
    using fmilliseconds = std::chrono::duration<double, std::milli>;
    void start() { t1 = std::chrono::steady_clock::now(); }
    void stop() { t2 = std::chrono::steady_clock::now(); }
    std::chrono::steady_clock::duration::rep difference() { return (t2 - t1).count(); }
    double diff_in_ms() { return std::chrono::duration_cast<fmilliseconds>(t2 - t1).count(); }

private:
    std::chrono::time_point<std::chrono::steady_clock> t1, t2;
};

template <typename T>
class Array2DView
{
private:
    T *arr;
    size_t w;

public:
    Array2DView(size_t _w, T *ptr = nullptr) : arr(ptr), w(_w) {}
    Array2DView<T> &operator=(T *ptr) { arr = ptr; }
    void resize(size_t _w) { w = _w; }
    T *operator[](size_t idx) { return arr + idx * w; }
    T *data() const { return arr; }
};

#endif