#include <std/base.h>
#include <std/os.h>
#include <std/project.h>

#include <std/base.c>
#include <std/os.c>

typedef enum C_Compiler
{
    C_COMPILER_GCC,
    C_COMPILER_CLANG,
    C_COMPILER_MSVC,
    C_COMPILER_TCC,
    C_COMPILER_COUNT,
} C_Compiler;

global_variable char* c_compiler_names[] = {
    "gcc",
    "clang",
    "cl",
    "tcc",
};

typedef enum CompilerArgumentStyle
{
    COMPILER_ARGUMENT_STYLE_GNU,
    COMPILER_ARGUMENT_STYLE_MSVC,
    COMPILER_ARGUMENT_STYLE_COUNT,
} CompilerArgumentStyle;

global_variable C_Compiler preferred_c_compiler = C_COMPILER_COUNT;
global_variable char** environment_pointer;

typedef enum BuildType
{
    BUILD_TYPE_DEBUG,
    BUILD_TYPE_RELEASE_SAFE,
    BUILD_TYPE_RELEASE_FAST,
    BUILD_TYPE_RELEASE_SMALL,
    BUILD_TYPE_COUNT,
} BuildType;

const char* build_type_strings[BUILD_TYPE_COUNT] = {
    "debug",
    "release_safe",
    "release_fast",
    "release_small",
};

char* optimization_switches[COMPILER_ARGUMENT_STYLE_COUNT][BUILD_TYPE_COUNT] = {
    [COMPILER_ARGUMENT_STYLE_GNU] = {
        [BUILD_TYPE_DEBUG] = "-O0",
        [BUILD_TYPE_RELEASE_SAFE] = "-O2",
        [BUILD_TYPE_RELEASE_FAST] = "-O3",
        [BUILD_TYPE_RELEASE_SMALL] = "-Oz",
    },
    [COMPILER_ARGUMENT_STYLE_MSVC] = {
        [BUILD_TYPE_DEBUG] = "/Od",
        [BUILD_TYPE_RELEASE_SAFE] = "/Ox",
        [BUILD_TYPE_RELEASE_FAST] = "/O2",
        [BUILD_TYPE_RELEASE_SMALL] = "/O1",
    },
};

STRUCT(CompileFlags)
{
    u64 colored_output:1;
    u64 debug:1;
    u64 error_limit:1;
    u64 time_trace:1;
};

STRUCT(CompileOptions)
{
    String source_path;
    String output_path;
    String compiler_path;
    RenderingBackend rendering_backend;
    WindowingBackend windowing_backend;
    BuildType build_type;
    CompileFlags flags;
};

typedef enum CompilerSwitch
{
    COMPILER_SWITCH_DEBUG_INFO,
    COMPILER_SWITCH_COUNT,
} CompilerSwitch;


global_variable char* compiler_switches[COMPILER_ARGUMENT_STYLE_COUNT][COMPILER_SWITCH_COUNT] = {
    [COMPILER_ARGUMENT_STYLE_GNU] = {
        [COMPILER_SWITCH_DEBUG_INFO] = "-g",
    },
    [COMPILER_ARGUMENT_STYLE_MSVC] = {
        [COMPILER_SWITCH_DEBUG_INFO] = "/Zi",
    },
};

fn C_Compiler c_compiler_from_path(String path)
{
    C_Compiler result = C_COMPILER_COUNT;
    let(last_ch_slash, string_last_ch(path, '/'));
    let(start, last_ch_slash);
#if _WIN32
    let(last_ch_backslash, string_last_ch(path, '\\'));
    start = MIN(last_ch_slash, last_ch_backslash);
#endif
    assert(start != STRING_NO_MATCH); // This ensures us the path is not just the executable name
    let(compiler_name, s_get_slice(u8, path, start + 1, path.length));

    for (C_Compiler i = 0; i < C_COMPILER_COUNT; i += 1)
    {
        let(candidate_compiler_name, cstr(c_compiler_names[i]));
        if (string_contains(compiler_name, candidate_compiler_name))
        {
            result = i;
            break;
        }
    }

    return result;
}

fn u8 c_compiler_is_supported_by_os(C_Compiler compiler)
{
#ifdef __linux__
    switch (compiler)
    {
        case C_COMPILER_TCC: case C_COMPILER_GCC: case C_COMPILER_CLANG: return 1;
        case C_COMPILER_MSVC: return 0;
        case C_COMPILER_COUNT: unreachable();
    }
#elif __APPLE__
    switch (compiler)
    {
        case C_COMPILER_TCC: case C_COMPILER_CLANG: return 1;
        case C_COMPILER_MSVC: case C_COMPILER_GCC: return 0;
        case C_COMPILER_COUNT: unreachable();
    }
#elif _WIN32
    switch (compiler)
    {
        case C_COMPILER_MSVC: case C_COMPILER_TCC: case C_COMPILER_CLANG: return 1;
        case C_COMPILER_GCC: return 0;
    }
#endif
    unreachable();
}

fn String c_compiler_to_string(C_Compiler c_compiler)
{
    switch (c_compiler)
    {
        case C_COMPILER_GCC: return strlit("gcc");
        case C_COMPILER_MSVC: return strlit("MSVC");
        case C_COMPILER_CLANG: return strlit("clang");
        case C_COMPILER_TCC: return strlit("tcc");
        default: unreachable();
    }
}

// Returns the absolute path of a C compiler
fn String get_c_compiler_path(Arena* arena, BuildType build_type)
{
    String cc_path = {};
    String cc_env = os_get_environment_variable("CC");
    String path_env = os_get_environment_variable("PATH");
    String extension = {};
#if _WIN32
    extension = strlit(".exe");
#endif
    if (cc_env.pointer)
    {
        cc_path = cc_env;
    }

    if (!cc_path.pointer)
    {
#if _WIN32
        cc_path = strlit("cl");
#elif defined(__APPLE__)
        cc_path = strlit("clang");
#elif defined(__linux__)
        cc_path = strlit("clang");
#else
#error "Operating system not supported"
#endif
    }

    let(no_path_sep, string_first_ch(cc_path, '/') == STRING_NO_MATCH);
#ifdef _WIN32
    no_path_sep = no_path_sep && string_first_ch(cc_path, '\\') == STRING_NO_MATCH;
#endif
    if (no_path_sep)
    {
        cc_path = file_find_in_path(arena, cc_path, path_env, extension);
    }

#ifndef _WIN32
    if (cc_path.pointer)
    {
        u8 buffer[4096];
        let(realpath, os_realpath(cc_path, (String)array_to_slice(buffer)));
        if (!s_equal(realpath, cc_path))
        {
            cc_path.pointer = arena_allocate(arena, u8, realpath.length + 1);
            cc_path.length = realpath.length;
            memcpy(cc_path.pointer, realpath.pointer, realpath.length);
            cc_path.pointer[cc_path.length] = 0;
        }
    }
#endif

#if __APPLE__
    if (s_equal(cc_path, strlit("/usr/bin/cc")))
    {
        cc_path = strlit("/usr/bin/clang");
    }
#endif

    if (preferred_c_compiler != C_COMPILER_COUNT && c_compiler_is_supported_by_os(preferred_c_compiler))
    {
        String find_result = file_find_in_path(arena, c_compiler_to_string(preferred_c_compiler), path_env, extension);
        if (find_result.pointer)
        {
            cc_path = find_result;
        }
    }

    if (!BB_CI)
    {
        if (build_type != BUILD_TYPE_DEBUG)
        {
            return strlit("/usr/lib/llvm18/bin/clang-18");
        }
    }

    return cc_path;
}

fn u8 c_compiler_supports_colored_output(C_Compiler compiler)
{
    // TODO: fix
    switch (compiler)
    {
        case C_COMPILER_GCC: case C_COMPILER_CLANG: return 1;
        case C_COMPILER_TCC: case C_COMPILER_MSVC: return 0;
        default: unreachable();
    }
}

fn char* c_compiler_get_error_limit_switch(C_Compiler compiler)
{
    // TODO: fix
    switch (compiler)
    {
        case C_COMPILER_CLANG: return "-ferror-limit=1";
        case C_COMPILER_GCC: return "-fmax-errors=1";
        case C_COMPILER_MSVC: case C_COMPILER_TCC: return 0;
        default: unreachable();
    }
}

fn char* c_compiler_get_highest_c_standard_flag(C_Compiler compiler)
{
    switch (compiler)
    {
        case C_COMPILER_CLANG: case C_COMPILER_GCC: return "-std=gnu2x";
        case C_COMPILER_MSVC: return "/std:clatest";
        case C_COMPILER_TCC: return "-std=gnu2x"; // TODO: does it do anything in TCC?
        default: unreachable();
    }
}

fn RenderingBackend rendering_backend_parse_env(String env)
{
    unused(env);
    todo();
}

fn RenderingBackend rendering_backend_pick()
{
    RenderingBackend rendering_backend = RENDERING_BACKEND_COUNT;
#if BB_CI
    rendering_backend = RENDERING_BACKEND_NONE;
#else
    char* env = getenv("BB_RENDERING_BACKEND");
    if (env)
    {
        rendering_backend = rendering_backend_parse_env(cstr(env));
    }

    if (!rendering_backend_is_valid(rendering_backend))
    {
#ifdef __linux__
        rendering_backend = RENDERING_BACKEND_VULKAN;
#elif defined(__APPLE__)
        rendering_backend = RENDERING_BACKEND_METAL;
#elif _WIN32
        rendering_backend = RENDERING_BACKEND_VULKAN;
#endif
    }
#endif

    return rendering_backend;
}

fn WindowingBackend windowing_backend_parse_env(String env)
{
    unused(env);
    todo();
}

