thread_local scene_view *ThreadLocal_SceneView = 0;

static void SV_SetState(scene_view *View)
{
    ThreadLocal_SceneView = View;
}

static scene_view *SV_GetState()
{
    return(ThreadLocal_SceneView);
}

static void SV_SetCurrentSource(compiled_scene *Compiled)
{
    scene_view *SceneView = SV_GetState();
    
    // sixten(TODO): extract runtime information required to seamlessly transition between compilations
    
    ArenaClear(SceneView->SceneArena);
    SceneView->Runtime.Compiled = S_CopyCompiledScene(SceneView->SceneArena, Compiled);
    SceneView->Runtime.IP = 0;
    SceneView->Runtime.Stack.Count = 0;
    SceneView->Runtime.BranchCount = 0;
}

static void SV_Init(scene_view *SceneView, memory_arena *TextboxArena)
{
    SV_SetState(SceneView);
    
    SceneView->SceneArena = ArenaAllocate(Gigabytes(1));
    SceneView->Runtime.ErrorArena = ArenaAllocate(Megabytes(1));
    
    SceneView->Textbox.Capacity = 4096;
    SceneView->Textbox.String.Data = PushArray(TextboxArena, u8, SceneView->Textbox.Capacity);
    SceneView->Textbox.String.Count = 0;
}

struct text_properties
{
    font_id Font;
    r32 FontSize;
    r32 LineHeight;
};

static void RenderAnimatedText(render_group *Group, glyph_atlas *Atlas, text_properties Properties, string Text, r32 CharsToRender, v2_r32 P, r32 WrapWidth)
{
    v2_r32 Offset = V2R32(0, 0);
    
    u8 *TextBegin = Text.Data;
    u8 *TextEnd = TextBegin+Text.Count;
    u8 *Byte = TextBegin;
    
    u8 *WordBegin = TextBegin;
    u8 *WordEnd = WordBegin;
    u8 *TrueWordEnd = WordBegin;
    
    string_decode LastVisibleDecode = {};
    
    Byte = TextBegin;
    for(;Byte<=TextEnd;)
    {
        string_decode Decode = DecodeUTF8Codepoint(Byte, TextEnd-Byte);
        
        if(CharsToRender >= 1)
        {
            LastVisibleDecode = Decode;
        }
        
        if(Decode.Codepoint == ' ' || Byte==TextEnd)
        {
            string TrueWord = MakeString(WordBegin, TrueWordEnd);
            string Word = MakeString(WordBegin, WordEnd);
            r32 WordWidth = CalculateRasterizedTextWidth(Atlas, Properties.Font, Properties.FontSize, TrueWord);
            if(Offset.x+WordWidth > WrapWidth)
            {
                Offset.x = 0;
                Offset.y += Properties.LineHeight;
            }
            
            Offset.x += PushText(Group, Atlas, Properties.Font, P+Offset, Properties.FontSize, Color_White, Word);
            
            if(WordEnd != TrueWordEnd)
            {
                string LastChar = MakeString(WordEnd, LastVisibleDecode.Size);
                r32 TransitionAmount = Mod(CharsToRender, 1);
                Offset.y += (1-TransitionAmount)*5;
                PushText(Group, Atlas, Properties.Font, P+Offset, Properties.FontSize, SetAlpha(Color_White, TransitionAmount), LastChar);
            }
            
            WordBegin = TrueWordEnd;
            
            if(Byte==TextEnd || CharsToRender < 1)
            {
                break;
            }
        }
        
        Byte += Decode.Size;
        TrueWordEnd += Decode.Size;
        if(CharsToRender >= 1)
        {
            WordEnd += Decode.Size;
            CharsToRender -= 1;
        }
    }
}

static r32 CalculateGlobalScaleFromRootBox(ui_box *Box)
{
    v2 RenderDim = DimOfRange(Box->Rect);
    r32 GlobalScale = SquareRoot(RenderDim.x*RenderDim.y)/45;
    return(GlobalScale);
}

struct scene_textbox_data
{
    textbox *Textbox;
    ui_box *SceneViewBox;
};

