#include "generated/vn_ui.meta.c"

inline ui_size UI_Pixels(r32 Value, r32 Strictness)
{
    ui_size Result = {UI_SizeType_Pixels, Value, Strictness};
    return(Result);
}

inline ui_size UI_Em(r32 Value, r32 Strictness)
{
    ui_size Result = {UI_SizeType_Pixels, Value*UI_TopFontSize(), Strictness};
    return(Result);
}

inline ui_size UI_TextContent(r32 Value, r32 Strictness)
{
    ui_size Result = {UI_SizeType_TextContent, Value, Strictness};
    return(Result);
}

inline ui_size UI_Percent(r32 Value, r32 Strictness)
{
    ui_size Result = {UI_SizeType_PercentOfParent, Value, Strictness};
    return(Result);
}

inline ui_size UI_ChildrenSum(r32 Value, r32 Strictness)
{
    ui_size Result = {UI_SizeType_ChildrenSum, Value, Strictness};
    return(Result);
}

per_thread ui *ThreadLocal_UI;

inline void UI_SetState(ui *UI)
{
    ThreadLocal_UI = UI;
}

inline ui *UI_GetState(void)
{
    return(ThreadLocal_UI);
}

inline ui_key UI_GetHot(void)
{
    ui *UI = UI_GetState();
    return(UI->Hot);
}

inline ui_key UI_GetActive(void)
{
    ui *UI = UI_GetState();
    return(UI->Active);
}

inline platform_event_list *UI_EventList(void)
{
    ui *UI = UI_GetState();
    return(UI->EventList);
}

inline memory_arena *UI_FrameArena(void)
{
    ui *UI = UI_GetState();
    return(UI->FrameArena);
}

inline v2_r32 UI_MouseP(void)
{
    ui *UI = UI_GetState();
    return(UI->MouseP);
}

inline glyph_atlas *UI_GlyphAtlas(void)
{
    ui *UI = UI_GetState();
    return(UI->GlyphAtlas);
}


inline void UI_SetDragStartP(v2 P)
{
    ui *UI = UI_GetState();
    UI->DragStartP = P;
}

inline void UI_UpdateDragStartP(void)
{
    ui *UI = UI_GetState();
    UI->DragStartP = UI->MouseP;
}

inline v2 UI_GetDragStartP(void)
{
    ui *UI = UI_GetState();
    return(UI->DragStartP);
}

inline void UI_StoreDragV2(v2 DragData)
{
    ui *UI = UI_GetState();
    *(v2 *)&UI->DragData = DragData;
}

inline v2 UI_GetDragV2(void)
{
    ui *UI = UI_GetState();
    v2 Result = *(v2 *)UI->DragData;
    return(Result);
}

inline void UI_StoreDragR32(r32 DragData)
{
    ui *UI = UI_GetState();
    *(r32 *)&UI->DragData = DragData;
}

inline r32 UI_GetDragR32(void)
{
    ui *UI = UI_GetState();
    r32 Result = *(r32 *)UI->DragData;
    return(Result);
}

inline void UI_StoreDragPayload(void *Source)
{
    ui *UI = UI_GetState();
    Copy(UI->DragData, Source, sizeof(UI->DragData));
}

inline void UI_GetDragPayload(void *Dest)
{
    ui *UI = UI_GetState();
    Copy(Dest, UI->DragData, sizeof(UI->DragData));
}

inline void UI_StoreDragPointer(void *Data)
{
    ui *UI = UI_GetState();
    *(void **)&UI->DragData = Data;
}

inline void *UI_GetDragDataPointer(void)
{
    ui *UI = UI_GetState();
    void *Result = *(void **)UI->DragData;
    return(Result);
}

inline ui_key UI_EmptyKey(void)
{
    ui_key Key = {};
    return(Key);
}

inline ui_key UI_SeedKey(ui_key Key, ui_key Seed)
{
    ui_key Result = {((Key.Value + Seed.Value) << 5) + Key.Value};
    return(Result);
}