fn WindowingBackend windowing_backend_pick()
{
    WindowingBackend windowing_backend = WINDOWING_BACKEND_COUNT;
#if BB_CI
    windowing_backend = WINDOWING_BACKEND_NONE;
#else
    // Only done for Linux because it is the only operating system in which two windowing backends officially coexist
#ifdef __linux__
    char* env = getenv("BB_WINDOWING_BACKEND");
    if (env)
    {
        windowing_backend = windowing_backend_parse_env(cstr(env));
    }
#endif

    if (!windowing_backend_is_valid(windowing_backend))
    {
#ifdef __linux__
        // Prefer X11 over Wayland because:
        // 1) It works both on Wayland and on X11 desktops
        // 2) It works with debugging tools like RenderDoc
        windowing_backend = WINDOWING_BACKEND_X11;
#elif _WIN32
        windowing_backend = WINDOWING_BACKEND_WIN32;
#elif __APPLE__
        windowing_backend = WINDOWING_BACKEND_COCOA;
#endif
    }
#endif

    return windowing_backend;
}

fn u8 c_compiler_supports_time_trace(C_Compiler compiler)
{
    switch (compiler)
    {
        case C_COMPILER_CLANG: return 1;
        default: return 0;
        case C_COMPILER_COUNT: unreachable();
    }
}

fn BuildType build_type_pick()
{
    String build_type_string = strlit(BB_BUILD_TYPE);
    BuildType build_type;

    for (build_type = 0; build_type < BUILD_TYPE_COUNT; build_type += 1)
    {
        if (s_equal(build_type_string, cstr(build_type_strings[build_type])))
        {
            break;
        }
    }

    return build_type;
}

fn void compile_program(Arena* arena, CompileOptions options)
{
    if (!options.compiler_path.pointer)
    {
        char* cc_env = getenv("CC");
        if (options.flags.debug)
        {
            print("Could not find a valid compiler for CC: \"{cstr}\"\n", cc_env ? cc_env : "");
            print("PATH: {cstr}\n", getenv("PATH"));
        }

        failed_execution();
    }

    if (options.flags.debug)
    {
        print("C compiler path: {s}\n", options.compiler_path);
    }

    C_Compiler c_compiler = c_compiler_from_path(options.compiler_path);
    if (c_compiler != C_COMPILER_COUNT)
    {
        String compiler_name = c_compiler_to_string(c_compiler);
        if (options.flags.debug)
        {
            print("Identified compiler as {s}\n", compiler_name);
        }
    }
    else
    {
        print("Unrecognized C compiler: {s}\n", options.compiler_path);
        os_exit(1);
    }
    char* args[4096];
    u64 arg_i = 0;
#define add_arg(arg) args[arg_i++] = (arg)
    add_arg(string_to_c(options.compiler_path));

    if (c_compiler == C_COMPILER_MSVC)
    {
        add_arg("/nologo");
    }

    u8 llvm_mca = 0;
    if (llvm_mca)
    {
        add_arg("-S");
        add_arg("-masm=intel");
    }

#if __APPLE__
    add_arg("-x");
    add_arg("objective-c");
#endif

    add_arg(string_to_c(options.source_path));

    switch (c_compiler)
    {
        case C_COMPILER_MSVC:
            {
                String strings[] = {
                    strlit("/Fe"),
                    options.output_path,
                };
                String arg = arena_join_string(arena, (Slice(String))array_to_slice(strings));
                add_arg(string_to_c(arg));

                add_arg("/Fo" BUILD_DIR "\\");
                add_arg("/Fd" BUILD_DIR "\\");
            } break;
        case C_COMPILER_GCC:
            {
            } break;
        case C_COMPILER_CLANG:
            {
                // add_arg("-working-directory");
                // add_arg(BUILD_DIR);
                // add_arg("-save-temps");
            } break;
        default: break;
    }

    if (c_compiler != C_COMPILER_MSVC)
    {
        add_arg("-o");
        add_arg(string_to_c(options.output_path));
    }

#ifdef __linux__
    add_arg("-fuse-ld=mold");
#endif

    add_arg("-Ibootstrap");
    add_arg("-Idependencies/stb");
    add_arg("-I" BUILD_DIR); // Include the build dir for generated files

    char* c_include_path = getenv("C_INCLUDE_PATH");
    if (c_include_path)
    {
        String c_include_path_string = cstr(c_include_path);

        u64 previous_i = 0;
        for (u64 i = 0; i < c_include_path_string.length; i += 1)
        {
            u8 ch = c_include_path_string.pointer[i];
            if (ch == ':')
            {
                todo();
            }
        }

        String strings[] = {
            strlit("-I"),
            s_get_slice(u8, c_include_path_string, previous_i, c_include_path_string.length),
        };
        String arg = arena_join_string(arena, (Slice(String))array_to_slice(strings));
        add_arg(string_to_c(arg));
    }

    let(debug_info, options.build_type != BUILD_TYPE_RELEASE_SMALL && !llvm_mca);
    if (debug_info)
    {
        add_arg(compiler_switches[c_compiler == C_COMPILER_MSVC][COMPILER_SWITCH_DEBUG_INFO]);
    }

    if (c_compiler != C_COMPILER_TCC)
    {
        add_arg(optimization_switches[c_compiler == C_COMPILER_MSVC][options.build_type]);
    }

    switch (options.build_type)
    {
        case BUILD_TYPE_COUNT: unreachable();
        case BUILD_TYPE_DEBUG:
        case BUILD_TYPE_RELEASE_SAFE:
        {
            add_arg("-DBB_DEBUG=1");
            add_arg("-D_DEBUG=1");
        } break;
        case BUILD_TYPE_RELEASE_FAST:
        case BUILD_TYPE_RELEASE_SMALL:
        {
            add_arg("-DBB_DEBUG=0");
            add_arg("-DNDEBUG=1");
            if (c_compiler != C_COMPILER_MSVC)
            {
                add_arg("-fno-stack-protector");
            }
        } break;
    }

    if (BB_CI)
    {
        add_arg("-DBB_CI=1");
    }
    else
    {
        add_arg("-DBB_CI=0");
    }

    // TODO: careful. If handing binaries built by CI to people, we need to be specially careful about this
    if (c_compiler == C_COMPILER_MSVC)
    {
        add_arg("/arch:AVX512");
    }
    else
    {
        add_arg("-march=native");
    }

    // Immutable options
    switch (c_compiler)
    {
        case C_COMPILER_MSVC:
            {
                add_arg("/Wall");
#if BB_ERROR_ON_WARNINGS
                add_arg("/WX");
#endif
                add_arg("/wd4255");
                add_arg("/J");
            } break;
        default:
            {
                add_arg("-pedantic");
                add_arg("-Wall");
                add_arg("-Wextra");
                add_arg("-Wpedantic");
                add_arg("-Wno-unused-function");
                add_arg("-Wno-nested-anon-types");
                add_arg("-Wno-keyword-macro");
                add_arg("-Wno-gnu-auto-type");
                add_arg("-Wno-gnu-binary-literal");
#ifndef __APPLE__
                add_arg("-Wno-auto-decl-extensions");
#endif
                add_arg("-Wno-gnu-empty-initializer");
                add_arg("-Wno-fixed-enum-extension");
                add_arg("-Wno-overlength-strings");
                add_arg("-Wno-gnu-zero-variadic-macro-arguments");
#if BB_ERROR_ON_WARNINGS
                add_arg("-Werror");
#endif

                add_arg("-fno-signed-char");
                add_arg("-fno-strict-aliasing");
                add_arg("-fwrapv");
            } break;
    }

    if (options.flags.colored_output && c_compiler_supports_colored_output(c_compiler))
    {
        add_arg("-fdiagnostics-color=auto");
    }

    if (options.flags.error_limit)
    {
        char* error_limit = c_compiler_get_error_limit_switch(c_compiler);
        if (error_limit)
        {
            add_arg(error_limit);
        }
    }

    if (options.flags.time_trace && c_compiler_supports_time_trace(c_compiler))
    {
        add_arg("-ftime-trace");
    }

    if (c_compiler == C_COMPILER_MSVC)
    {
        add_arg("/diagnostics:caret");
    }
    else
    {
        add_arg("-fdiagnostics-show-option");
    }

    add_arg(c_compiler_get_highest_c_standard_flag(c_compiler));

    switch (options.windowing_backend)
    {
        case WINDOWING_BACKEND_NONE:
            {
                add_arg("-DBB_WINDOWING_BACKEND_NONE=1");
            } break;
        case WINDOWING_BACKEND_WIN32:
            {
                add_arg("-DBB_WINDOWING_BACKEND_WIN32=1");
            } break;
        case WINDOWING_BACKEND_COCOA:
            {
                add_arg("-DBB_WINDOWING_BACKEND_COCOA=1");
            } break;
        case WINDOWING_BACKEND_X11:
            {
                add_arg("-DBB_WINDOWING_BACKEND_X11=1");
            } break;
        case WINDOWING_BACKEND_WAYLAND:
            {
                add_arg("-DBB_WINDOWING_BACKEND_WAYLAND=1");
            } break;
        case WINDOWING_BACKEND_COUNT: unreachable();
    }

    switch (options.rendering_backend)
    {
        case RENDERING_BACKEND_NONE:
            {
                add_arg("-DBB_RENDERING_BACKEND_NONE=1");
            } break;
        case RENDERING_BACKEND_METAL:
            {
                add_arg("-DBB_RENDERING_BACKEND_METAL=1");
            } break;
        case RENDERING_BACKEND_DIRECTX12:
            {
                add_arg("-DBB_RENDERING_BACKEND_DIRECTX12=1");
            } break;
        case RENDERING_BACKEND_VULKAN:
            {
                add_arg("-DBB_RENDERING_BACKEND_VULKAN=1");
#if _WIN32
                char* vk_sdk_path = getenv("VK_SDK_PATH");
                if (vk_sdk_path)
                {
                    if (c_compiler == C_COMPILER_MSVC)
                    {
                        String strings[] = {
                            strlit("-I"),
                            cstr(vk_sdk_path),
                            strlit("\\Include"),
                        };
                        String arg = arena_join_string(arena, (Slice(String))array_to_slice(strings));
                        add_arg(string_to_c(arg));
                    }
                    else
                    {
                        todo();
                    }
                }
                else
                {
                    print("VK_SDK_PATH environment variable not found\n");
                }
#endif
            } break;
        case RENDERING_BACKEND_COUNT: unreachable();
    }

#ifndef _WIN32
    add_arg("-lm");

    String path_env = cstr(getenv("PATH"));
    String llvm_config_path = executable_find_in_path(arena, strlit("llvm-config"), path_env);
    u8 buffer[16*1024];
    u32 length = 0;
    char* llvm_config_c = string_to_c(llvm_config_path);
    {
        char* arguments[] = {
            llvm_config_c,
            "--components",
            0,
        };
        RunCommandOptions run_options = {
            .stdout_stream = {
                .buffer = buffer,
                .length = &length,
                .capacity = sizeof(buffer),
                .policy = CHILD_PROCESS_STREAM_PIPE,
            },
            .debug = options.flags.debug,
        };
        RunCommandResult result = run_command(arena, (CStringSlice)array_to_slice(arguments), environment_pointer, run_options);
        let(success, result.termination_kind == PROCESS_TERMINATION_EXIT && result.termination_code == 0); 
        if (!success)
        {
            os_exit(1);
        }
    }

    {
        char* argv_buffer[4096];
        argv_buffer[0] = llvm_config_c;
        argv_buffer[1] = "--libs";
        u32 local_arg_i = 2;

        String llvm_components = { .pointer = buffer, .length = length };
        u32 i = 0;
        while (i < length)
        {
            String slice = s_get_slice(u8, llvm_components, i, llvm_components.length);
            u64 space_index = string_first_ch(slice, ' ');
            u8 there_is_space = space_index != STRING_NO_MATCH;
            u64 argument_length = unlikely(there_is_space) ? space_index : slice.length;

            String argument_slice = s_get_slice(u8, slice, 0, argument_length - !there_is_space);
            argv_buffer[local_arg_i] = string_to_c(arena_duplicate_string(arena, argument_slice));
            local_arg_i += 1;

            i += argument_length + there_is_space;
        }

        argv_buffer[local_arg_i] = 0;
        local_arg_i += 1;

        length = 0;

        RunCommandOptions run_options = {
            .stdout_stream = {
                .buffer = buffer,
                .length = &length,
                .capacity = sizeof(buffer),
                .policy = CHILD_PROCESS_STREAM_PIPE,
            },
            .debug = options.flags.debug,
        };
        CStringSlice arguments = { .pointer = argv_buffer, .length = local_arg_i };
        RunCommandResult result = run_command(arena, arguments, environment_pointer, run_options);
        let(success, result.termination_kind == PROCESS_TERMINATION_EXIT && result.termination_code == 0); 
        if (!success)
        {
            os_exit(1);
        }

        i = 0;

        String llvm_libraries = { .pointer = buffer, .length = length };
        while (i < length)
        {
            String slice = s_get_slice(u8, llvm_libraries, i, llvm_libraries.length);
            u64 space_index = string_first_ch(slice, ' ');
            u8 there_is_space = space_index != STRING_NO_MATCH;
            u64 argument_length = unlikely(there_is_space) ? space_index : slice.length;

            String argument_slice = s_get_slice(u8, slice, 0, argument_length - !there_is_space);
            add_arg(string_to_c(arena_duplicate_string(arena, argument_slice)));

            i += argument_length + there_is_space;
        }
    }
#endif

    switch (options.windowing_backend)
    {
        case WINDOWING_BACKEND_NONE:
            {
            } break;
        case WINDOWING_BACKEND_WIN32:
            {
            } break;
        case WINDOWING_BACKEND_COCOA:
            {
                add_arg("-framework");
                add_arg("AppKit");
            } break;
        case WINDOWING_BACKEND_X11:
            {
                add_arg("-lxcb");
            } break;
        case WINDOWING_BACKEND_WAYLAND:
            {
            } break;
        case WINDOWING_BACKEND_COUNT: unreachable();
    }

    switch (options.rendering_backend)
    {
        case RENDERING_BACKEND_NONE:
            {
            } break;
        case RENDERING_BACKEND_METAL:
            {
                add_arg("-framework");
                add_arg("Metal");
                add_arg("-framework");
                add_arg("QuartzCore");
            } break;
        case RENDERING_BACKEND_DIRECTX12:
            {
            } break;
        case RENDERING_BACKEND_VULKAN:
            {
#if __APPLE__
                add_arg("-framework");
                add_arg("QuartzCore");
#endif
            } break;
        case RENDERING_BACKEND_COUNT: unreachable();
    }

    add_arg(0);
    CStringSlice arguments = { .pointer = args, .length = arg_i };
    RunCommandOptions run_options = {
        .debug = options.flags.debug,
    };
    RunCommandResult result = run_command(arena, arguments, environment_pointer, run_options);
    let(success, result.termination_kind == PROCESS_TERMINATION_EXIT && result.termination_code == 0); 
    if (!success)
    {
        os_exit(1);
    }
}

