#ifndef CPP_HEADER
#define CPP_HEADER

/*
@todo:
Implement pragma once


*/

#ifndef CPP_API_FUNCTION
    #ifdef __cplusplus
        #define CPP_API_FUNCTION extern "C"
    #else
        #define CPP_API_FUNCTION
    #endif
#endif

/*#meta.gen_enum(meta.macro_kind, decl=True, enum_name="CPP_MacroKind", prefix="CPP_MACRO")*/
typedef enum CPP_MacroKind {
    CPP_MACRO_NULL,
    CPP_MACRO_ERROR,
    CPP_MACRO_DEFINE,
    CPP_MACRO_IF,
    CPP_MACRO_ELSE,
    CPP_MACRO_ELIF,
} CPP_MacroKind;
/*END*/

typedef struct CPP_Macro CPP_Macro;
struct CPP_Macro {
    CPP_MacroKind kind;
    CPP_Macro *next;
    union {
        struct {
            char *name;
            bool function_like;
            bool vargs;
            CL_Tokens args;
            CL_Tokens body;
        } defn;
        struct {
            int evaluation;
        } ifn;
    };
};

typedef struct CPP_Message CPP_Message;
struct CPP_Message {
    CL_MessageKind kind;
    CPP_Message *next;
    char *string;

    struct CPP_Macro *macro;
    CL_Token token;
};

typedef struct CPP_HashEntry CPP_HashEntry;
struct CPP_HashEntry {
    CPP_HashEntry *next;
    void *pointer;
    uint64_t hash;
    uint64_t key;
};

typedef struct CPP_HashTable CPP_HashTable;
struct CPP_HashTable {
    CPP_HashEntry *entries;
    int entry_count;
    int occupied_entry_count;
    CL_Arena *arena;
};

// Preprocessing stage assumptions:
// We get a list of tokens (and list of files with tokens to handle includes)
// All identifiers should be interned, dont care how
//
// Returns:
// Tokens, messages
// +
// Define table
// It can be reused
typedef struct CPP_PreprocResult {
    CL_Tokens tokens;
    CPP_Message *first_message;
    CPP_Message *last_message;
    CPP_HashTable *defines;

    // Private for preprocessing
    CL_LexResult **pragma_onced;
    int pragma_onced_count;

    CL_LexList lex_list;
    CPP_Macro *if_stack;
    CL_ArenaTuple arena;
} CPP_PreprocResult;

#endif // CPP_HEADER
#ifdef CPP_IMPLEMENTATION

static CPP_Macro CPP_NullMacro;

#define CPP_SLL_STACK_ADD_MOD(stack_base, new_stack_base, next) \
    do {                                                        \
        (new_stack_base)->next = (stack_base);                  \
        (stack_base) = (new_stack_base);                        \
    } while (0)
#define CPP_SLL_STACK_ADD(stack_base, new_stack_base) \
    CPP_SLL_STACK_ADD_MOD(stack_base, new_stack_base, next)

#define CPP_SLL_STACK_POP_AND_STORE(stack_base, out_node) \
    do {                                                  \
        if (stack_base) {                                 \
            (out_node) = (stack_base);                    \
            (stack_base) = (stack_base)->next;            \
            (out_node)->next = 0;                         \
        }                                                 \
    } while (0)

/*#
meta.gen_enum(meta.macro_kind, table=True, table_name="CPP_MacroKindString")*/
#ifndef CPP_PRIVATE_FUNCTION
    #if defined(__GNUC__) || defined(__clang__)
        #define CPP_PRIVATE_FUNCTION __attribute__((unused)) static
    #else
        #define CPP_PRIVATE_FUNCTION static
    #endif
#endif

char *CPP_MacroKindString[] = {
    "NULL",
    "ERROR",
    "DEFINE",
    "IF",
    "ELSE",
    "ELIF",
};
/*END*/

#define CPP_IS_POW2(x) (((x) & ((x)-1)) == 0)
#define CPP_WRAP_AROUND_POWER_OF_2(x, pow2) (((x) & ((pow2)-1llu)))

#ifndef CPP_HASH_BYTES
    #define CPP_HASH_BYTES CPP__HashBytes
CPP_PRIVATE_FUNCTION uint64_t CPP__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;
}
#endif

CPP_API_FUNCTION void CPP_InitHashTable(CL_Arena *arena, CPP_HashTable *table, int size) {
    CL_ASSERT(CPP_IS_POW2(size) && "Required to be a power of 2");
    table->arena = arena;
    table->entries = (CPP_HashEntry *)CL_PushSize(arena, sizeof(CPP_HashEntry) * size);
    CL__MemoryZero(table->entries, sizeof(CPP_HashEntry) * size);
    table->entry_count = size;
    table->occupied_entry_count = 0;
}

CPP_API_FUNCTION CPP_HashTable *CPP_CreateHashTable(CL_Arena *arena, int size) {
    CPP_HashTable *result = (CPP_HashTable *)CL_PushSize(arena, sizeof(CPP_HashTable));
    CL__MemoryZero(result, sizeof(*result));
    CPP_InitHashTable(arena, result, size);
    return result;
}