inline ui_key UI_GenerateKeyFromString(string String)
{
    ui_key Key;
    if(String.Count)
    {
        Key.Value = HashString(String);
    }
    else
    {
        Key = UI_EmptyKey();
    }
    
    return(Key);
}

static string UI_GetBoxNameByKey(ui_key Key)
{
    ui *UI = UI_GetState();
    string Result = StrLit("Empty or Not Found");
    
    if(!AreEqual(Key, UI_EmptyKey()))
    {
        for(s32 BucketIndex = 0;
            BucketIndex < ArrayCount(UI->BoxBuckets);
            ++BucketIndex)
        {
            ui_box_bucket *Bucket = UI->BoxBuckets + BucketIndex;
            for(ui_box *Box = Bucket->First;
                Box != 0;
                Box = Box->HashNext)
            {
                if(AreEqual(Key, Box->Key))
                {
                    Result = Box->String;
                    goto FoundName;
                }
            }
        }
    }
    
    FoundName:
    return(Result);
}

inline ui_box *UI_GetBoxByKey(ui *UI, ui_key Key)
{
    u64 Hash = Key.Value;
    u64 Slot = Hash % ArrayCount(UI->BoxBuckets);
    
    ui_box_bucket *Bucket = UI->BoxBuckets + Slot;
    
    ui_box *Result = 0;
    if(!AreEqual(Key, UI_EmptyKey()))
    {
        for(ui_box *Box = Bucket->First;
            Box != 0;
            Box = Box->HashNext)
        {
            if(AreEqual(Box->Key, Key))
            {
                Result = Box;
                break;
            }
        }
    }
    
    if(!Result)
    {
        // sixten: Check if we have already allocated a box
        if(DLLIsEmpty(UI->FirstFreeBox))
        {
            // sixten: If not, simply allocate one
            Result = PushStruct(UI->Arena, ui_box);
        }
        else
        {
            // sixten: If there exists an already allocated, remove it from the free list and use it.
            Result = UI->FirstFreeBox;
            DLLRemove_NP(UI->FirstFreeBox, UI->LastFreeBox, Result, HashNext, HashPrev);
        }
        
        // sixten: Insert the box into the hashmap.
        DLLInsertLast_NP(Bucket->First, Bucket->Last, Result, HashNext, HashPrev);
    }
    
    Result->Key = Key;
    
    return(Result);
}

inline ui_box *UI_MakeBox(ui_box_flags Flags, string String)
{
    ui *UI = UI_GetState();
    
    ui_box *Parent = UI_TopParent();
    
    ui_key BaseKey = UI_GenerateKeyFromString(String);
    ui_key Seed = UI_SeedKey(BaseKey, Parent ? Parent->Seed : UI_EmptyKey());
    ui_key Key = BaseKey.Value ? Seed : UI_EmptyKey();
    
    // sixten: Check for duplicate keys.
#if VN_SLOW
    if(Parent && !AreEqual(Key, UI_EmptyKey()))
    {
        for(ui_box *Child = Parent->First;
            Child != 0;
            Child = Child->Next)
        {
            Assert(!AreEqual(Child->Key, Key));
        }
    }
#endif
    
    ui_box *Box = UI_GetBoxByKey(UI, Key);
    Box->Seed = Seed;
    
    Box->First = Box->Last = Box->Next = Box->Prev = Box->Parent = 0;
    
    Box->ComputedRelativeP = V2(0, 0);
    Box->ComputedDim = V2(0, 0);
    
    Box->LastFrameTouched = UI->CurrentFrame;
    
    Box->Flags = Flags;
    Box->String = PushString(UI->FrameArena, String);
    
    s64 HashIndex = LastIndexOf(Box->String, '#');
    if(HashIndex != -1)
    {
        Box->String = Prefix(Box->String, HashIndex);
    }
    
    UI_ApplyStyles(Box);
    
    if(Parent)
    {
        DLLInsertLast(Parent->First, Parent->Last, Box);
    }
    
    return(Box);
}