STRUCT(Load)
{
    u64 mask;
    u8 index;
    u8 size;
};
decl_vb(Load);
declare_slice(Load);

STRUCT(Combine)
{
    Slice(Load) loads;
    u64 size;
};

STRUCT(Merge)
{
    Load values[2];
    u8 is_valid[2];
};

typedef enum ProgramId
{
    PROGRAM_MERGE,
    PROGRAM_COMBINE,
    PROGRAM_LOAD,
} ProgramId;

STRUCT(Program)
{
    union
    {
        Combine combine;
        Load load;
        Merge merge;
    };
    ProgramId id;
};

STRUCT(Lookup)
{
    Slice(s32) indices;
    SliceP(u8) words;
};

declare_slice(SliceP(u8));
declare_slice(SliceP(void));

fn u64 pext(u64 w, u64 m)
{
    u64 result = 0;
    u64 bit = 1;

    while (w != 0)
    {
        if ((m & 1) == 1)
        {
            if ((w & 1) == 1)
            {
                result |= bit;
            }

            bit <<= 1;
        }

        w >>= 1;
        m >>= 1;
    }

    return result;
}

fn void n_word_mask(SliceP(u8) words, u8* mask, u8 length)
{
    for (u8 i = 0; i < length; i += 1)
    {
        mask[i] = 0xff;
    }

    for (u8 byte = 0; byte < length; byte += 1)
    {
        for (u8 bit = 0; bit < 8; bit += 1)
        {
            u8 old = mask[byte];
            mask[byte] &= ~(u8)(1 << bit);

            u8 map[16*16][16] = {};
            u32 map_item_count = 0;
            u8 candidate[16] = {};

            for (u64 word_index = 0; word_index < words.length; word_index += 1)
            {
                let(word, words.pointer[word_index]);
                for (u8 mask_index = 0; mask_index < length; mask_index += 1)
                {
                    candidate[mask_index] = word[mask_index] & mask[mask_index];
                }

                u8 map_index;
                for (map_index = 0; map_index < map_item_count; map_index += 1)
                {
                    if (memcmp(map[map_index], candidate, length) == 0)
                    {
                        break;
                    }
                }

                if (map_index != map_item_count)
                {
                    mask[byte] = old;
                    break;
                }

                memcpy(map[map_item_count], candidate, length);
                map_item_count += 1;
            }
        }
    }
}

fn u64 program_lookup_size(Program program)
{
    u64 n = 0;

    switch (program.id)
    {
        case PROGRAM_COMBINE:
            {
                for (u64 i = 0; i < program.combine.loads.length; i += 1)
                {
                    n += __builtin_popcountll(program.combine.loads.pointer[i].mask);
                }
            } break;
        case PROGRAM_LOAD:
            {
                n = __builtin_popcountll(program.load.mask);
            } break;
        case PROGRAM_MERGE: todo();
    }

    return n;
}

fn u64 load_load(Load load, u8* word)
{
    u64 result;

    switch (load.size)
    {
        case 1: result = word[load.index]; break;
        case 2: result = *(u16*)&word[load.index]; break;
        case 4: result = *(u32*)&word[load.index]; break;
        case 8: result = *(u64*)&word[load.index]; break;
        default: unreachable();
    }

    return result;
}

fn u64 program_evaluate(Program program, u8* word)
{
    u64 result;

    switch (program.id)
    {
        case PROGRAM_COMBINE:
            {
                u64 q = 0;
                u64 m = 0;
                u64 shift = 0;

                for (u64 i = 0; i < program.combine.loads.length; i += 1)
                {
                    Load load = program.combine.loads.pointer[i];
                    let(qi, load_load(load, word));
                    let(mi, load.mask);

                    q |= qi << shift;
                    m |= mi << shift;

                    shift += 8 * load.size;
                }

                result = pext(q, m);
            } break;
        case PROGRAM_LOAD:
            {
                let(q, load_load(program.load, word));
                result = pext(q, program.load.mask);
            } break;
        case PROGRAM_MERGE: todo();
    }

    return result;
}

fn Slice(s32) pdep_lookup(Arena* arena, Program program, SliceP(u8) words)
{
    let(length, 1 << program_lookup_size(program));
    let(result, arena_allocate(arena, s32, length));
    for (u64 i = 0; i < length; i += 1)
    {
        result[i] = -1;
    }

    for (u64 i = 0; i < words.length; i += 1)
    {
        let(value, program_evaluate(program, words.pointer[i]));
        result[value] = i;
    }

    return (Slice(s32)) { .pointer = result, .length = length };
}

fn u8 load_trim(Load in, Load* out)
{
    u8 result = 0;
    Load l;
    switch (in.size)
    {
        case 8:
            {
                todo();
            } break;
        case 4:
            {
                if ((in.mask & 0xffff) == 0)
                {
                    l = (Load) {
                        .index = in.index + 4,
                        .size = 4,
                        .mask = in.mask >> 32,
                    };
                }
            } break;
        case 2:
            {
                if ((in.mask & 0xff) == 0)
                {
                    todo();
                }
            } break;
    }

    if (result)
    {
        *out = l;
    }

    return result;
}

fn u8 can_merge(Load* load, u8* is_valid)
{
    if (!is_valid[0] || !is_valid[1])
    {
        return 0;
    }

    if ((load[0].mask & load[1].mask) == 0)
    {
        return 1;
    }

    return 0;
}

fn u8 new_merge(Load* loads, u8* is_valid, Merge* out)
{
    u8 result = 0;

    if (can_merge(loads, is_valid))
    {
        result = 1;
        *out = (Merge)
        {
            .values = { loads[0], loads[1] },
            .is_valid = { is_valid[0], is_valid[1] },
        };
    }

    return result;
}