CPP_API_FUNCTION CPP_HashEntry *CPP_InsertEntry(CPP_HashTable *table, uint64_t key, void *pointer) {
    /*HT_Insert("table->entries", "table->entry_count", "key", "pointer")*/
    CL_ASSERT(table->arena && "Uninitialized hash table");
    uint64_t hash = CPP_HASH_BYTES(&key, sizeof(key));
    if (hash == 0) hash += 1;

    uint64_t index = CPP_WRAP_AROUND_POWER_OF_2(hash, table->entry_count);
    CPP_HashEntry *it = table->entries + index;
    for (;;) {
        if (it->hash == 0) {
            it->pointer = pointer;
            it->hash = hash;
            it->key = key;
            return it;
        }
        else if (it->key == key && it->hash == hash) {
            return 0;
        }

        if (!it->next) {
            it->next = (CPP_HashEntry *)CL_PushSize(table->arena, sizeof(CPP_HashEntry));
            CL__MemoryZero(it->next, sizeof(CPP_HashEntry));
        }
        it = it->next;
    }
}

static CPP_HashEntry CPP_NullHashEntry;
CPP_API_FUNCTION CPP_HashEntry *CPP_LookupEntry(CPP_HashTable *table, uint64_t key) {
    CL_ASSERT(table->arena && "Uninitialized hash table");
    uint64_t hash = CPP_HASH_BYTES(&key, sizeof(key));
    if (hash == 0) hash += 1;
    uint64_t index = CPP_WRAP_AROUND_POWER_OF_2(hash, table->entry_count);

    for (CPP_HashEntry *it = table->entries + index; it; it = it->next) {
        if (it->hash == hash && it->key == key) {
            return it;
        }
    }
    return &CPP_NullHashEntry;
}

CPP_API_FUNCTION bool CPP_RemoveEntry(CPP_HashTable *table, uint64_t key) {
    CPP_HashEntry *entry = CPP_LookupEntry(table, key);
    if (entry->hash) {
        CL__MemoryZero(entry, sizeof(*entry));
        return true;
    }
    return false;
}

CPP_API_FUNCTION CPP_HashEntry *CPP_InsertI(CPP_HashTable *table, char *key, void *value) {
    return CPP_InsertEntry(table, (uint64_t)key, value);
}
CPP_API_FUNCTION void *CPP_LookupI(CPP_HashTable *table, char *key) {
    return CPP_LookupEntry(table, (uint64_t)key)->pointer;
}
CPP_API_FUNCTION bool CPP_RemoveI(CPP_HashTable *table, char *key) {
    return CPP_LookupEntry(table, (uint64_t)key)->pointer;
}

CPP_PRIVATE_FUNCTION CPP_Message *CPP_CreateMessage(CL_Arena *arena, CL_MessageKind kind, char *string) {
    CPP_Message *result = CL_PushStruct(arena, CPP_Message);
    result->kind = kind;
    result->string = (char *)string;
    return result;
}

CPP_PRIVATE_FUNCTION void CPP_ReportError(CPP_PreprocResult *ctx, CPP_Macro **macro, CL_Token *token, const char *string, ...) {
    CL__FORMAT(ctx->arena.other, string, message_string);
    CPP_Message *result = CPP_CreateMessage(ctx->arena.other, CLM_ERROR, message_string);
    CL_SLL_QUEUE_ADD(ctx->first_message, ctx->last_message, result);
    result->token = *token;

    if (macro && macro[0]) {
        macro[0]->kind = CPP_MACRO_ERROR;
        result->macro = macro[0];
    }
#if TEST_DEBUG
    printf("%s:%d %s\n", token->file, token->line, message_string);
    __debugbreak();
#endif
}

CPP_PRIVATE_FUNCTION void CPP_ReportWarning(CPP_PreprocResult *ctx, CL_Token *token, const char *string, ...) {
    CL__FORMAT(ctx->arena.other, string, message_string);
    CPP_Message *result = CPP_CreateMessage(ctx->arena.other, CLM_WARNING, message_string);
    CL_SLL_QUEUE_ADD(ctx->first_message, ctx->last_message, result);
    result->token = *token;
}

