#include "generated/vn_scene.meta.h"
#include "generated/vn_scene.meta.c"

////////////////////////////////
//~ sixten: Scene Compiler Functions

static void S_ParseError(scene_compiler *Compiler, char *Message, s64 TokenOffset)
{
    if(!Compiler->InPanicMode)
    {
        Compiler->EncounteredError = true;
        Compiler->InPanicMode = true;
        scene_compile_error *Error = PushStruct(Compiler->Arena, scene_compile_error);
        Error->Message = PushFormat(Compiler->Arena, Message);
        Error->Token = Compiler->At[TokenOffset];
        QueuePush(Compiler->Errors.First, Compiler->Errors.Last, Error);
        Compiler->Errors.Count += 1;
    }
}

static void S_EmitByte(scene_compiler *Compiler, u8 Byte)
{
    scene_emission_target *Target = &Compiler->TargetStack[Compiler->TargetStackIndex];
    scene_annotated_bytecode_bucket *Bucket = Target->Bucket;
    scene_annotated_bytecode_chunk *Chunk = Bucket->Last;
    if(!Chunk || Chunk->Count >= ArrayCount(Chunk->Data) || (Target->Type == S_EmissionTarget_Named && !AreEqual(Chunk->Name, Target->Name)))
    {
        Chunk = PushStruct(Target->Arena, scene_annotated_bytecode_chunk);
        Chunk->Name = Target->Name;
        
        QueuePush(Bucket->First, Bucket->Last, Chunk);
        Bucket->Count += 1;
    }
    Chunk->Data[Chunk->Count] = Byte;
    Chunk->Count += 1;
}

static void S_EmitU32(scene_compiler *Compiler, u32 Value)
{
    S_EmitByte(Compiler, Value >> 0);
    S_EmitByte(Compiler, Value >> 8);
    S_EmitByte(Compiler, Value >> 16);
    S_EmitByte(Compiler, Value >> 24);
}

static void S_EmitBucket(scene_compiler *Compiler, scene_annotated_bytecode_bucket *Bucket)
{
    for(scene_annotated_bytecode_chunk *Chunk = Bucket->First; Chunk != 0; Chunk = Chunk->Next)
    {
        for(s64 Index = 0; Index < Chunk->Count; Index += 1)
        {
            S_EmitByte(Compiler, Chunk->Data[Index]);
        }
    }
}

static u64 S_MakeConstant(scene_compiler *Compiler, scene_value Value)
{
    scene_value_chunk *Chunk = Compiler->LastValueChunk;
    if(!Chunk || Chunk->Count >= ArrayCount(Chunk->Values))
    {
        Chunk = PushStruct(Compiler->Arena, scene_value_chunk);
        QueuePush(Compiler->FirstValueChunk, Compiler->LastValueChunk, Chunk);
    }
    u64 Result = Compiler->ValueCount;
    Chunk->Values[Chunk->Count] = Value;
    Chunk->Count += 1;
    Compiler->ValueCount += 1;
    return(Result);
}

static void S_EmitConstant(scene_compiler *Compiler, scene_value Value)
{
    S_EmitByte(Compiler, S_Op_Constant);
    S_EmitU32(Compiler, S_MakeConstant(Compiler, Value));
}

static void S_SetEmissionTarget(scene_compiler *Compiler, scene_emission_target Target)
{
    Compiler->TargetStack[Compiler->TargetStackIndex] = Target;
}

static void S_PushEmissionTarget(scene_compiler *Compiler, scene_emission_target Target)
{
    Compiler->TargetStackIndex += 1;
    Compiler->TargetStack[Compiler->TargetStackIndex] = Target;
}

static void S_PopEmissionTarget(scene_compiler *Compiler)
{
    Compiler->TargetStackIndex -= 1;
}

static scene_annotated_bytecode_chunk *S_FindBytecodeChunkByName(scene_compiler *Compiler, string Name)
{
    scene_annotated_bytecode_chunk *Result = 0;
    u64 Hash = HashString(Name);
    scene_annotated_bytecode_bucket *Bucket = &Compiler->ProcBuckets[Hash%ArrayCount(Compiler->ProcBuckets)];
    for(scene_annotated_bytecode_chunk *Chunk = Bucket->First; Chunk != 0; Chunk = Chunk->Next)
    {
        if(AreEqual(Chunk->Name, Name))
        {
            Result = Chunk;
            break;
        }
    }
    return(Result);
}

static void S_AdvanceCompiler(scene_compiler *Compiler)
{
    Compiler->At += 1;
}