fn Program compile_mask(u8* mask, u8 mask_length)
{
    Program result = {};
    VirtualBuffer(Load) loads = {};
    const u8 load_sizes[] = {8, 4, 2, 1};

    while (1)
    {
        u32 active = 0;
        for (u8 i = 0; i < mask_length; i += 1)
        {
            active += mask[i] != 0;
        }

        if (active == 0)
        {
            break;
        }

        if (active == 1)
        {
            for (u8 i = 0; i < mask_length; i += 1)
            {
                u8 mask_byte = mask[i];
                if (mask_byte != 0)
                {
                    *vb_add(&loads, 1) = (Load) {
                        .index = i,
                        .size = 1,
                        .mask = mask_byte,
                    };
                    break;
                }
            }

            break;
        }

        for (u8 size_index = 0; size_index < array_length(load_sizes); size_index += 1)
        {
            u8 size = load_sizes[size_index];
            if (size > mask_length)
            {
                continue;
            }

            u8 best_count = 0;
            u8 best_index = 0;

            for (u8 i = 0; i < mask_length - size + 1; i += 1)
            {
                u8 k = 0;
                for (u8 mask_i = 0; mask_i < size; mask_i += 1)
                {
                    k += mask[mask_i + i] != 0;
                }

                if (k > best_count)
                {
                    best_count = k;
                    best_index = i;
                }
            }

            if (best_count > 0)
            {
                Load load = {
                    .index = best_index,
                    .size = size,
                };

                for (u8 i = 0; i < size; i += 1)
                {
                    load.mask |= (u64)mask[best_index + i] << (i * 8);
                }

                *vb_add(&loads, 1) = load;

                for (u8 i = 0; i < size; i += 1)
                {
                    mask[best_index + i] = 0;
                }

                break;
            }
        }
    }

    if (loads.length == 1)
    {
        while (1)
        {
            Load l;
            if (!load_trim(loads.pointer[0], &l))
            {
                break;
            }

            loads.pointer[0] = l;
        }

        return (Program) {
            .load = loads.pointer[0],
            .id = PROGRAM_LOAD,
        };
    }
    else if (loads.length == 2)
    {
        let(first, loads.pointer[0]);
        let(second, loads.pointer[1]);

        let(trimmed, first);
        u8 trimmed_is_valid = 1;
        Merge merge_memory;
        Load load_memory;
        Merge merge = {};
        u8 merge_is_valid = 0;

        while (trimmed_is_valid)
        {
            Load merge_loads[2] = { trimmed, second };
            u8 is_valid[2] = { 1, 1 };
            Merge merge_candidate = {};
            if (new_merge(merge_loads, is_valid, &merge_candidate))
            {
                merge = merge_candidate;
            }

            trimmed_is_valid = load_trim(trimmed, &load_memory);
            if (trimmed_is_valid)
            {
                trimmed = load_memory;
            }
        }

        if (merge_is_valid)
        {
            todo();
        }
    }
    
    u64 total = 0;
    for (u64 i = 0; i < loads.length; i += 1)
    {
        total += loads.pointer[i].size;
    }

    u64 size = total < 4 ? 4 : 8;
    result = (Program) {
        .combine = {
            .loads = { .pointer = loads.pointer, .length = loads.length },
            .size = size,
        },
        .id = PROGRAM_COMBINE,
    };
    return result;
}

typedef enum TypeKind
{
    TYPE_KIND_U16,
} TypeKind;

STRUCT(PerfectHashArguments)
{
    VirtualBuffer(u8)* file_h;
    VirtualBuffer(u8)* file_c;
    Slice_SliceP_u8 words_by_length;
    u8* mask;
    Lookup* lookups;
    Program* programs;
    Arena* arena;
    String kind;
    void** values_by_length;
    TypeKind value_type;
};

fn void vb_indent(VirtualBuffer(u8)* buffer, u32 indentation_level)
{
    if (likely(indentation_level > 0))
    {
        vb_copy_byte_repeatedly(buffer, ' ', 4 * indentation_level);
    }
}

fn String type_from_size(u8 size)
{
    String type;
    switch (size)
    {
        case 8: type = strlit("u64"); break;
        case 4: type = strlit("u32"); break;
        case 2: type = strlit("u16"); break;
        case 1: type = strlit("u8"); break;
        default: unreachable();
    }

    return type;
}

fn void load_write(Load load, VirtualBuffer(u8)* buffer, u32 load_name_index, u32 indentation_level, u32 length)
{
    String load_type = type_from_size(load.size);

    vb_indent(buffer, indentation_level);
    vb_format(buffer, "{s} v{u32}_{u32};\n", load_type, load_name_index, length);

    vb_indent(buffer, indentation_level);
    vb_format(buffer, "memcpy(&v{u32}_{u32}, &string_pointer[{u32}], {u32});\n", load_name_index, length, load.index, load.size);
}

fn void program_write(VirtualBuffer(u8)* buffer, Program program, u32 indentation_level, u32 length)
{
    switch (program.id)
    {
        case PROGRAM_LOAD:
            {
                // Write the load
                Load load = program.load;
                u32 load_name_index = 0;

                load_write(load, buffer, load_name_index, indentation_level, length);

                // Write result
                vb_indent(buffer, indentation_level);
                vb_format(buffer, "u64 index_{u32} = _pext_u64(v{u32}_{u32}, 0x{u64:x});\n", length, load_name_index, length, load.mask);
            } break;
        case PROGRAM_COMBINE:
            {
                Combine combine = program.combine;
                String combine_type_string = type_from_size(combine.size);
                vb_indent(buffer, indentation_level);
                vb_format(buffer, "{s} v_{u32} = 0;\n", combine_type_string, length);
                
                u64 shift = 0;
                u64 mask = 0;

                for (u64 i = 0; i < combine.loads.length; i += 1)
                {
                    Load load = combine.loads.pointer[i];
                    mask |= load.mask << shift;

                    load_write(load, buffer, (u32)i, indentation_level, length);

                    vb_indent(buffer, indentation_level);
                    vb_format(buffer, "v_{u32} |= ({s})(v{u64}_{u32}) << {u64};\n", length, combine_type_string, i, length, shift);

                    shift += 8 * load.size;
                }

                // Write result
                vb_indent(buffer, indentation_level);
                vb_format(buffer, "u64 index_{u32} = _pext_u64(v_{u32}, 0x{u64:x});\n", length, length, mask);
            } break;
        case PROGRAM_MERGE:
            {
                todo();
            } break;
    }

    // vb_indent(buffer, indentation_level);
    // vb_format(buffer, "const char* word_{u32} = words_{u32}[index_{u32}];\n", length, length, length);
    // vb_indent(buffer, indentation_level);
    // vb_format(buffer, "let(value_{u32}, values_{u32}[index_{u32}]);\n", length, length, length);
}