inline ui_box *UI_MakeBoxF(ui_box_flags Flags, char *Format, ...)
{
    ui *UI = UI_GetState();
    
    // sixten(TODO): Allocate on scratch, copy to frame arena
    // (alternatively keep two versions of UI_MakeBox, to make sure everything works)
    
    va_list Arguments;
    va_start(Arguments, Format);
    string String = PushFormatVariadic(UI->FrameArena, Format, Arguments);
    va_end(Arguments);
    
    ui_box *Box = UI_MakeBox(Flags, String);
    return(Box);
}

inline void UI_SetNextHot(ui_key Key)
{
    ui *UI = UI_GetState();
    if(!UI->NextHotSet)
    {
        UI->NextHot = Key;
        UI->NextHotSet = true;
    }
}

static void UI_EquipBoxText(ui_box *Box, string String)
{
    Box->String = PushString(UI_FrameArena(), String);
}

static void UI_EquipBoxCustomDrawCallback(ui_box *Box, ui_custom_draw_callback *DrawCallback, void *Data)
{
    Box->DrawCallback = DrawCallback;
    Box->DrawCallbackData = Data;
}

// sixten(NOTE): ClippingRect = Intersection(TrueClippingRect, Parent->Rect);
static b32 UI_ChildrenContainsP(ui_box *Parent, v2 P, range2_r32 Clip)
{
    b32 Result = false;
    
    for(ui_box *Child = Parent->First;
        Child != 0;
        Child = Child->Next)
    {
        range2_r32 IntersectionRect = Intersection(Clip, Child->Rect);
        
        if(Child->Flags & UI_BoxFlag_Clickable && InRange(IntersectionRect, P))
        {
            Result = true;
            break;
        }
        else
        {
            if(Child->Flags & UI_BoxFlag_Clip)
            {
                Result = UI_ChildrenContainsP(Child, P, IntersectionRect);
            }
            else
            {
                Result = UI_ChildrenContainsP(Child, P, Clip);
            }
            
            if(Result)
            {
                break;
            }
        }
        
        if(Child == Parent->Last)
        {
            break;
        }
    }
    
    return(Result);
}