static scene_parse_rule S_ParseRuleFromToken(scene_compiler *Compiler, token Token)
{
    scene_parse_rule Result = {};
    switch(Token.Kind)
    {
        case TokenKind_ParenthesisOpen: { Result = { S_ParseGrouping, 0,             S_Precedence_None       }; } break;
        case TokenKind_Bang:            { Result = { S_ParseUnary,    0,             S_Precedence_None       }; } break;
        case TokenKind_Minus:           { Result = { S_ParseUnary,    S_ParseBinary, S_Precedence_Term       }; } break;
        case TokenKind_Plus:            { Result = { 0,               S_ParseBinary, S_Precedence_Term       }; } break;
        case TokenKind_Star:            { Result = { 0,               S_ParseBinary, S_Precedence_Factor     }; } break;
        case TokenKind_Slash:           { Result = { 0,               S_ParseBinary, S_Precedence_Factor     }; } break;
        case TokenKind_EqualEqual:      { Result = { 0,               S_ParseBinary, S_Precedence_Equality   }; } break;
        case TokenKind_BangEqual:       { Result = { 0,               S_ParseBinary, S_Precedence_Equality   }; } break;
        case TokenKind_Greater:         { Result = { 0,               S_ParseBinary, S_Precedence_Comparison }; } break;
        case TokenKind_GreaterEqual:    { Result = { 0,               S_ParseBinary, S_Precedence_Comparison }; } break;
        case TokenKind_Less:            { Result = { 0,               S_ParseBinary, S_Precedence_Comparison }; } break;
        case TokenKind_LessEqual:       { Result = { 0,               S_ParseBinary, S_Precedence_Comparison }; } break;
        case TokenKind_False:           { Result = { S_ParseLiteral,  0,             S_Precedence_None       }; } break;
        case TokenKind_True:            { Result = { S_ParseLiteral,  0,             S_Precedence_None       }; } break;
        case TokenKind_Numeric:         { Result = { S_ParseNumber,   0,             S_Precedence_None       }; } break;
        case TokenKind_StringLiteral:   { Result = { S_ParseString,   0,             S_Precedence_None       }; } break;
        case TokenKind_Identifier:      { Result = { S_ParseVariable, 0,             S_Precedence_None       }; } break;
        default:
        {
            //InvalidCodepath;
        } break;
    }
    
    return(Result);
}

static b32 S_MatchToken(scene_compiler *Compiler, token Token, token_kind Kind)
{
    b32 Result = false;
    string String = T_StringFromToken(Compiler->Text, Token);
    if(Token.Kind == Kind)
    {
        Result = true;
    }
    return(Result);
}

static token S_ConsumeToken(scene_compiler *Compiler, token_kind Kind, char *Message)
{
    token Token = Compiler->At[0];
    string String = T_StringFromToken(Compiler->Text, Token);
    if(Token.Kind != Kind)
    {
        S_ParseError(Compiler, Message);
    }
    
    Compiler->At += 1;
    return(Token);
}

static void S_ParseTopLevelDeclaration(scene_compiler *Compiler)
{
    if(Compiler->At[0].Kind == TokenKind_Proc)
    {
        Compiler->At += 1;
        S_ParseProcedure(Compiler);
    }
    else if(Compiler->At[0].Kind == TokenKind_Var)
    {
        Compiler->At += 1;
        S_ParseVariableDeclaration(Compiler);
    }
    else
    {
        S_ParseError(Compiler, "Expected top-level declaration (proc or var).", 0);
        Compiler->At += 1;
    }
}

