/*
4coder_config.cpp - Parsing *.4coder files.
*/

// TOP

////////////////////////////////
// NOTE(allen): Config Search List

function void
def_search_normal_load_list(Arena *arena, String8List *list){
    Variable_Handle prj_var = vars_read_key(vars_get_root(), vars_save_string_lit("prj_config"));
    String_Const_u8 prj_dir = prj_path_from_project(arena, prj_var);
    if (prj_dir.size > 0){
        string_list_push(arena, list, prj_dir);
    }
    def_search_list_add_system_path(arena, list, SystemPath_UserDirectory);
    def_search_list_add_system_path(arena, list, SystemPath_Binary);
}

function String8
def_search_normal_full_path(Arena *arena, String8 relative){
    String8List list = {};
    def_search_normal_load_list(arena, &list);
    String8 result = def_search_get_full_path(arena, &list, relative);
    return(result);
}

function FILE*
def_search_normal_fopen(Arena *arena, char *file_name, char *opt){
    Temp_Memory_Block block(arena);
    String8List list = {};
    def_search_normal_load_list(arena, &list);
    FILE *file = def_search_fopen(arena, &list, file_name, opt);
    return(file);
}

////////////////////////////////
// NOTE(allen): Extension List

function String_Const_u8_Array
parse_extension_line_to_extension_list(Arena *arena, String_Const_u8 str){
    i32 count = 0;
    for (u64 i = 0; i < str.size; i += 1){
        if (str.str[i] == '.'){
            count += 1;
        }
    }
    
    String_Const_u8_Array array = {};
    array.count = count;
    array.strings = push_array(arena, String_Const_u8, count);
    
    push_align(arena, 1);
    str = string_skip(str, string_find_first(str, '.') + 1);
    for (i32 i = 0; i < count; i += 1){
        u64 next_period = string_find_first(str, '.');
        String_Const_u8 extension = string_prefix(str, next_period);
        str = string_skip(str, next_period + 1);
        array.strings[i] = push_string_copy(arena, extension);
    }
    push_align(arena, 8);
    
    return(array);
}


function String_Const_u8_Array
parse_extension_line_to_extension_list(Application_Links *app, Arena *arena, String_Const_u8 str){
    ProfileScope(app, "parse extension line to extension list");
    return parse_extension_line_to_extension_list(arena, str);
}

////////////////////////////////
// NOTE(allen): Token Array

function Token_Array
token_array_from_text(Application_Links *app, Arena *arena, String_Const_u8 data){
    ProfileScope(app, "token array from text");
    Token_List list = lex_full_input_cpp(arena, data);
    return(token_array_from_list(arena, &list));
}

////////////////////////////////
// NOTE(allen): Built in Mapping

function void
setup_built_in_mapping(Application_Links *app, String_Const_u8 name, Mapping *mapping, i64 global_id, i64 file_id, i64 code_id){
    Thread_Context *tctx = get_thread_context(app);
    if (string_match(name, string_u8_litexpr("default"))){
        mapping_release(tctx, mapping);
        mapping_init(tctx, mapping);
        setup_default_mapping(mapping, global_id, file_id, code_id);
    }
    else if (string_match(name, string_u8_litexpr("mac-default"))){
        mapping_release(tctx, mapping);
        mapping_init(tctx, mapping);
        setup_mac_mapping(mapping, global_id, file_id, code_id);
    }
    else if (string_match(name, string_u8_litexpr("choose"))){
        mapping_release(tctx, mapping);
        mapping_init(tctx, mapping);
#if OS_MAC
        setup_mac_mapping(mapping, global_id, file_id, code_id);
#else
        setup_default_mapping(mapping, global_id, file_id, code_id);
#endif
    }
}

////////////////////////////////
// NOTE(allen): Errors

function Error_Location
get_error_location(Application_Links *app, u8 *base, u8 *pos){
    ProfileScope(app, "get error location");
    Error_Location location = {};
    location.line_number = 1;
    location.column_number = 1;
    for (u8 *ptr = base;
         ptr < pos;
         ptr += 1){
        if (*ptr == '\n'){
            location.line_number += 1;
            location.column_number = 1;
        }
        else{
            location.column_number += 1;
        }
    }
    return(location);
}

function String_Const_u8
config_stringize_errors(Application_Links *app, Arena *arena, Config *parsed){
    ProfileScope(app, "stringize errors");
    String_Const_u8 result = {};
    if (parsed->errors.first != 0){
        List_String_Const_u8 list = {};
        for (Config_Error *error = parsed->errors.first;
             error != 0;
             error = error->next){
            Error_Location location = get_error_location(app, parsed->data.str, error->pos);
            string_list_pushf(arena, &list, "%.*s:%d:%d: %.*s\n",
                              string_expand(error->file_name), location.line_number, location.column_number, string_expand(error->text));
        }
        result = string_list_flatten(arena, list);
    }
    return(result);
}

////////////////////////////////
// NOTE(allen): Parser

function Config_Parser
def_config_parser_init(Arena *arena, String_Const_u8 file_name, String_Const_u8 data, Token_Array array){
    Config_Parser ctx = {};
    ctx.token = array.tokens - 1;
    ctx.opl = array.tokens + array.count;
    ctx.file_name = file_name;
    ctx.data = data;
    ctx.arena = arena;
    def_config_parser_inc(&ctx);
    return(ctx);
}

function void
def_config_parser_inc(Config_Parser *ctx){
    Token *t = ctx->token;
    Token *opl = ctx->opl;
    for (t += 1;
         t < opl && (t->kind == TokenBaseKind_Comment ||
                     t->kind == TokenBaseKind_Whitespace);
         t += 1);
    ctx->token = t;
}

function u8*
def_config_parser_get_pos(Config_Parser *ctx){
    return(ctx->data.str + ctx->token->pos);
}

function b32
def_config_parser_recognize_base_kind(Config_Parser *ctx, Token_Base_Kind kind){
    b32 result = false;
    if (ctx->token < ctx->opl){
        result = (ctx->token->kind == kind);
    }
    else if (kind == TokenBaseKind_EOF){
        result = true;
    }
    return(result);
}

function b32
def_config_parser_recognize_cpp_kind(Config_Parser *ctx, Token_Cpp_Kind kind){
    b32 result = false;
    if (ctx->token < ctx->opl){
        result = (ctx->token->sub_kind == kind);
    }
    else if (kind == TokenCppKind_EOF){
        result = true;
    }
    return(result);
}

function b32
def_config_parser_recognize_boolean(Config_Parser *ctx){
    b32 result = false;
    Token *token = ctx->token;
    if (ctx->token < ctx->opl){
        result = (token->sub_kind == TokenCppKind_LiteralTrue ||
                  token->sub_kind == TokenCppKind_LiteralFalse);
    }
    return(result);
}

function b32
def_config_parser_recognize_text(Config_Parser *ctx, String_Const_u8 text){
    String_Const_u8 lexeme = def_config_parser_get_lexeme(ctx);
    return(lexeme.str != 0 && string_match(lexeme, text));
}

function b32
def_config_parser_match_cpp_kind(Config_Parser *ctx, Token_Cpp_Kind kind){
    b32 result = def_config_parser_recognize_cpp_kind(ctx, kind);
    if (result){
        def_config_parser_inc(ctx);
    }
    return(result);
}

