static config *CreateConfig(void)
{
    memory_arena *Arena = ArenaAllocate(Gigabytes(1));
    config *Config = PushStruct(Arena, config);
    Config->Arena = Arena;
    return(Config);
}

static config_entry *Config_FindEntryByName(config *Config, string Name)
{
    config_entry *Result = 0;
    
    u32 BucketSlot = HashString(Name) % ArrayCount(Config->EntryBuckets);
    config_entry_bucket *Bucket = Config->EntryBuckets + BucketSlot;
    
    for(config_entry *Entry = Bucket->First;
        Entry != 0;
        Entry = Entry->Next)
    {
        if(AreEqual(Entry->Name, Name))
        {
            Result = Entry;
            break;
        }
    }
    
    return(Result);
}

static void Config_BindEntry(config *Config, string Name, config_entry_type Type, void *Target)
{
    config_entry *Entry = Config_FindEntryByName(Config, Name);
    if(!Entry)
    {
        Entry = PushStruct(Config->Arena, config_entry);
        Entry->Name = PushString(Config->Arena, Name);
        Entry->Type = Type;
        
        u32 BucketSlot = HashString(Name) % ArrayCount(Config->EntryBuckets);
        config_entry_bucket *Bucket = Config->EntryBuckets + BucketSlot;
        
        DLLInsertLast(Bucket->First, Bucket->Last, Entry);
        
        if(Config->LastInternal)
        {
            Config->LastInternal->NextInternal = Entry;
            Config->LastInternal = Entry;
        }
        else
        {
            Config->FirstInternal = Config->LastInternal = Entry;
        }
    }
    
    Assert(Entry->Type == Type);
    Entry->Target = Target;
}

inline void Config_BindS32(config *Config, string Name, s32 *Target, s32 Default)
{
    *Target = Default;
    Config_BindEntry(Config, Name, Config_Entry_S32, Target);
}

inline void Config_BindS64(config *Config, string Name, s64 *Target, s64 Default)
{
    *Target = Default;
    Config_BindEntry(Config, Name, Config_Entry_S64, Target);
}

inline void Config_BindB32(config *Config, string Name, b32 *Target, b32 Default)
{
    *Target = Default;
    Config_BindEntry(Config, Name, Config_Entry_B32, Target);
}

static void Config_ParseError(string Message, string FileText, s64 Offset, memory_arena *Arena)
{
    text_point Point = TextPointFromOffset(FileText, Offset);
    string String = PushFormat(Arena, "Config: At %i:%i - %S", Point.Line, Point.Column, Message);
    Platform.ShowMessage(String, Platform_Message_Warning);
}

static void Config_ReadFile(config *Config, string Path)
{
    temporary_memory Scratch = GetScratch();
    
    //- sixten: read & tokenize input file
    string Text = Platform_ReadEntireFile(Scratch.Arena, Path);
    tokenize_result TokenizeResult = T_TokenizeFromText(Scratch.Arena, Text, T_IsIrregular);
    token_array Tokens = TokenizeResult.Tokens;
    
    // sixten: parse context
    config_parse_list FullPath = {};
    config_parse_mode ParseMode = ConfigParseMode_Main;
    
    //- sixten: parse tokens
    token *TokensStart = Tokens.Tokens;
    token *TokensEnd = Tokens.Tokens + Tokens.Count;
    token *Token = TokensStart;
    for(;Token < TokensEnd;)
    {
        string TokenString = Substring(Text, Token->Range);
        
        //- sixten: get next name
        if(ParseMode == ConfigParseMode_Main && Token->Kind & TokenKind_Identifier)
        {
            Config_ParseListPush(Scratch.Arena, &FullPath, TokenString);
            ParseMode = ConfigParseMode_ScanForCurlyOpenOrEquals;
            Token += 1;
            goto TokenConsumed;
        }
        
        //- sixten: scan for curly close
        if(ParseMode == ConfigParseMode_Main && Token->Kind == TokenKind_CurlyClose)
        {
            Config_ParseListPop(&FullPath);
            Token += 1;
            goto TokenConsumed;
        }
        
        //- sixten: scan for curly open
        if(ParseMode == ConfigParseMode_ScanForCurlyOpenOrEquals && Token->Kind == TokenKind_CurlyOpen)
        {
            ParseMode = ConfigParseMode_Main;
            Token += 1;
            goto TokenConsumed;
        }
        
        //- sixten: scan for equals
        if(ParseMode == ConfigParseMode_ScanForCurlyOpenOrEquals && Token->Kind & TokenKind_Equal)
        {
            ParseMode = ConfigParseMode_ScanForValue;
            Token += 1;
            goto TokenConsumed;
        }
        
        //- sixten: scan for semicolon
        if(ParseMode == ConfigParseMode_ScanForSemicolon && Token->Kind == TokenKind_Semicolon)
        {
            ParseMode = ConfigParseMode_Main;
            Token += 1;
            goto TokenConsumed;
        }
        
        //- sixten: scan for boolean value
        if(ParseMode == ConfigParseMode_ScanForValue && (Token->Kind == TokenKind_True || Token->Kind == TokenKind_False))
        {
            string FullName = Config_ParseListJoin(Scratch.Arena, &FullPath);
            config_entry *Entry = Config_FindEntryByName(Config, FullName);
            if(Entry)
            {
                b32 Value = AreEqual(TokenString, StrLit("true"));
                Assert(Entry->Type == Config_Entry_B32);
                *(b32 *)Entry->Target = Value;
            }
            Config_ParseListPop(&FullPath);
            ParseMode = ConfigParseMode_ScanForSemicolon;
            Token += 1;
            goto TokenConsumed;
        }
        
        //- sixten: scan for integer value
        if(ParseMode == ConfigParseMode_ScanForValue && Token->Kind & TokenKind_Numeric)
        {
            string FullName = Config_ParseListJoin(Scratch.Arena, &FullPath);
            config_entry *Entry = Config_FindEntryByName(Config, FullName);
            if(Entry)
            {
                s64 Value = ConvertStringToS64(TokenString);
                if(Entry->Type == Config_Entry_S32)
                {
                    *(s32 *)Entry->Target = Value;
                }
                else if(Entry->Type == Config_Entry_S64)
                {
                    *(s64 *)Entry->Target = Value;
                }
                else
                {
                    InvalidCodepath;
                }
            }
            Config_ParseListPop(&FullPath);
            ParseMode = ConfigParseMode_ScanForSemicolon;
            Token += 1;
            goto TokenConsumed;
        }
        
        //- sixten: if the token has not been consumed, something's gone wrong
        {
            string ErrorMessage = StrLit("Unknown parse error");
            //- sixten: determine error message
            switch(ParseMode)
            {
                case ConfigParseMode_Main:                     { ErrorMessage = StrLit("Expected identifier or '}'"); } break;
                case ConfigParseMode_ScanForCurlyOpenOrEquals: { ErrorMessage = StrLit("Expected '{' or '='")      ;  } break;
                case ConfigParseMode_ScanForValue:             { ErrorMessage = StrLit("Expected value");             } break;
                case ConfigParseMode_ScanForSemicolon:         { ErrorMessage = StrLit("Expected ';'");               } break;
            }
            
            Config_ParseError(ErrorMessage, Text, Token->Range.Min, Scratch.Arena);
            Token += 1;
        }
        
        TokenConsumed:;
    }
    
    ReleaseScratch(Scratch);
}

