#pragma once
#ifndef _CRT_SECURE_NO_WARNINGS
    #define _CRT_SECURE_NO_WARNINGS
#endif
#include <stdint.h>
#include <assert.h>
#include <string.h>
#include <stdarg.h>
#define Assert(x)       \
    if (!(x)) {         \
        __debugbreak(); \
    }

#if defined(__APPLE__) && defined(__MACH__)
    #define OS_MAC 1
#elif defined(_WIN32)
    #define OS_WINDOWS 1
#elif defined(__linux__)
    #define OS_POSIX 1
    #define OS_LINUX 1
#else
    #error Unsupported platform
#endif

#if defined(__clang__)
    #define COMPILER_CLANG 1
#elif defined(__GNUC__) || defined(__GNUG__)
    #define COMPILER_GCC 1
#elif defined(_MSC_VER)
    #define COMPILER_MSVC 1
#else
    #error Unsupported compiler
#endif

#ifndef OS_MAC
    #define OS_MAC 0
#endif

#ifndef OS_WINDOWS
    #define OS_WINDOWS 0
#endif

#ifndef OS_LINUX
    #define OS_LINUX 0
#endif

#ifndef OS_POSIX
    #define OS_POSIX 0
#endif

#ifndef COMPILER_MSVC
    #define COMPILER_MSVC 0
#endif

#ifndef COMPILER_CLANG
    #define COMPILER_CLANG 0
#endif

#ifndef COMPILER_GCC
    #define COMPILER_GCC 0
#endif

#define KiB(x) ((x##ull) * 1024ull)
#define MiB(x) (KiB(x) * 1024ull)
#define GiB(x) (MiB(x) * 1024ull)
#define TiB(x) (GiB(x) * 1024ull)
#define Lengthof(x) ((int64_t)((sizeof(x) / sizeof((x)[0]))))

template <class T>
T Min(T a, T b) {
    if (a > b) return b;
    return a;
}

template <class T>
T ClampTop(T a, T top) {
    return Min(a, top);
}

template <class T>
T Max(T a, T b) {
    if (a > b) return a;
    return b;
}

template <class T>
T ClampBottom(T bottom, T b) {
    return Max(bottom, b);
}

template <class T>
T Clamp(T value, T min, T max) {
    if (value < min) return min;
    if (value > max) return max;
    return value;
}

template <class T>
void Swap(T *a, T *b) {
    T temp = *a;
    *a     = *b;
    *b     = temp;
}

inline bool IsPowerOf2(size_t x) {
    size_t result = (((x) & ((x)-1)) == 0);
    return result;
}

inline size_t WrapAroundPowerOf2(size_t x, size_t pow2) {
    Assert(IsPowerOf2(pow2));
    size_t result = (((x) & ((pow2)-1llu)));
    return result;
}

inline uint64_t HashBytes(void *data, unsigned size) {
    uint8_t *data8 = (uint8_t *)data;
    uint64_t hash  = (uint64_t)14695981039346656037ULL;
    for (unsigned i = 0; i < size; i++) {
        hash = hash ^ (uint64_t)(data8[i]);
        hash = hash * (uint64_t)1099511628211ULL;
    }
    return hash;
}

inline size_t GetAlignOffset(size_t size, size_t align) {
    Assert(IsPowerOf2(align));
    size_t mask = align - 1;
    size_t val  = size & mask;
    if (val) {
        val = align - val;
    }
    return val;
}

inline size_t AlignUp(size_t size, size_t align) {
    size_t result = size + GetAlignOffset(size, align);
    return result;
}

inline size_t AlignDown(size_t size, size_t align) {
    size += 1; // Make sure when align is 8 doesn't get rounded down to 0
    size_t result = size - (align - GetAlignOffset(size, align));
    return result;
}

const int AllocatorKind_Allocate   = 1;
const int AllocatorKind_Deallocate = 2;
struct Allocator {
    void *(*proc)(void *object, int kind, void *p, size_t size);
    void *object;
};

inline void *AllocSize(Allocator alo, size_t size) {
    void *result = alo.proc(alo.object, AllocatorKind_Allocate, NULL, size);
    memset(result, 0, size);
    return result;
}
#define AllocType(alo, Type) (Type *)AllocSize(alo, sizeof(Type))
#define AllocArray(alo, Type, count) (Type *)AllocSize(alo, sizeof(Type) * (count))

template <class T>
void Dealloc(Allocator alo, T **p) {
    if (*p == NULL) return;
    alo.proc(alo.object, AllocatorKind_Deallocate, *p, 0);
    *p = NULL;
}

Allocator GetSystemAllocator();
#define MemoryZero(x, size) memset(x, 0, size)

/*
Iterating and removing elements

for (int i = 0; i < array.len; i += 1) {
    auto &it = array[i];
    bool remove_item = false;
    defer {
        if (remove_item) {
            array.ordered_remove(it);
            i -= 1;
        }
    }
}

// Simple delete

IterRemove(arr) {
    IterRemovePrepare(arr);

    remove_item = true;
}

//
// Deleting backwards
//

For(arr.reverse_iter()) {
    defer{ arr.unordered_remove(it); };
}



*/
#define IterRemove(a) for (int i = 0; i < (a).len; i += 1)
#define IterRemovePrepare(a)        \
    auto &it          = (a)[i];     \
    bool  remove_item = false;      \
    defer {                         \
        if (remove_item) {          \
            (a).ordered_remove(it); \
            i -= 1;                 \
        }                           \
    }
#define ForItem(it, array) for (auto &it : (array))
#define For(array) ForItem(it, array)

constexpr int64_t SLICE_LAST = INT64_MIN;

template <class T>
struct Slice {
    T      *data;
    int64_t len;

    Slice() = default;
    Slice(T *s, int64_t l) : data(s), len(l) {}
    Slice(char *s) : data(s), len(strlen(s)) {}
    Slice(const char *s) : data((char *)s), len(strlen((char *)s)) {}
    Slice(const char *s, int64_t l) : data((char *)s), len(l) {}

    Slice<T> copy(Allocator allocator) {
        Slice<T> result = {AllocArray(allocator, T, len), len};
        return result;
    }

    // @copy_paste array slice