static ui_signal UI_SignalFromBox(ui_box *Box)
{
    ui *UI = UI_GetState();
    
    ui_signal Signal = {};
    
    // sixten: Gather the clipping rects of all the parents.
    range2_r32 ClippedRect = Box->Rect;
    for(ui_box *Parent = Box->Parent;
        Parent != 0;
        Parent = Parent->Parent)
    {
        if(Parent->Flags & UI_BoxFlag_Clip)
        {
            ClippedRect = Intersection(ClippedRect, Parent->Rect);
        }
    }
    
    Signal.Hovering = InRange(ClippedRect, UI->MouseP);
    
    // sixten: Make sure no children boxes overlap.
    Signal.Hovering &= !UI_ChildrenContainsP(Box, UI->MouseP, ClippedRect);
    
    // sixten: Make sure no previous boxes overlap.
    if(Box->Prev)
    {
        ui_box PrevBoxParent = {};
        PrevBoxParent.Rect = Box->Rect;
        PrevBoxParent.First = Box->Parent->First;
        PrevBoxParent.Last = Box->Prev;
        
        Signal.Hovering &= !UI_ChildrenContainsP(&PrevBoxParent, UI->MouseP, ClippedRect);
    }
    
    // sixten: Make sure the tooltip is not overlapping.
    {
        // sixten: Are we the tooltip?
        b32 FoundTooltip = false;
        for(ui_box *Parent = Box->Parent;
            Parent != 0;
            Parent = Parent->Parent)
        {
            if(Parent == UI->TooltipNode)
            {
                FoundTooltip = true;
                break;
            }
        }
        
        if(!FoundTooltip)
        {
            Signal.Hovering &= ~UI_ChildrenContainsP(UI->TooltipNode, UI->MouseP,
                                                     Range2R32(V2(0, 0), V2(InfinityR32, InfinityR32)));
        }
    }
    
    if(Box->Flags & UI_BoxFlag_Clickable)
    {
        if(AreEqual(UI->Active, Box->Key))
        {
            if(Platform_KeyRelease(UI->EventList, Key_MouseLeft))
            {
                Signal.Clicked = Signal.Hovering;
                Signal.Released = true;
                
                UI->Active = UI_EmptyKey();
                
                if(!Signal.Hovering)
                {
                    UI_SetNextHot(UI_EmptyKey());
                }
            }
            else
            {
                Signal.Dragging = true;
            }
        }
        else
        {
            if(AreEqual(UI->Hot, Box->Key))
            {
                if(Platform_KeyPress(UI->EventList, Key_MouseLeft))
                {
                    UI->Active = Box->Key;
                    UI->DragStartP = UI->MouseP;
                    
                    Signal.Dragging = true;
                    Signal.Pressed = true;
                }
                else if(!Signal.Hovering)
                {
                    UI_SetNextHot(UI_EmptyKey());
                }
                
                if(Platform_KeyPress(UI->EventList, Key_MouseRight))
                {
                    Signal.PressedRight = true;
                }
            }
            else
            {
                if(AreEqual(UI->Hot, UI_EmptyKey()) && Signal.Hovering)
                {
                    UI_SetNextHot(Box->Key);
                }
            }
        }
    }
    
    if(AreEqual(UI->Hot, Box->Key))
    {
        Platform.SetCursor(Box->HoverCursor);
    }
    
    Signal.MouseP = UI->MouseP;
    Signal.DragDelta = UI->MouseP - UI->DragStartP;
    Signal.Box = Box;
    
    return(Signal);
}

static void UI_SolveSizeViolations(ui_box *Box, axis2 Axis)
{
    r32 TotalSpace = 0;
    r32 FixupBudget = 0;
    
    if(!(Box->Flags & (UI_BoxFlag_OverflowX<<Axis)))
    {
        for(ui_box *Child = Box->First;
            Child != 0;
            Child = Child->Next)
        {
            if(!(Child->Flags & (UI_BoxFlag_FloatingX<<Axis)))
            {
                if(Axis == Box->LayoutAxis)
                { 
                    TotalSpace += Child->ComputedDim.E[Axis];
                }
                else
                {
                    TotalSpace = Maximum(TotalSpace, Child->ComputedDim.E[Axis]);
                }
                FixupBudget += Child->ComputedDim.E[Axis] * (1 - Child->SemanticSize[Axis].Strictness);
            }
        }
        
        r32 Violation = TotalSpace - Box->ComputedDim.E[Axis];
        if(Violation > 0 && FixupBudget > 0)
        {
            for(ui_box *Child = Box->First;
                Child != 0;
                Child = Child->Next)
            {
                if(!(Child->Flags & (UI_BoxFlag_FloatingX<<Axis)))
                {
                    r32 ChildFixupBudget = Child->ComputedDim.E[Axis] * (1 - Child->SemanticSize[Axis].Strictness);
                    r32 ChildFixupSize = 0;
                    if(Axis == Box->LayoutAxis)
                    {
                        ChildFixupSize = ChildFixupBudget * (Violation / FixupBudget);
                    }
                    else
                    {
                        ChildFixupSize = Child->ComputedDim.E[Axis] - Box->ComputedDim.E[Axis];
                    }
                    
                    ChildFixupSize = Clamp(ChildFixupSize, 0, ChildFixupBudget);
                    Child->ComputedDim.E[Axis] -= ChildFixupSize;
                }
            }
        }
    }
    
    if(Axis == Box->LayoutAxis)
    {
        r32 Position = 0;
        for(ui_box *Child = Box->First;
            Child;
            Child = Child->Next)
        {
            if((Child->Flags & (UI_BoxFlag_FloatingX<<Axis)))
            {
                Child->ComputedRelativeP.E[Axis] = Child->FixedP.E[Axis];
            }
            else
            {
                Child->ComputedRelativeP.E[Axis] = Position;
                Position += Child->ComputedDim.E[Axis];
            }
        }
    }
    else
    {
        for(ui_box *Child = Box->First;
            Child;
            Child = Child->Next)
        {
            if((Child->Flags & (UI_BoxFlag_FloatingX<<Axis)))
            {
                Child->ComputedRelativeP.E[Axis] = Child->FixedP.E[Axis];
            }
            else
            {
                Child->ComputedRelativeP.E[Axis] = 0;
            }
        }
    }
}

