inline WindowID AllocWindowID(Window *window) {
    return {WindowIDs.id++, window};
}

Window *CreateWind() {
    Allocator allocator = GetSystemAllocator();
    Window *w = AllocType(allocator, Window);
    w->font = &PrimaryFont;
    w->visible = true;
    w->layout = true;
    w->draw_scrollbar = StyleDrawScrollbar;
    w->draw_line_numbers = StyleDrawLineNumbers;
    w->draw_line_highlight = true;
    w->jump_history = true;
    w->id = AllocWindowID(w);
    w->weight = 1.0;
    Add(&Windows, w);
    return w;
}

inline Window *GetWindow(WindowID id, Window *default_window = Windows[0]) {
    For(Windows) {
        if (it->id == id) return it;
    }
    return default_window;
}

Window *FindWindow(ViewID view_id, Window *default_window = NULL) {
    For(Windows) {
        if (it->active_view == view_id) {
            return it;
        }
    }
    return default_window;
}

Window *FindWindow(String buffer_name, Window *default_window = NULL) {
    For(Windows) {
        View   *it_view   = GetView(it->active_view);
        Buffer *it_buffer = GetBuffer(it_view->active_buffer);
        if (it_buffer->name == buffer_name) {
            return it;
        }
    }
    return default_window;
}

Window *FindWindow(BufferID buffer_id) {
    For(Windows) {
        View *view = GetView(it->active_view);
        if (view->active_buffer == buffer_id) return it;
    }
    return NULL;
}

inline Window *GetActiveWind() {
    return GetWindow(ActiveWindowID);
}

inline bool IsDocumentSelectionValid() {
    if (DocumentSelected.id == -1) return false;
    return true;
}

inline bool IsScrollbarSelectionValid() {
    if (ScrollbarSelected.id == -1) return false;
    return true;
}

Array<Window *> GetWindowZOrder(Allocator allocator) {
    Array<Window *> order = {allocator};
    For(Windows) if (it->z == 2) Add(&order, it);
    For(Windows) if (it->z == 1) Add(&order, it);
    For(Windows) if (it->z == 0) Add(&order, it);
    return order;
}

Int GetExpandingBarSize(Window *window) {
    View   *view   = GetView(window->active_view);
    Buffer *buffer = GetBuffer(view->active_buffer);
    float   result = (float)buffer->line_starts.len * window->font->line_spacing;
    return (Int)result;
}

View *WindowOpenBufferView(Window *new_parent_window, String name) {
    View *view = FindView(name);
    if (!view) {
        View *result                   = OpenBufferView(name);
        new_parent_window->active_view = result->id;
        return result;
    }

    Window *window = FindWindow(view->id);
    if (!window) {
        new_parent_window->active_view = view->id;
        return view;
    }
    if (window == new_parent_window) {
        return view;
    }

    Assert(window->active_view.id == view->id.id);
    View *result                   = OpenBufferView(name);
    new_parent_window->active_view = result->id;
    return result;
}

void InitWindows() {
    Scratch scratch;

    CreateWind();
    CreateWind();

    // COMMAND BAR
    {
        Window *window = CreateWind();
        CommandBarWindowID = window->id;
        Buffer *buffer = CreateBuffer(SysAllocator, "command_bar");
        View *view = CreateView(buffer->id);
        window->active_view = view->id;
        window->draw_line_numbers = false;
        window->draw_scrollbar = false;
        window->draw_darker = true;
        window->draw_line_highlight = false;
        window->layout = false;
        window->visible = false;
        window->sync_visibility_with_focus = true;
        window->lose_focus_on_escape = true;
        window->jump_history = false;
        view->fuzzy_search = true;
    }

    // SEARCH BAR
    {
        Window *window = CreateWind();
        SearchBarWindowID = window->id;
        Buffer *buffer = CreateBuffer(SysAllocator, "search_bar");
        SearchBufferID = buffer->id;
        View *view = CreateView(buffer->id);
        SearchViewID = view->id;
        window->active_view = view->id;
        window->draw_line_numbers = false;
        window->draw_scrollbar = false;
        window->draw_darker = true;
        window->draw_line_highlight = false;
        window->layout = false;
        window->visible = false;
        window->lose_focus_on_escape = true;
    }

    // STATUS BAR at the bottom
    {
        Window *window = CreateWind();
        StatusBarWindowID = window->id;
        Buffer *buffer = CreateBuffer(SysAllocator, "status_bar");
        View *view = CreateView(buffer->id);
        window->active_view = view->id;
        window->font = &SecondaryFont;
        window->draw_line_numbers = false;
        window->draw_scrollbar = false;
        window->draw_line_highlight = false;
        window->draw_darker = true;
        window->layout = false;
    }

    // DEBUG WINDOW
    {
        Window  *window           = CreateWind();
        DebugWindowID             = window->id;
        window->draw_line_numbers = false;
        window->draw_scrollbar    = false;
        window->visible           = false;
        window->z                 = 2;
        window->layout            = false;

        Buffer *buffer     = CreateBuffer(SysAllocator, GetUniqueBufferName(WorkDir, "debug"));
        DebugBufferID      = buffer->id;
        buffer->no_history = true;

        View *view          = CreateView(buffer->id);
        DebugViewID         = view->id;
        window->active_view = view->id;

        window->visible = false;
    }
}