    bool contains(T item) {
        for (int64_t i = 0; i < len; i += 1) {
            if (data[i] == item) return true;
        }
        return false;
    }

    T get_or_default(int64_t i, T default_value = {}) {
        T result = default_value;
        if (i >= 0 && i < len) {
            result = data[i];
        }
        return result;
    }

    T pop() {
        Assert(len > 0);
        return data[--len];
    }

    void unordered_remove(T &item) { // DONT USE IN LOOPS !!!!
        Assert(&item >= begin() && &item < end());
        item = data[--len];
    }

    void unordered_remove_index(int64_t index) {
        Assert(index >= 0 && index < len);
        data[index] = data[--len];
    }

    void ordered_remove(T &item) { // DONT USE IN LOOPS !!!
        Assert(&item >= begin() && &item < end());
        int64_t index = get_index(item);
        ordered_remove_index(index);
    }

    void ordered_remove_index(int64_t index) {
        Assert(index >= 0 && index < len);
        int64_t right_len = len - index - 1;
        memmove(data + index, data + index + 1, right_len * sizeof(T));
        len -= 1;
    }

    // @copy_paste array slice string

    Slice<T> chop(int64_t ilen) {
        ilen            = ClampTop(ilen, len);
        Slice<T> result = {data, len - ilen};
        return result;
    }

    Slice<T> skip(int64_t ilen) {
        ilen            = ClampTop(ilen, len);
        Slice<T> result = {data + ilen, len - ilen};
        return result;
    }

    Slice<T> get_postfix(int64_t ilen) {
        ilen                = ClampTop(ilen, len);
        int64_t  remain_len = len - ilen;
        Slice<T> result     = {data + remain_len, ilen};
        return result;
    }

    Slice<T> get_prefix(int64_t ilen) {
        ilen            = ClampTop(ilen, len);
        Slice<T> result = {data, ilen};
        return result;
    }

    Slice<T> slice(int64_t first_index, int64_t one_past_last_index) {
        // Negative indexes work in python style, they return you the index counting from end of list
        if (one_past_last_index == SLICE_LAST) one_past_last_index = len;
        if (one_past_last_index < 0) one_past_last_index = len + one_past_last_index;

        if (first_index == SLICE_LAST) first_index = len;
        if (first_index < 0) first_index = len + first_index;

        Slice<T> result = *this;
        if (len > 0) {
            if (one_past_last_index > first_index) {
                first_index         = ClampTop(first_index, len - 1);
                one_past_last_index = ClampTop(one_past_last_index, len);
                result.data += first_index;
                result.len = one_past_last_index - first_index;
            } else {
                result.len = 0;
            }
        }
        return result;
    }

    struct Reverse_Iter {
        T        *data;
        Slice<T> *arr;

        Reverse_Iter operator++(int) {
            Reverse_Iter ret = *this;
            data -= 1;
            return ret;
        }
        Reverse_Iter &operator++() {
            data -= 1;
            return *this;
        }

        T &operator*() { return data[0]; }
        T *operator->() { return data; }

        friend bool operator==(const Reverse_Iter &a, const Reverse_Iter &b) { return a.data == b.data; };
        friend bool operator!=(const Reverse_Iter &a, const Reverse_Iter &b) { return a.data != b.data; };

        Reverse_Iter begin() { return Reverse_Iter{arr->end() - 1, arr}; }
        Reverse_Iter end() { return Reverse_Iter{arr->begin() - 1, arr}; }
    };
    Reverse_Iter reverse_iter() { return {end() - 1, this}; }

    bool contains(T &item) {
        bool result = &item >= data && &item < data + len;
        return result;
    }

    int64_t get_index(const T &item) {
        ptrdiff_t index = (ptrdiff_t)(&item - data);
        Assert(index >= 0 && index < len);
        return (int64_t)index;
    }

    bool is_first(T &item) { return &item == first(); }
    bool is_last(T &item) { return &item == last(); }
    T   *first() {
        Assert(len > 0);
        return data;
    }
    T *last() {
        Assert(len > 0);
        return data + len - 1;
    }
    T *front() {
        Assert(len > 0);
        return data;
    }
    T *back() {
        Assert(len > 0);
        return data + len - 1;
    }
    T &operator[](int64_t index) {
        Assert(index < len);
        return data[index];
    }
    T *begin() { return data; }
    T *end() { return data + len; }
};

template <class T>
struct Array {
    Allocator allocator;
    T        *data;
    int64_t   cap, len;

    void add(const T &item) {
        try_growing();
        data[len++] = item;
    }

    void bounded_add(T item) {
        if (len + 1 <= cap) {
            data[len++] = item;
        }
    }

    T *alloc(const T &item) {
        try_growing();
        T *ref = data + len++;
        *ref   = item;
        return ref;
    }

    T *alloc() {
        try_growing();
        T *ref = data + len++;
        *ref   = {};
        return ref;
    }

    T *alloc_multiple(int64_t size) {
        try_growing_to_fit_item_count(size);
        T *result = data + len;
        len += size;
        return result;
    }

    void add_array(T *items, int64_t item_count) {
        for (int64_t i = 0; i < item_count; i += 1) {
            add(items[i]);
        }
    }

    void add_array(Array<T> items) {
        add_array(items.data, items.len);
    }

    void reserve(int64_t size) {
        if (size > cap) {
            if (!allocator.proc) allocator = GetSystemAllocator();

            void *new_data = AllocSize(allocator, size * sizeof(T));
            Assert(new_data);
            memcpy(new_data, data, len * sizeof(T));
            Dealloc(allocator, &data);

            data = (T *)new_data;
            cap  = size;
        }
    }

    void init(Allocator allocator, int64_t size) {
        len             = 0;
        cap             = 0;
        data            = 0;
        this->allocator = allocator;
        reserve(size);
    }

    void clear() {
        len = 0;
    }

    void insert(T item, int64_t index) {
        if (index == len) {
            add(item);
            return;
        }

        Assert(index < len);
        Assert(index >= 0);

        try_growing();
        int64_t right_len = len - index;
        memmove(data + index + 1, data + index, sizeof(T) * right_len);
        data[index] = item;
        len += 1;
    }

    void dealloc() {
        if (data) Dealloc(allocator, &data);
        len = cap = 0;
    }