function b32
def_config_parser_match_text(Config_Parser *ctx, String_Const_u8 text){
    b32 result = def_config_parser_recognize_text(ctx, text);
    if (result){
        def_config_parser_inc(ctx);
    }
    return(result);
}

function String_Const_u8
def_config_parser_get_lexeme(Config_Parser *ctx){
    String_Const_u8 lexeme = {};
    Token *token = ctx->token;
    if (token < ctx->opl){
        lexeme = SCu8(ctx->data.str + token->pos, token->size);
    }
    return(lexeme);
}

function Config_Integer
def_config_parser_get_int(Config_Parser *ctx){
    Config_Integer config_integer = {};
    String_Const_u8 str = def_config_parser_get_lexeme(ctx);
    if (string_match(string_prefix(str, 2), string_u8_litexpr("0x"))){
        config_integer.is_signed = false;
        config_integer.uinteger = (u32)(string_to_integer(string_skip(str, 2), 16));
    }
    else{
        b32 is_negative = (string_get_character(str, 0) == '-');
        if (is_negative){
            str = string_skip(str, 1);
        }
        config_integer.is_signed = true;
        config_integer.integer = (i32)(string_to_integer(str, 10));
        if (is_negative){
            config_integer.integer *= -1;
        }
    }
    return(config_integer);
}

function b32
def_config_parser_get_boolean(Config_Parser *ctx){
    String_Const_u8 str = def_config_parser_get_lexeme(ctx);
    return(string_match(str, string_u8_litexpr("true")));
}

function Config*
def_config_parser_top(Config_Parser *ctx){
    i32 *version = def_config_parser_version(ctx);
    
    Config_Assignment *first = 0;
    Config_Assignment *last = 0;
    i32 count = 0;
    for (;!def_config_parser_recognize_cpp_kind(ctx, TokenCppKind_EOF);){
        Config_Assignment *assignment = def_config_parser_assignment(ctx);
        if (assignment != 0){
            zdll_push_back(first, last, assignment);
            count += 1;
        }
    }
    
    Config *config = push_array(ctx->arena, Config, 1);
    block_zero_struct(config);
    config->version = version;
    config->first = first;
    config->last = last;
    config->count = count;
    config->errors = ctx->errors;
    config->file_name = ctx->file_name;
    config->data = ctx->data;
    return(config);
}

function i32*
def_config_parser_version(Config_Parser *ctx){
    require(def_config_parser_match_text(ctx, str8_lit("version")));
    
    if (!def_config_parser_match_cpp_kind(ctx, TokenCppKind_ParenOp)){
        def_config_parser_push_error_here(ctx, "expected token '(' for version specifier: 'version(#)'");
        def_config_parser_recover(ctx);
        return(0);
    }
    
    if (!def_config_parser_recognize_base_kind(ctx, TokenBaseKind_LiteralInteger)){
        def_config_parser_push_error_here(ctx, "expected an integer constant for version specifier: 'version(#)'");
        def_config_parser_recover(ctx);
        return(0);
    }
    
    Config_Integer value = def_config_parser_get_int(ctx);
    def_config_parser_inc(ctx);
    
    if (!def_config_parser_match_cpp_kind(ctx, TokenCppKind_ParenCl)){
        def_config_parser_push_error_here(ctx, "expected token ')' for version specifier: 'version(#)'");
        def_config_parser_recover(ctx);
        return(0);
    }
    
    if (!def_config_parser_match_cpp_kind(ctx, TokenCppKind_Semicolon)){
        def_config_parser_push_error_here(ctx, "expected token ';' for version specifier: 'version(#)'");
        def_config_parser_recover(ctx);
        return(0);
    }
    
    i32 *ptr = push_array(ctx->arena, i32, 1);
    *ptr = value.integer;
    return(ptr);
}

function Config_Assignment*
def_config_parser_assignment(Config_Parser *ctx){
    u8 *pos = def_config_parser_get_pos(ctx);
    
    Config_LValue *l = def_config_parser_lvalue(ctx);
    if (l == 0){
        def_config_parser_push_error_here(ctx, "expected an l-value; l-value formats: 'identifier', 'identifier[#]'");
        def_config_parser_recover(ctx);
        return(0);
    }
    
    if (!def_config_parser_match_cpp_kind(ctx, TokenCppKind_Eq)){
        def_config_parser_push_error_here(ctx, "expected token '=' for assignment: 'l-value = r-value;'");
        def_config_parser_recover(ctx);
        return(0);
    }
    
    Config_RValue *r = def_config_parser_rvalue(ctx);
    if (r == 0){
        def_config_parser_push_error_here(ctx, "expected an r-value; r-value formats:\n"
                                          "\tconstants (true, false, integers, hexadecimal integers, strings, characters)\n"
                                          "\tany l-value that is set in the file\n"
                                          "\tcompound: '{ compound-element, compound-element, compound-element ...}'\n"
                                          "\ta compound-element is an r-value, and can have a layout specifier\n"
                                          "\tcompound-element with layout specifier: .name = r-value, .integer = r-value");
        def_config_parser_recover(ctx);
        return(0);
    }
    
    if (!def_config_parser_match_cpp_kind(ctx, TokenCppKind_Semicolon)){
        def_config_parser_push_error_here(ctx, "expected token ';' for assignment: 'l-value = r-value;'");
        def_config_parser_recover(ctx);
        return(0);
    }
    
    Config_Assignment *assignment = push_array_zero(ctx->arena, Config_Assignment, 1);
    assignment->pos = pos;
    assignment->l = l;
    assignment->r = r;
    return(assignment);
}

function Config_LValue*
def_config_parser_lvalue(Config_Parser *ctx){
    require(def_config_parser_recognize_cpp_kind(ctx, TokenCppKind_Identifier));
    String_Const_u8 identifier = def_config_parser_get_lexeme(ctx);
    def_config_parser_inc(ctx);
    
    i32 index = 0;
    if (def_config_parser_match_cpp_kind(ctx, TokenCppKind_BrackOp)){
        require(def_config_parser_recognize_base_kind(ctx, TokenBaseKind_LiteralInteger));
        Config_Integer value = def_config_parser_get_int(ctx);
        index = value.integer;
        def_config_parser_inc(ctx);
        require(def_config_parser_match_cpp_kind(ctx, TokenCppKind_BrackCl));
    }
    
    Config_LValue *lvalue = push_array_zero(ctx->arena, Config_LValue, 1);
    lvalue->identifier = identifier;
    lvalue->index = index;
    return(lvalue);
}