void CalcNiceties(Window *n) {
    float scrollbar_size = (10.f * DPIScale);
    float line_numbers_size = (float)n->font->char_spacing * 10.f;
    if (n->draw_scrollbar) n->scrollbar_rect = CutRight(&n->document_rect, (Int)scrollbar_size);
    if (n->draw_line_numbers) n->line_numbers_rect = CutLeft(&n->document_rect, (Int)line_numbers_size);
}

double WindowCalcEvenResizerValue(Int screen_size_x, Int *out_count = NULL) {
    double w = 0;
    Int c = 0;
    ForItem(n, Windows) {
        if (n->layout) {
            w += n->weight;
            c += 1;
        }
    }
    if (out_count) *out_count = c;
    return (double)screen_size_x / w;
}

void LayoutWindows(int16_t wx, int16_t wy) {
    Rect2I screen_rect = RectI0Size(wx, wy);

    // Command bar
    {
        Window *n = GetWindow(CommandBarWindowID);
        Rect2I *rect = &screen_rect;
        Rect2I copy_rect = screen_rect;
        if (!n->visible) {
            rect = &copy_rect;
        }
        Int barsize = Clamp((Int)n->font->line_spacing*10, (Int)0, (Int)wx - 100);
        n->document_rect = n->total_rect = CutBottom(rect, barsize);
    }

    // bar at the bottom
    {
        Window *n = GetWindow(StatusBarWindowID);
        Rect2I *rect = &screen_rect;
        Rect2I copy_rect = screen_rect;
        if (!n->visible) {
            rect = &copy_rect;
        }
        Int barsize = GetExpandingBarSize(n);
        n->document_rect = n->total_rect = CutBottom(rect, barsize);
    }

    // search bar
    {
        Window *n = GetWindow(SearchBarWindowID);
        Rect2I *rect = &screen_rect;
        Rect2I copy_rect = screen_rect;
        if (!n->visible) {
            rect = &copy_rect;
        }
        Int barsize = GetExpandingBarSize(n);
        n->document_rect = n->total_rect = CutBottom(rect, barsize);
    }

    // floating debug window
    {
        Window *n           = GetWindow(DebugWindowID);
        Rect2   screen_rect = Rect0Size(wx, wy);
        Vec2    size        = GetSize(screen_rect);

        Rect2 a = CutLeft(&screen_rect, 0.3f * size.x);
        Rect2 b = CutBottom(&a, 0.4f * size.y);
        Rect2 c = Shrink(b, 20);
        n->document_rect = n->total_rect = ToRect2I(c);
    }

    // Column layout
    Int c = 0;
    double size = WindowCalcEvenResizerValue(wx, &c);
    if (c == 0) {
        return;
    }

    int i = 0;
    ForItem(n, Windows) {
        if (!n->layout) {
            continue;
        }

        n->total_rect = n->document_rect = CutLeft(&screen_rect, (Int)(size * n->weight));
        if (i != (c - 1)) {
            Int resizer_size = (Int)(PrimaryFont.char_spacing*0.5f);
            n->resizer_rect = CutRight(&n->document_rect, resizer_size);
        } else {
            n->resizer_rect = {};
        }
        CalcNiceties(n);
        i += 1;
    }
}