    Array<T> copy(Allocator allocator) {
        Array result     = {};
        result.allocator = allocator;
        result.reserve(cap);

        memmove(result.data, data, sizeof(T) * len);
        result.len = len;
        return result;
    }

    Array<T> tight_copy(Allocator allocator) {
        Array result     = {};
        result.allocator = allocator;
        result.reserve(len);

        memmove(result.data, data, sizeof(T) * len);
        result.len = len;
        return result;
    }

    void try_growing() {
        if (len + 1 > cap) {
            int64_t new_size = cap * 2;
            if (new_size < 16) new_size = 16;

            reserve(new_size);
        }
    }

    void try_growing_to_fit_item_count(int64_t item_count) {
        if (len + item_count > cap) {
            int64_t new_size = (cap + item_count) * 2;
            if (new_size < 16) new_size = 16;
            reserve(new_size);
        }
    }

    // @copy_paste array slice

    bool contains(T item) {
        for (int64_t i = 0; i < len; i += 1) {
            if (data[i] == item) return true;
        }
        return false;
    }

    T get_or_default(int64_t i, T default_value = {}) {
        T result = default_value;
        if (i >= 0 && i < len) {
            result = data[i];
        }
        return result;
    }

    T pop() {
        Assert(len > 0);
        return data[--len];
    }

    void unordered_remove(T &item) { // DONT USE IN LOOPS !!!!
        Assert(len > 0);
        Assert(&item >= begin() && &item < end());
        item = data[--len];
    }

    void unordered_remove_index(int64_t index) {
        Assert(index >= 0 && index < len);
        data[index] = data[--len];
    }

    void ordered_remove(T &item) {
        Assert(len > 0);
        Assert(&item >= begin() && &item < end());
        int64_t index = get_index(item);
        ordered_remove_index(index);
    }

    void ordered_remove_index(int64_t index) {
        Assert(index >= 0 && index < len);
        int64_t right_len = len - index - 1;
        memmove(data + index, data + index + 1, right_len * sizeof(T));
        len -= 1;
    }

    // @copy_paste array string slice
    struct Reverse_Iter {
        T        *data;
        Array<T> *arr;

        Reverse_Iter operator++(int) {
            Reverse_Iter ret = *this;
            data -= 1;
            return ret;
        }
        Reverse_Iter &operator++() {
            data -= 1;
            return *this;
        }

        T &operator*() { return data[0]; }
        T *operator->() { return data; }

        friend bool operator==(const Reverse_Iter &a, const Reverse_Iter &b) { return a.data == b.data; };
        friend bool operator!=(const Reverse_Iter &a, const Reverse_Iter &b) { return a.data != b.data; };

        Reverse_Iter begin() { return Reverse_Iter{arr->end() - 1, arr}; }
        Reverse_Iter end() { return Reverse_Iter{arr->begin() - 1, arr}; }
    };

    Reverse_Iter reverse_iter() { return {end() - 1, this}; }

    Slice<T> chop(int64_t ilen) {
        ilen            = ClampTop(ilen, len);
        Slice<T> result = {data, len - ilen};
        return result;
    }

    Slice<T> skip(int64_t ilen) {
        ilen            = ClampTop(ilen, len);
        Slice<T> result = {data + ilen, len - ilen};
        return result;
    }

    Slice<T> get_postfix(int64_t ilen) {
        ilen                = ClampTop(ilen, len);
        int64_t  remain_len = len - ilen;
        Slice<T> result     = {data + remain_len, ilen};
        return result;
    }

    Slice<T> get_prefix(int64_t ilen) {
        ilen            = ClampTop(ilen, len);
        Slice<T> result = {data, ilen};
        return result;
    }

    Slice<T> slice(int64_t first_index = 0, int64_t one_past_last_index = SLICE_LAST) {
        // Negative indexes work in python style, they return you the index counting from end of list
        if (one_past_last_index == SLICE_LAST) one_past_last_index = len;
        if (one_past_last_index < 0) one_past_last_index = len + one_past_last_index;

        if (first_index == SLICE_LAST) first_index = len;
        if (first_index < 0) first_index = len + first_index;

        Slice<T> result = {data, len};
        if (len > 0) {
            if (one_past_last_index > first_index) {
                first_index         = ClampTop(first_index, len - 1);
                one_past_last_index = ClampTop(one_past_last_index, len);
                result.data += first_index;
                result.len = one_past_last_index - first_index;
            } else {
                result.len = 0;
            }
        }
        return result;
    }

    int64_t get_index(const T &item) {
        ptrdiff_t index = (ptrdiff_t)(&item - data);
        Assert(index >= 0 && index < len);
        // Assert(index > INT_MIN && index < INT_MAX);
        return (int64_t)index;
    }

    bool contains(T &item) {
        bool result = &item >= data && &item < data + len;
        return result;
    }

    bool is_first(T &item) { return &item == first(); }
    bool is_last(T &item) { return &item == last(); }
    T   *first() {
        Assert(len > 0);
        return data;
    }
    T *last() {
        Assert(len > 0);
        return data + len - 1;
    }
    T *front() {
        Assert(len > 0);
        return data;
    }
    T *back() {
        Assert(len > 0);
        return data + len - 1;
    }
    T &operator[](int64_t index) {
        Assert(index >= 0 && index < len);
        return data[index];
    }
    T *begin() { return data; }
    T *end() { return data + len; }
};

struct UTF32Result {
    uint32_t out_str;
    int64_t  advance;
    int64_t  error;
};

struct UTF8Result {
    uint8_t out_str[4];
    int64_t len;
    int64_t error;
};

struct UTF16Result {
    uint16_t out_str[2];
    int64_t  len;
    int64_t  error;
};

struct UTF8Iter {
    char    *data;
    int64_t  len;
    int64_t  utf8_codepoint_byte_size;
    int64_t  i;
    uint32_t item;

    UTF8Iter &operator++() {
        void Advance(UTF8Iter * iter);
        Advance(this);
        return *this;
    }
    friend bool operator!=(const UTF8Iter &a, const UTF8Iter &b) { return a.item != b.item; }
    UTF8Iter   &operator*() { return *this; }
    UTF8Iter    begin() {
        UTF8Iter IterateUTF8Ex(char *data, int64_t len);
        return {IterateUTF8Ex(data, len)};
    }
    UTF8Iter end() { return {}; }
};