inline void UI_BeginTooltip(void)
{
    ui *UI = UI_GetState();
    UI_PushParent(UI->TooltipNode);
}

inline void UI_EndTooltip(void)
{
    UI_PopParent();
}

inline void UI_SetNextTooltip(void)
{
    ui *UI = UI_GetState();
    UI_SetNextParent(UI->TooltipNode);
}

#define UI_Tooltip DeferLoop(UI_BeginTooltip(), UI_EndTooltip())

static void UI_DrawBox(ui_box *Box, render_group *Group, glyph_atlas *GlyphAtlas)
{
    ui *UI = UI_GetState();
    Box->HotTransition += ((r32)AreEqual(UI->Hot, Box->Key) - Box->HotTransition) * 0.6f;
    Box->ActiveTransition += ((r32)AreEqual(UI->Active, Box->Key) - Box->ActiveTransition) * 0.6f;
    
    if(Box->Flags & UI_BoxFlag_DrawBackground)
    {
        PushQuad(Group, Box->Rect, Box->BackgroundColor, Box->CornerRadius, 0, 0);
    }
    
    if(Box->Flags & UI_BoxFlag_HotAnimation)
    {
        v4 Top = V4(1, 1, 1, 0.08F*Box->HotTransition);
        v4 Bottom = V4(1, 1, 1, 0.0);
        
        PushQuad(Group, Box->Rect, Top, Top, Bottom, Bottom, Box->CornerRadius, 0, 0);
    }
    
    if(Box->Flags & UI_BoxFlag_ActiveAnimation)
    {
        v4 Top  = V4(0, 0, 0, 0.7F*Box->ActiveTransition);
        v4 Bottom = V4(0, 0, 0, 0.1F*Box->ActiveTransition);
        
        PushQuad(Group, Box->Rect, Top, Top, Bottom, Bottom, Box->CornerRadius, 0, 0);
    }
    
    if(Box->Flags & UI_BoxFlag_DrawText)
    {
        v2 TextDim = V2(CalculateRasterizedTextWidth(GlyphAtlas, Box->Font, Box->FontSize, Box->String),
                        CalculateRasterizedTextHeight(GlyphAtlas, Box->Font, Box->FontSize, Box->String));
        
        v2 P = Box->Rect.Min + (Box->ComputedDim - TextDim)*0.5;
        
        PushText(Group, GlyphAtlas, Box->Font, P, Box->FontSize, Box->TextColor, Box->String);
    }
    
    if(Box->DrawCallback)
    {
        Box->DrawCallback(Group, GlyphAtlas, Box, Box->DrawCallbackData);
    }
    
#if VN_INTERNAL
    if(DEBUG_DebugSettings->RenderUIDebugRects)
    {
        // sixten: Render debug rects around boxes.
        r32 R = (((Box->Key.Value >> 0)  & ((1 << 22) - 1)) / (r32)((1 << 22) - 1));
        r32 G = (((Box->Key.Value >> 21) & ((1 << 22) - 1)) / (r32)((1 << 22) - 1));
        r32 B = (((Box->Key.Value >> 42) & ((1 << 22) - 1)) / (r32)((1 << 22) - 1));
        v4 Red = V4R32(R, G, B, 1);
        PushQuad(Group, Box->Rect, Red, Red, Red, Red, 0, 1.8, 1.8);
    }
#endif
    
    for(ui_box *Child = Box->First;
        Child != 0;
        Child = Child->Next)
    {
        if(Child->Flags & UI_BoxFlag_DrawDropShadow)
        {
            r32 ShadowRadius = 10;
            
            range2_r32 Rect = Range2R32(Child->Rect.Min - V2R32(ShadowRadius, ShadowRadius),
                                        Child->Rect.Max + V2R32(ShadowRadius, ShadowRadius));
            
            v4 ShadowColor = V4(0, 0, 0, 0.3);
            
            PushQuad(Group, Rect, ShadowColor, 0, ShadowRadius, 0);
        }
    }
    
    if(Box->Flags & UI_BoxFlag_Clip)
    {
        range2_r32 Rect = Intersection(Group->ClipStack[Group->ClipStackUsed], Box->Rect);
        PushClip(Group, Rect);
    }
    
    for(ui_box *Child = Box->First;
        Child != 0;
        Child = Child->Next)
    {
        UI_DrawBox(Child, Group, GlyphAtlas);
    }
    
    if(Box->Flags & UI_BoxFlag_Clip)
    {
        PopClip(Group);
    }
    
    if(Box->Flags & UI_BoxFlag_DrawBorder)
    {
        PushQuad(Group, Box->Rect, Box->BorderColor, Box->CornerRadius, 0.8, Box->BorderThickness);
    }
    
}