static void S_ParseProcedure(scene_compiler *Compiler)
{
    token NameToken = S_ConsumeToken(Compiler, TokenKind_Identifier, "Expected procedure name after 'proc'");
    S_ConsumeToken(Compiler, TokenKind_CurlyOpen, "Expected '{' after procedure name.");
    
    if(!Compiler->InPanicMode)
    {
        string Name = T_StringFromToken(Compiler->Text, NameToken);
        scene_annotated_bytecode_bucket *Bucket = &Compiler->ProcBuckets[HashString(Name)%ArrayCount(Compiler->ProcBuckets)];
        S_SetEmissionTarget(Compiler, S_NamedEmissionTarget(Compiler->Arena, Bucket, Name));
        
        for(;Compiler->At < Compiler->TokensEnd;)
        {
            if(Compiler->At[0].Kind == TokenKind_CurlyClose)
            {
                Compiler->At += 1;
                break;
            }
            else
            {
                S_ParseDeclaration(Compiler);
            }
        }
    }
}
static void S_ParseDeclaration(scene_compiler *Compiler)
{
    switch(Compiler->At[0].Kind)
    {
        case TokenKind_Var:
        {
            Compiler->At += 1;
            S_ParseVariableDeclaration(Compiler);
        } break;
        case TokenKind_Jump:
        {
            Compiler->At += 1;
            S_ParseJumpStatement(Compiler);
        } break;
        case TokenKind_Branch:
        {
            Compiler->At += 1;
            S_ParseBranchStatement(Compiler);
        } break;
        case TokenKind_StringLiteral:
        {
            S_ParseExpression(Compiler);
            S_EmitByte(Compiler, S_Op_LineEntry);
            
            //- sixten: parse tags
            {
                b32 EmitAwait = true;
                scene_line_entry_flag Flags = 0;
                for(;Compiler->At[0].Kind == TokenKind_PoundSign;)
                {
                    Compiler->At += 1;
                    token TagToken = S_ConsumeToken(Compiler, TokenKind_Identifier, "Expected tag name after '#'.");
                    string TagString = T_StringFromToken(Compiler->Text, TagToken);
                    if(AreEqual(TagString, StrLit("noclear")))
                    {
                        Flags |= S_LineEntryFlag_NoClear;
                    }
                    else if(AreEqual(TagString, StrLit("noawait")))
                    {
                        EmitAwait = false;
                    }
                    else
                    {
                        S_ParseError(Compiler, "Unknown tag.");
                    }
                }
                
                if(EmitAwait)
                {
                    S_EmitByte(Compiler, S_Op_AwaitInput);
                }
                
                S_ConsumeToken(Compiler, TokenKind_Semicolon, "Expected ';' after statement.");
            }
        } break;
        default:
        {
            S_ParseStatement(Compiler);
            S_EmitByte(Compiler, S_Op_Pop);
        } break;
    }
    
    if(Compiler->InPanicMode)
    {
        for(;Compiler->At < Compiler->TokensEnd;)
        {
            if(Compiler->At[-1].Kind == TokenKind_Semicolon)
            {
                goto End;
            }
            
            switch(Compiler->At[0].Kind)
            {
                case TokenKind_Var: goto End;
                case TokenKind_StringLiteral: goto End;
                case TokenKind_Jump: goto End;
                case TokenKind_Branch: goto End;
                default: break;
            }
            
            Compiler->At += 1;
        }
        
        End:
        Compiler->InPanicMode = false;
    }
}

static void S_ParseVariableDeclaration(scene_compiler *Compiler)
{
    S_ConsumeToken(Compiler, TokenKind_Identifier, "Expected variable name.");
    u64 NameConstant = S_MakeConstant(Compiler, S_MakeSourceRef(Compiler->At[-1]));
    
    if(Compiler->At[0].Kind == TokenKind_Equal)
    {
        Compiler->At += 1;
        S_ParseExpression(Compiler);
    }
    else
    {
        S_EmitByte(Compiler, S_Op_Nil);
    }
    
    S_ConsumeToken(Compiler, TokenKind_Semicolon, "Expected ';' after variable declaration.");
    
    S_EmitByte(Compiler, S_Op_DefineGlobal);
    
    Assert(NameConstant < U32_Max);
    S_EmitU32(Compiler, NameConstant);
}

static void S_ParseVariable(scene_compiler *Compiler, b32 CanAssign)
{
    S_ParseNamedVariable(Compiler, Compiler->At[-1], CanAssign);
}

static void S_ParseNamedVariable(scene_compiler *Compiler, token Token, b32 CanAssign)
{
    u64 NameConstant = S_MakeConstant(Compiler, S_MakeSourceRef(Token));
    if(CanAssign && Compiler->At[0].Kind == TokenKind_Equal)
    {
        Compiler->At += 1;
        S_ParseExpression(Compiler);
        S_EmitByte(Compiler, S_Op_SetGlobal);
    }
    else
    {
        S_EmitByte(Compiler, S_Op_GetGlobal);
    }
    S_EmitU32(Compiler, NameConstant);
}

static void S_ParseJumpStatement(scene_compiler *Compiler)
{
    S_ConsumeToken(Compiler, TokenKind_Identifier, "Expected jump destination.");
    token DestToken = Compiler->At[-1];
    
    S_EmitByte(Compiler, S_Op_Jump);
    S_EmitU32(Compiler, S_MakeConstant(Compiler, S_MakeSourceRef(DestToken)));
    
    S_ConsumeToken(Compiler, TokenKind_Semicolon, "Expected ';' after jump statement.");
}