using String   = Slice<char>;
using String16 = Slice<wchar_t>;
bool     IsValid(UTF8Iter &iter);
void     Advance(UTF8Iter *iter);
UTF8Iter IterateUTF8Ex(char *data, int64_t len);
UTF8Iter IterateUTF8(char *data);
UTF8Iter IterateUTF8(String string);

#define FmtString(string) (int)(string).len, (string).data
bool        AreEqual(String a, String b, unsigned ignore_case = false);
int64_t     CreateWidecharFromChar(wchar_t *buffer, int64_t buffer_size, char *in, int64_t inlen);
inline bool operator==(String a, String b) { return AreEqual(a, b); }
inline bool operator!=(String a, String b) { return !AreEqual(a, b); }

#if defined(__has_attribute)
    #if __has_attribute(format)
        #define PrintfFormatAttribute(fmt, va) __attribute__((format(printf, fmt, va)))
    #endif
#endif

#ifndef PrintfFormatAttribute
    #define PrintfFormatAttribute(fmt, va)
#endif

#define STRING_FORMAT(allocator, data, result)       \
    va_list args1;                                   \
    va_start(args1, data);                           \
    String result = FormatV(allocator, data, args1); \
    va_end(args1)

String   Format(Allocator allocator, const char *data, ...) PrintfFormatAttribute(2, 3);
String   FormatV(Allocator allocator, const char *data, va_list args1);
String   ToString(Allocator allocator, String16 string);
String   ToString(Allocator allocator, wchar_t *string, int64_t len);
String   ToString(Allocator allocator, wchar_t *wstring);
String16 ToString16(Allocator allocator, String string);
wchar_t *ToWidechar(Allocator allocator, String string);
int64_t  WideLength(wchar_t *string);
void     NormalizePathInPlace(String s);
String   ChopLastSlash(String s);
String   ChopLastPeriod(String s);
String   SkipToLastSlash(String s);
String   SkipToLastPeriod(String s);
String   Copy(Allocator allocator, String string);
/*
    Hash table implementation:
        Pointers to values
        Open adressing
        Linear Probing
        Power of 2
        Robin Hood hashing
        Resizes on high probe count (min max load factor)

        Hash 0 is reserved for empty hash table entry
*/
template <class Value>
struct Table {
    struct Entry {
        uint64_t hash;
        uint64_t key;
        size_t   distance;
        Value    value;
    };

    Allocator allocator;
    size_t    len, cap;
    Entry    *values;

    static const size_t max_load_factor      = 80;
    static const size_t min_load_factor      = 50;
    static const size_t significant_distance = 8;

    // load factor calculation was rearranged
    // to get rid of division:
    //> 100 * len / cap = load_factor
    //> len * 100 = load_factor * cap
    inline bool reached_load_factor(size_t lfactor) {
        return (len + 1) * 100 >= lfactor * cap;
    }

    inline bool is_empty(Entry *entry) { return entry->hash == 0; }
    inline bool is_occupied(Entry *entry) { return entry->hash != 0; }

    void reserve(size_t size) {
        Assert(size > cap && "New size is smaller then original size");
        Assert(IsPowerOf2(size));
        if (!allocator.proc) allocator = GetSystemAllocator();

        Entry *old_values = values;
        size_t old_cap    = cap;

        values = (Entry *)AllocSize(allocator, sizeof(Entry) * size);
        for (int64_t i = 0; i < size; i += 1) values[i] = {};
        cap = size;

        Assert(!(old_values == 0 && len != 0));
        if (len == 0) {
            if (old_values) Dealloc(allocator, &old_values);
            return;
        }

        len = 0;
        for (size_t i = 0; i < old_cap; i += 1) {
            Entry *it = old_values + i;
            if (is_occupied(it)) {
                insert(it->key, it->value);
            }
        }
        Dealloc(allocator, &old_values);
    }

    Entry *get_table_entry(uint64_t key) {
        if (len == 0) return 0;
        uint64_t hash = HashBytes(&key, sizeof(key));
        if (hash == 0) hash += 1;
        uint64_t index    = WrapAroundPowerOf2(hash, cap);
        uint64_t i        = index;
        uint64_t distance = 0;
        for (;;) {
            Entry *it = values + i;
            if (distance > it->distance) {
                return 0;
            }

            if (it->hash == hash && it->key == key) {
                return it;
            }

            distance += 1;
            i = WrapAroundPowerOf2(i + 1, cap);
            if (i == index) return 0;
        }
        Assert(!"Invalid codepath");
    }

    void insert(uint64_t key, const Value &value) {
        if (reached_load_factor(max_load_factor)) {
            if (cap == 0) cap = 16; // 32 cause cap*2
            reserve(cap * 2);
        }

        uint64_t hash = HashBytes(&key, sizeof(key));
        if (hash == 0) hash += 1;
        uint64_t index     = WrapAroundPowerOf2(hash, cap);
        uint64_t i         = index;
        Entry    to_insert = {hash, key, 0, value};
        for (;;) {
            Entry *it = values + i;
            if (is_empty(it)) {
                *it = to_insert;
                len += 1;
                // If we have more then 8 consecutive items we try to resize
                if (to_insert.distance > 8 && reached_load_factor(min_load_factor)) {
                    reserve(cap * 2);
                }
                return;
            }
            if (it->hash == hash && it->key == key) {
                *it = to_insert;
                // If we have more then 8 consecutive items we try to resize
                if (to_insert.distance > 8 && reached_load_factor(min_load_factor)) {
                    reserve(cap * 2);
                }
                return;
            }

            // Robin hood hashing
            if (to_insert.distance > it->distance) {
                Entry temp = to_insert;
                to_insert  = *it;
                *it        = temp;
            }

            to_insert.distance += 1;
            i = WrapAroundPowerOf2(i + 1, cap);
            Assert(i != index && "Did a full 360 through a hash table, no good :( that shouldnt be possible");
        }
        Assert(!"Invalid codepath");
    }

    void remove(uint64_t key) {
        Entry *entry    = get_table_entry(key);
        entry->hash     = 0;
        entry->distance = 0;
        len -= 1;
    }