function Config_RValue*
def_config_parser_rvalue(Config_Parser *ctx){
    Config_RValue *rvalue = 0;
    if (def_config_parser_recognize_cpp_kind(ctx, TokenCppKind_Identifier)){
        Config_LValue *l = def_config_parser_lvalue(ctx);
        require(l != 0);
        rvalue = push_array_zero(ctx->arena, Config_RValue, 1);
        rvalue->type = ConfigRValueType_LValue;
        rvalue->lvalue = l;
    }
    else if (def_config_parser_recognize_cpp_kind(ctx, TokenCppKind_BraceOp)){
        def_config_parser_inc(ctx);
        Config_Compound *compound = def_config_parser_compound(ctx);
        require(compound != 0);
        rvalue = push_array_zero(ctx->arena, Config_RValue, 1);
        rvalue->type = ConfigRValueType_Compound;
        rvalue->compound = compound;
    }
    else if (def_config_parser_recognize_boolean(ctx)){
        b32 b = def_config_parser_get_boolean(ctx);
        def_config_parser_inc(ctx);
        rvalue = push_array_zero(ctx->arena, Config_RValue, 1);
        rvalue->type = ConfigRValueType_Boolean;
        rvalue->boolean = b;
    }
    else if (def_config_parser_recognize_base_kind(ctx, TokenBaseKind_LiteralInteger)){
        Config_Integer value = def_config_parser_get_int(ctx);
        def_config_parser_inc(ctx);
        rvalue = push_array_zero(ctx->arena, Config_RValue, 1);
        rvalue->type = ConfigRValueType_Integer;
        if (value.is_signed){
            rvalue->integer = value.integer;
        }
        else{
            rvalue->uinteger = value.uinteger;
        }
    }
    else if (def_config_parser_recognize_cpp_kind(ctx, TokenCppKind_LiteralString)){
        String_Const_u8 s = def_config_parser_get_lexeme(ctx);
        def_config_parser_inc(ctx);
        s = string_chop(string_skip(s, 1), 1);
        String_Const_u8 interpreted = string_interpret_escapes(ctx->arena, s);
        rvalue = push_array_zero(ctx->arena, Config_RValue, 1);
        rvalue->type = ConfigRValueType_String;
        rvalue->string = interpreted;
    }
    return(rvalue);
}

function void
config_parser__compound__check(Config_Parser *ctx, Config_Compound *compound){
    b32 implicit_index_allowed = true;
    for (Config_Compound_Element *node = compound->first;
         node != 0;
         node = node->next){
        if (node->l.type != ConfigLayoutType_Unset){
            implicit_index_allowed = false;
        }
        else if (!implicit_index_allowed){
            def_config_parser_push_error(ctx, node->l.pos,
                                         "encountered unlabeled member after one or more labeled members");
        }
    }
}

function Config_Compound*
def_config_parser_compound(Config_Parser *ctx){
    Config_Compound_Element *first = 0;
    Config_Compound_Element *last = 0;
    i32 count = 0;
    
    Config_Compound_Element *element = def_config_parser_element(ctx);
    require(element != 0);
    zdll_push_back(first, last, element);
    count += 1;
    
    for (;def_config_parser_match_cpp_kind(ctx, TokenCppKind_Comma);){
        if (def_config_parser_recognize_cpp_kind(ctx, TokenCppKind_BraceCl)){
            break;
        }
        element = def_config_parser_element(ctx);
        require(element != 0);
        zdll_push_back(first, last, element);
        count += 1;
    }
    
    require(def_config_parser_match_cpp_kind(ctx, TokenCppKind_BraceCl));
    
    Config_Compound *compound = push_array(ctx->arena, Config_Compound, 1);
    block_zero_struct(compound);
    compound->first = first;
    compound->last = last;
    compound->count = count;
    config_parser__compound__check(ctx, compound);
    return(compound);
}

function Config_Compound_Element*
def_config_parser_element(Config_Parser *ctx){
    Config_Layout layout = {};
    layout.pos = def_config_parser_get_pos(ctx);
    if (def_config_parser_match_cpp_kind(ctx, TokenCppKind_Dot)){
        if (def_config_parser_recognize_cpp_kind(ctx, TokenCppKind_Identifier)){
            layout.type = ConfigLayoutType_Identifier;
            layout.identifier = def_config_parser_get_lexeme(ctx);
            def_config_parser_inc(ctx);
        }
        else if (def_config_parser_recognize_base_kind(ctx, TokenBaseKind_LiteralInteger)){
            layout.type = ConfigLayoutType_Integer;
            Config_Integer value = def_config_parser_get_int(ctx);
            layout.integer = value.integer;
            def_config_parser_inc(ctx);
        }
        else{
            return(0);
        }
        require(def_config_parser_match_cpp_kind(ctx, TokenCppKind_Eq));
    }
    Config_RValue *rvalue = def_config_parser_rvalue(ctx);
    require(rvalue != 0);
    Config_Compound_Element *element = push_array(ctx->arena, Config_Compound_Element, 1);
    block_zero_struct(element);
    element->l = layout;
    element->r = rvalue;
    return(element);
}

function Config*
def_config_parse(Application_Links *app, Arena *arena, String_Const_u8 file_name, String_Const_u8 data, Token_Array array){
    ProfileScope(app, "config parse");
    Temp_Memory restore_point = begin_temp(arena);
    Config_Parser ctx = def_config_parser_init(arena, file_name, data, array);
    Config *config = def_config_parser_top(&ctx);
    if (config == 0){
        end_temp(restore_point);
    }
    return(config);
}

function Config*
def_config_from_text(Application_Links *app, Arena *arena, String_Const_u8 file_name, String_Const_u8 data){
    Config *parsed = 0;
    Temp_Memory restore_point = begin_temp(arena);
    Token_Array array = token_array_from_text(app, arena, data);
    if (array.tokens != 0){
        parsed = def_config_parse(app, arena, file_name, data, array);
        if (parsed == 0){
            end_temp(restore_point);
        }
    }
    return(parsed);
}

function Config_Error*
def_config_push_error(Arena *arena, Config_Error_List *list, String_Const_u8 file_name, u8 *pos, char *error_text){
    Config_Error *error = push_array(arena, Config_Error, 1);
    zdll_push_back(list->first, list->last, error);
    list->count += 1;
    error->file_name = file_name;
    error->pos = pos;
    error->text = push_string_copy(arena, SCu8(error_text));
    return(error);
}

function Config_Error*
def_config_push_error(Arena *arena, Config *config, u8 *pos, char *error_text){
    return(def_config_push_error(arena, &config->errors, config->file_name, pos, error_text));
}

function void
def_config_parser_push_error(Config_Parser *ctx, u8 *pos, char *error_text){
    def_config_push_error(ctx->arena, &ctx->errors, ctx->file_name, pos, error_text);
}

function void
def_config_parser_push_error_here(Config_Parser *ctx, char *error_text){
    def_config_parser_push_error(ctx, def_config_parser_get_pos(ctx), error_text);
}

function void
def_config_parser_recover(Config_Parser *ctx){
    for (;;){
        if (def_config_parser_match_cpp_kind(ctx, TokenCppKind_Semicolon)){
            break;
        }
        if (def_config_parser_recognize_cpp_kind(ctx, TokenCppKind_EOF)){
            break;
        }
        def_config_parser_inc(ctx);
    }
}


////////////////////////////////
// NOTE(allen): Dump Config to Variables

function Config_Get_Result
config_var(Config *config, String_Const_u8 var_name, i32 subscript);