fn void perfect_hash_generate(PerfectHashArguments arguments)
{
    VirtualBuffer(u8)* h = arguments.file_h;
    VirtualBuffer(u8)* c = arguments.file_c;

    String type_string;
    switch (arguments.value_type)
    {
        case TYPE_KIND_U16: type_string = strlit("u16"); break;
    }

    u64 word_character_count;
    u64 value_count;

    {
        u64 word_character_offset = 0;
        u64 value_offset = 0;
        for (u64 length = 0; length < arguments.words_by_length.length; length += 1)
        {
            SliceP(u8) words = arguments.words_by_length.pointer[length];
            n_word_mask(words, arguments.mask, length);
            Program program = compile_mask(arguments.mask, length);
            arguments.programs[length] = program;
            arguments.lookups[length].indices = pdep_lookup(arguments.arena, program, words);
            arguments.lookups[length].words = words;
            value_offset += arguments.lookups[length].indices.length;
            word_character_offset += arguments.lookups[length].indices.length * length;
        }

        word_character_count = word_character_offset;
        value_count = value_offset;
    }

    u32 indentation_level = 0;
    vb_indent(h, indentation_level);
    vb_format(h, "global_variable const {s} {s}_value_lut[] = {\n", type_string, arguments.kind);

    indentation_level += 1;

    for (u64 length = 0; length < arguments.words_by_length.length; length += 1)
    {
        SliceP(u8) words = arguments.words_by_length.pointer[length];
        Lookup lookup = arguments.lookups[length];
        let(generic_values_by_length, arguments.values_by_length[length]);

        vb_indent(h, indentation_level);
        vb_format(h, "// Values [{u64}]\n", length);

        if (words.length > 0)
        {
            for (u64 i = 0; i < lookup.indices.length; i += 1)
            {
                let(index, lookup.indices.pointer[i]);
                vb_indent(h, indentation_level);

                if (index == -1)
                {
                    switch (arguments.value_type)
                    {
                        case TYPE_KIND_U16:
                            {
                                vb_copy_string(h, strlit("0xffff,\n"));
                            } break;
                    }
                }
                else
                {
                    switch (arguments.value_type)
                    {
                        case TYPE_KIND_U16:
                            {
                                let(value, ((u16*)generic_values_by_length)[index]);
                                String word = { .pointer = words.pointer[index], .length = length };
                                vb_format(h, "0x{u32:x,w=4}, // {s}\n", value, word);
                            } break;
                    }
                }
            }
        }
        else
        {
            vb_indent(h, indentation_level);
            switch (arguments.value_type)
            {
                case TYPE_KIND_U16:
                    {
                        vb_copy_string(h, strlit("0xffff,\n"));
                    } break;
            }
        }
    }

    indentation_level -= 1;

    vb_indent(h, indentation_level);
    vb_copy_string(h, strlit("};\n"));

    vb_indent(h, indentation_level);
    vb_format(h, "static_assert(array_length({s}_value_lut) == {u64});\n", arguments.kind, value_count);

    assert(is_power_of_two_u64(arguments.words_by_length.length));
    u64 epi = 512 / arguments.words_by_length.length;

    String upper_names_by_batch_flag[] = { strlit("Single"), strlit("Batch") };
    String lower_names_by_batch_flag[] = { strlit("single"), strlit("batch") };

    switch (arguments.value_type)
    {
        case TYPE_KIND_U16:
            {
                vb_format(h, "STRUCT(PextLookup{s}Result_{s})\n{\n    __m512i v[2];\n};\n", upper_names_by_batch_flag[1], arguments.kind);
            } break;
    }

    for (u8 is_batch = 0; is_batch < 2; is_batch += 1)
    {
        u64 signature_length;
        u8 result_type_buffer[256];
        String result_type_string;
        if (is_batch)
        {
            result_type_string = format_string((String)array_to_slice(result_type_buffer), "PextLookupBatchResult_{s}", arguments.kind);
        }
        else
        {
            result_type_string = type_string;
        }

        if (is_batch)
        {
            signature_length = vb_format(h, "fn {s} pext_lookup_{s}_{s}(const u8* const restrict string_base, const u32* const restrict string_offsets, const u32* const restrict string_lengths)", result_type_string, arguments.kind, lower_names_by_batch_flag[is_batch], type_string);
        }
        else
        {
            signature_length =  vb_format(h, "fn {s} pext_lookup_{s}_{s}(const u8* const restrict string_pointer, u8 string_length)", result_type_string, arguments.kind, lower_names_by_batch_flag[is_batch], type_string);
        }

        vb_copy_string(c, (String) { h->pointer + h->length - signature_length, .length = signature_length, });

        vb_copy_string(h, strlit(";\n"));

        vb_copy_string(c, strlit("\n{\n"));
        u32 indentation_level = 1;

        vb_indent(c, indentation_level);
        if (is_batch)
        {
            vb_format(c, "PextLookupBatchResult_{s} result = {};\n", arguments.kind);
        }

        assert(is_power_of_two_u64(arguments.words_by_length.length));
        {
            vb_indent(c, indentation_level);
            vb_format(c, "__m512i lengths = _mm512_set_epi{u64}(", epi);
            for (s64 length = arguments.words_by_length.length - 1; length >= 0; length -= 1)
            {
                vb_format(c, "{u64}, ", length);
            }

            c->length -= 2;

            vb_copy_string(c, strlit(");\n"));

            {
                vb_indent(c, indentation_level);
                assert(is_power_of_two_u64(arguments.words_by_length.length));
                vb_format(c, "__m512i raw_value_offsets = _mm512_set_epi{u64}(", epi);
                let(value_offset, value_count);
                for (s64 length = arguments.words_by_length.length - 1; length >= 0; length -= 1)
                {
                    value_offset -= arguments.lookups[length].indices.length;
                    vb_format(c, "{u64}, ", value_offset);
                }
                c->length -= 2;
                vb_copy_string(c, strlit(");\n"));
            }

#if 0
            // vb_indent(c, indentation_level);
            // vb_copy_string(c, strlit("global_variable const char words[] = {\n"));
            // 
            // indentation_level += 1;
            //
            // for (u64 length = 0; length < arguments.words_by_length.length; length += 1)
            // {
            //     Lookup lookup = arguments.lookups[length];
            //     SliceP(u8) words = arguments.words_by_length.pointer[length];
            //
            //     vb_indent(c, indentation_level);
            //     vb_format(c, "// Words [{u64}]\n", length);
            //
            //     if (words.length > 0)
            //     {
            //         for (u64 i = 0; i < lookup.indices.length; i += 1)
            //         {
            //             let(index, lookup.indices.pointer[i]);
            //
            //             vb_indent(c, indentation_level);
            //
            //             if (index == -1)
            //             {
            //                 *vb_add(c, 1) = '\"';
            //                 for (u64 i = 0; i < length; i += 1)
            //                 {
            //                     vb_copy_string(c, strlit("\\x00"));
            //                 }
            //
            //                 *vb_add(c, 1) = '\"';
            //                 *vb_add(c, 1) = '\n';
            //             }
            //             else
            //             {
            //                 String word = { .pointer = words.pointer[index], .length = length };
            //                 *vb_add(c, 1) = '\"';
            //                 vb_copy_string(c, word);
            //                 *vb_add(c, 1) = '\"';
            //                 *vb_add(c, 1) = '\n';
            //             }
            //         }
            //     }
            //     else
            //     {
            //         vb_indent(c, indentation_level);
            //         *vb_add(c, 1) = '\"';
            //         for (u64 i = 0; i < length; i += 1)
            //         {
            //             vb_copy_string(c, strlit("\\x00"));
            //         }
            //
            //         *vb_add(c, 1) = '\"';
            //         *vb_add(c, 1) = '\n';
            //     }
            // }
            //
            // indentation_level -= 1;
            //
            // vb_indent(c, indentation_level);
            // vb_copy_string(c, strlit("};\n"));
            //
            // vb_indent(c, indentation_level);
            // vb_format(c, "static_assert(array_length(words) == {u64} + 1);\n", word_character_count);
#endif

            // vb_indent(c, indentation_level);
            // vb_copy_string(c, strlit("u64 error_mask = 0;\n"));

            if (is_batch)
            {
                vb_indent(c, indentation_level);
                vb_copy_string(c, strlit("for (u32 string_index = 0; string_index < 64; string_index += 1)\n"));

                vb_indent(c, indentation_level);
                vb_copy_string(c, strlit("{\n"));

                indentation_level += 1;

                vb_indent(c, indentation_level);
                vb_copy_string(c, strlit("const u8* const restrict string_pointer = string_base + string_offsets[string_index];\n"));

                vb_indent(c, indentation_level);
                vb_copy_string(c, strlit("u32 string_length = string_lengths[string_index];\n"));
            }

            vb_indent(c, indentation_level);
            vb_format(c, "__mmask{u64} length_compare_mask = _mm512_cmpeq_epi{u64}_mask(_mm512_set1_epi{u64}(string_length), lengths);\n", arguments.words_by_length.length, epi, epi);

            // vb_indent(c, indentation_level);
            // vb_copy_string(c, strlit("__mmask64 length_mask = _cvtu64_mask64(_cvtmask16_u32(length_compare_mask) - 1);\n"));

            // vb_indent(c, indentation_level);
            // vb_copy_string(c, strlit("__m512i word_offsets = _mm512_permutexvar_epi32(_mm512_setzero(), _mm512_maskz_compress_epi32(length_compare_mask, raw_word_offsets));\n"));

            vb_indent(c, indentation_level);
            vb_format(c, "__m512i value_offsets = _mm512_permutexvar_epi{u64}(_mm512_setzero(), _mm512_maskz_compress_epi{u64}(length_compare_mask, raw_value_offsets));\n", epi, epi);

            // vb_indent(c, indentation_level);
            // vb_copy_string(c, strlit("__m512i candidate_string_in_memory = _mm512_maskz_loadu_epi8(length_mask, &string_pointer[0]);\n"));
        }

        for (u64 length = 0; length < arguments.words_by_length.length; length += 1)
        {
            SliceP(u8) words = arguments.words_by_length.pointer[length];

            if (words.length != 0)
            {
                program_write(c, arguments.programs[length], indentation_level, length);

                *vb_add(c, 1) = '\n';
            }
        }

        vb_indent(c, indentation_level);
        assert(is_power_of_two_u64(arguments.words_by_length.length));
        vb_format(c, "__m512i raw_indices = _mm512_set_epi{u64}(", epi);

        for (s64 length = arguments.words_by_length.length - 1; length >= 0; length -= 1)
        {
            SliceP(u8) words = arguments.words_by_length.pointer[length];
            if (words.length != 0)
            {
                vb_format(c, "index_{u64}, ", length);
            }
            else
            {
                vb_copy_string(c, strlit("0, "));
            }
        }

        c->length -= 2;
        vb_copy_string(c, strlit(");\n"));

        vb_indent(c, indentation_level);
        vb_format(c, "__m512i indices = _mm512_permutexvar_epi{u64}(_mm512_setzero(), _mm512_maskz_compress_epi{u64}(length_compare_mask, raw_indices));\n", epi, epi);

        // vb_indent(c, indentation_level);
        // vb_copy_string(c, strlit("__m512i word_indices = _mm512_add_epi32(word_offsets, indices);\n"));

        vb_indent(c, indentation_level);
        vb_format(c, "__m512i value_indices = _mm512_add_epi{u64}(value_offsets, indices);\n", epi);

        // vb_indent(c, indentation_level);
        // vb_copy_string(c, strlit("let(word_index, _mm_extract_epi32(_mm512_extracti32x4_epi32(word_indices, 0), 0));\n"));

        vb_indent(c, indentation_level);
        vb_format(c, "let(value_index, _mm_extract_epi{u64}(_mm512_extracti{u64}x{u64}_epi{u64}(value_indices, 0), 0));\n", epi, epi, (512 / 4) / epi, epi);

        // vb_indent(c, indentation_level);
        // vb_copy_string(c, strlit("__m512i string_in_memory = _mm512_maskz_loadu_epi8(length_mask, &words[word_index]);\n"));

        vb_indent(c, indentation_level);
        vb_format(c, "{s} value = {s}_value_lut[value_index];\n", type_string, arguments.kind);

        if (is_batch)
        {
            vb_indent(c, indentation_level);
            vb_copy_string(c, strlit("__mmask32 index_mask = _cvtu32_mask32(1 << string_index);\n"));

            vb_indent(c, indentation_level);
            vb_copy_string(c, strlit("result.v[string_index > 31] = _mm512_mask_blend_epi16(index_mask, result.v[string_index > 31], _mm512_set1_epi16(value));\n"));

            indentation_level -= 1;

            vb_indent(c, indentation_level);
            vb_copy_string(c, strlit("}\n"));

            vb_indent(c, indentation_level);
            vb_copy_string(c, strlit("return result;\n"));
        }
        else
        {
            vb_indent(c, indentation_level);
            vb_copy_string(c, strlit("return value;\n"));
        }
        // vb_copy_string(c, strlit("u16 asd[32];\nif (string_index == 31) { _mm512_storeu_epi16(asd, result.v[0]); breakpoint(); }\n"));

        // vb_indent(c, indentation_level);
        // vb_copy_string(c, strlit("out_values[value_index] = value;\n"));

        // vb_indent(c, indentation_level);
        // vb_copy_string(c, strlit("__mmask64 string_compare_mask = _mm512_cmpeq_epi8_mask(candidate_string_in_memory, string_in_memory);\n"));
        //
        // vb_indent(c, indentation_level);
        // vb_copy_string(c, strlit("error_mask |= (_cvtmask64_u64(_knot_mask64(string_compare_mask)) != 0) << string_index;\n"));


        vb_copy_string(c, strlit("}\n"));
    }
}