    Value *get(uint64_t key) {
        Entry *v = get_table_entry(key);
        if (!v) return 0;
        return &v->value;
    }

    Value get(uint64_t key, Value default_value) {
        Entry *v = get_table_entry(key);
        if (!v) return default_value;
        return v->value;
    }

    Value *get(String s) {
        uint64_t hash = HashBytes(s.data, (unsigned)s.len);
        return get(hash);
    }

    Value get(String s, Value default_value) {
        uint64_t hash = HashBytes(s.data, (unsigned)s.len);
        return get(hash, default_value);
    }

    void put(String s, const Value &value) {
        uint64_t hash = HashBytes(s.data, (unsigned)s.len);
        insert(hash, value);
    }

    void reset() {
        len = 0;
        for (size_t i = 0; i < cap; i += 1) {
            Entry *it = values + i;
            it->hash  = 0;
        }
    }

    void dealloc() {
        Dealloc(allocator, &values);
        len = 0;
        cap = 0;
    }
};

template <typename T>
struct DEFER_ExitScope {
    T lambda;
    DEFER_ExitScope(T lambda) : lambda(lambda) {}
    ~DEFER_ExitScope() { lambda(); }
    DEFER_ExitScope(const DEFER_ExitScope &i) : lambda(i.lambda){};

  private:
    DEFER_ExitScope &operator=(const DEFER_ExitScope &);
};

class DEFER_ExitScopeHelp {
  public:
    template <typename T>
    DEFER_ExitScope<T> operator+(T t) { return t; }
};

#define DEFER_CONCAT_INTERNAL(x, y) x##y
#define DEFER_CONCAT(x, y) DEFER_CONCAT_INTERNAL(x, y)
#define defer const auto DEFER_CONCAT(defer__, __LINE__) = DEFER_ExitScopeHelp() + [&]()

const int PAGE_SIZE         = 4096;
const int DEFAULT_ALIGNMENT = sizeof(void *);

struct Arena {
    uint8_t *data;
    size_t   len;
    size_t   base_len; // to prevent self deleting the arena
    size_t   reserve;
    size_t   commit;
    size_t   align;

    operator Allocator() {
        void *ArenaAllocatorProc(void *object, int kind, void *p, size_t size);
        return {ArenaAllocatorProc, this};
    }
};

struct TempArena {
    Arena *arena;
    size_t len;
};

inline void      SetLen(Arena *arena, size_t len) { arena->len = Clamp(len, arena->base_len, arena->len); }
inline void      Pop(Arena *arena, size_t size) { SetLen(arena, arena->len - size); }
inline TempArena BeginTemp(Arena *arena) { return {arena, arena->len}; }
inline void      EndTemp(TempArena temp) { SetLen(temp.arena, temp.len); }
inline void      Clear(Arena *arena) { SetLen(arena, 0); }

void *VReserve(size_t size);
bool  VCommit(void *p, size_t size);
bool  VRelease(void *p);
bool  VDecommit(void *p, size_t size);

void      InitArena(Arena *arena, size_t reserve = GiB(4));
Arena    *AllocArena(size_t reserve = GiB(4));
Arena    *AllocArena(Allocator allocator, size_t size);
void     *PushSize(Arena *arena, size_t size);
void      Release(Arena *arena);
void      InitScratch();
TempArena GetScratchEx(Arena **conflicts, int conflict_count);

inline TempArena GetScratch(Arena *c1 = NULL, Arena *c2 = NULL) {
    int count = c1 ? 1 : 0;
    count += c2 ? 1 : 0;
    Arena *conflicts[] = {c1, c2};
    return GetScratchEx(conflicts, count);
}

struct Scratch {
    TempArena checkpoint;
    Scratch() { this->checkpoint = GetScratch(); }
    Scratch(Arena *conflict) { this->checkpoint = GetScratch(conflict); }
    Scratch(Arena *c1, Arena *c2) { this->checkpoint = GetScratch(c1, c2); }
    ~Scratch() { EndTemp(checkpoint); }
    operator Arena *() { return checkpoint.arena; }
    operator Allocator() { return *checkpoint.arena; }

  private: // @Note: Disable copy constructors, cause its error prone
    Scratch(Scratch &arena);
    Scratch(Scratch &arena, Scratch &a2);
};

//
// Implementation
//
#ifdef BASIC_IMPL

UTF32Result UTF16ToUTF32(uint16_t *c, int64_t max_advance) {
    UTF32Result result;
    MemoryZero(&result, sizeof(result));
    if (max_advance >= 1) {
        result.advance = 1;
        result.out_str = c[0];
        if (c[0] >= 0xD800 && c[0] <= 0xDBFF && c[1] >= 0xDC00 && c[1] <= 0xDFFF) {
            if (max_advance >= 2) {
                result.out_str = 0x10000;
                result.out_str += (uint32_t)(c[0] & 0x03FF) << 10u | (c[1] & 0x03FF);
                result.advance = 2;
            } else
                result.error = 2;
        }
    } else {
        result.error = 1;
    }
    return result;
}

UTF8Result UTF32ToUTF8(uint32_t codepoint) {
    UTF8Result result;
    MemoryZero(&result, sizeof(result));

    if (codepoint <= 0x7F) {
        result.len        = 1;
        result.out_str[0] = (char)codepoint;
    } else if (codepoint <= 0x7FF) {
        result.len        = 2;
        result.out_str[0] = 0xc0 | (0x1f & (codepoint >> 6));
        result.out_str[1] = 0x80 | (0x3f & codepoint);
    } else if (codepoint <= 0xFFFF) { // 16 bit word
        result.len        = 3;
        result.out_str[0] = 0xe0 | (0xf & (codepoint >> 12)); // 4 bits
        result.out_str[1] = 0x80 | (0x3f & (codepoint >> 6)); // 6 bits
        result.out_str[2] = 0x80 | (0x3f & codepoint);        // 6 bits
    } else if (codepoint <= 0x10FFFF) {                       // 21 bit word
        result.len        = 4;
        result.out_str[0] = 0xf0 | (0x7 & (codepoint >> 18));  // 3 bits
        result.out_str[1] = 0x80 | (0x3f & (codepoint >> 12)); // 6 bits
        result.out_str[2] = 0x80 | (0x3f & (codepoint >> 6));  // 6 bits
        result.out_str[3] = 0x80 | (0x3f & codepoint);         // 6 bits
    } else {
        result.error = 1;
    }

    return result;
}