function void
def_var_dump_rvalue(Application_Links *app, Config *config, Variable_Handle dst, String_ID l_value, Config_RValue *r){
    Scratch_Block scratch(app);
    
    b32 *boolean = 0;
    i32 *integer = 0;
    String_Const_u8 *string = 0;
    Config_Compound *compound = 0;
    
    Config_Get_Result get_result = {};
    
    switch (r->type){
        case ConfigRValueType_LValue:
        {
            Config_LValue *l = r->lvalue;
            if (l != 0){
                get_result = config_var(config, l->identifier, l->index);
                if (get_result.success){
                    switch (get_result.type){
                        case ConfigRValueType_Boolean:
                        {
                            boolean = &get_result.boolean;
                        }break;
                        
                        case ConfigRValueType_Integer:
                        {
                            integer = &get_result.integer;
                        }break;
                        
                        case ConfigRValueType_String:
                        {
                            string = &get_result.string;
                        }break;
                        
                        case ConfigRValueType_Compound:
                        {
                            compound = get_result.compound;
                        }break;
                    }
                }
            }
        }break;
        
        case ConfigRValueType_Boolean:
        {
            boolean = &r->boolean;
        }break;
        
        case ConfigRValueType_Integer:
        {
            integer = &r->integer;
        }break;
        
        case ConfigRValueType_String:
        {
            string = &r->string;
        }break;
        
        case ConfigRValueType_Compound:
        {
            compound = r->compound;
        }break;
    }
    
    if (boolean != 0){
        String_ID val = 0;
        if (*boolean){
            val = vars_save_string(str8_lit("true"));
        }
        else{
            val = vars_save_string(str8_lit("false"));
        }
        vars_new_variable(dst, l_value, val);
    }
    else if (integer != 0){
        // TODO(allen): signed/unsigned problem
        String_ID val = vars_save_string(push_stringf(scratch, "%d", *integer));
        vars_new_variable(dst, l_value, val);
    }
    else if (string != 0){
        String_ID val = vars_save_string(*string);
        vars_new_variable(dst, l_value, val);
    }
    else if (compound != 0){
        Variable_Handle sub_var = vars_new_variable(dst, l_value);
        
        i32 implicit_index = 0;
        b32 implicit_allowed = true;
        
        Config_Compound_Element *node = 0;
        if (compound != 0){
            node = compound->first;
        }
        for (; node != 0;
             node = node->next, implicit_index += 1){
            String_ID sub_l_value = 0;
            
            switch (node->l.type){
                case ConfigLayoutType_Unset:
                {
                    if (implicit_allowed){
                        sub_l_value = vars_save_string(push_stringf(scratch, "%d", implicit_index));
                    }
                }break;
                
                case ConfigLayoutType_Identifier:
                {
                    implicit_allowed = false;
                    sub_l_value = vars_save_string(node->l.identifier);
                }break;
                
                case ConfigLayoutType_Integer:
                {
                    implicit_allowed = false;
                    sub_l_value = vars_save_string(push_stringf(scratch, "%d", node->l.integer));
                }break;
            }
            
            if (sub_l_value != 0){
                Config_RValue *r = node->r;
                if (r != 0){
                    def_var_dump_rvalue(app, config, sub_var, sub_l_value, r);
                }
            }
        }
    }
}

function Variable_Handle
def_fill_var_from_config(Application_Links *app, Variable_Handle parent, String_ID key, Config *config){
    Variable_Handle result = vars_get_nil();
    
    if (key != 0){
        String_ID file_name_id = vars_save_string(config->file_name);
        result = vars_new_variable(parent, key, file_name_id);
        
        Variable_Handle var = result;
        
        Scratch_Block scratch(app);
        
        if (config->version != 0){
            String_ID version_key = vars_save_string(string_u8_litexpr("version"));
            String_ID version_value = vars_save_string(push_stringf(scratch, "%d", *config->version));
            vars_new_variable(parent, version_key, version_value);
        }
        
        for (Config_Assignment *node = config->first;
             node != 0;
             node = node->next){
            String_ID l_value = 0;
            Config_LValue *l = node->l;
            if (l != 0){
                String_Const_u8 string = l->identifier;
                if (l->index != 0){
                    string = push_stringf(scratch, "%.*s.%d", string_expand(string), l->index);
                }
                l_value = vars_save_string(string);
            }
            
            if (l_value != 0){
                Config_RValue *r = node->r;
                if (r != 0){
                    def_var_dump_rvalue(app, config, var, l_value, r);
                }
            }
        }
    }
    
    return(result);
}


////////////////////////////////
// NOTE(allen): Config Variables Read

global const u64 def_config_lookup_count = 4;
global String_ID def_config_lookup_table[def_config_lookup_count] = {};

function void
_def_config_table_init(void){
    if (def_config_lookup_table[0] == 0){
        def_config_lookup_table[0] = vars_save_string(str8_lit("ses_config"));
        def_config_lookup_table[1] = vars_save_string(str8_lit("prj_config"));
        def_config_lookup_table[2] = vars_save_string(str8_lit("usr_config"));
        def_config_lookup_table[3] = vars_save_string(str8_lit("def_config"));
    }
}

function Variable_Handle
def_get_config_var(String_ID key){
    _def_config_table_init();
    
    Variable_Handle result = vars_get_nil();
    Variable_Handle root = vars_get_root();
    for (u64 i = 0; i < def_config_lookup_count; i += 1){
        String_ID block_key = def_config_lookup_table[i];
        Variable_Handle block_var = vars_read_key(root, block_key);
        Variable_Handle var = vars_read_key(block_var, key);
        if (!vars_is_nil(var)){
            result = var;
            break;
        }
    }
    
    return(result);
}

function void
def_set_config_var(String_ID key, String_ID val){
    _def_config_table_init();
    Variable_Handle root = vars_get_root();
    Variable_Handle block_var = vars_read_key(root, def_config_lookup_table[0]);
	if (vars_is_nil(block_var)){
		block_var = vars_new_variable(root, def_config_lookup_table[0]);
	}
    vars_new_variable(block_var, key, val);
}

function b32
def_get_config_b32(String_ID key){
    Variable_Handle var = def_get_config_var(key);
    String_ID val = vars_string_id_from_var(var);
    b32 result = (val != 0 && val != vars_save_string_lit("false"));
    return(result);
}

function void
def_set_config_b32(String_ID key, b32 val){
    String_ID val_id = val?vars_save_string_lit("true"):vars_save_string_lit("false");
    def_set_config_var(key, val_id);
}

function String_Const_u8
def_get_config_string(Arena *arena, String_ID key){
    Variable_Handle var = def_get_config_var(key);
    String_Const_u8 result = vars_string_from_var(arena, var);
    return(result);
}

function void
def_set_config_string(String_ID key, String_Const_u8 val){
    def_set_config_var(key, vars_save_string(val));
}

function u64
def_get_config_u64(Application_Links *app, String_ID key){
    Scratch_Block scratch(app);
    Variable_Handle var = def_get_config_var(key);
    u64 result = vars_u64_from_var(app, var);
    return(result);
}

function void
def_set_config_u64(Application_Links *app, String_ID key, u64 val){
    Scratch_Block scratch(app);
    String_Const_u8 val_string = push_stringf(scratch, "%llu", val);
    def_set_config_var(key, vars_save_string(val_string));
}


////////////////////////////////
// NOTE(allen): Eval