STRUCT(x86_64_Register)
{
    String name;
    u16 value;
};

typedef enum x86_64_RegisterClass : u8
{
    REGISTER_CLASS_GPR,
    REGISTER_CLASS_VECTOR,
    REGISTER_CLASS_CONTROL,
    REGISTER_CLASS_DEBUG,
} x86_64_RegisterClass;

STRUCT(RegisterSpec)
{
    String name;
    x86_64_RegisterClass class;
    u8 raw_value;
    u8 is_high:1;
    u8 size;
};

fn x86_64_Register define_register(RegisterSpec spec)
{
    x86_64_Register reg = {
        .name = spec.name,
        .value = spec.raw_value,
    };
    return reg;
}

fn void metaprogram(Arena* arena)
{
    let(file, file_read(arena, strlit("bootstrap/bloat-buster/data/x86_mnemonic.dat")));
    String enum_prefix = strlit("MNEMONIC_x86_64_");
    String it = file;
    VirtualBuffer(u8) generated_h = {};
    VirtualBuffer(u8) generated_c = {};

    vb_copy_string(&generated_h, strlit("#pragma once\n\n"));
    vb_copy_string(&generated_c, strlit("#pragma once\n\n"));

    vb_copy_string(&generated_h, strlit("#if defined(__x86_64__)\n"));
    vb_copy_string(&generated_h, strlit("#include <immintrin.h>\n"));
    vb_copy_string(&generated_h, strlit("#endif\n\n"));

    {

        STRUCT(BitsetComponent)
        {
            String name;
            u64 bit_count;
        };

        STRUCT(ByteComponent)
        {
            String type_name;
            String field_name;
            u8 array_length;
            u8 type_size;
            u8 type_alignment;
            u8 bit_count;
        };

        BitsetComponent bitset_components[] = {
            { strlit("is_rm_register"), 1 },
            { strlit("is_reg_register"), 1 },
            { strlit("implicit_register"), 1 },
            { strlit("is_immediate"), 1 },
            { strlit("immediate_size"), 2 },
            { strlit("is_displacement"), 1 },
            { strlit("is_relative"), 1 },
            { strlit("displacement_size"), 1 },
            { strlit("rex_w"), 1 },
            { strlit("opcode_plus_register"), 1 },
            { strlit("opcode_extension"), 3 },
            { strlit("prefix_0f"), 1 },
        };

        ByteComponent byte_components[] = {
            // TODO: opcode, length -> 1 byte
            { .type_name = strlit("u8"), .type_size = sizeof(u8), .type_alignment = alignof(u8), .field_name = strlit("opcode"), .array_length = 2, },
        };

        u8 bit_offsets[array_length(bitset_components)];

        u64 total_bit_count = 0;
        for (u64 i = 0; i < array_length(bitset_components); i += 1)
        {
            bit_offsets[i] = total_bit_count;
            total_bit_count += bitset_components[i].bit_count;
        }

        u64 aligned_bit_count = next_power_of_two(total_bit_count);
        if (aligned_bit_count < 8 || aligned_bit_count > 16)
        {
            os_exit(1);
        }

        u64 alignment = aligned_bit_count / 8;
        u64 bit_remainder = aligned_bit_count - total_bit_count;

        assert(aligned_bit_count % 8 == 0);
        u64 total_size = aligned_bit_count / 8;
        for (u64 i = 0; i < array_length(byte_components); i += 1)
        {
            alignment = MAX(byte_components[i].type_alignment, alignment);
            total_size += byte_components[i].type_size * byte_components[i].array_length ? byte_components[i].array_length : 1;
        }

        u64 aligned_total_size = next_power_of_two(align_forward_u64(total_size, alignment));
        u64 padding_bytes = aligned_total_size - total_size;
        
        vb_copy_string(&generated_h, strlit("STRUCT(EncodingInvariantData)\n{\n"));

        for (u64 i = 0; i < array_length(bitset_components); i += 1)
        {
            BitsetComponent component = bitset_components[i];
            vb_format(&generated_h, "    u{u64} {s}:{u32};\n", aligned_bit_count, component.name, (u32)component.bit_count);
        }

        if (bit_remainder)
        {
            vb_format(&generated_h, "    u{u64} bit_reserved:{u64};\n", aligned_bit_count, bit_remainder);
        }

        for (u64 i = 0; i < array_length(byte_components); i += 1)
        {
            ByteComponent component = byte_components[i];
            if (component.bit_count)
            {
                vb_format(&generated_h, "    {s} {s}:{u32};\n", component.type_name, component.field_name, (u32)component.bit_count);
            }
            else if (component.array_length)
            {
                vb_format(&generated_h, "    {s} {s}[{u32}];\n", component.type_name, component.field_name, (u32)component.array_length);
            }
            else
            {
                vb_format(&generated_h, "    {s} {s};\n", component.type_name, component.field_name);
            }
        }

        if (padding_bytes)
        {
            vb_format(&generated_h, "    u8 byte_reserved[{u64}];\n", padding_bytes);
        }

        vb_copy_string(&generated_h, strlit("};\n\nstatic_assert(sizeof(EncodingInvariantData) <= sizeof(u64));\n\n"));

        for (u64 i = 0; i < array_length(bitset_components); i += 1)
        {
            vb_format(&generated_h, "#define {s}_bit_offset ({u64})\n", bitset_components[i].name, (u64)bit_offsets[i]);
        }

        *vb_add(&generated_h, 1) = '\n';
    }

    vb_copy_string(&generated_h, strlit("typedef enum Mnemonic_x86_64\n{\n"));
    VirtualBufferP(u8) mnemonic_names_by_length_buffer[16] = {};
    VirtualBuffer(u16) mnemonic_values_by_length_buffer[array_length(mnemonic_names_by_length_buffer)] = {};
    SliceP(u8) mnemonic_names_by_length[array_length(mnemonic_names_by_length_buffer)] = {};
    void* mnemonic_values_by_length[array_length(mnemonic_names_by_length_buffer)] = {};
    vb_copy_string(&generated_c, strlit("fn String mnemonic_x86_64_to_string(Mnemonic_x86_64 mnemonic)\n{\n    switch (mnemonic)\n    {\n"));

    u16 mnemonic_index = 0;

    while (it.length)
    {
        let(next_eol_index, string_first_ch(it, '\n'));
        if (next_eol_index == STRING_NO_MATCH)
        {
            todo();
        }

        String mnemonic = { .pointer = it.pointer, .length = next_eol_index };
        *vb_add(&mnemonic_names_by_length_buffer[mnemonic.length], 1) = mnemonic.pointer;
        *vb_add(&mnemonic_values_by_length_buffer[mnemonic.length], 1) = mnemonic_index;

        // Generated h
        vb_copy_string(&generated_h, strlit("    "));
        vb_copy_string(&generated_h, enum_prefix);
        vb_copy_string(&generated_h, mnemonic);
        vb_format(&generated_h, " = 0x{u32:x,w=4},\n", mnemonic_index);
        mnemonic_index += 1;

        // Generated c
        vb_copy_string(&generated_c, strlit("        case "));
        vb_copy_string(&generated_c, enum_prefix);
        vb_copy_string(&generated_c, mnemonic);
        vb_copy_string(&generated_c, strlit(": return strlit(\""));
        vb_copy_string(&generated_c, mnemonic);
        vb_copy_string(&generated_c, strlit("\");\n"));

        it = s_get_slice(u8, it, next_eol_index + 1, it.length);
    }

    vb_copy_string(&generated_h, strlit("} Mnemonic_x86_64;\n"));
    vb_format(&generated_h, "#define mnemonic_x86_64_count ({u32})\n", mnemonic_index);

    vb_copy_string(&generated_c, strlit("    }\n}\n"));

    for (u32 i = 0; i < array_length(mnemonic_names_by_length_buffer); i += 1)
    {
        mnemonic_names_by_length[i] = (SliceP(u8)) { .pointer = mnemonic_names_by_length_buffer[i].pointer, .length = mnemonic_names_by_length_buffer[i].length };
        mnemonic_values_by_length[i] = mnemonic_values_by_length_buffer[i].pointer;
    }

    {
        {
            u8 mask[array_length(mnemonic_names_by_length)];
            Lookup lookups[array_length(mnemonic_names_by_length)];
            Program programs[array_length(mnemonic_names_by_length)];
            PerfectHashArguments perfect_hash = {
                .file_h = &generated_h,
                .file_c = &generated_c,
                .words_by_length = array_to_slice(mnemonic_names_by_length),
                .values_by_length = mnemonic_values_by_length,
                .value_type = TYPE_KIND_U16,
                .mask = mask,
                .lookups = lookups,
                .programs = programs,
                .arena = arena,
                .kind = strlit("mnemonic"),
            };

            perfect_hash_generate(perfect_hash);
        }

        {
#define reg(n, v, c, ...) define_register((RegisterSpec) { .name = strlit(n), .raw_value = (v), .class = REGISTER_CLASS_ ## c, __VA_ARGS__ })
#define regs(n, v, c, s, ...) define_register((RegisterSpec) { .name = strlit(n), .raw_value = (v), .class = (REGISTER_CLASS_ ## c), .size = (s), __VA_ARGS__ })
            x86_64_Register gpr_registers[] = {
                regs("al",  0b000, GPR, 0),
                regs("ah",  0b000, GPR, 1, .is_high = 1),
                regs("ax",  0b000, GPR, 1),
                regs("eax", 0b000, GPR, 2),
                regs("rax", 0b000, GPR, 3),

                regs("cl",  0b0001, GPR, 0),
                regs("ch",  0b0001, GPR, 1, .is_high = 1),
                regs("cx",  0b0001, GPR, 1),
                regs("ecx", 0b0001, GPR, 2),
                regs("rcx", 0b0001, GPR, 3),

                regs("dl",  0b0010, GPR, 0),
                regs("dh",  0b0010, GPR, 1, .is_high = 1),
                regs("dx",  0b0010, GPR, 1),
                regs("edx", 0b0010, GPR, 2),
                regs("rdx", 0b0010, GPR, 3),

                regs("bl",  0b0011, GPR, 0),
                regs("bh",  0b0011, GPR, 1, .is_high = 1),
                regs("bx",  0b0011, GPR, 1),
                regs("ebx", 0b0011, GPR, 2),
                regs("rbx", 0b0011, GPR, 3),

                regs("spl", 0b0100, GPR, 0),
                regs("sp",  0b0100, GPR, 1),
                regs("esp", 0b0100, GPR, 2),
                regs("rsp", 0b0100, GPR, 3),

                regs("bpl", 0b0101, GPR, 0),
                regs("bp",  0b0101, GPR, 1),
                regs("ebp", 0b0101, GPR, 2),
                regs("rbp", 0b0101, GPR, 3),

                regs("sil", 0b0110, GPR, 0),
                regs("si",  0b0110, GPR, 1),
                regs("esi", 0b0110, GPR, 2),
                regs("rsi", 0b0110, GPR, 3),

                regs("dil", 0b0111, GPR, 0),
                regs("di",  0b0111, GPR, 1),
                regs("edi", 0b0111, GPR, 2),
                regs("rdi", 0b0111, GPR, 3),

                regs("r8l", 0b1000, GPR, 0),
                regs("r8w", 0b1000, GPR, 1),
                regs("r8d", 0b1000, GPR, 2),
                regs("r8",  0b1000, GPR, 3),

                regs("r9l", 0b1001, GPR, 0),
                regs("r9w", 0b1001, GPR, 1),
                regs("r9d", 0b1001, GPR, 2),
                regs("r9",  0b1001, GPR, 3),

                regs("r10l", 0b1010, GPR, 0),
                regs("r10w", 0b1010, GPR, 1),
                regs("r10d", 0b1010, GPR, 2),
                regs("r10",  0b1010, GPR, 3),

                regs("r11l", 0b1011, GPR, 0),
                regs("r11w", 0b1011, GPR, 1),
                regs("r11d", 0b1011, GPR, 2),
                regs("r11",  0b1011, GPR, 3),

                regs("r12l", 0b1100, GPR, 0),
                regs("r12w", 0b1100, GPR, 1),
                regs("r12d", 0b1100, GPR, 2),
                regs("r12",  0b1100, GPR, 3),

                regs("r13l", 0b1101, GPR, 0),
                regs("r13w", 0b1101, GPR, 1),
                regs("r13d", 0b1101, GPR, 2),
                regs("r13",  0b1101, GPR, 3),

                regs("r14l", 0b1110, GPR, 0),
                regs("r14w", 0b1110, GPR, 1),
                regs("r14d", 0b1110, GPR, 2),
                regs("r14",  0b1110, GPR, 3),

                regs("r15l", 0b1111, GPR, 0),
                regs("r15w", 0b1111, GPR, 1),
                regs("r15d", 0b1111, GPR, 2),
                regs("r15",  0b1111, GPR, 3),
            };

            VirtualBufferP(u8) register_names_by_length_buffer[8] = {};
            VirtualBuffer(u16) register_values_by_length_buffer[array_length(register_names_by_length_buffer)] = {};
            SliceP(u8) register_names_by_length[array_length(register_names_by_length_buffer)] = {};
            void* register_values_by_length[array_length(register_names_by_length_buffer)] = {};

            vb_copy_string(&generated_h, strlit("typedef enum x86_64_Register : u16\n{\n"));

            for (u32 i = 0; i < array_length(gpr_registers); i += 1)
            {
                x86_64_Register reg = gpr_registers[i];
                *vb_add(&register_names_by_length_buffer[reg.name.length], 1) = reg.name.pointer;
                *vb_add(&register_values_by_length_buffer[reg.name.length], 1) = reg.value;
                vb_format(&generated_h, "    REGISTER_X86_64_{s} = 0x{u32:x,w=4},\n", reg.name, reg.value);
            }

            u8 vector_registers[32][3][5];

            for (u8 i = 0; i < 32; i += 1)
            {
                for (u8 size = 0; size < 3; size += 1)
                {
                    u8 decimal_digit_high = i / 10;
                    u8 decimal_digit_low = i % 10;
                    u8 decimal_digit_high_character = decimal_digit_high + '0';
                    u8 decimal_digit_low_character = decimal_digit_low + '0';

                    vector_registers[i][size][0] = 'x' + size;
                    vector_registers[i][size][1] = 'm';
                    vector_registers[i][size][2] = 'm';
                    vector_registers[i][size][3] = decimal_digit_high ? decimal_digit_high_character : decimal_digit_low_character;
                    vector_registers[i][size][4] = decimal_digit_low_character;
                    RegisterSpec spec = { .name = { .pointer = vector_registers[i][size], .length = 4 + (decimal_digit_high != 0) }, .raw_value = i, .class = REGISTER_CLASS_VECTOR, .size = size, };
                    let(reg, define_register(spec));
                    *vb_add(&register_names_by_length_buffer[reg.name.length], 1) = reg.name.pointer;
                    *vb_add(&register_values_by_length_buffer[reg.name.length], 1) = reg.value;
                    vb_format(&generated_h, "    REGISTER_X86_64_{s} = 0x{u32:x,w=4},\n", reg.name, reg.value);
                }
            }

            vb_copy_string(&generated_h, strlit("} x86_64_Register;\n"));

            for (u32 i = 0; i < array_length(register_names_by_length_buffer); i += 1)
            {
                register_names_by_length[i] = (SliceP(u8)) { .pointer = register_names_by_length_buffer[i].pointer, .length = register_names_by_length_buffer[i].length };
                register_values_by_length[i] = register_values_by_length_buffer[i].pointer;
            }

            u8 mask[array_length(register_names_by_length)];
            Lookup lookups[array_length(register_names_by_length)];
            Program programs[array_length(register_names_by_length)];
            PerfectHashArguments perfect_hash = {
                .file_h = &generated_h,
                .file_c = &generated_c,
                .words_by_length = array_to_slice(register_names_by_length),
                .values_by_length = register_values_by_length,
                .value_type = TYPE_KIND_U16,
                .mask = mask,
                .lookups = lookups,
                .programs = programs,
                .arena = arena,
                .kind = strlit("register"),
            };

            perfect_hash_generate(perfect_hash);
        }
    }

    String generated_h_slice = { .pointer = generated_h.pointer, .length = generated_h.length };
    String generated_c_slice = { .pointer = generated_c.pointer, .length = generated_c.length };

    {
        FileWriteOptions options = {
            .path = strlit(BUILD_DIR "/generated.h"),
            .content = generated_h_slice,
        };
        file_write(options);
    }

    {
        FileWriteOptions options = {
            .path = strlit(BUILD_DIR "/generated.c"),
            .content = generated_c_slice,
        };
        file_write(options);
    }
}