UI_CUSTOM_DRAW_CALLBACK(BuildSceneTextboxDrawCallback)
{
    scene_textbox_data *TextboxData = (scene_textbox_data *)Data;
    textbox *Textbox = TextboxData->Textbox;
    
    //- sixten: render textbox
    v4 TopColor = V4(0, 0, 0, 0.8f);
    v4 BottomColor = V4(0, 0, 0, 0.5f);
    range2_r32 Dest = Range2R32(Box->Rect.Min, Box->Rect.Max);
    PushQuad(Group, Dest, TopColor, TopColor, BottomColor, BottomColor, 0, 0, 0);
    
    //- sixten: render text
    string Text = Textbox->String;
    r32 CharsRevealed = Textbox->CharsRevealed;
    r32 GlobalScale = CalculateGlobalScaleFromRootBox(TextboxData->SceneViewBox);
    text_properties Properties = {};
    Properties.Font = Font_Fancy;
    Properties.FontSize = GlobalScale;
    Properties.LineHeight = GlobalScale*1.5f;
    r32 Padding = 1.5f*GlobalScale;
    v2 Offset = V2R32(Padding, Padding);
    RenderAnimatedText(Group, Atlas, Properties, Text, CharsRevealed, Box->Rect.Min+Offset, DimOfRange(Box->Rect).x-2*Padding);
}

// sixten(NOTE): This is most likely temporarary and just contains all the data required to render the scene.
struct scene_render_data
{
    render_handle BackgroundTexture;
    scene_runtime *Runtime;
};

UI_CUSTOM_DRAW_CALLBACK(BuildSceneDrawCallback)
{
    scene_render_data *RenderData = (scene_render_data *)Data;
    
    //- sixten: render background
    // sixten(TODO, but soon): Currently we add Box->Rect.Min to everything, but that should really be a transform
    // on the render group.
    v2 RenderDim = DimOfRange(Box->Rect);
    range2_r32 Dest = Range2R32(Box->Rect.Min, RenderDim+Box->Rect.Min);
    range2_r32 Source = Range2R32(V2R32(0, 0), ConvertV2ToR32(DimFromTexture(RenderData->BackgroundTexture)));
    PushTexturedQuad(Group, Dest, Source, Color_White, Color_White, Color_White, Color_White, 0, 0, 0, RenderData->BackgroundTexture);
}

static void BuildScene(scene_runtime *Runtime, render_handle BackgroundTexture, textbox *Textbox)
{
    UI_SetNextWidth(UI_Percent(1, 0));
    UI_SetNextHeight(UI_Percent(1, 0));
    UI_SetNextLayoutAxis(Axis2_Y);
    ui_box *Box = UI_MakeBox(0, StrLit("Scene View"));
    scene_render_data *Data = PushStruct(UI_FrameArena(), scene_render_data);
    Data->BackgroundTexture = BackgroundTexture;
    Data->Runtime = Runtime;
    UI_EquipBoxCustomDrawCallback(Box, BuildSceneDrawCallback, Data);
    
    UI_Parent(Box)
    {
        UI_WidthFill UI_Height(UI_Percent(1, 0)) UI_Row() UI_FillPadding UI_Column() UI_FillPadding
        {
            b32 FoundOffset = false;
            s64 Offset = 0;
            for(s32 BranchIndex = 0; BranchIndex < Runtime->BranchCount; BranchIndex += 1)
            {
                branch_case *Branch = &Runtime->Branches[BranchIndex];
                if(UI_ButtonF("%S#%i", Branch->Name, BranchIndex).Clicked)
                {
                    Offset = Branch->Offset;
                    FoundOffset = true;
                }
            }
            if(FoundOffset)
            {
                Runtime->IP += 1+Offset;
                Runtime->BranchCount = 0;
            }
        }
        
        UI_SetNextWidth(UI_Percent(1, 1));
        UI_SetNextHeight(UI_Percent(0.3, 1));
        ui_box *TextBox = UI_MakeBox(0, StrLit("Scene Textbox"));
        scene_textbox_data *TextboxData = PushStruct(UI_FrameArena(), scene_textbox_data);
        TextboxData->Textbox = Textbox;
        TextboxData->SceneViewBox = Box;
        UI_EquipBoxCustomDrawCallback(TextBox, BuildSceneTextboxDrawCallback, TextboxData);
    }
}