function Config_Assignment*
config_lookup_assignment(Config *config, String_Const_u8 var_name, i32 subscript){
    Config_Assignment *assignment = 0;
    for (assignment = config->first;
         assignment != 0;
         assignment = assignment->next){
        Config_LValue *l = assignment->l;
        if (l != 0 && string_match(l->identifier, var_name) && l->index == subscript){
            break;
        }
    }
    return(assignment);
}

function Config_Get_Result
config_evaluate_rvalue(Config *config, Config_Assignment *assignment, Config_RValue *r){
    Config_Get_Result result = {};
    if (r != 0 && !assignment->visited){
        if (r->type == ConfigRValueType_LValue){
            assignment->visited = true;
            Config_LValue *l = r->lvalue;
            result = config_var(config, l->identifier, l->index);
            assignment->visited = false;
        }
        else{
            result.success = true;
            result.pos = assignment->pos;
            result.type = r->type;
            switch (r->type){
                case ConfigRValueType_Boolean:
                {
                    result.boolean = r->boolean;
                }break;
                
                case ConfigRValueType_Integer:
                {
                    result.integer = r->integer;
                }break;
                
                case ConfigRValueType_String:
                {
                    result.string = r->string;
                }break;
                
                case ConfigRValueType_Compound:
                {
                    result.compound = r->compound;
                }break;
            }
        }
    }
    return(result);
}

function Config_Get_Result
config_var(Config *config, String_Const_u8 var_name, i32 subscript){
    Config_Get_Result result = {};
    Config_Assignment *assignment = config_lookup_assignment(config, var_name, subscript);
    if (assignment != 0){
        result = config_evaluate_rvalue(config, assignment, assignment->r);
    }
    return(result);
}


////////////////////////////////
// NOTE(allen): Nonsense from the old system

function Config_Get_Result
config_compound_member(Config *config, Config_Compound *compound, String_Const_u8 var_name, i32 index){
    Config_Get_Result result = {};
    i32 implicit_index = 0;
    b32 implicit_index_is_valid = true;
    for (Config_Compound_Element *element = compound->first;
         element != 0;
         element = element->next, implicit_index += 1){
        b32 element_matches_query = false;
        switch (element->l.type){
            case ConfigLayoutType_Unset:
            {
                if (implicit_index_is_valid && index == implicit_index){
                    element_matches_query = true;
                }
            }break;
            
            case ConfigLayoutType_Identifier:
            {
                implicit_index_is_valid = false;
                if (string_match(element->l.identifier, var_name)){
                    element_matches_query = true;
                }
            }break;
            
            case ConfigLayoutType_Integer:
            {
                implicit_index_is_valid = false;
                if (element->l.integer == index){
                    element_matches_query = true;
                }
            }break;
        }
        if (element_matches_query){
            Config_Assignment dummy_assignment = {};
            dummy_assignment.pos = element->l.pos;
            result = config_evaluate_rvalue(config, &dummy_assignment, element->r);
            break;
        }
    }
    return(result);
}

function Config_Iteration_Step_Result
typed_array_iteration_step(Config *parsed, Config_Compound *compound, Config_RValue_Type type, i32 index);

function i32
typed_array_get_count(Config *parsed, Config_Compound *compound, Config_RValue_Type type);

function Config_Get_Result_List
typed_array_reference_list(Arena *arena, Config *parsed, Config_Compound *compound, Config_RValue_Type type);

#define config_fixed_string_var(c,v,s,o,a) config_placed_string_var((c),(v),(s),(o),(a),sizeof(a))

////////////////////////////////

function b32
config_bool_var(Config *config, String_Const_u8 var_name, i32 subscript, b32* var_out){
    Config_Get_Result result = config_var(config, var_name, subscript);
    b32 success = (result.success && result.type == ConfigRValueType_Boolean);
    if (success){
        *var_out = result.boolean;
    }
    return(success);
}
function b32
config_bool_var(Config *config, String_Const_u8 var_name, i32 subscript, b8 *var_out){
    b32 temp = false;
    b32 success = config_bool_var(config, var_name, subscript, &temp);
    if (success){
        *var_out = (temp != false);
    }
    return(success);
}
function b32
config_bool_var(Config *config, char *var_name, i32 subscript, b32* var_out){
    return(config_bool_var(config, SCu8(var_name), subscript, var_out));
}
function b32
config_bool_var(Config *config, char* var_name, i32 subscript, b8 *var_out){
    b32 temp = false;
    b32 success = config_bool_var(config, SCu8(var_name), subscript, &temp);
    if (success){
        *var_out = (temp != false);
    }
    return(success);
}

function b32
config_int_var(Config *config, String_Const_u8 var_name, i32 subscript, i32* var_out){
    Config_Get_Result result = config_var(config, var_name, subscript);
    b32 success = result.success && result.type == ConfigRValueType_Integer;
    if (success){
        *var_out = result.integer;
    }
    return(success);
}

function b32
config_int_var(Config *config, char *var_name, i32 subscript, i32* var_out){
    return(config_int_var(config, SCu8(var_name), subscript, var_out));
}

function b32
config_uint_var(Config *config, String_Const_u8 var_name, i32 subscript, u32* var_out){
    Config_Get_Result result = config_var(config, var_name, subscript);
    b32 success = result.success && result.type == ConfigRValueType_Integer;
    if (success){
        *var_out = result.uinteger;
    }
    return(success);
}

function b32
config_uint_var(Config *config, char *var_name, i32 subscript, u32* var_out){
    return(config_uint_var(config, SCu8(var_name), subscript, var_out));
}

function b32
config_string_var(Config *config, String_Const_u8 var_name, i32 subscript, String_Const_u8* var_out){
    Config_Get_Result result = config_var(config, var_name, subscript);
    b32 success = result.success && result.type == ConfigRValueType_String;
    if (success){
        *var_out = result.string;
    }
    return(success);
}

function b32
config_string_var(Config *config, char *var_name, i32 subscript, String_Const_u8* var_out){
    return(config_string_var(config, SCu8(var_name), subscript, var_out));
}

function b32
config_placed_string_var(Config *config, String_Const_u8 var_name, i32 subscript, String_Const_u8* var_out, u8 *space, u64 space_size){
    Config_Get_Result result = config_var(config, var_name, subscript);
    b32 success = (result.success && result.type == ConfigRValueType_String);
    if (success){
        u64 size = result.string.size;
        size = clamp_top(size, space_size);
        block_copy(space, result.string.str, size);
        *var_out = SCu8(space, size);
    }
    return(success);
}

function b32
config_placed_string_var(Config *config, char *var_name, i32 subscript, String_Const_u8* var_out, u8 *space, u64 space_size){
    return(config_placed_string_var(config, SCu8(var_name), subscript, var_out, space, space_size));
}

function b32
config_compound_var(Config *config, String_Const_u8 var_name, i32 subscript, Config_Compound** var_out){
    Config_Get_Result result = config_var(config, var_name, subscript);
    b32 success = (result.success && result.type == ConfigRValueType_Compound);
    if (success){
        *var_out = result.compound;
    }
    return(success);
}