STRUCT(Parser)
{
    u8* pointer;
    u32 length;
    u32 i;
};

fn String parse_mnemonic(Parser* parser)
{
    u32 start = parser->i;
    u8* pointer = parser->pointer;
    String result = { .pointer = pointer + start };

    while (1)
    {
        u32 i = parser->i;
        u8 ch = pointer[i];
        u8 ch_is_alphanumeric = is_alphanumeric(ch);
        parser->i = i + ch_is_alphanumeric;
        if (!ch_is_alphanumeric)
        {
            break;
        }
    }

    result.length = parser->i - start;

    return result;
}

fn String parse_identifier(Parser* parser)
{
    u32 start = parser->i;
    u8* pointer = parser->pointer;
    String result = { .pointer = parser->pointer + parser->i };

    while (1)
    {
        u32 i = parser->i;
        u8 ch = pointer[i];
        u8 is_identifier_ch = is_alphanumeric(ch) | (ch == '_');
        parser->i = i + is_identifier_ch;
        if (!is_identifier_ch)
        {
            break;
        }
    }

    result.length = parser->i - start;

    return result;
}

fn u8 consume_character(Parser* parser, u8 expected_ch)
{
    u32 i = parser->i;
    u8 ch = parser->pointer[i];
    let(is_expected_ch, unlikely((ch == expected_ch) & (i < parser->length)));
    let(new_parser_i, i + is_expected_ch);
    parser->i = new_parser_i;
    return new_parser_i - i;
}

fn void expect_character(Parser* parser, u8 expected_ch)
{
    if (!likely(consume_character(parser, expected_ch)))
    {
        print("Expected character failed!\n");
        os_exit(1);
    }
}


fn u8 get_ch(Parser* parser)
{
    assert(parser->i < parser->length);
    return parser->pointer[parser->i];
}

fn u8 expect_decimal_digit(Parser* parser)
{
    u32 i = parser->i;
    assert(i < parser->length);
    u8 ch = parser->pointer[i];
    u8 is_decimal_digit = (ch >= '0') & (ch <= '9');
    parser->i = i + is_decimal_digit;
    if (likely(is_decimal_digit))
    {
        return ch - '0';
    }
    else
    {
        print("Expect integer digit failed!\n");
        os_exit(1);
    }
}

fn u8 consume_hex_byte(Parser* parser, u8* hex_byte)
{
    u32 i = parser->i;
    assert(i < parser->length - 1);
    u8* pointer = parser->pointer;
    u8 high_ch = pointer[i];
    u8 low_ch = pointer[i + 1];
    u8 is_high_digit_hex = is_hex_digit(high_ch);
    u8 is_low_digit_hex = is_hex_digit(low_ch);
    u8 is_hex_byte = is_high_digit_hex & is_low_digit_hex;
    parser->i = i + (2 * is_hex_byte);
    u8 result = is_hex_byte;
    if (likely(result))
    {
        u8 high_int = hex_ch_to_int(high_ch);
        u8 low_int = hex_ch_to_int(low_ch);
        u8 byte = (high_int << 4) | low_int;
        *hex_byte = byte;
    }

    return result;
}