static void S_ParseBranchStatement(scene_compiler *Compiler)
{
    temporary_memory Scratch = GetScratch();
    
    scene_branch_case *FirstBranch = 0, *LastBranch = 0;
    
    S_ConsumeToken(Compiler, TokenKind_CurlyOpen, "Expected '{' after 'branch'.");
    for(;Compiler->At < Compiler->TokensEnd;)
    {
        if(Compiler->At[0].Kind == TokenKind_CurlyClose)
        {
            Compiler->At += 1; 
            break;
        }
        else
        {
            //- sixten: parse branch declarations
            switch(Compiler->At[0].Kind)
            {
                case TokenKind_StringLiteral:
                {
                    scene_branch_case *Branch = S_ParseBranchCase(Compiler, Scratch.Arena);
                    QueuePush(FirstBranch, LastBranch, Branch);
                } break;
                
                default:
                {
                    S_ParseError(Compiler, "Expected branch case.");
                    Compiler->At += 1;
                } break;
            }
        }
    }
    
    //- sixten: emit add_branch calls
    for(scene_branch_case *Branch = FirstBranch; Branch != 0; Branch = Branch->Next)
    {
        S_EmitByte(Compiler, S_Op_AddBranch);
        S_EmitU32(Compiler, S_MakeConstant(Compiler, S_MakeSourceRef(Branch->Name)));
        S_EmitU32(Compiler, S_MakeConstant(Compiler, S_MakeOffset(0)));
        scene_value_chunk *Chunk = Compiler->LastValueChunk;
        Branch->OffsetValue = &Chunk->Values[Chunk->Count-1];
    }
    
    S_EmitByte(Compiler, S_Op_Halt);
    
    // sixten: We need to keep track of the amount of bytes that have been emitted between branch cases, such that we can patch our relative jumps
    // to point to the correct address.
    s64 BaseOffset = 0;
    
    //- sixten: emit branch contents
    for(scene_branch_case *Branch = FirstBranch; Branch != 0; Branch = Branch->Next)
    {
        //- sixten: patch branch jump dest
        Branch->OffsetValue->Offset = BaseOffset;
        
        if(Branch->Bucket.Count > 0)
        {
            S_EmitBucket(Compiler, &Branch->Bucket);
            BaseOffset += (Branch->Bucket.Count - 1)*ArrayCount(scene_bytecode_chunk::Data)+Branch->Bucket.Last->Count;
        }
        
        S_EmitByte(Compiler, S_Op_JumpClose);
        BaseOffset += 1;
        S_EmitU32(Compiler, S_MakeConstant(Compiler, S_MakeOffset(BaseOffset)));
        BaseOffset += 4;
        
        scene_value_chunk *Chunk = Compiler->LastValueChunk;
        Branch->EndOffsetValue = &Chunk->Values[Chunk->Count-1];
    }
    
    //- sixten: patch the last jump
    for(scene_branch_case *Branch = FirstBranch; Branch != 0; Branch = Branch->Next)
    {
        // sixten(NOTE): This little line here feels rather sketchy, it may one day fail on us.
        Branch->EndOffsetValue->Offset = BaseOffset-Branch->EndOffsetValue->Offset-4;
    }
    
    ReleaseScratch(Scratch);
}

static scene_branch_case *S_ParseBranchCase(scene_compiler *Compiler, memory_arena *Arena)
{
    scene_branch_case *Branch = PushStruct(Arena, scene_branch_case);
    Branch->Name = S_ConsumeToken(Compiler, TokenKind_StringLiteral, "Expected branch label at start of branch case.");
    
    S_ConsumeToken(Compiler, TokenKind_CurlyOpen, "Expected '{' after branch label.");
    
    S_PushEmissionTarget(Compiler, S_RawEmissionTarget(Arena, &Branch->Bucket));
    for(;Compiler->At < Compiler->TokensEnd;)
    {
        if(Compiler->At[0].Kind == TokenKind_CurlyClose)
        {
            Compiler->At += 1;
            break;
        }
        else
        {
            S_ParseDeclaration(Compiler);
        }
    }
    S_PopEmissionTarget(Compiler);
    
    return(Branch);
}

static void S_ParseStatement(scene_compiler *Compiler)
{
    S_ParseExpression(Compiler);
    S_ConsumeToken(Compiler, TokenKind_Semicolon, "Expected ';' after statement.");
}

static void S_ParseExpression(scene_compiler *Compiler)
{
    S_ParsePrecedence(Compiler, S_Precedence_Assignment);
}

static void S_ParseLiteral(scene_compiler *Compiler, b32 CanAssign)
{
    string Value = T_StringFromToken(Compiler->Text, Compiler->At[-1]);
    switch(Compiler->At[-1].Kind)
    {
        case TokenKind_False: { S_EmitByte(Compiler, S_Op_False); } break;
        case TokenKind_True: { S_EmitByte(Compiler, S_Op_True); } break;
        InvalidDefaultCase;
    }
}

static void S_ParseNumber(scene_compiler *Compiler, b32 CanAssign)
{
    r64 Value = DoubleFromString(T_StringFromToken(Compiler->Text, Compiler->At[-1]));
    
    S_EmitConstant(Compiler, S_MakeNumber(Value));
}

static void S_ParseString(scene_compiler *Compiler, b32 CanAssign)
{
    S_EmitConstant(Compiler, S_MakeSourceRef(Compiler->At[-1]));
}

static void S_ParseGrouping(scene_compiler *Compiler, b32 CanAssign)
{
    S_ParseExpression(Compiler);
    S_ConsumeToken(Compiler, TokenKind_ParenthesisClose, "Expected ')' after expression.");
}