function b32
config_compound_var(Config *config, char *var_name, i32 subscript, Config_Compound** var_out){
    return(config_compound_var(config, SCu8(var_name), subscript, var_out));
}

function b32
config_compound_bool_member(Config *config, Config_Compound *compound,
                            String_Const_u8 var_name, i32 index, b32* var_out){
    Config_Get_Result result = config_compound_member(config, compound, var_name, index);
    b32 success = result.success && result.type == ConfigRValueType_Boolean;
    if (success){
        *var_out = result.boolean;
    }
    return(success);
}

function b32
config_compound_bool_member(Config *config, Config_Compound *compound,
                            char *var_name, i32 index, b32* var_out){
    return(config_compound_bool_member(config, compound, SCu8(var_name), index, var_out));
}

function b32
config_compound_int_member(Config *config, Config_Compound *compound,
                           String_Const_u8 var_name, i32 index, i32* var_out){
    Config_Get_Result result = config_compound_member(config, compound, var_name, index);
    b32 success = result.success && result.type == ConfigRValueType_Integer;
    if (success){
        *var_out = result.integer;
    }
    return(success);
}

function b32
config_compound_int_member(Config *config, Config_Compound *compound,
                           char *var_name, i32 index, i32* var_out){
    return(config_compound_int_member(config, compound, SCu8(var_name), index, var_out));
}

function b32
config_compound_uint_member(Config *config, Config_Compound *compound,
                            String_Const_u8 var_name, i32 index, u32* var_out){
    Config_Get_Result result = config_compound_member(config, compound, var_name, index);
    b32 success = result.success && result.type == ConfigRValueType_Integer;
    if (success){
        *var_out = result.uinteger;
    }
    return(success);
}

function b32
config_compound_uint_member(Config *config, Config_Compound *compound,
                            char *var_name, i32 index, u32* var_out){
    return(config_compound_uint_member(config, compound, SCu8(var_name), index, var_out));
}

function b32
config_compound_string_member(Config *config, Config_Compound *compound,
                              String_Const_u8 var_name, i32 index, String_Const_u8* var_out){
    Config_Get_Result result = config_compound_member(config, compound, var_name, index);
    b32 success = (result.success && result.type == ConfigRValueType_String);
    if (success){
        *var_out = result.string;
    }
    return(success);
}

function b32
config_compound_string_member(Config *config, Config_Compound *compound,
                              char *var_name, i32 index, String_Const_u8* var_out){
    return(config_compound_string_member(config, compound, SCu8(var_name), index, var_out));
}

function b32
config_compound_placed_string_member(Config *config, Config_Compound *compound,
                                     String_Const_u8 var_name, i32 index, String_Const_u8* var_out, u8 *space, u64 space_size){
    Config_Get_Result result = config_compound_member(config, compound, var_name, index);
    b32 success = (result.success && result.type == ConfigRValueType_String);
    if (success){
        u64 size = result.string.size;
        size = clamp_top(size, space_size);
        block_copy(space, result.string.str, size);
        *var_out = SCu8(space, size);
    }
    return(success);
}

function b32
config_compound_placed_string_member(Config *config, Config_Compound *compound,
                                     char *var_name, i32 index, String_Const_u8* var_out, u8 *space, u64 space_size){
    return(config_compound_placed_string_member(config, compound, SCu8(var_name), index, var_out, space, space_size));
}

function b32
config_compound_compound_member(Config *config, Config_Compound *compound,
                                String_Const_u8 var_name, i32 index, Config_Compound** var_out){
    Config_Get_Result result = config_compound_member(config, compound, var_name, index);
    b32 success = (result.success && result.type == ConfigRValueType_Compound);
    if (success){
        *var_out = result.compound;
    }
    return(success);
}

function b32
config_compound_compound_member(Config *config, Config_Compound *compound,
                                char *var_name, i32 index, Config_Compound** var_out){
    return(config_compound_compound_member(config, compound, SCu8(var_name), index, var_out));
}

function Iteration_Step_Result
typed_bool_array_iteration_step(Config *config, Config_Compound *compound, i32 index, b32* var_out){
    Config_Iteration_Step_Result result = typed_array_iteration_step(config, compound, ConfigRValueType_Boolean, index);
    b32 success = (result.step == Iteration_Good);
    if (success){
        *var_out = result.get.boolean;
    }
    return(result.step);
}

function Iteration_Step_Result
typed_int_array_iteration_step(Config *config, Config_Compound *compound, i32 index, i32* var_out){
    Config_Iteration_Step_Result result = typed_array_iteration_step(config, compound, ConfigRValueType_Integer, index);
    b32 success = (result.step == Iteration_Good);
    if (success){
        *var_out = result.get.integer;
    }
    return(result.step);
}

function Iteration_Step_Result
typed_uint_array_iteration_step(Config *config, Config_Compound *compound, i32 index, u32* var_out){
    Config_Iteration_Step_Result result = typed_array_iteration_step(config, compound, ConfigRValueType_Integer, index);
    b32 success = (result.step == Iteration_Good);
    if (success){
        *var_out = result.get.uinteger;
    }
    return(result.step);
}

function Iteration_Step_Result
typed_string_array_iteration_step(Config *config, Config_Compound *compound, i32 index, String_Const_u8* var_out){
    Config_Iteration_Step_Result result = typed_array_iteration_step(config, compound, ConfigRValueType_String, index);
    b32 success = (result.step == Iteration_Good);
    if (success){
        *var_out = result.get.string;
    }
    return(result.step);
}

function Iteration_Step_Result
typed_placed_string_array_iteration_step(Config *config, Config_Compound *compound, i32 index, String_Const_u8* var_out, u8 *space, u64 space_size){
    Config_Iteration_Step_Result result = typed_array_iteration_step(config, compound, ConfigRValueType_String, index);
    b32 success = (result.step == Iteration_Good);
    if (success){
        u64 size = result.get.string.size;
        size = clamp_top(size, space_size);
        block_copy(space, result.get.string.str, size);
        *var_out = SCu8(space, size);
    }
    return(result.step);
}

function Iteration_Step_Result
typed_compound_array_iteration_step(Config *config, Config_Compound *compound, i32 index, Config_Compound** var_out){
    Config_Iteration_Step_Result result = typed_array_iteration_step(config, compound, ConfigRValueType_Compound, index);
    b32 success = (result.step == Iteration_Good);
    if (success){
        *var_out = result.get.compound;
    }
    return(result.step);
}

function i32
typed_bool_array_get_count(Config *config, Config_Compound *compound){
    i32 count = typed_array_get_count(config, compound, ConfigRValueType_Boolean);
    return(count);
}

function i32
typed_int_array_get_count(Config *config, Config_Compound *compound){
    i32 count = typed_array_get_count(config, compound, ConfigRValueType_Integer);
    return(count);
}

function i32
typed_string_array_get_count(Config *config, Config_Compound *compound){
    i32 count = typed_array_get_count(config, compound, ConfigRValueType_String);
    return(count);
}

function i32
typed_compound_array_get_count(Config *config, Config_Compound *compound){
    i32 count = typed_array_get_count(config, compound, ConfigRValueType_Compound);
    return(count);
}