fn u8 expect_hex_byte(Parser* parser)
{
    u8 result;
    if (!consume_hex_byte(parser, &result))
    {
        print("Expect hex byte failed!\n");
        os_exit(1);
    }

    return result;
}

// TODO: this might be a perf bottleneck
fn u8 consume_tab(Parser* parser)
{
    u8 space0 = consume_character(parser, ' ');
    u8 space1 = consume_character(parser, ' ');
    u8 space2 = consume_character(parser, ' ');
    u8 space3 = consume_character(parser, ' ');
    u8 result = (space0 + space1) + (space2 + space3);
    return result == 4;
}

typedef enum InstructionClass
{
    INSTRUCTION_CLASS_BASE_ARITHMETIC,
    INSTRUCTION_CLASS_UNSIGNED_ADD_FLAG,
    INSTRUCTION_CLASS_BITTEST,
    INSTRUCTION_CLASS_CMOV,
    INSTRUCTION_CLASS_JCC,
    INSTRUCTION_CLASS_ROTATE,
    INSTRUCTION_CLASS_SHIFT,
    INSTRUCTION_CLASS_SETCC,
} InstructionClass;

fn String parse_encoding_type(Parser* parser)
{
    u32 i = parser->i;
    while (1)
    {
        u8 ch = get_ch(parser);
        u8 is_valid_encoding_type_ch = is_lower(ch) | (ch == '-');
        parser->i += is_valid_encoding_type_ch;
        if (is_valid_encoding_type_ch)
        {
            if (parser->i - i > 4)
            {
                todo();
            }
        }
        else
        {
            break;
        }
    }

    u64 length = parser->i - i;
    if (length == 0)
    {
        todo();
    }
    if (length > 4)
    {
        todo();
    }

    String result = { .pointer = parser->pointer + i, .length = length };
    return result;
}

fn void parse_encoding_details(Parser* parser)
{
    expect_character(parser, '[');
    String encoding_type = parse_encoding_type(parser);
    expect_character(parser, ':');
    expect_character(parser, ' ');

    while (!consume_character(parser, ']'))
    {
        // Parser encoding atom
        u8 byte;
        if (consume_hex_byte(parser, &byte))
        {
            u8 ch = get_ch(parser);
            u8 is_plus = ch == '+';
            parser->i += is_plus;
            if (unlikely(is_plus))
            {
                expect_character(parser, 'r');
            }
        }
        else
        {
            String identifier = parse_identifier(parser);
            if (identifier.length)
            {
                if (identifier.pointer[0] == 'i')
                {
                    assert(identifier.length == 2);
                    u8 imm_byte = identifier.pointer[1];
                    u8 is_valid_imm_byte = ((imm_byte == 'b') | (imm_byte == 'w')) | ((imm_byte == 'd') | (imm_byte == 'q'));
                    if (!likely(is_valid_imm_byte))
                    {
                        print("Bad immediate value\n");
                        os_exit(1);
                    }
                }
                else if (s_equal(identifier, strlit("rex")))
                {
                    expect_character(parser, '.');
                    u8 rex_ch = get_ch(parser);
                    u8 is_valid_rex_ch = ((rex_ch == 'w') | (rex_ch == 'r')) | ((rex_ch == 'x') | (rex_ch == 'b'));
                    parser->i += is_valid_rex_ch;
                    if (!likely(is_valid_rex_ch))
                    {
                        todo();
                    }
                }
                else if (string_starts_with(identifier, strlit("rel")))
                {
                    // todo
                }
                else
                {
                    todo();
                }
            }
            else
            {
                u8 ch = get_ch(parser);
                switch (ch)
                {
                    case '/':
                        {
                            parser->i += 1;
                            if (consume_character(parser, 'r'))
                            {
                                // TODO
                            }
                            else
                            {
                                expect_decimal_digit(parser);
                            }
                        } break;
                    default:
                        todo();
                }
            }
        }

        consume_character(parser, ' ');
    }
}

fn void parse_encoding(Parser* parser)
{
    u8 first_ch = get_ch(parser);
    u32 start = parser->i;
    if (first_ch != '[')
    {
        while (1)
        {
            u32 i = parser->i;
            String operand = parse_mnemonic(parser);
            assert(operand.length);
            if (consume_character(parser, ','))
            {
                expect_character(parser, ' ');
            }
            else
            {
                break;
            }
        }

        expect_character(parser, ' ');
    }

    parse_encoding_details(parser);
}

fn void parse_instruction_table(Arena* arena)
{
    String file = file_read(arena, strlit("bootstrap/bloat-buster/data/instructions.dat"));
    Parser parser_memory = {
        .pointer = file.pointer,
        .length = file.length,
    };
    Parser* parser = &parser_memory;

    VirtualBuffer(u8) file_memory = {};
    VirtualBuffer(u8)* f = &file_memory;

    let_cast(u32, file_length, file.length);
    while (parser->i < file_length)
    {
        String mnemonic = parse_mnemonic(parser);
        expect_character(parser, ':');

        if (consume_character(parser, '\n'))
        {
            while (consume_tab(parser))
            {
                parse_encoding(parser);
                expect_character(parser, '\n');
            }
        }
        else if (consume_character(parser, ' '))
        {
            u8 next_ch = get_ch(parser);
            switch (next_ch)
            {
                case '[':
                    {
                        parse_encoding_details(parser);
                    } break;
                default:
                    {
                        String identifier = parse_identifier(parser);
                        if (s_equal(identifier, strlit("class")))
                        {
                            expect_character(parser, ' ');
                            String class_identifier = parse_identifier(parser);
                            InstructionClass instruction_class;

                            if (s_equal(class_identifier, strlit("base_arithmetic")))
                            {
                                instruction_class = INSTRUCTION_CLASS_BASE_ARITHMETIC;
                            }
                            else if (s_equal(class_identifier, strlit("unsigned_add_flag")))
                            {
                                instruction_class = INSTRUCTION_CLASS_UNSIGNED_ADD_FLAG;
                            }
                            else if (s_equal(class_identifier, strlit("bittest")))
                            {
                                instruction_class = INSTRUCTION_CLASS_BITTEST;
                            }
                            else if (s_equal(class_identifier, strlit("cmov")))
                            {
                                instruction_class = INSTRUCTION_CLASS_CMOV;
                            }
                            else if (s_equal(class_identifier, strlit("jcc")))
                            {
                                instruction_class = INSTRUCTION_CLASS_JCC;
                            }
                            else if (s_equal(class_identifier, strlit("rotate")))
                            {
                                instruction_class = INSTRUCTION_CLASS_ROTATE;
                            }
                            else if (s_equal(class_identifier, strlit("shift")))
                            {
                                instruction_class = INSTRUCTION_CLASS_SHIFT;
                            }
                            else if (s_equal(class_identifier, strlit("setcc")))
                            {
                                instruction_class = INSTRUCTION_CLASS_SETCC;
                            }
                            else
                            {
                                todo();
                            }

                            switch (instruction_class)
                            {
                                case INSTRUCTION_CLASS_BASE_ARITHMETIC:
                                    {
                                        u8 opcodes[3];
                                        expect_character(parser, '(');

                                        expect_character(parser, '/');
                                        u8 imm_digit = expect_decimal_digit(parser);
                                        expect_character(parser, ',');
                                        expect_character(parser, ' ');

                                        opcodes[0] = expect_hex_byte(parser);
                                        expect_character(parser, ',');
                                        expect_character(parser, ' ');

                                        opcodes[1] = expect_hex_byte(parser);
                                        expect_character(parser, ',');
                                        expect_character(parser, ' ');

                                        opcodes[2] = expect_hex_byte(parser);
                                        expect_character(parser, ')');
                                    } break;
                                case INSTRUCTION_CLASS_UNSIGNED_ADD_FLAG:
                                    {
                                        expect_character(parser, '(');
                                        u8 opcode = expect_hex_byte(parser);
                                        expect_character(parser, ')');
                                    } break;
                                case INSTRUCTION_CLASS_BITTEST:
                                    {
                                        expect_character(parser, '(');

                                        expect_character(parser, '/');
                                        u8 imm_digit = expect_decimal_digit(parser);
                                        expect_character(parser, ',');
                                        expect_character(parser, ' ');

                                        u8 opcode = expect_hex_byte(parser);
                                        expect_character(parser, ')');
                                    } break;
                                case INSTRUCTION_CLASS_CMOV:
                                    {
                                    } break;
                                case INSTRUCTION_CLASS_JCC:
                                    {
                                    } break;
                                case INSTRUCTION_CLASS_ROTATE:
                                    {
                                        expect_character(parser, '(');

                                        expect_character(parser, '/');
                                        u8 imm_digit = expect_decimal_digit(parser);

                                        expect_character(parser, ')');
                                    } break;
                                case INSTRUCTION_CLASS_SHIFT:
                                    {
                                        expect_character(parser, '(');

                                        expect_character(parser, '/');
                                        u8 imm_digit = expect_decimal_digit(parser);

                                        expect_character(parser, ')');
                                    } break;
                                case INSTRUCTION_CLASS_SETCC:
                                    {
                                    } break;
                            }
                        }
                        else
                        {
                            parser->i -= identifier.length;
                            parse_encoding(parser);
                        }
                    } break;
            }

            expect_character(parser, '\n');
        }
        else
        {
            todo();
        }
    }
}

int main(int argc, char* argv[], char** envp)
{
    environment_pointer = envp;
    Arena* arena = arena_initialize_default(KB(64));
    metaprogram(arena);
    parse_instruction_table(arena);
    BuildType build_type = build_type_pick();
    CompileOptions compile_options = {
        .compiler_path = get_c_compiler_path(arena, build_type),
        .source_path = strlit("bootstrap/bloat-buster/bb.c"),
        .output_path = strlit("cache/bb" EXECUTABLE_EXTENSION),
        .windowing_backend = windowing_backend_pick(),
        .rendering_backend = rendering_backend_pick(),
        .build_type = build_type,
        .flags = {
            .colored_output = 1,
            .error_limit = BB_ERROR_LIMIT,
            .debug = BB_CI,
            .time_trace = BB_TIMETRACE,
        },
    };
    compile_program(arena, compile_options);
    return 0;
}