static void S_ParseUnary(scene_compiler *Compiler, b32 CanAssign)
{
    scene_operator Operator = S_OperatorFromString(T_StringFromToken(Compiler->Text, Compiler->At[-1]));
    S_ParsePrecedence(Compiler, S_Precedence_Unary);
    
    switch(Operator)
    {
        case S_Operator_Minus: { S_EmitByte(Compiler, S_Op_Negate); } break;
        case S_Operator_Not:   { S_EmitByte(Compiler, S_Op_Not);    } break;
        InvalidDefaultCase;
    }
}

static void S_ParseBinary(scene_compiler *Compiler, b32 CanAssign)
{
    token Token = Compiler->At[-1];
    scene_operator Operator = S_OperatorFromString(T_StringFromToken(Compiler->Text, Token));
    scene_parse_rule Rule = S_ParseRuleFromToken(Compiler, Token);
    S_ParsePrecedence(Compiler, (scene_precedence)(Rule.Precedence + 1));
    
    switch(Operator)
    {
        case S_Operator_Add:                 { S_EmitByte(Compiler, S_Op_Add);                                      } break;
        case S_Operator_Minus:               { S_EmitByte(Compiler, S_Op_Subtract);                                 } break;
        case S_Operator_Multiply:            { S_EmitByte(Compiler, S_Op_Multiply);                                 } break;
        case S_Operator_Divide:              { S_EmitByte(Compiler, S_Op_Divide);                                   } break;
        case S_Operator_Equals:              { S_EmitByte(Compiler, S_Op_Equal);                                    } break;
        case S_Operator_NotEquals:           { S_EmitByte(Compiler, S_Op_Equal);    S_EmitByte(Compiler, S_Op_Not); } break;
        case S_Operator_Greater:             { S_EmitByte(Compiler, S_Op_Greater);                                  } break;
        case S_Operator_GreaterThanOrEquals: { S_EmitByte(Compiler, S_Op_Less);     S_EmitByte(Compiler, S_Op_Not); } break;
        case S_Operator_Less:                { S_EmitByte(Compiler, S_Op_Less);                                     } break;
        case S_Operator_LessThanOrEquals:    { S_EmitByte(Compiler, S_Op_Greater);  S_EmitByte(Compiler, S_Op_Not); } break;
        InvalidDefaultCase;
    }
}

static void S_ParsePrecedence(scene_compiler *Compiler, scene_precedence Precedence)
{
    b32 CanAssign = (Precedence <= S_Precedence_Assignment);
    
    S_AdvanceCompiler(Compiler);
    scene_parse_rule Rule = S_ParseRuleFromToken(Compiler, Compiler->At[-1]);
    if(Rule.PrefixRule)
    {
        Rule.PrefixRule(Compiler, CanAssign);
    }
    else
    {
        S_ParseError(Compiler, "Expected expression.");
    }
    
    
    while(Precedence <= (Rule = S_ParseRuleFromToken(Compiler, Compiler->At[0])).Precedence)
    {
        S_AdvanceCompiler(Compiler);
        Rule.InfixRule(Compiler, CanAssign);
    }
    
    if(CanAssign && Compiler->At[0].Kind == TokenKind_Equal)
    {
        S_ParseError(Compiler, "Invalid assignment target.");
    }
}

struct proc_from_chunks_result
{
    scene_proc *Proc;
    scene_annotated_bytecode_chunk *NextChunk;
};

static proc_from_chunks_result S_ProcFromChunks(memory_arena *Arena, scene_annotated_bytecode_chunk *First)
{
    Assert(First != 0);
    string ChunkName = First->Name;
    
    //- sixten: find required bytes
    s64 RequiredBytes = 0;
    scene_annotated_bytecode_chunk *NextChunk = 0;
    {
        scene_annotated_bytecode_chunk *Chunk = First;
        for(;Chunk != 0 && AreEqual(Chunk->Name, ChunkName); Chunk = Chunk->Next)
        {
            RequiredBytes += Chunk->Count;
        }
        NextChunk = Chunk;
    }
    
    scene_proc *Proc = PushStruct(Arena, scene_proc);
    Proc->Name = ChunkName;
    Proc->Data = PushArray(Arena, u8, RequiredBytes);
    Proc->Count = RequiredBytes;
    
    //- sixten: copy over data from chunks
    u8 *Dest = Proc->Data;
    for(scene_annotated_bytecode_chunk *Chunk = First; Chunk != NextChunk; Chunk = Chunk->Next)
    {
        Copy(Dest, Chunk->Data, Chunk->Count);
        Dest += Chunk->Count;
    }
    
    //- sixten: fill & return
    proc_from_chunks_result Result;
    {
        Result.Proc = Proc;
        Result.NextChunk = NextChunk;
    }
    return(Result);
}