function Config_Get_Result_List
typed_bool_array_reference_list(Arena *arena, Config *config, Config_Compound *compound){
    Config_Get_Result_List list = typed_array_reference_list(arena, config, compound, ConfigRValueType_Boolean);
    return(list);
}

function Config_Get_Result_List
typed_int_array_reference_list(Arena *arena, Config *config, Config_Compound *compound){
    Config_Get_Result_List list = typed_array_reference_list(arena, config, compound, ConfigRValueType_Integer);
    return(list);
}

function Config_Get_Result_List
typed_string_array_reference_list(Arena *arena, Config *config, Config_Compound *compound){
    Config_Get_Result_List list = typed_array_reference_list(arena, config, compound, ConfigRValueType_String);
    return(list);
}

function Config_Get_Result_List
typed_compound_array_reference_list(Arena *arena, Config *config, Config_Compound *compound){
    Config_Get_Result_List list = typed_array_reference_list(arena, config, compound, ConfigRValueType_Compound);
    return(list);
}

////////////////////////////////

function Config_Iteration_Step_Result
typed_array_iteration_step(Config *parsed, Config_Compound *compound, Config_RValue_Type type, i32 index){
    Config_Iteration_Step_Result result = {};
    result.step = Iteration_Quit;
    Config_Get_Result get_result = config_compound_member(parsed, compound, string_u8_litexpr("~"), index);
    if (get_result.success){
        if (get_result.type == type){
            result.step = Iteration_Good;
            result.get = get_result;
        }
        else{
            result.step = Iteration_Skip;
        }
    }
    return(result);
}

function i32
typed_array_get_count(Config *parsed, Config_Compound *compound, Config_RValue_Type type){
    i32 count = 0;
    for (i32 i = 0;; ++i){
        Config_Iteration_Step_Result result = typed_array_iteration_step(parsed, compound, type, i);
        if (result.step == Iteration_Skip){
            continue;
        }
        else if (result.step == Iteration_Quit){
            break;
        }
        count += 1;
    }
    return(count);
}

function Config_Get_Result_List
typed_array_reference_list(Arena *arena, Config *parsed, Config_Compound *compound, Config_RValue_Type type){
    Config_Get_Result_List list = {};
    for (i32 i = 0;; ++i){
        Config_Iteration_Step_Result result = typed_array_iteration_step(parsed, compound, type, i);
        if (result.step == Iteration_Skip){
            continue;
        }
        else if (result.step == Iteration_Quit){
            break;
        }
        Config_Get_Result_Node *node = push_array(arena, Config_Get_Result_Node, 1);
        node->result = result.get;
        zdll_push_back(list.first, list.last, node);
        list.count += 1;
    }
    return(list);
}

////////////////////////////////

function void
change_mode(Application_Links *app, String_Const_u8 mode){
    fcoder_mode = FCoderMode_Original;
    if (string_match(mode, string_u8_litexpr("4coder"))){
        fcoder_mode = FCoderMode_Original;
    }
    else if (string_match(mode, string_u8_litexpr("notepad-like"))){
        begin_notepad_mode(app);
    }
    else{
        print_message(app, string_u8_litexpr("Unknown mode.\n"));
    }
}

////////////////////////////////

// TODO(allen): cleanup this mess some more

function Config*
theme_parse__data(Application_Links *app, Arena *arena, String_Const_u8 file_name, String_Const_u8 data, Arena *color_arena, Color_Table *color_table){
    Config *parsed = def_config_from_text(app, arena, file_name, data);
    if (parsed != 0){
        for (Config_Assignment *node = parsed->first;
             node != 0;
             node = node->next){
            Scratch_Block scratch(app, arena);
            Config_LValue *l = node->l;
            String_Const_u8 l_name = push_string_copy(scratch, l->identifier);
            Managed_ID id = managed_id_get(app, string_u8_litexpr("colors"), l_name);
            if (id != 0){
                u32 color = 0;
                if (config_uint_var(parsed, l_name, 0, &color)){
                    color_table->arrays[id%color_table->count] = make_colors(color_arena, color);
                }
                else{
                    Config_Compound *compound = 0;
                    if (config_compound_var(parsed, l_name, 0, &compound)){
                        local_persist u32 color_array[256];
                        i32 counter = 0;
                        for (i32 i = 0;; i += 1){
                            Config_Iteration_Step_Result result = typed_array_iteration_step(parsed, compound, ConfigRValueType_Integer, i);
                            if (result.step == Iteration_Skip){
                                continue;
                            }
                            else if (result.step == Iteration_Quit){
                                break;
                            }
                            
                            color_array[counter] = result.get.uinteger;
                            counter += 1;
                            if (counter == 256){
                                break;
                            }
                        }
                        
                        color_table->arrays[id%color_table->count] = make_colors(color_arena, color_array, counter);
                    }
                }
            }
            
        }
    }
    return(parsed);
}

function Config*
theme_parse__buffer(Application_Links *app, Arena *arena, Buffer_ID buffer, Arena *color_arena, Color_Table *color_table){
    String_Const_u8 contents = push_whole_buffer(app, arena, buffer);
    Config *parsed = 0;
    if (contents.str != 0){
        String_Const_u8 file_name = push_buffer_file_name(app, arena, buffer);
        parsed = theme_parse__data(app, arena, file_name, contents, color_arena, color_table);
    }
    return(parsed);
}

function Config*
theme_parse__file_name(Application_Links *app, Arena *arena, char *file_name, Arena *color_arena, Color_Table *color_table){
    Config *parsed = 0;
	FILE* file = fopen(file_name, "rb");
    if (file == 0){
        file = def_search_normal_fopen(arena, file_name, "rb");
    }
    if (file != 0){
        String_Const_u8 data = dump_file_handle(arena, file);
        fclose(file);
        parsed = theme_parse__data(app, arena, SCu8(file_name), data, color_arena, color_table);
    }
    if (parsed == 0){
        Scratch_Block scratch(app, arena);
        String_Const_u8 str = push_u8_stringf(scratch, "Did not find %s, theme not loaded", file_name);
        print_message(app, str);
    }
    return(parsed);
}

////////////////////////////////