static void BuildErrorScreen(scene_runtime *Runtime, vn_input *Input)
{
    UI_SetNextLayoutAxis(Axis2_X);
    UI_Parent(UI_MakeBox(UI_BoxFlag_DrawBackground, StrLit("Container")))
    {
        UI_Padding(UI_Em(3, 1)) UI_Width(UI_Percent(1, 0)) UI_Column() UI_Padding(UI_Em(3, 1))
        {
            UI_Font(Font_Bold) UI_Size(UI_TextContent(0, 1), UI_TextContent(0, 1)) UI_FontSize(32) UI_LabelF("A runtime error has occurred");
            s64 ErrorIndex = 0;
            for(scene_runtime_error *Error = Runtime->FirstError; Error != 0; Error = Error->Next, ErrorIndex += 1)
            {
                UI_Spacer(UI_Em(3, 1));
                UI_SetNextCornerRadius(3);
                UI_Size(UI_Percent(1, 1), UI_Percent(1, 0)) UI_Parent(UI_MakeBoxF(UI_BoxFlag_DrawDropShadow|UI_BoxFlag_DrawBorder, "%i", ErrorIndex))
                    UI_Size(UI_TextContent(30, 1), UI_TextContent(30, 1))
                {
                    UI_LabelF("Message: %S", Error->Message);
                }
            }
            UI_Spacer(UI_Em(3, 1));
            UI_Size(UI_Percent(1, 1), UI_Em(2, 1)) UI_Row()
                UI_Width(UI_TextContent(30, 1)) UI_CornerRadius(4)
            {
                ui_signal IgnoreSignal = UI_ButtonF("Ignore");
                if(IgnoreSignal.Hovering)
                {
                    UI_TooltipLabel(StrLit("Continue running the script, may lead to more errors."), UI_MouseP());
                }
                if(IgnoreSignal.Clicked)
                {
                    Runtime->FirstError = Runtime->LastError = 0;
                    ArenaClear(Runtime->ErrorArena);
                }
                
                UI_Spacer(UI_Em(1, 1));
                
                ui_signal RestartSignal = UI_ButtonF("Restart");
                if(RestartSignal.Hovering)
                {
                    UI_TooltipLabel(StrLit("Restarts the script, may lose progress."), UI_MouseP());
                }
                if(RestartSignal.Clicked)
                {
                    Runtime->FirstError = Runtime->LastError = 0;
                    Runtime->IP = 0;
                    Runtime->CurrentProc = 0;
                    ArenaClear(Runtime->ErrorArena);
                }
                
                UI_Spacer(UI_Em(1, 1));
                if(UI_ButtonF("Exit Program").Clicked)
                {
                    Input->ExitRequested = true;
                }
            }
        }
    }
}

static void SV_Update(memory_arena *FrameArena, vn_input *Input)
{
    scene_view *SceneView = SV_GetState();
    textbox *Textbox = &SceneView->Textbox;
    scene_runtime *Runtime = &SceneView->Runtime;
    platform_event_list *EventList = Input->EventList;
    compiled_scene *Compiled = &Runtime->Compiled;
    
    if(Compiled && Compiled->IsValid)
    {
        b32 PlayerAction = (Platform_KeyPress(EventList, Key_Space)||Platform_KeyPress(EventList, Key_MouseLeft));
        
        if(DLLIsEmpty(Runtime->FirstError))
        {
            b32 AdvanceOnAwait = (Textbox->CharsRevealed >= Textbox->String.Count) && PlayerAction;
            
            for(;;)
            {
                scene_runtime_result RunResult = S_Run(Runtime, FrameArena, AdvanceOnAwait);
                if(RunResult.ReachedAwait || RunResult.HadError)
                {
                    break;
                }
            }
        }
        r32 CharsPerSecond = 10.0f;//35.0f;
        Textbox->CharsRevealed += Input->dtForFrame*CharsPerSecond;
        Textbox->CharsRevealed = Min(Textbox->CharsRevealed, (r32)Textbox->String.Count);
        
        if(Textbox->CharsRevealed < Textbox->String.Count && PlayerAction)
        {
            Textbox->CharsRevealed = Textbox->String.Count;
        }
        
        //- sixten: apply the textbox actions
        for(textbox_action *Action = Runtime->FirstTextboxAction; Action != 0; Action = Action->Next)
        {
            if(Action->Kind == TextboxActionKind_Set)
            {
                string ReplaceString = Action->String;
                Textbox->String.Count = Min(ReplaceString.Count, Textbox->Capacity);
                Copy(Textbox->String.Data, ReplaceString.Data, Textbox->String.Count);
                Textbox->CharsRevealed = 0;
            }
            else if(Action->Kind == TextboxActionKind_Append)
            {
                string Addend = Action->String;
                Textbox->CharsRevealed = Textbox->String.Count;
                s64 NewCount = Min(Textbox->String.Count+Addend.Count, Textbox->Capacity-1);
                Copy(Textbox->String.Data+Textbox->String.Count, Action->String.Data, NewCount-Textbox->String.Count);
                Textbox->String.Count = NewCount;
            }
            else
            {
                InvalidCodepath;
            }
        }
        Runtime->FirstTextboxAction = Runtime->LastTextboxAction = 0;
    }
}

static void SV_BuildSceneView(vn_input *Input)
{
    scene_view *SceneView = SV_GetState();
    scene_runtime_result LastRun = SceneView->Runtime.LastResult;
    if(LastRun.HadError)
    {
        BuildErrorScreen(&SceneView->Runtime, Input);
    }
    else if(SceneView->Runtime.Compiled.IsValid)
    {
        BuildScene(&SceneView->Runtime, SceneView->BackgroundTexture, &SceneView->Textbox);
    }
    else
    {
        UI_LabelF("Invalid source");
    }
}