static r32 UI_CalculateChildrenSum(ui_box *Box, axis2 Axis);

static r32 UI_CalculateBoxSize(ui_box *Box, axis2 Axis)
{
    r32 Result = 0;
    
    ui *UI = UI_GetState();
    ui_box *Parent = Box->Parent;
    
    switch(Box->SemanticSize[Axis].Type)
    {
        case UI_SizeType_Pixels:
        {
            Result = Box->SemanticSize[Axis].Value;
        } break;
        
        case UI_SizeType_TextContent:
        {
            glyph_atlas *Atlas = UI->GlyphAtlas;
            
            Result = ((Axis == Axis2_X) ?
                      CalculateRasterizedTextWidth(Atlas, Box->Font, Box->FontSize, Box->String) :
                      CalculateRasterizedTextHeight(Atlas, Box->Font, Box->FontSize, Box->String)) +
                Box->SemanticSize[Axis].Value;
        } break;
        
        case UI_SizeType_PercentOfParent:
        {
            if(Parent && Parent->SemanticSize[Axis].Type != UI_SizeType_ChildrenSum)
            {
                r32 Size = Parent->ComputedDim.E[Axis];
                // sixten: if the size is zero, try to find it.
                if(Size == 0.0)
                {
                    Size = DimOfRange(Parent->Rect).E[Axis];
                }
                Result = Size*Box->SemanticSize[Axis].Value;
            }
            else
            {
                // sixten(NOTE): A box that depends on the size of the parent can not be allowed if the parent depends
                // on the children sum.
                InvalidCodepath;
            }
        } break;
        
        case UI_SizeType_ChildrenSum:
        {
            Result = UI_CalculateChildrenSum(Box, Axis)*Box->SemanticSize[Axis].Value;
        } break;
        
        InvalidDefaultCase;
    }
    
    return(Result);
}

static r32 UI_CalculateChildrenSum(ui_box *Box, axis2 Axis)
{
    r32 Result = 0;
    
    if(Box->LayoutAxis == Axis)
    {
        for(ui_box *Child = Box->First;
            Child != 0;
            Child = Child->Next)
        {
            Result += UI_CalculateBoxSize(Child, Axis);
        }
    }
    else
    {
        for(ui_box *Child = Box->First;
            Child != 0;
            Child = Child->Next)
        {
            Result = Max(Result, UI_CalculateBoxSize(Child, Axis));
        }
    }
    return(Result);
}