////////////////////////////////
//~ sixten: Config Parse Type Functions
static void Config_ParseListPush(memory_arena *Arena, config_parse_list *List, string Name)
{
    config_parse_node *Node = PushStruct(Arena, config_parse_node);
    Node->Name = Name;
    List->TotalCountPlusOne += Name.Count + 1;
    DLLInsertLast(List->First, List->Last, Node);
}

static void Config_ParseListPop(config_parse_list *List)
{
    config_parse_node *Node = List->Last;
    if(Node)
    {
        List->TotalCountPlusOne -= Node->Name.Count + 1;
        DLLRemove(List->First, List->Last, Node);
    }
}

static string Config_ParseListJoin(memory_arena *Arena, config_parse_list *List)
{
    s64 TotalCount = List->TotalCountPlusOne - 1;
    string Result = MakeString(PushArray(Arena, u8, List->TotalCountPlusOne), TotalCount);
    s64 Index = 0;
    for(config_parse_node *Node = List->First; Node != 0; Node = Node->Next)
    {
        Copy(Result.Data + Index, Node->Name.Data, Node->Name.Count);
        Index += Node->Name.Count;
        if(Node->Next)
        {
            Result.Data[Index] = '/';
            Index += 1;
        }
    }
    return(Result);
}

static void Config_WriteFile(config *Config, string Path)
{
    string_list Out = {};
    temporary_memory Scratch = GetScratch();
    
    string LastDir = MakeString(0, 0LL);
    for(config_entry *Entry = Config->FirstInternal;
        Entry != 0;
        Entry = Entry->NextInternal)
    {
        s64 LastSlash = LastIndexOf(Entry->Name, '/');
        Assert(LastSlash != -1);
        
        string Dir = Prefix(Entry->Name, LastSlash);
        string Name = Suffix(Entry->Name, Entry->Name.Count - LastSlash - 1);
        
        if(!AreEqual(Dir, LastDir))
        {
            if(!AreEqual(LastDir, MakeString(0, 0LL)))
            {
                AppendString(&Out, StrLit("}\n\n"), Scratch.Arena);
            }
            
            AppendString(&Out, Dir, Scratch.Arena);
            AppendString(&Out, StrLit("\n{\n"), Scratch.Arena);
            
            LastDir = Dir;
        }
        
        AppendString(&Out, StrLit("\t"), Scratch.Arena);
        AppendString(&Out, Name, Scratch.Arena);
        AppendString(&Out, StrLit(" = "), Scratch.Arena);
        
        // sixten: Output the value of the entry
        if(Entry->Type == Config_Entry_S32 || Entry->Type == Config_Entry_S64)
        {
            s64 IntegerValue;
            if(Entry->Type == Config_Entry_S32)
            {
                IntegerValue = *(s32 *)Entry->Target;
            }
            else
            {
                IntegerValue = *(s64 *)Entry->Target;
            }
            
            string Value = ConvertS64ToString(Scratch.Arena, IntegerValue);
            AppendString(&Out, Value, Scratch.Arena);
        }
        else if(Entry->Type == Config_Entry_B32)
        {
            string Value = (*(b32 *)Entry->Target)?StrLit("true"):StrLit("false");
            AppendString(&Out, Value, Scratch.Arena);
        }
        else
        {
            UnimplementedCodepath;
        }
        
        AppendString(&Out, StrLit(";\n"), Scratch.Arena);
    }
    
    if(!AreEqual(LastDir, MakeString(0, 0LL)))
    {
        AppendString(&Out, StrLit("}"), Scratch.Arena);
    }
    
    string FinalOut = JoinStringList(&Out, Scratch.Arena);
    
    platform_file_handle Handle = Platform.OpenFile(Path, PlatformAccess_Write);
    if(Handle.IsValid)
    {
        Platform.WriteFile(Handle, FinalOut.Data, 0, FinalOut.Count);
        Platform.CloseFile(Handle);
    }
    
    ReleaseScratch(Scratch);
}