// TODO(allen): review this function
function void
load_config_and_apply(Application_Links *app, Arena *out_arena, i32 override_font_size, b32 override_hinting){
    Scratch_Block scratch(app, out_arena);
    
    linalloc_clear(out_arena);
    
    Config *parsed = 0;
    FILE *file = def_search_normal_fopen(scratch, "config.4coder", "rb");
    if (file != 0){
        String_Const_u8 data = dump_file_handle(scratch, file);
        fclose(file);
        if (data.str != 0){
            parsed = def_config_from_text(app, scratch, str8_lit("config.4coder"), data);
        }
    } 
    
    if (parsed != 0){
        // Errors
        String_Const_u8 error_text = config_stringize_errors(app, scratch, parsed);
        if (error_text.str != 0){
            print_message(app, string_u8_litexpr("trying to load config file:\n"));
            print_message(app, error_text);
        }
        
        // NOTE(allen): Save As Variables
        if (error_text.str == 0){
            // TODO(allen): this always applies to "def_config" need to get "usr_config" working too
            Variable_Handle config_var = def_fill_var_from_config(app, vars_get_root(),
                                                                  vars_save_string_lit("def_config"),
                                                                  parsed);
			vars_print(app, config_var);
            print_message(app, string_u8_litexpr("\n"));
        }
    }
    else{
        print_message(app, string_u8_litexpr("Using default config:\n"));
        Face_Description description = get_face_description(app, 0);
        if (description.font.file_name.str != 0){
            def_set_config_string(vars_save_string_lit("default_font_name"), description.font.file_name);
        }
    }
    
    String_Const_u8 default_font_name = def_get_config_string(scratch, vars_save_string_lit("default_font_name"));
    if (default_font_name.size == 0){
        default_font_name = string_u8_litexpr("liberation-mono.ttf");
    }
    
    // TODO(allen): this part seems especially weird now.
    // We want these to be effected by evals of the config system,
    // not by a state that gets evaled and saved *now*!!
    
    // Apply config
    String_Const_u8 mode = def_get_config_string(scratch, vars_save_string_lit("mode"));
    change_mode(app, mode);
    
    b32 lalt_lctrl_is_altgr = def_get_config_b32(vars_save_string_lit("lalt_lctrl_is_altgr"));
    global_set_setting(app, GlobalSetting_LAltLCtrlIsAltGr, lalt_lctrl_is_altgr);
    
    String_Const_u8 default_theme_name = def_get_config_string(scratch, vars_save_string_lit("default_theme_name"));
    Color_Table *colors = get_color_table_by_name(default_theme_name);
    set_active_color(colors);
    
    Face_Description description = {};
    if (override_font_size != 0){
        description.parameters.pt_size = override_font_size;
    }
    else{
        description.parameters.pt_size = (i32)def_get_config_u64(app, vars_save_string_lit("default_font_size"));
    }
    if (description.parameters.pt_size == 0){
        description.parameters.pt_size = 12;
    }
    
    b32 default_font_hinting = def_get_config_b32(vars_save_string_lit("default_font_hinting"));
    description.parameters.hinting = default_font_hinting || override_hinting;
    
    Face_Antialiasing_Mode aa_mode = FaceAntialiasingMode_8BitMono;
    String8 aa_mode_string = def_get_config_string(scratch, vars_save_string_lit("default_font_aa_mode"));
    if (string_match(aa_mode_string, str8_lit("8bit"))){
        aa_mode = FaceAntialiasingMode_8BitMono;
    }
    else if (string_match(aa_mode_string, str8_lit("1bit"))){
        aa_mode = FaceAntialiasingMode_1BitMono;
    }
    description.parameters.aa_mode = aa_mode;
    
    description.font.file_name = default_font_name;
    if (!modify_global_face_by_description(app, description)){
        String8 name_in_fonts_folder = push_u8_stringf(scratch, "fonts/%.*s", string_expand(default_font_name));
        description.font.file_name = def_search_normal_full_path(scratch, name_in_fonts_folder);
        modify_global_face_by_description(app, description);
    }
    
    b32 bind_by_physical_key = def_get_config_b32(vars_save_string_lit("bind_by_physical_key"));
    if (bind_by_physical_key){
        system_set_key_mode(KeyMode_Physical);
    }
    else{
        system_set_key_mode(KeyMode_LanguageArranged);
    }
}

function void
load_theme_file_into_live_set(Application_Links *app, char *file_name){
    Arena *arena = &global_theme_arena;
    Color_Table color_table = make_color_table(app, arena);
    Scratch_Block scratch(app, arena);
    Config *config = theme_parse__file_name(app, scratch, file_name, arena, &color_table);
    String_Const_u8 error_text = config_stringize_errors(app, scratch, config);
    print_message(app, error_text);
    
    String_Const_u8 name = SCu8(file_name);
    name = string_front_of_path(name);
    if (string_match(string_postfix(name, 7), string_u8_litexpr(".4coder"))){
        name = string_chop(name, 7);
    }
    save_theme(color_table, name);
}

function void
load_folder_of_themes_into_live_set(Application_Links *app, String_Const_u8 path){
    Scratch_Block scratch(app);
    
    File_List list = system_get_file_list(scratch, path);
    for (File_Info **ptr = list.infos, **end = list.infos + list.count;
         ptr < end;
         ptr += 1){
        File_Info *info = *ptr;
        if (!HasFlag(info->attributes.flags, FileAttribute_IsDirectory)){
            String_Const_u8 name = info->file_name;
            if (string_match(string_postfix(name, 7), str8_lit(".4coder"))){
                Temp_Memory_Block temp(scratch);
                String_Const_u8 full_name = push_u8_stringf(scratch, "%.*s/%.*s",
                                                            string_expand(path),
                                                            string_expand(name));
                load_theme_file_into_live_set(app, (char*)full_name.str);
            }
        }
    }
}

////////////////////////////////
// NOTE(allen): Commands

CUSTOM_COMMAND_SIG(load_theme_current_buffer)
CUSTOM_DOC("Parse the current buffer as a theme file and add the theme to the theme list. If the buffer has a .4coder postfix in it's name, it is removed when the name is saved.")
{
    View_ID view = get_active_view(app, Access_ReadVisible);
    Buffer_ID buffer = view_get_buffer(app, view, Access_ReadVisible);
    
    Scratch_Block scratch(app);
    String_Const_u8 file_name = push_buffer_file_name(app, scratch, buffer);
    if (file_name.size > 0){
        Arena *arena = &global_theme_arena;
        Color_Table color_table = make_color_table(app, arena);
        Config *config = theme_parse__buffer(app, scratch, buffer, arena, &color_table);
        String_Const_u8 error_text = config_stringize_errors(app, scratch, config);
        print_message(app, error_text);
        
        u64 problem_score = 0;
        if (color_table.count < defcolor_line_numbers_text){
            problem_score = defcolor_line_numbers_text - color_table.count;
        }
        for (i32 i = 0; i < color_table.count; i += 1){
            if (color_table.arrays[i].count == 0){
                problem_score += 1;
            }
        }
        
        if (error_text.size > 0 || problem_score >= 10){
            String_Const_u8 string = push_u8_stringf(scratch, "There appears to be a problem parsing %.*s; no theme change applied\n", string_expand(file_name));
            print_message(app, string);
        }
        else{
            String_Const_u8 name = string_front_of_path(file_name);
            if (string_match(string_postfix(name, 7), string_u8_litexpr(".4coder"))){
                name = string_chop(name, 7);
            }
            save_theme(color_table, name);
            
            Color_Table_Node *node = global_theme_list.last;
            if (node != 0 && string_match(node->name, name)){
                active_color_table = node->table;
            }
        }
    }
}

CUSTOM_COMMAND_SIG(go_to_user_directory)
CUSTOM_DOC("Go to the 4coder user directory")
{
    Scratch_Block scratch(app);
    String_Const_u8 hot = push_hot_directory(app, scratch);
    String8 user_4coder_path = system_get_path(scratch, SystemPath_UserDirectory);
    String8 cmd = push_u8_stringf(scratch, "mkdir \"%.*s\"", string_expand(user_4coder_path));
    exec_system_command(app, 0, buffer_identifier(0), hot, cmd, 0);
    set_hot_directory(app, user_4coder_path);
}

// BOTTOM