UTF32Result UTF8ToUTF32(char *c, int64_t max_advance) {
    UTF32Result result;
    MemoryZero(&result, sizeof(result));

    if ((c[0] & 0x80) == 0) { // Check if leftmost zero of first byte is unset
        if (max_advance >= 1) {
            result.out_str = c[0];
            result.advance = 1;
        } else result.error = 1;
    }

    else if ((c[0] & 0xe0) == 0xc0) {
        if ((c[1] & 0xc0) == 0x80) { // Continuation byte required
            if (max_advance >= 2) {
                result.out_str = (uint32_t)(c[0] & 0x1f) << 6u | (c[1] & 0x3f);
                result.advance = 2;
            } else result.error = 2;
        } else result.error = 2;
    }

    else if ((c[0] & 0xf0) == 0xe0) {
        if ((c[1] & 0xc0) == 0x80 && (c[2] & 0xc0) == 0x80) { // Two continuation bytes required
            if (max_advance >= 3) {
                result.out_str = (uint32_t)(c[0] & 0xf) << 12u | (uint32_t)(c[1] & 0x3f) << 6u | (c[2] & 0x3f);
                result.advance = 3;
            } else result.error = 3;
        } else result.error = 3;
    }

    else if ((c[0] & 0xf8) == 0xf0) {
        if ((c[1] & 0xc0) == 0x80 && (c[2] & 0xc0) == 0x80 && (c[3] & 0xc0) == 0x80) { // Three continuation bytes required
            if (max_advance >= 4) {
                result.out_str = (uint32_t)(c[0] & 0xf) << 18u | (uint32_t)(c[1] & 0x3f) << 12u | (uint32_t)(c[2] & 0x3f) << 6u | (uint32_t)(c[3] & 0x3f);
                result.advance = 4;
            } else result.error = 4;
        } else result.error = 4;
    } else result.error = 4;

    return result;
}

UTF16Result UTF32ToUTF16(uint32_t codepoint) {
    UTF16Result result;
    MemoryZero(&result, sizeof(result));
    if (codepoint < 0x10000) {
        result.out_str[0] = (uint16_t)codepoint;
        result.out_str[1] = 0;
        result.len        = 1;
    } else if (codepoint <= 0x10FFFF) {
        uint32_t code     = (codepoint - 0x10000);
        result.out_str[0] = (uint16_t)(0xD800 | (code >> 10));
        result.out_str[1] = (uint16_t)(0xDC00 | (code & 0x3FF));
        result.len        = 2;
    } else {
        result.error = 1;
    }

    return result;
}

    #define UTF__HANDLE_DECODE_ERROR(question_mark)                           \
        {                                                                     \
            if (outlen < buffer_size - 1) buffer[outlen++] = (question_mark); \
            break;                                                            \
        }

int64_t CreateCharFromWidechar(char *buffer, int64_t buffer_size, wchar_t *in, int64_t inlen) {
    int64_t outlen = 0;
    for (int64_t i = 0; i < inlen && in[i];) {
        UTF32Result decode = UTF16ToUTF32((uint16_t *)(in + i), (int64_t)(inlen - i));
        if (!decode.error) {
            i += decode.advance;
            UTF8Result encode = UTF32ToUTF8(decode.out_str);
            if (!encode.error) {
                for (int64_t j = 0; j < encode.len; j++) {
                    if (outlen < buffer_size - 1) {
                        buffer[outlen++] = encode.out_str[j];
                    }
                }
            } else UTF__HANDLE_DECODE_ERROR('?');
        } else UTF__HANDLE_DECODE_ERROR('?');
    }

    buffer[outlen] = 0;
    return outlen;
}

int64_t CreateWidecharFromChar(wchar_t *buffer, int64_t buffer_size, char *in, int64_t inlen) {
    int64_t outlen = 0;
    for (int64_t i = 0; i < inlen;) {
        UTF32Result decode = UTF8ToUTF32(in + i, (int64_t)(inlen - i));
        if (!decode.error) {
            i += decode.advance;
            UTF16Result encode = UTF32ToUTF16(decode.out_str);
            if (!encode.error) {
                for (int64_t j = 0; j < encode.len; j++) {
                    if (outlen < buffer_size - 1) {
                        buffer[outlen++] = encode.out_str[j];
                    }
                }
            } else UTF__HANDLE_DECODE_ERROR(0x003f);
        } else UTF__HANDLE_DECODE_ERROR(0x003f);
    }

    buffer[outlen] = 0;
    return outlen;
}

bool IsValid(UTF8Iter &iter) {
    return iter.item;
}

void Advance(UTF8Iter *iter) {
    iter->i += iter->utf8_codepoint_byte_size;
    UTF32Result r = UTF8ToUTF32(iter->data + iter->i, iter->len - iter->i);
    if (r.error) {
        iter->item = 0;
        return;
    }

    iter->utf8_codepoint_byte_size = r.advance;
    iter->item                     = r.out_str;
}

UTF8Iter IterateUTF8Ex(char *data, int64_t len) {
    UTF8Iter result;
    MemoryZero(&result, sizeof(result));
    result.data = data;
    result.len  = len;
    if (len) Advance(&result);
    return result;
}

UTF8Iter IterateUTF8(char *data) {
    int64_t length = 0;
    while (data[length]) length += 1;
    return IterateUTF8Ex(data, length);
}

UTF8Iter IterateUTF8(String string) {
    return IterateUTF8Ex(string.data, string.len);
}

bool IsUTF8ContinuationByte(char c) {
    char result = (c & 0b11000000) == 0b10000000;
    return result;
}

char ToLowerCase(char a) {
    if (a >= 'A' && a <= 'Z') a += 32;
    return a;
}

char ToUpperCase(char a) {
    if (a >= 'a' && a <= 'z') a -= 32;
    return a;
}

bool IsWhitespace(char w) {
    bool result = w == '\n' || w == ' ' || w == '\t' || w == '\v' || w == '\r';
    return result;
}