static void UI_LayoutBox(ui_box *Box)
{
    for(ui_box *Child = Box->First;
        Child != 0;
        Child = Child->Next)
    {
        Child->ComputedDim.E[Axis2_X] = UI_CalculateBoxSize(Child, Axis2_X);
        Child->ComputedDim.E[Axis2_Y] = UI_CalculateBoxSize(Child, Axis2_Y);
    }
    
    UI_SolveSizeViolations(Box, Axis2_X);
    UI_SolveSizeViolations(Box, Axis2_Y);
    
    for(ui_box *Child = Box->First;
        Child != 0;
        Child = Child->Next)
    {
        if(Child->Flags & UI_BoxFlag_AnimatePosition)
        {
            Child->ApproachingRelativeP.x = AC_AnimateValueF(Child->ComputedRelativeP.x, Child->ComputedRelativeP.x, 0.1, "Box P.X %p", Child);
            Child->ApproachingRelativeP.y = AC_AnimateValueF(Child->ComputedRelativeP.y, Child->ComputedRelativeP.y, 0.1, "Box P.Y %p", Child);
            
            Child->Rect.Min = Box->Rect.Min + Child->ApproachingRelativeP + Box->Offset;
            Child->Rect.Max = Child->Rect.Min + Child->ComputedDim;
        }
        else
        {
            Child->Rect.Min = Box->Rect.Min + Child->ComputedRelativeP + Box->Offset;
            Child->Rect.Max = Child->Rect.Min + Child->ComputedDim;
        }
        UI_LayoutBox(Child);
    }
}

static void UI_Init(ui *UI)
{
    UI->Arena = ArenaAllocate(Gigabytes(1));
    UI->FrameArena = ArenaAllocate(Gigabytes(1));
}

static void UI_BeginBuild(v2 ScreenDim)
{
    ui *UI = UI_GetState();
    
    UI_PushParent(0);
    UI_PushWidth(UI_Pixels(ScreenDim.x, 1));
    UI_PushHeight(UI_Pixels(ScreenDim.y, 1));
    UI_PushFixedX(0);
    UI_PushFixedY(0);
    UI_PushTextColor(Theme_TextColor);
    UI_PushBackgroundColor(ColorFromHex(0x111111FF));
    UI_PushBorderColor(Theme_BorderColor);
    UI_PushBorderThickness(1.8);
    UI_PushLayoutAxis(Axis2_Y);
    UI_PushCornerRadius(0);
    UI_PushFont(Font_Regular);
    UI_PushFontSize(15.0f);
    UI_PushOffsetX(0);
    UI_PushOffsetY(0);
    UI_PushHoverCursor(PlatformCursor_Arrow);
    
    UI->RootNode = UI_MakeBox(UI_BoxFlag_DrawBackground, StrLit("UI Root Node"));
    UI->Stacks.ParentStack[0] = UI->RootNode;
    
    UI->ContainerNode = UI_MakeBox(UI_BoxFlag_Clickable, StrLit("UI Container Node"));
    UI->TooltipNode = UI_MakeBox(UI_BoxFlag_FloatingX|UI_BoxFlag_FloatingY, StrLit("UI Tooltip Node"));
    
    UI->Stacks.ParentStack[0] = UI->ContainerNode;
    
    UI->NextHotSet = false;
}