#define CPP_REPORT_ERROR(ctx, macro, token, ...)                 \
    do {                                                         \
        CPP_ReportError((ctx), (macro), (token), ##__VA_ARGS__); \
        error_occured = true;                                    \
        goto end_of_iteration;                                   \
    } while (0)

CPP_API_FUNCTION void CPP_StringifyMessage(char *buff, int buff_size, CPP_Message *msg) {
    char *kind = CL_MessageKindString[msg->kind];
    CL_SNPRINTF(buff, buff_size, "%s:%d %15s %15s", msg->token.file, msg->token.line, kind, msg->string);
}

CPP_API_FUNCTION void CPP_PrintMessages(CPP_PreprocResult *preproc_result) {
    char buff[1024];
    for (CPP_Message *it = preproc_result->first_message; it; it = it->next) {
        CPP_StringifyMessage(buff, sizeof(buff), it);
        printf("%s\n", buff);
    }
}

CPP_API_FUNCTION int CPP__EvalExpr(CPP_PreprocResult *ctx, CL_Tokens *it);
CPP_PRIVATE_FUNCTION int CPP_EvalAtom(CPP_PreprocResult *ctx, CL_Tokens *it) {
    CL_Token *token = CL_Next(it);
    int result = 0;
    switch (token->kind) {
        case CL_OPENPAREN: {
            result = CPP__EvalExpr(ctx, it);
            if (!CL_Match(it, CL_CLOSEPAREN)) CPP_ReportError(ctx, 0, token, "No matching open parenthesis '(' found");
        } break;
        case CL_FLOAT: CPP_ReportError(ctx, 0, token, "Floats illegal in preprocessor expressions"); break;
        case CL_STRINGLIT: CPP_ReportError(ctx, 0, token, "Strings illegal in preprocessor expressions"); break;
        case CL_INT: result = (int)token->u64; break;
        case CL_IDENTIFIER: result = 0; break; // @todo: check if this is correct
        default: CPP_ReportError(ctx, 0, token, "Illegal token in preprocessor expression"); break;
    }
    return result;
}

CPP_PRIVATE_FUNCTION int CPP_EvalUnary(CPP_PreprocResult *ctx, CL_Tokens *it) {
    int result = 0;
    CL_Token *token = CL_Get(it);
    switch (token->kind) {
        case CL_ADD:
            CL_Next(it);
            result = +CPP_EvalUnary(ctx, it);
            break;
        case CL_SUB:
            CL_Next(it);
            result = -CPP_EvalUnary(ctx, it);
            break;
        case CL_NEG:
            CL_Next(it);
            result = ~CPP_EvalUnary(ctx, it);
            break;
        case CL_NOT:
            CL_Next(it);
            result = !CPP_EvalUnary(ctx, it);
            break;
        default: result = CPP_EvalAtom(ctx, it);
    }
    return result;
}

CPP_PRIVATE_FUNCTION int CPP_EvalMul(CPP_PreprocResult *ctx, CL_Tokens *it) {
    int left = CPP_EvalUnary(ctx, it);

    CL_Token *op = CL_Get(it);
    while (CL_Match(it, CL_MUL) || CL_Match(it, CL_DIV) || CL_Match(it, CL_MOD)) {
        int right = CPP_EvalUnary(ctx, it);
        switch (op->kind) {
            case CL_MUL: left = left * right; break;
            case CL_DIV: left = left / right; break;
            case CL_MOD: left = left % right; break;
            default: CL_ASSERT(!"Invalid default case");
        }
        op = CL_Get(it);
    }

    return left;
}

CPP_PRIVATE_FUNCTION int CPP_EvalAdd(CPP_PreprocResult *ctx, CL_Tokens *it) {
    int left = CPP_EvalMul(ctx, it);

    CL_Token *op = CL_Get(it);
    while (CL_Match(it, CL_ADD) || CL_Match(it, CL_SUB)) {
        int right = CPP_EvalMul(ctx, it);
        switch (op->kind) {
            case CL_ADD: left = left + right; break;
            case CL_SUB: left = left - right; break;
            default: CL_ASSERT(!"Invalid default case");
        }
        op = CL_Get(it);
    }

    return left;
}

CPP_PRIVATE_FUNCTION int CPP_EvalShift(CPP_PreprocResult *ctx, CL_Tokens *it) {
    int left = CPP_EvalAdd(ctx, it);

    CL_Token *op = CL_Get(it);
    while (CL_Match(it, CL_LEFTSHIFT) || CL_Match(it, CL_RIGHTSHIFT)) {
        int right = CPP_EvalAdd(ctx, it);
        switch (op->kind) {
            case CL_LEFTSHIFT: left = left << right; break;
            case CL_RIGHTSHIFT: left = left >> right; break;
            default: CL_ASSERT(!"Invalid default case");
        }
        op = CL_Get(it);
    }

    return left;
}

CPP_PRIVATE_FUNCTION int CPP_EvalLesserThen(CPP_PreprocResult *ctx, CL_Tokens *it) {
    int left = CPP_EvalShift(ctx, it);

    CL_Token *op = CL_Get(it);
    while (op->kind == CL_LESSERTHEN || op->kind == CL_GREATERTHEN || op->kind == CL_GREATERTHEN_OR_EQUAL || op->kind == CL_LESSERTHEN_OR_EQUAL) {
        CL_Next(it);
        int right = CPP_EvalShift(ctx, it);
        switch (op->kind) {
            case CL_LESSERTHEN: left = left < right; break;
            case CL_GREATERTHEN: left = left > right; break;
            case CL_GREATERTHEN_OR_EQUAL: left = left >= right; break;
            case CL_LESSERTHEN_OR_EQUAL: left = left <= right; break;
            default: CL_ASSERT(!"Invalid default case");
        }
        op = CL_Get(it);
    }

    return left;
}

CPP_PRIVATE_FUNCTION int CPP_EvalEqual(CPP_PreprocResult *ctx, CL_Tokens *it) {
    int left = CPP_EvalLesserThen(ctx, it);

    CL_Token *op = CL_Get(it);
    while (CL_Match(it, CL_EQUALS) || CL_Match(it, CL_NOTEQUALS)) {
        int right = CPP_EvalLesserThen(ctx, it);
        switch (op->kind) {
            case CL_EQUALS: left = left == right; break;
            case CL_NOTEQUALS: left = left != right; break;
            default: CL_ASSERT(!"Invalid default case");
        }

        op = CL_Get(it);
    }

    return left;
}

CPP_PRIVATE_FUNCTION int CPP_EvalBitAnd(CPP_PreprocResult *ctx, CL_Tokens *it) {
    int left = CPP_EvalEqual(ctx, it);

    CL_Token *op = CL_Get(it);
    while (CL_Match(it, CL_BITAND)) {
        int right = CPP_EvalEqual(ctx, it);
        left = left & right;
        op = CL_Get(it);
    }

    return left;
}

CPP_PRIVATE_FUNCTION int CPP_EvalBitXor(CPP_PreprocResult *ctx, CL_Tokens *it) {
    int left = CPP_EvalBitAnd(ctx, it);

    CL_Token *op = CL_Get(it);
    while (CL_Match(it, CL_BITXOR)) {
        int right = CPP_EvalBitAnd(ctx, it);
        left = left ^ right;
        op = CL_Get(it);
    }

    return left;
}

CPP_PRIVATE_FUNCTION int CPP_EvalBitOr(CPP_PreprocResult *ctx, CL_Tokens *it) {
    int left = CPP_EvalBitXor(ctx, it);

    CL_Token *op = CL_Get(it);
    while (CL_Match(it, CL_BITOR)) {
        int right = CPP_EvalBitXor(ctx, it);
        left = left | right;
        op = CL_Get(it);
    }

    return left;
}

CPP_PRIVATE_FUNCTION int CPP_EvalAnd(CPP_PreprocResult *ctx, CL_Tokens *it) {
    int left = CPP_EvalBitOr(ctx, it);

    CL_Token *op = CL_Get(it);
    while (CL_Match(it, CL_AND)) {
        int right = CPP_EvalBitOr(ctx, it);
        left = left && right;
        op = CL_Get(it);
    }

    return left;
}

CPP_PRIVATE_FUNCTION int CPP_EvalOr(CPP_PreprocResult *ctx, CL_Tokens *it) {
    int left = CPP_EvalAnd(ctx, it);

    CL_Token *op = CL_Get(it);
    while (CL_Match(it, CL_OR)) {
        int right = CPP_EvalAnd(ctx, it);
        left = left || right;
        op = CL_Get(it);
    }

    return left;
}

CPP_PRIVATE_FUNCTION int CPP_EvalTernary(CPP_PreprocResult *ctx, CL_Tokens *it) {
    int expr = CPP_EvalOr(ctx, it);
    CL_Token *pos = CL_Get(it);
    if (CL_Match(it, CL_QUESTION)) {
        int on_true = CPP_EvalTernary(ctx, it);
        if (!CL_Match(it, CL_COLON)) {
            CPP_ReportError(ctx, 0, pos, "Missing ':' in a ternary expression");
            return 0;
        }
        int on_false = CPP_EvalTernary(ctx, it);
        expr = expr ? on_true : on_false;
    }

    return expr;
}

CPP_API_FUNCTION int CPP__EvalExpr(CPP_PreprocResult *ctx, CL_Tokens *tokens) {
    int result = CPP_EvalTernary(ctx, tokens);
    return result;
}

CPP_API_FUNCTION int CPP_EvalExpr(CPP_PreprocResult *ctx, CL_Tokens tokens) {
    return CPP__EvalExpr(ctx, &tokens);
}

CPP_API_FUNCTION CPP_Macro *CPP_CreateMacro(CL_Arena *arena, CPP_MacroKind kind) {
    CPP_Macro *result = CL_PushStruct(arena, CPP_Macro);
    result->kind = kind;
    return result;
}

CPP_API_FUNCTION CL_Hideset *CPP_CreateHideset(CL_Arena *arena, char *name) {
    CL_Hideset *result = CL_PushStruct(arena, CL_Hideset);
    result->name = name;
    return result;
}

CPP_API_FUNCTION CL_Hideset *CPP_CopyHideset(CL_Arena *arena, CL_Hideset *hideset) {
    CL_Hideset *result = 0;
    for (CL_Hideset *it = hideset; it; it = it->next) {
        CL_Hideset *copy = CPP_CreateHideset(arena, it->name);
        CPP_SLL_STACK_ADD(result, copy);
    }
    return result;
}

CPP_API_FUNCTION bool CL_ContainsHideset(CL_Hideset *hideset, char *name) {
    for (CL_Hideset *it = hideset; it; it = it->next) {
        if (it->name == name) {
            return true;
        }
    }
    return false;
}

CPP_API_FUNCTION CL_Hideset *CPP_HidesetIntersection(CL_Arena *arena, CL_Hideset *a, CL_Hideset *b) {
    CL_Hideset *first = 0;
    CL_Hideset *last = 0;
    for (CL_Hideset *it = b; it; it = it->next) {
        if (CL_ContainsHideset(a, b->name)) {
            CL_Hideset *hideset = CPP_CreateHideset(arena, b->name);
            CL_SLL_QUEUE_ADD(first, last, hideset);
        }
    }
    return first;
}

CPP_API_FUNCTION CL_Tokens CPP_ConsumePreprocTokens(CL_Tokens *it) {
    CL_Tokens result = *it;
    result.count = 0;
    for (;;) {
        CL_Token *token = CL_Get(it);
        if ((token->flags & CL_INSIDE_OF_MACRO) && CL_IsMacro(token->kind) == false) {
            CL_Next(it);
            result.count += 1;
        }
        else break;
    }
    return result;
}

CPP_API_FUNCTION CL_Tokens CPP_EatMacroCallParam(CL_Tokens *it) {
    CL_Tokens result = *it;
    result.count = 0;
    int paren_counter = 0;
    for (;;) {
        CL_Token *t = CL_Get(it);
        if (t->kind == CL_EOF) {
            break;
        }
        if (t->kind == CL_OPENPAREN) {
            paren_counter += 1;
        }
        if (paren_counter == 0 && t->kind == CL_COMMA) {
            break;
        }
        if (paren_counter == 0 && t->kind == CL_CLOSEPAREN) {
            break;
        }
        if (t->kind == CL_CLOSEPAREN) {
            paren_counter -= 1;
        }
        CL_Next(it);
        result.count += 1;
    }
    return result;
}

CPP_API_FUNCTION void CPP_SkipPreprocIf(CL_Tokens *it) {
    // Any #ifs inside of the conditional block need to be ended with #endif.
    // #elses we can skip, but the #if and #endif counts how to match.
    int inner_if_count = 0;
    for (;;) {
        CL_Token *t = CL_Get(it);
        if (t->kind == CL_EOF) {
            break;
        }
        if (t->kind == CL_PREPROC_IF || t->kind == CL_PREPROC_IFNDEF || t->kind == CL_PREPROC_IFDEF) {
            inner_if_count += 1;
        }
        if (t->kind == CL_PREPROC_ENDIF || t->kind == CL_PREPROC_ELIF || t->kind == CL_PREPROC_ELSE) {
            if (inner_if_count == 0) {
                break;
            }
        }
        if (t->kind == CL_PREPROC_ENDIF) {
            inner_if_count -= 1;
        }
        CL_Next(it);
    }
}

CPP_PRIVATE_FUNCTION CL_Tokens CPP_CopyTokens(CL_Arena *arena, CL_Tokens tokens) {
    CL_Tokens result = tokens;
    result.data = CL_PushArray(arena, CL_Token, tokens.count);
    CL__MemoryCopy(result.data, tokens.data, tokens.count * sizeof(CL_Token));
    return result;
}

CPP_PRIVATE_FUNCTION void CPP_AddHideset(CL_Arena *arena, CL_Tokens tokens, CL_Hideset *hideset, char *additional_hideset_name) {
    for (int i = 0; i < tokens.count; i += 1) {
        CL_Token *it = tokens.data + i;
        it->hideset = CPP_CopyHideset(arena, hideset);
        CL_Hideset *additional = CPP_CreateHideset(arena, additional_hideset_name);
        CPP_SLL_STACK_ADD(it->hideset, additional);
    }
}

CPP_API_FUNCTION CL_Tokens CPP__PreprocTokens(CL_Arena *arena, CPP_PreprocResult *ctx, CL_Tokens *it);
CPP_API_FUNCTION int CPP_EvalPreprocConstExpr(CPP_PreprocResult *ctx, CL_Tokens tokens) {
    MA_Checkpoint scratch = MA_Save(ctx->arena.macro_token);

    // Const expr eval :=
    // 1. Expand defined()
    // 2. Preprocess
    // 3. Replace remaining identifiers with 0 numbers
    // 4. Parse
    // 5. Evaluate
    CL_Token *save = tokens.data;
    CL_Tokens *it = &tokens;
    CL_Tokens tokens_with_expanded_defined = CL_ZeroStruct();
    for (;;) {
        CL_Token *token = CL_Next(it);
        if (token->kind == CL_IDENTIFIER && CL_StringsAreEqual(token->str, token->len, "defined", 7)) {
            CL_Token *open_paren = CL_Match(it, CL_OPENPAREN);
            CL_Token *ident = CL_Next(it);
            if (CL_IsKeywordOrIdent(ident->kind) == false) {
                CPP_ReportError(ctx, 0, ident, "Expected a macro name");
                return 0;
            }

            void *is_defined = CPP_LookupI(ctx->defines, ident->intern);
            token->kind = CL_INT;
            token->u64 = is_defined == 0 ? 0 : 1;

            if (open_paren && !CL_Match(it, CL_CLOSEPAREN)) {
                CPP_ReportError(ctx, 0, ident, "Expected ')' close parenthesis");
                return 0;
            }
        }

        if (token->kind == CL_EOF) break;
        if (token) CL_AddTokenEx(scratch.arena, &tokens_with_expanded_defined, token);
    }

    CL_Tokens *temp = &tokens_with_expanded_defined;
    CL_Tokens preprocessed_tokens = CPP__PreprocTokens(scratch.arena, ctx, temp);
    for (int i = 0; i < preprocessed_tokens.count; i += 1) {
        CL_Token *it = preprocessed_tokens.data + i;
        if (CL_IsKeywordOrIdent(it->kind)) {
            it->kind = CL_INT;
            it->u64 = 0;
        }
    }

    int result = CPP_EvalExpr(ctx, preprocessed_tokens);
    MA_Load(scratch);
    return result;
}

CPP_API_FUNCTION CL_Tokens CPP__PreprocTokens(CL_Arena *arena, CPP_PreprocResult *ctx, CL_Tokens *it) {
    bool error_occured = false;
    CL_Tokens result = CL_ZeroStruct();
    for (;;) {
        CL_Token *token = CL_Next(it);
        switch (token->kind) {
            case CL_EOF: goto end_of_loop; break;
            case CL_PREPROC_DEFINE: {

                CPP_Macro *macro = CPP_CreateMacro(ctx->arena.other, CPP_MACRO_DEFINE);
                CL_Token *name = CL_Get(it);
                if (CL_IsKeywordOrIdent(name->kind) == false) {
                    CPP_REPORT_ERROR(ctx, &macro, CL_Get(it), "Failed to parse macro definition, identifier is required");
                }
                else {
                    CL_Next(it);
                    macro->defn.name = name->intern;
                }

                // idea: we insert before parsing everything so that we have an error node in the
                // table instead of undefined

                // @todo: Allow for multiple definitions and issue warning
                CPP_HashEntry *result = CPP_InsertI(ctx->defines, macro->defn.name, (void *)macro);
                if (result == 0) {
                    CPP_REPORT_ERROR(ctx, &macro, name, "Macro with name %s was already defined", name->intern);
                }

                // PARSE MACRO ARGUMENTS START
                // #define MACRO(we, are, here)
                CL_Token *open_paren = CL_Get(it);
                bool whitespace_before_paren = (open_paren->flags & CL_WHITESPACE_BEFORE_TOKEN);
                if ((open_paren->kind == CL_OPENPAREN) && whitespace_before_paren == false) {
                    CL_Next(it);
                    macro->defn.function_like = true;
                    if (!CL_Match(it, CL_CLOSEPAREN)) {
                        for (;;) {
                            CL_Token *ident = CL_Next(it);
                            if (ident->kind == CL_THREEDOTS) {
                                macro->defn.vargs = true;
                                break;
                            }
                            if (CL_IsKeywordOrIdent(ident->kind) == false) {
                                CPP_REPORT_ERROR(ctx, &macro, ident, "Failed to parse macro definition, invalid token inside of function like macro argument");
                            }
                            CL_AddTokenEx(ctx->arena.macro_token, &macro->defn.args, ident);
                            if (!CL_Match(it, CL_COMMA)) {
                                break;
                            }
                        }
                        if (!CL_Match(it, CL_CLOSEPAREN)) {
                            CPP_REPORT_ERROR(ctx, &macro, CL_Get(it), "Failed to parse macro definition, unclosed parenthesis '('");
                        }
                    }
                }
                // PARSE MACRO ARGUMENTS END

                macro->defn.body = CPP_ConsumePreprocTokens(it);

            } break;

            case CL_PREPROC_IFNDEF:
            case CL_PREPROC_IFDEF: {
                CPP_Macro *macro = CPP_CreateMacro(ctx->arena.other, CPP_MACRO_IF);
                CL_Token *name = CL_Next(it);
                if (CL_IsKeywordOrIdent(name->kind) == false) {
                    CPP_REPORT_ERROR(ctx, &macro, name, "Failed to parse macro definition, expected an identifier after #ifdef");
                }

                char *ident = name->intern;
                CPP_Macro *definition = (CPP_Macro *)CPP_LookupI(ctx->defines, ident);
                CPP_SLL_STACK_ADD(ctx->if_stack, macro);

                if ((definition && token->kind == CL_PREPROC_IFDEF) || (definition == 0 && token->kind == CL_PREPROC_IFNDEF)) {
                    macro->ifn.evaluation = 1;
                }
                if (macro->ifn.evaluation == 0) {
                    CPP_SkipPreprocIf(it);
                }
            } break;

            case CL_PREPROC_IF: {
                CL_Tokens tokens = CPP_ConsumePreprocTokens(it);
                int result = CPP_EvalPreprocConstExpr(ctx, tokens);
                CPP_Macro *macro = CPP_CreateMacro(ctx->arena.other, CPP_MACRO_IF);
                macro->ifn.evaluation = result;
                CPP_SLL_STACK_ADD(ctx->if_stack, macro);
                if (macro->ifn.evaluation == 0) CPP_SkipPreprocIf(it);
            } break;

            case CL_PREPROC_ELSE: {
                CPP_Macro *macro = ctx->if_stack;
                if (macro == 0) {
                    CPP_REPORT_ERROR(ctx, &macro, token, "Stumbled across a stray #else directive");
                }

                macro->kind = CPP_MACRO_ELSE;
                if (macro->ifn.evaluation) {
                    CPP_SkipPreprocIf(it);
                }
            } break;

            case CL_PREPROC_ELIF: {
                CPP_Macro *macro = ctx->if_stack;
                if (macro == 0) {
                    CPP_REPORT_ERROR(ctx, &macro, token, "Stumbled across a stray #elif directive");
                }

                if (macro->kind == CPP_MACRO_ELSE) {
                    CPP_REPORT_ERROR(ctx, &macro, token, "Stumbled across #elif after #else directive");
                }

                macro->kind = CPP_MACRO_ELIF;
                if (macro->ifn.evaluation == 1) {
                    CPP_SkipPreprocIf(it);
                }
                else {
                    CL_Tokens tokens = CPP_ConsumePreprocTokens(it);
                    int result = CPP_EvalPreprocConstExpr(ctx, tokens);
                    macro->ifn.evaluation = result;
                    if (macro->ifn.evaluation == 0) CPP_SkipPreprocIf(it);
                }
            } break;

            case CL_PREPROC_ENDIF: {
                CPP_Macro *macro = 0;
                CPP_SLL_STACK_POP_AND_STORE(ctx->if_stack, macro);
                if (macro == 0) {
                    CPP_REPORT_ERROR(ctx, &macro, token, "Stumbled across a stray #endif directive");
                }
            } break;

            case CL_PREPROC_INCLUDE: {
                MA_Checkpoint scratch = MA_Save(ctx->arena.macro_token);
                if (scratch.arena == arena) scratch = MA_Save(ctx->arena.scratch2);

                bool is_sys_inc = token->flags & CL_SYSTEM_INCLUDE;
                char *resolved_filename = CL_ResolveFilepath(scratch.arena, &ctx->lex_list.search_paths, token->str, token->file, is_sys_inc);
                if (!resolved_filename) {
                    CPP_REPORT_ERROR(ctx, 0, token, "Failed to expand include: %s, Couldn't find file", token->str);
                }
                CL_LexResult *lex = CL_GetFile(&ctx->lex_list, resolved_filename);
                if (!lex) {
                    CPP_REPORT_ERROR(ctx, 0, token, "Failed to expand include: %s, File is not included in LexList", token->str);
                }

#if 0
// This is wrong because first file is not included in pragma once list
                // Check if pragma once
                if (lex->tokens.count >= 2) {
                    CL_Token *it = lex->tokens.data;
                    if (it[0].kind == CL_PREPROC_PRAGMA && CL_IsIdentifier(it + 1, "once")) {

                        bool found = false;
                        for (int pragma_it = 0; pragma_it < ctx->pragma_onced_count; pragma_it += 1) {
                            CL_LexResult *it = ctx->pragma_onced[pragma_it];
                            if (it == lex) {
                                found = true;
                                break;
                            }
                        }

                        if (!found) {
                            if (ctx->pragma_onced_count < ctx->lex_list.count) {
                                ctx->pragma_onced[ctx->pragma_onced_count++] = lex;
                            }
                        }
                        else {
                            CPP_ReportWarning(ctx, token, "Pragma once, skipping inclusion ... ");
                            goto skip_inclusion;
                        }
                    }
                }
#endif

                CL_Tokens tokens = CPP__PreprocTokens(arena, ctx, &lex->tokens);
                if (result.data == 0) result = tokens;
                else result.count += tokens.count;

                MA_Load(scratch);
            } break;

            case CL_PREPROC_PRAGMA: {
                CL_Tokens tokens = CPP_ConsumePreprocTokens(it);
                if (!(tokens.count == 1 && CL_IsIdentifier(tokens.data, "once"))) {
                    CPP_ReportWarning(ctx, token, "Unkown pragma, skipping ... ");
                }
            } break;

            case CL_PREPROC_ERROR: {
                CL_Tokens tokens = CPP_ConsumePreprocTokens(it);
                CL_Token *first_token = tokens.data;
                CL_Token *last_token = tokens.data + tokens.count - 1;
                if (tokens.count == 0) CPP_ReportError(ctx, 0, token, "Empty #error directive");
                else {
                    int64_t len64 = (last_token->str - first_token->str) + last_token->len;
                    int len = (int)len64;
                    CPP_ReportError(ctx, 0, token, "%.*s", len, first_token->str);
                }
            } break;

            case CL_PREPROC_UNDEF: {
                CL_Token *token = CL_Next(it);
                if (CL_IsKeywordOrIdent(token->kind) == false) {
                    CPP_REPORT_ERROR(ctx, 0, token, "Invalid #undef syntax, expected an identifier");
                }
                CPP_RemoveI(ctx->defines, token->intern);
                // Nothing to do here ~
                // It doesn't matter if it's defined or not
            } break;

            case CL_PREPROC_NULL: {
                // Nothing to do ~
                // '#' single hash without directive
            } break;

            case CL_PREPROC_STRINGIFY: {                                 // NOT TESTED
                MA_Checkpoint scratch = MA_Save(ctx->arena.macro_token); // @todo: dependency
                if (scratch.arena == arena) scratch = MA_Save(ctx->arena.scratch2);

                CL_Token *ident = CL_PushStruct(scratch.arena, CL_Token);
                *ident = *token;
                ident->kind = CL_IDENTIFIER;

                CL_Tokens tokens = {ident, 1};
                CL_Tokens result = CPP__PreprocTokens(scratch.arena, ctx, &tokens);

                CL_Token *string = 0;
                if (result.count) {
                    string = CL_PushStruct(scratch.arena, CL_Token);
                    *string = *token;

                    string->kind = CL_STRINGLIT;

                    CL_Token *last = result.data + result.count - 1;
                    CL_Token *first = result.data;
                    string->str = first->str;
                    string->len = (int)((last->str - first->str) + last->len);
                }

                CL_AddTokenEx(arena, &result, string);
                MA_Load(scratch);
            } break;

            default: {
                bool add_token = true;

                // @todo:
                // - What to do with arenas ??
                // - There is a bug where EOF end up in tokens buffer probably from expansion

                CPP_Macro *macro = (CPP_Macro *)CPP_LookupI(ctx->defines, token->intern);
                if (macro) {
                    if (!CL_ContainsHideset(token->hideset, macro->defn.name)) {
                        MA_Checkpoint scratch = MA_Save(ctx->arena.macro_token); // @todo: dependency
                        if (scratch.arena == arena) scratch = MA_Save(ctx->arena.scratch2);

                        CL_Token *open_paren = CL_Get(it);
                        if (macro->defn.function_like && open_paren->kind == CL_OPENPAREN) {
                            CL_Next(it);

                            //
                            // Read macro call params
                            //
                            CL_Tokens *call_params = CL_PushArray(scratch.arena, CL_Tokens, macro->defn.args.count);
                            int call_param_i = 0;
                            if (!CL_Match(it, CL_CLOSEPAREN)) {
                                for (;;) {
                                    CL_Tokens param = CPP_EatMacroCallParam(it);
                                    if (param.count == 0) {
                                        CPP_REPORT_ERROR(ctx, &macro, CL_Get(it), "Empty macro argument is not allowed!");
                                    }
                                    if (call_param_i >= macro->defn.args.count) {
                                        CPP_REPORT_ERROR(ctx, &macro, CL_Get(it), "Too many arguments in macro invocation");
                                    }
                                    call_params[call_param_i++] = param;
                                    if (!CL_Match(it, CL_COMMA)) {
                                        break;
                                    }
                                }
                                if (!CL_Match(it, CL_CLOSEPAREN)) {
                                    CPP_REPORT_ERROR(ctx, &macro, CL_Get(it), "Failed to parse macro call, unclosed parenthesis '('");
                                }
                            }

                            if (call_param_i != macro->defn.args.count) {
                                CPP_REPORT_ERROR(ctx, &macro, CL_Get(it), "Not enough arguments in macro invocation");
                            }

                            //
                            // Expand params inside the macro body
                            //
                            CL_Tokens body1 = CL_ZeroStruct(); // body with expanded params
                            // token->hideset, macro->defn.name
                            CL_Tokens body0 = CPP_CopyTokens(scratch.arena, macro->defn.body);
                            CL_Tokens args = macro->defn.args;
                            for (int body_i = 0; body_i < body0.count; body_i += 1) {
                                CL_Token *body_it = body0.data + body_i;

                                // Match current token with argument and expand corresponding list of
                                // tokens found in the macro call invocation
                                bool expanded = false;
                                for (int arg_i = 0; arg_i < args.count; arg_i += 1) {
                                    CL_Token *arg_it = args.data + arg_i;
                                    CL_ASSERT(CL_IsKeywordOrIdent(arg_it->kind));
                                    if (CL_IsKeywordOrIdent(body_it->kind) && body_it->intern == arg_it->intern) {
                                        CL_Tokens call_param = call_params[arg_i];
                                        CL_AddTokenList(scratch.arena, &body1, &call_param);
                                        expanded = true;
                                        break;
                                    }
                                }

                                if (!expanded) CL_AddTokenEx(scratch.arena, &body1, body_it);
                            }

                            // Took this from chibicc source code, no idea what's going on here
                            // in rui314 we believe.
                            CL_Hideset *hideset = CPP_HidesetIntersection(ctx->arena.other, token->hideset, open_paren->hideset);
                            CPP_AddHideset(ctx->arena.other, body1, token->hideset, macro->defn.name);
                            CL_Tokens tokens = CPP__PreprocTokens(scratch.arena, ctx, &body1);

                            CL_AddTokenList(arena, &result, &tokens);
                            add_token = false;
                        }

                        else {
                            CL_Tokens body = CPP_CopyTokens(scratch.arena, macro->defn.body);
                            CPP_AddHideset(ctx->arena.other, macro->defn.body, token->hideset, macro->defn.name);
                            CL_Tokens tokens = CPP__PreprocTokens(scratch.arena, ctx, &body);
                            CL_AddTokenList(arena, &result, &tokens);
                            add_token = false;
                        }

                        MA_Load(scratch);
                    }
                }

                if (add_token) {
                    CL_AddTokenEx(arena, &result, token);
                }
            }
        }
    end_of_iteration:;
        if (error_occured) {
            error_occured = false;
            CPP_ConsumePreprocTokens(it);
        }
    }
end_of_loop:;
    return result;
}

CPP_API_FUNCTION CPP_PreprocResult CPP_MakePreprocResult(CL_ArenaTuple *arena) {
    CPP_PreprocResult result = CL_ZeroStruct();
    result.arena = *arena;
    result.defines = CPP_CreateHashTable(arena->other, 512);
    return result;
}

CPP_API_FUNCTION CPP_PreprocResult CPP_PreprocTokens(CL_ArenaTuple *arena, CL_Tokens tokens) {
    CPP_PreprocResult result = CPP_MakePreprocResult(arena);
    result.tokens = CPP__PreprocTokens(arena->token, &result, &tokens);
    return result;
}

CPP_API_FUNCTION CPP_PreprocResult CPP_PreprocFiles(CL_ArenaTuple *arena, CL_LexList list) {
    CPP_PreprocResult result = CPP_MakePreprocResult(arena);
    result.pragma_onced = CL_PushArray(arena->other, CL_LexResult *, list.count);
    result.lex_list = list;
    CL_Tokens tokens = list.first_result->tokens;
    result.tokens = CPP__PreprocTokens(arena->token, &result, &tokens);
    return result;
}

#endif // CPP_IMPLEMENTATION