bool IsAlphabetic(char a) {
    bool result = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z');
    return result;
}

bool IsIdent(char a) {
    bool result = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z') || a == '_';
    return result;
}

bool IsDigit(char a) {
    bool result = a >= '0' && a <= '9';
    return result;
}

bool IsAlphanumeric(char a) {
    bool result = IsDigit(a) || IsAlphabetic(a);
    return result;
}

bool AreEqual(String a, String b, unsigned ignore_case) {
    if (a.len != b.len) return false;
    for (int64_t i = 0; i < a.len; i++) {
        char A = a.data[i];
        char B = b.data[i];
        if (ignore_case) {
            A = ToLowerCase(A);
            B = ToLowerCase(B);
        }
        if (A != B)
            return false;
    }
    return true;
}

bool EndsWith(String a, String end, unsigned ignore_case = false) {
    String a_end  = a.get_postfix(end.len);
    bool   result = AreEqual(end, a_end, ignore_case);
    return result;
}

bool StartsWith(String a, String start, unsigned ignore_case = false) {
    String a_start = a.get_prefix(start.len);
    bool   result  = AreEqual(start, a_start, ignore_case);
    return result;
}

String Trim(String string) {
    if (string.len == 0)
        return string;

    int64_t whitespace_begin = 0;
    for (; whitespace_begin < string.len; whitespace_begin++) {
        if (!IsWhitespace(string.data[whitespace_begin])) {
            break;
        }
    }

    int64_t whitespace_end = string.len;
    for (; whitespace_end != whitespace_begin; whitespace_end--) {
        if (!IsWhitespace(string.data[whitespace_end - 1])) {
            break;
        }
    }

    if (whitespace_begin == whitespace_end) {
        string.len = 0;
    } else {
        string = string.slice(whitespace_begin, whitespace_end);
    }

    return string;
}

String TrimEnd(String string) {
    int64_t whitespace_end = string.len;
    for (; whitespace_end != 0; whitespace_end--) {
        if (!IsWhitespace(string.data[whitespace_end - 1])) {
            break;
        }
    }

    String result = string.get_prefix(whitespace_end);
    return result;
}

String Copy(Allocator allocator, String string) {
    char *copy = (char *)AllocSize(allocator, sizeof(char) * (string.len + 1));
    memcpy(copy, string.data, string.len);
    copy[string.len] = 0;
    String result    = {copy, string.len};
    return result;
}

String Copy(Allocator allocator, char *string) {
    return Copy(allocator, {string, (int64_t)strlen(string)});
}

char *NullTerminate(Allocator allocator, String string) {
    if (string.data[string.len] != 0) return Copy(allocator, string).data;
    return string.data;
}

void NormalizePathInPlace(String s) {
    for (int64_t i = 0; i < s.len; i++) {
        if (s.data[i] == '\\')
            s.data[i] = '/';
    }
}

String NormalizePath(Allocator allocator, String s) {
    String copy = Copy(allocator, s);
    NormalizePathInPlace(copy);
    return copy;
}

typedef int SeekFlag;
enum {
    SeekFlag_None          = 0,
    SeekFlag_IgnoreCase    = 1,
    SeekFlag_MatchFindLast = 2,
};

bool Seek(String string, String find, int64_t *index_out = NULL, SeekFlag flags = SeekFlag_None) {
    bool ignore_case = flags & SeekFlag_IgnoreCase ? true : false;
    bool result      = false;
    if (flags & SeekFlag_MatchFindLast) {
        for (int64_t i = string.len; i != 0; i--) {
            int64_t index     = i - 1;
            String  substring = string.slice(index, index + find.len);
            if (AreEqual(substring, find, ignore_case)) {
                if (index_out)
                    *index_out = index;
                result = true;
                break;
            }
        }
    } else {
        for (int64_t i = 0; i < string.len; i++) {
            String substring = string.slice(i, i + find.len);
            if (AreEqual(substring, find, ignore_case)) {
                if (index_out)
                    *index_out = i;
                result = true;
                break;
            }
        }
    }

    return result;
}

Array<String> Split(Allocator allocator, String string, String delimiter) {
    Array<String> result = {allocator};
    int64_t       index  = 0;
    while (Seek(string, delimiter, &index)) {
        String before_match = {string.data, index};
        result.add(before_match);
        string = string.skip(index + delimiter.len);
    }
    result.add(string);
    return result;
}

String ChopLastSlash(String s) {
    String result = s;
    Seek(s, "/", &result.len, SeekFlag_MatchFindLast);
    return result;
}

String ChopLastPeriod(String s) {
    String result = s;
    Seek(s, ".", &result.len, SeekFlag_MatchFindLast);
    return result;
}

String SkipToLastSlash(String s) {
    int64_t pos;
    String  result = s;
    if (Seek(s, "/", &pos, SeekFlag_MatchFindLast)) {
        result = result.skip(pos + 1);
    }
    return result;
}

String SkipToLastPeriod(String s) {
    int64_t pos;
    String  result = s;
    if (Seek(s, ".", &pos, SeekFlag_MatchFindLast)) {
        result = result.skip(pos + 1);
    }
    return result;
}

String Merge(Allocator allocator, Array<String> list, String separator = " ") {
    int64_t char_count = 0;
    For(list) char_count += it.len;
    if (char_count == 0) return {};
    int64_t node_count = list.len;

    int64_t base_size = (char_count + 1);
    int64_t sep_size  = (node_count - 1) * separator.len;
    int64_t size      = base_size + sep_size;
    char   *buff      = (char *)AllocSize(allocator, sizeof(char) * (size + 1));
    String  string    = {buff, 0};
    For(list) {
        Assert(string.len + it.len <= size);
        memcpy(string.data + string.len, it.data, it.len);
        string.len += it.len;
        if (!list.is_last(it)) {
            memcpy(string.data + string.len, separator.data, separator.len);
            string.len += separator.len;
        }
    }
    Assert(string.len == size - 1);
    string.data[size] = 0;
    return string;
}

    #include <stdio.h>