static void UI_EndBuild(void)
{
    ui *UI = UI_GetState();
    
    UI_SignalFromBox(UI_GetState()->ContainerNode);
    
    if(UI->NextHotSet)
    {
        UI->Hot = UI->NextHot;
    }
    
    UI_PopParent();
    UI_PopWidth();
    UI_PopHeight();
    UI_PopFixedX();
    UI_PopFixedY();
    UI_PopTextColor();
    UI_PopBackgroundColor();
    UI_PopBorderColor();
    UI_PopBorderThickness();
    UI_PopLayoutAxis();
    UI_PopCornerRadius();
    UI_PopFont();
    UI_PopFontSize();
    UI_PopOffsetX();
    UI_PopOffsetY();
    UI_PopHoverCursor();
    
    UI_LayoutBox(UI->RootNode);
}

static void UI_RenderFrame(render_group *Group, glyph_atlas *GlyphAtlas)
{
    ui *UI = UI_GetState();
    UI_DrawBox(UI->RootNode, Group, GlyphAtlas);
}

inline void UI_ScanForHotAndActive(ui_box *Box, b32 *FoundHot, b32 *FoundActive)
{
    ui *UI = UI_GetState();
    
    if(AreEqual(UI->Hot, Box->Key) && (Box->Flags & UI_BoxFlag_Clickable))
    {
        *FoundHot = true;
    }
    if(AreEqual(UI->Active, Box->Key) && (Box->Flags & UI_BoxFlag_Clickable))
    {
        *FoundActive = true;
    }
    
    if(Box->First)
    {
        UI_ScanForHotAndActive(Box->First, FoundHot, FoundActive);
    }
    if(Box->Next)
    {
        UI_ScanForHotAndActive(Box->Next, FoundHot, FoundActive);
    }
}

static void UI_NewFrame(ui *UI, platform_event_list *EventList, v2 MouseP, glyph_atlas *GlyphAtlas)
{
    UI_SetState(UI);
    
    ArenaClear(UI->FrameArena);
    
    UI->EventList = EventList;
    UI->MouseP = MouseP;
    
    UI->GlyphAtlas = GlyphAtlas;
    
    // sixten: Make sure that the hot and active boxes are valid.
    if(UI->RootNode)
    {
        b32 FoundHot = false;
        b32 FoundActive = false;
        UI_ScanForHotAndActive(UI->RootNode, &FoundHot, &FoundActive);
        
        // sixten(TODO): Do we inform the builder code about this somehow?
        if(!FoundHot)
        {
            UI->Hot = UI_EmptyKey();
        }
        if(!FoundActive)
        {
            UI->Active = UI_EmptyKey();
        }
    }
    
    // sixten: Prune uncached boxes.
    ui_box_bucket *FirstBucket = UI->BoxBuckets;
    for(ui_box *Box = FirstBucket->First; Box != 0;)
    {
        if(AreEqual(Box->Key, UI_EmptyKey()))
        {
            ui_box *ToRemove = Box;
            Box = Box->HashNext;
            
            DLLRemove_NP(FirstBucket->First, FirstBucket->Last, ToRemove, HashNext, HashPrev);
            *ToRemove = {};
            
            DLLInsertLast_NP(UI->FirstFreeBox, UI->LastFreeBox, ToRemove, HashNext, HashPrev);
        }
        else
        {
            Box = Box->HashNext;
        }
    }
    
    // sixten: Prune any unused boxes.
    for(s32 BucketIndex = 0;
        BucketIndex < ArrayCount(UI->BoxBuckets);
        ++BucketIndex)
    {
        ui_box_bucket *Bucket = UI->BoxBuckets + BucketIndex;
        for(ui_box *Box = Bucket->First; Box != 0;)
        {
            if(Box->LastFrameTouched != UI->CurrentFrame)
            {
                ui_box *ToRemove = Box;
                Box = Box->HashNext;
                
                DLLRemove_NP(Bucket->First, Bucket->Last, ToRemove, HashNext, HashPrev);
                *ToRemove = {};
                
                DLLInsertLast_NP(UI->FirstFreeBox, UI->LastFreeBox, ToRemove, HashNext, HashPrev);
            }
            else
            {
                Box = Box->HashNext;
            }
        }
    }
    
    ++UI->CurrentFrame;
}