static compiled_scene S_ScriptFromText(memory_arena *Arena, string Text)
{
    compiled_scene Result = {};
    
    temporary_memory Scratch = GetScratch(&Arena, 1);
    tokenize_result TokenizeResult = T_TokenizeFromText(Arena, Text, T_IsIrregular);
    
    scene_compiler Compiler = {};
    {
        Compiler.Arena = Scratch.Arena;
        Compiler.Text = Text;
        Compiler.TokensBegin = TokenizeResult.Tokens.Tokens;
        Compiler.TokensEnd = Compiler.TokensBegin+TokenizeResult.Tokens.Count;
        Compiler.At = Compiler.TokensBegin;
    };
    S_SetEmissionTarget(&Compiler, S_RawEmissionTarget(Compiler.Arena, &Compiler.GlobalScope));
    
    //- sixten: append tokenization errors
    b32 FoundTokenizationError = false;
    for(tokenizer_message *Message = TokenizeResult.Messages.First; Message != 0; Message = Message->Next)
    {
        if(Message->Kind == T_MessageKind_Error)
        {
            S_ParseError(&Compiler, (char *)Message->String.Data);
            FoundTokenizationError = true;
        }
    }
    
    //- sixten: tokens -> bytecode
    if(!FoundTokenizationError)
    {
        for(;Compiler.At < Compiler.TokensEnd;)
        {
            S_ParseTopLevelDeclaration(&Compiler);
        }
    }
    
    //- sixten: bake compiled chunks
    for(s64 BucketIndex = 0; BucketIndex < ArrayCount(Compiler.ProcBuckets); BucketIndex += 1)
    {
        scene_annotated_bytecode_bucket *Bucket = &Compiler.ProcBuckets[BucketIndex];
        for(scene_annotated_bytecode_chunk *Chunk = Bucket->First; Chunk != 0;)
        {
            proc_from_chunks_result ProcResult = S_ProcFromChunks(Arena, Chunk);
            s64 Hash = HashString(Chunk->Name);
            scene_proc_bucket *DestBucket = &Result.Buckets[Hash%ArrayCount(Result.Buckets)];
            QueuePush(DestBucket->First, DestBucket->Last, ProcResult.Proc);
            Chunk = ProcResult.NextChunk;
        }
    }
    
    //- sixten: bake value chunks
    {
        Result.Values = PushArray(Arena, scene_value, Compiler.ValueCount);
        Result.ValueCount = Compiler.ValueCount;
        scene_value *Dest = Result.Values;
        for(scene_value_chunk *Chunk = Compiler.FirstValueChunk; Chunk != 0; Chunk = Chunk->Next)
        {
            Copy(Dest, Chunk->Values, Chunk->Count*sizeof(scene_value));
            Dest += Chunk->Count;
        }
    }
    
    //- sixten: copy errors
    for(scene_compile_error *Error = Compiler.Errors.First; Error != 0; Error = Error->Next)
    {
        scene_compile_error *New = PushStruct(Arena, scene_compile_error);
        New->Message = PushString(Arena, Error->Message);
        New->Token = Error->Token;
        QueuePush(Result.Errors.First, Result.Errors.Last, New);
    }
    
    Result.Errors.Count = Compiler.Errors.Count;
    
    // sixten(IMPORTANT): The text is assumed to remain in memory for the duration of the scene.
    Result.Source = Text;
    
    Result.IsValid = !Compiler.EncounteredError;
    
    ReleaseScratch(Scratch);
    return(Result);
}

static compiled_scene S_CopyCompiledScene(memory_arena *Arena, compiled_scene *Compiled)
{
    compiled_scene Result = {};
    
    Assert(Compiled->Errors.Count == 0);
    
    //- sixten: copy the procs
    for(s64 BucketIndex = 0; BucketIndex < ArrayCount(Compiled->Buckets); BucketIndex += 1)
    {
        scene_proc_bucket *SourceBucket = &Compiled->Buckets[BucketIndex];
        scene_proc_bucket *DestBucket = &Result.Buckets[BucketIndex];
        for(scene_proc *Proc = SourceBucket->First; Proc != 0; Proc = Proc->Next)
        {
            scene_proc *NewProc = PushStruct(Arena, scene_proc);
            NewProc->Name = PushString(Arena, Proc->Name);
            NewProc->Data = PushArray(Arena, u8, Proc->Count);
            Copy(NewProc->Data, Proc->Data, Proc->Count);
            NewProc->Count = Proc->Count;
            QueuePush(DestBucket->First, DestBucket->Last, NewProc);
        }
    }
    
    //- sixten: copy the values
    Result.Values = PushArray(Arena, scene_value, Compiled->ValueCount);
    Copy(Result.Values, Compiled->Values, Compiled->ValueCount*sizeof(scene_value));
    Result.ValueCount = Compiled->ValueCount;
    
    //- sixten: copy the source
    Result.Source = PushString(Arena, Compiled->Source);
    
    Result.IsValid = true;//Compiled->IsValid; sixten(TODO): I don't know why this is commented out.
    
    return(Result);
}