String FormatV(Allocator allocator, const char *data, va_list args1) {
    va_list args2;
    va_copy(args2, args1);
    int64_t len = vsnprintf(0, 0, data, args2);
    va_end(args2);

    char *result = (char *)AllocSize(allocator, sizeof(char) * (len + 1));
    vsnprintf(result, (int)(len + 1), data, args1);
    String res = {result, len};
    return res;
}

String Format(Allocator allocator, const char *data, ...) PrintfFormatAttribute(2, 3) {
    STRING_FORMAT(allocator, data, result);
    return result;
}

int64_t WideLength(wchar_t *string) {
    int64_t len = 0;
    while (*string++ != 0)
        len++;
    return len;
}

String16 ToString16(Allocator allocator, String string) {
    Assert(sizeof(wchar_t) == 2);
    wchar_t *buffer = (wchar_t *)AllocSize(allocator, sizeof(wchar_t) * (string.len + 1));
    int64_t  size   = CreateWidecharFromChar(buffer, string.len + 1, string.data, string.len);
    String16 result = {buffer, size};
    return result;
}

wchar_t *ToWidechar(Allocator allocator, String string) {
    String16 result = ToString16(allocator, string);
    return result.data;
}

String ToString(Allocator allocator, String16 string) {
    Assert(sizeof(wchar_t) == 2);

    int64_t buffer_size = (string.len + 1) * 2;
    char   *buffer      = (char *)AllocSize(allocator, buffer_size);
    int64_t size        = CreateCharFromWidechar(buffer, buffer_size, string.data, string.len);
    String  result      = {buffer, size};

    Assert(size < buffer_size);
    return result;
}

String ToString(Allocator allocator, wchar_t *string, int64_t len) {
    return ToString(allocator, {string, len});
}

String ToString(Allocator allocator, wchar_t *wstring) {
    int64_t size   = WideLength(wstring);
    String  result = ToString(allocator, {wstring, size});
    return result;
}

void InitArena(Arena *arena, size_t reserve) {
    reserve      = AlignUp(reserve, PAGE_SIZE);
    arena->align = DEFAULT_ALIGNMENT;
    arena->data  = (uint8_t *)VReserve(reserve);
    if (arena->data) {
        arena->reserve = reserve;
    }
}

Arena *AllocArena(Allocator allocator, size_t size) {
    Arena *result   = AllocType(allocator, Arena);
    result->data    = (uint8_t *)AllocSize(allocator, size);
    result->reserve = size;
    result->commit  = size;
    result->align   = DEFAULT_ALIGNMENT;
    return result;
}

Arena *AllocArena(size_t reserve) {
    Arena *result = NULL;

    void *data = VReserve(reserve);
    if (!data) return result;

    bool success = VCommit(data, PAGE_SIZE);
    if (!success) {
        VRelease(data);
        return result;
    }

    result          = (Arena *)data;
    result->data    = (uint8_t *)data;
    result->reserve = reserve;
    result->commit  = PAGE_SIZE;
    result->len = result->base_len = sizeof(Arena);
    result->align                  = DEFAULT_ALIGNMENT;
    return result;
}

void *PushSize(Arena *arena, size_t size) {
    // base_len is used for bootstraping arenas, it denotes the
    // space occupied by the arena. If len is smaller then base_len then
    // we start to overwrite the arena itself - pure barbarism.
    Assert(arena->len >= arena->base_len);

    size_t align_offset = 0;
    if (arena->align) {
        align_offset = GetAlignOffset((uintptr_t)arena->data + arena->len, arena->align);
    }
    size_t align_len           = arena->len + align_offset;
    size_t size_with_alignment = size + align_offset;
    size_t new_len             = arena->len + size_with_alignment;
    if (new_len > arena->commit) {
        size_t new_len_aligned_to_page_size = AlignUp(new_len, PAGE_SIZE);
        size_t to_commit                    = new_len_aligned_to_page_size - arena->commit;
        size_t to_commit_clamped            = ClampTop(to_commit, arena->reserve);
        if (to_commit_clamped > 0) {
            bool success = VCommit(arena->data + arena->commit, to_commit_clamped);
            if (success) arena->commit += to_commit_clamped;
        }
        if (new_len > arena->commit) {
            return NULL;
        }
    }
    uint8_t *result = arena->data + arena->len + align_offset;
    arena->len      = new_len;
    return (void *)result;
}

void Release(Arena *arena) {
    if (arena == NULL || arena->data == NULL) return;
    bool zero_memory = (uint8_t *)arena != arena->data;
    VRelease(arena->data);
    if (zero_memory) MemoryZero(arena, sizeof(Arena));
}

void *ArenaAllocatorProc(void *object, int kind, void *p, size_t size) {
    if (kind == AllocatorKind_Allocate) {
        return PushSize((Arena *)object, size);
    } else if (AllocatorKind_Deallocate) {
    } else {
        Assert(!"invalid codepath");
    }
    return NULL;
}

thread_local Arena *ScratchArenaPool[4];
void                InitScratch() {
    for (int i = 0; i < Lengthof(ScratchArenaPool); i += 1) {
        ScratchArenaPool[i] = AllocArena();
    }
}

TempArena GetScratchEx(Arena **conflicts, int conflict_count) {
    Arena *unoccupied = 0;
    for (int i = 0; i < Lengthof(ScratchArenaPool); i += 1) {
        Arena *from_pool = ScratchArenaPool[i];
        unoccupied       = from_pool;
        for (int conflict_i = 0; conflict_i < conflict_count; conflict_i += 1) {
            Arena *from_conflict = conflicts[conflict_i];
            if (from_pool == from_conflict) {
                unoccupied = 0;
                break;
            }
        }

        if (unoccupied) {
            break;
        }
    }

    // Failed to get free scratch memory, this is a fatal error, this shouldnt happen
    Assert(unoccupied);
    TempArena result = BeginTemp(unoccupied);
    return result;
}

    #include <stdlib.h>
void *SystemAllocator_Alloc(void *object, int kind, void *p, size_t size) {
    void *result = NULL;
    if (kind == AllocatorKind_Allocate) {
        result = malloc(size);
        assert(result);
    } else if (kind == AllocatorKind_Deallocate) {
        free(p);
    }
    return result;
}

Allocator GetSystemAllocator() {
    Allocator result = {SystemAllocator_Alloc};
    return result;
}

#endif // BASIC_IMPL