////////////////////////////////
//~ sixten: Scene Runtime Functions
static void S_RuntimeError(scene_runtime *Runtime, string Message)
{
    scene_runtime_error *Error = PushStruct(Runtime->ErrorArena, scene_runtime_error);
    Error->Message = PushString(Runtime->ErrorArena, Message);
    DLLInsertLast(Runtime->FirstError, Runtime->LastError, Error);
}

static void S_RuntimeErrorF(scene_runtime *Runtime, char *Format, ...)
{
    va_list Arguments;
    va_start(Arguments, Format);
    
    scene_runtime_error *Error = PushStruct(Runtime->ErrorArena, scene_runtime_error);
    Error->Message = PushFormatVariadic(Runtime->ErrorArena, Format, Arguments);
    DLLInsertLast(Runtime->FirstError, Runtime->LastError, Error);
    
    va_end(Arguments);
}

static scene_proc *S_FindProcByName(compiled_scene *Compiled, string Name)
{
    scene_proc *Result = 0;
    u64 Hash = HashString(Name);
    scene_proc_bucket *Bucket = &Compiled->Buckets[Hash%ArrayCount(Compiled->Buckets)];
    for(scene_proc *Proc = Bucket->First; Proc != 0; Proc = Proc->Next)
    {
        if(AreEqual(Proc->Name, Name))
        {
            Result = Proc;
            break;
        }
    }
    return(Result);
}

static void S_PushStack(scene_runtime *Runtime, scene_value Value)
{
    scene_runtime_stack *Stack = &Runtime->Stack;
    if(Stack->Count < ArrayCount(Stack->Stack))
    {
        Stack->Stack[Stack->Count] = Value;
        Stack->Count += 1;
    }
    else
    {
        S_RuntimeError(Runtime, StrLit("Stack overflow"));
    }
}

static scene_value S_PopStack(scene_runtime *Runtime)
{
    scene_value Result = {};
    scene_runtime_stack *Stack = &Runtime->Stack;
    if(Stack->Count > 0)
    {
        Stack->Count -= 1;
        Result = Stack->Stack[Stack->Count];
    }
    else
    {
        S_RuntimeError(Runtime, StrLit("Stack underflow"));
    }
    return(Result);
}

static scene_runtime_result S_Run(scene_runtime *Runtime, memory_arena *FrameArena, b32 AdvanceOnAwait)
{
    scene_runtime_result Result = {};
    compiled_scene *Compiled = &Runtime->Compiled;
    
    Assert(Runtime != 0);
    Assert(Compiled->IsValid);
    
    if(Runtime->CurrentProc == 0)
    {
        Runtime->CurrentProc = S_FindProcByName(Compiled, StrLit("main"));
    }
    
    // sixten(NOTE): This will only work on little endian architectures.
#define S_ReadU32() (Runtime->IP += 4, *(u32 *)(Data+Runtime->IP-4))
#define S_ReadValue() Compiled->Values[S_ReadU32()]
    
    if(Runtime->CurrentProc)
    {
        if(Runtime->IP < Runtime->CurrentProc->Count)
        {
            u8 *Data = Runtime->CurrentProc->Data;
            
            switch(Data[Runtime->IP])
            {
                case S_Op_Constant:
                {
                    Runtime->IP += 1;
                    S_PushStack(Runtime, S_ReadValue());
                } break;
                
                case S_Op_Pop:
                {
                    Runtime->IP += 1;
                    S_PopStack(Runtime);
                } break;
                
                case S_Op_Nil:
                {
                    Runtime->IP += 1;
                    S_PushStack(Runtime, S_MakeNil());
                } break;
                
                case S_Op_True:
                {
                    Runtime->IP += 1;
                    S_PushStack(Runtime, S_MakeBoolean(true));
                } break;
                
                case S_Op_False:
                {
                    Runtime->IP += 1;
                    S_PushStack(Runtime, S_MakeBoolean(false));
                } break;
                
                case S_Op_Add:
                {
                    Runtime->IP += 1;
                    scene_value Value1 = S_PopStack(Runtime);
                    scene_value Value2 = S_PopStack(Runtime);
                    if(Value1.Kind == S_ValueKind_Number && Value2.Kind == S_ValueKind_Number)
                    {
                        S_PushStack(Runtime, S_MakeNumber(Value1.Number + Value2.Number));
                    }
                    else if(Value1.Kind == S_ValueKind_String && Value2.Kind == S_ValueKind_String)
                    {
                        S_RuntimeError(Runtime, StrLit("String concatination is not yet supported."));
                    }
                    else
                    {
                        S_RuntimeError(Runtime, StrLit("This operation is not supported."));
                    }
                } break;
                
                case S_Op_Subtract:
                {
                    Runtime->IP += 1;
                    scene_value Value1 = S_PopStack(Runtime);
                    scene_value Value2 = S_PopStack(Runtime);
                    if(Value1.Kind == S_ValueKind_Number && Value2.Kind == S_ValueKind_Number)
                    {
                        S_PushStack(Runtime, S_MakeNumber(Value1.Number - Value2.Number));
                    }
                    else
                    {
                        S_RuntimeError(Runtime, StrLit("This operation is not supported."));
                    }
                } break;
                
                case S_Op_Multiply:
                case S_Op_Divide:
                {
                    S_RuntimeError(Runtime, StrLit("This operation is not supported."));
                } break;
                
                case S_Op_Jump:
                {
                    Runtime->IP += 1;
                    scene_value Value = S_ReadValue();
                    if(Value.Kind == S_ValueKind_SourceRef)
                    {
                        string JumpDest = Substring(Compiled->Source, Value.SourceRef);
                        scene_proc *Dest = S_FindProcByName(Compiled, JumpDest);
                        if(Dest)
                        {
                            Runtime->CurrentProc = Dest;
                            Runtime->IP = 0;
                        }
                        else
                        {
                            S_RuntimeErrorF(Runtime, "Trying to jump to unknown proc: %S", JumpDest);
                        }
                    }
                    else
                    {
                        S_RuntimeErrorF(Runtime, "Incorrect value kind when retrieving jump destination.");
                    }
                } break;
                
                case S_Op_JumpClose:
                {
                    Runtime->IP += 1;
                    scene_value OffsetValue = S_ReadValue();
                    if(OffsetValue.Kind == S_ValueKind_Offset)
                    {
                        Runtime->IP += OffsetValue.Offset;
                    }
                    else
                    {
                        S_RuntimeErrorF(Runtime, "Incorrect value kind when retrieving offset.");
                    }
                } break;
                
                case S_Op_AddBranch:
                {
                    Runtime->IP += 1;
                    scene_value BranchName = S_ReadValue();
                    if(BranchName.Kind == S_ValueKind_SourceRef)
                    {
                        scene_value Offset = S_ReadValue();
                        if(Offset.Kind == S_ValueKind_Offset)
                        {
                            branch_case *Branch = &Runtime->Branches[Runtime->BranchCount];
                            Branch->Name = Substring(Compiled->Source, Pad(BranchName.SourceRef, -1));
                            Branch->Offset = Offset.Offset;
                            Runtime->BranchCount += 1;
                        }
                        else
                        {
                            S_RuntimeErrorF(Runtime, "Incorrect value kind when retrieving offset.");
                        }
                    }
                    else
                    {
                        S_RuntimeErrorF(Runtime, "Incorrect value kind when retrieving branch name.");
                    }
                } break;
                
                case S_Op_Halt: { Result.ReachedAwait = true; } break;
                
                case S_Op_AwaitInput:
                {
                    if(AdvanceOnAwait)
                    {
                        Runtime->IP += 1;
                    }
                    Result.ReachedAwait = true;
                } break;
                
                default:
                {
                    if(Data[Runtime->IP] & S_Op_LineEntry)
                    {
                        textbox_action *Action = PushStructNoClear(FrameArena, textbox_action);
                        if(Data[Runtime->IP] & S_LineEntryFlag_NoClear)
                        {
                            Action->Kind = TextboxActionKind_Append;
                        }
                        else
                        {
                            Action->Kind = TextboxActionKind_Set;
                        }
                        
                        Runtime->IP += 1;
                        
                        scene_value Value = S_PopStack(Runtime);
                        if(Value.Kind == S_ValueKind_SourceRef)
                        {
                            Action->String = Substring(Compiled->Source, Pad(Value.SourceRef, -1));
                            QueuePush(Runtime->FirstTextboxAction, Runtime->LastTextboxAction, Action);
                        }
                        else
                        {
                            S_RuntimeErrorF(Runtime, "Incorrect value kind when retrieving line entry.");
                        }
                    }
                    else
                    {
                        S_RuntimeErrorF(Runtime, "Unknown bytecode op: 0x%02x", Data[Runtime->IP]);
                    }
                } break;
            }
        }
        else
        {
            S_RuntimeErrorF(Runtime, "Reached end of proc.");
        }
    }
    else
    {
        S_RuntimeErrorF(Runtime, "No main entry was found");
    }
    
    Result.HadError = !DLLIsEmpty(Runtime->FirstError);
    
    Runtime->LastResult = Result;
    return(Result);
}