Vec2I GetCellSize(Window *window) {
    Vec2I result = {(Int)window->font->char_spacing, (Int)window->font->line_spacing};
    return result;
}

Rect2I GetVisibleCells(Window *window) {
    ProfileFunction();
    View *view = GetView(window->active_view);
    Vec2I size = GetSize(window->document_rect);

    Int _cx = size.x / window->font->char_spacing;
    Int _cy = size.y / window->font->line_spacing;
    Int cx  = _cx + 1;
    Int cy  = _cy + 2;

    Vec2I  pos    = {SafeDivide(view->scroll.x, window->font->char_spacing), SafeDivide(view->scroll.y, window->font->line_spacing)};
    Int    x      = pos.x;
    Int    y      = pos.y;
    Rect2I result = {x, y, x + cx, y + cy};
    return result;
}

Scroller ComputeScrollerRect(Window *window) {
    View   *view       = GetView(window->active_view);
    Buffer *buffer     = GetBuffer(view->active_buffer);
    Vec2I   size       = GetSize(window->scrollbar_rect);
    Rect2I  vis        = GetVisibleCells(window);
    Int     line_count = buffer->line_starts.len + GetSize(vis).y - 1;

    double begin = (double)vis.min.y / (double)line_count;
    double end   = (double)vis.max.y / (double)line_count;

    Rect2 rect = {
        {(float)window->scrollbar_rect.min.x, (float)window->scrollbar_rect.min.y + (float)((double)size.y * begin)},
        {(float)window->scrollbar_rect.max.x,   (float)window->scrollbar_rect.min.y + (float)((double)size.y * end)},
    };
    Scroller result = {rect, begin, end, line_count};
    return result;
}

void DrawVisibleText(Window *window, Color tint) {
    ProfileFunction();
    View   *view   = GetView(window->active_view);
    Buffer *buffer = GetBuffer(view->active_buffer);

    Rect2I visible = GetVisibleCells(window);
    for (Int line_index = visible.min.y; line_index < visible.max.y && line_index >= 0 && line_index < buffer->line_starts.len; line_index += 1) {
        String16 line_string = GetLineString(buffer, line_index);
        Vec2I pos = Vec2I{visible.min.x, line_index} * Vec2I{(Int)window->font->char_spacing, (Int)window->font->line_spacing} - view->scroll + window->document_rect.min;

        float text_offset_x = 0;
        for (Int col_index = visible.min.x; col_index < visible.max.x && col_index >= 0 && col_index < line_string.len; col_index += 1) {
            int    codepoint = line_string[col_index];
            Glyph *g         = GetGlyph(window->font, codepoint);
            Vec2   p         = ToVec2(pos);
            p.y += window->font->line_spacing + window->font->descent;
            p.x += text_offset_x;
            Rect2 rect = Rect2FromSize(p + g->offset, g->size);

            if (codepoint != '\n' && codepoint != '\r' && codepoint != ' ') {
                PushQuad2D(RenderArena, &Vertices, rect, g->atlas_bounding_box, tint);
            }

            text_offset_x += window->font->char_spacing;
        }
    }
}

Vec2I XYToWorldPos(Window *window, XY xy) {
    Vec2I result = {xy.col * (Int)window->font->char_spacing, xy.line * (Int)window->font->line_spacing};
    return result;
}

Rect2I XYToRect(Window *window, XY xy) {
    Rect2I result = Rect2IFromSize(XYToWorldPos(window, xy), GetCellSize(window));
    return result;
}

void DrawCaret(Window *window, XY xy, float size, Color color) {
    View  *view  = GetView(window->active_view);
    Rect2I _rect = XYToRect(window, xy);
    Rect2I rect  = CutLeft(&_rect, (Int)(size * (float)window->font->char_spacing));
    Rect2I scrolled_rect = rect - view->scroll + window->document_rect.min;
    DrawRect(ToRect2(scrolled_rect), color);
}

void DrawUnderline(Window *window, View *view, Buffer *buffer, Range range, Color color, Int size = 1) {
    XY xy_min = PosToXY(buffer, range.min);
    XY xy_max = PosToXY(buffer, range.max);

    // @todo: maybe consider underlining multiple lines but then we would need to consider
    // optimizing and so on
    if (xy_min.line != xy_max.line) return;

    Vec2I  min  = {xy_min.col * window->font->char_spacing, (xy_min.line + 1) * window->font->line_spacing - size};
    Vec2I  max  = {xy_max.col * window->font->char_spacing, (xy_max.line + 1) * window->font->line_spacing};
    Rect2I rect = {min, max};
    Rect2I scrolled_rect = rect - view->scroll + window->document_rect.min;
    DrawRectOutline(scrolled_rect, color);
}

void DrawWindow(Window *window, Event &event) {
    ProfileFunction();
    View   *view        = GetView(window->active_view);
    Buffer *buffer      = GetBuffer(view->active_buffer);
    Rect2   screen_rect = Rect0Size(event.xwindow, event.ywindow);
    SetScissor(screen_rect);

    bool is_active = window->id == ActiveWindowID;
    bool active_layed_out_doc = window->id == LastActiveLayoutWindowID;

    Color color_whitespace_during_selection = ColorWhitespaceDuringSelection;
    Color color_background                  = ColorBackground;
    Color color_line_highlight              = ColorLineHighlight;
    Color color_selection                   = ColorSelection;
    Color color_main_caret                  = ColorMainCaret;
    Color color_sub_caret                   = ColorSubCaret;
    Color color_text_line_numbers           = ColorTextLineNumbers;
    Color color_text                        = ColorText;

    if (window->draw_darker) {
        color_background = ColorTitleBarBackground;
        color_selection      = ColorTitleBarSelection;
        color_text           = ColorTitleBarText;
        color_line_highlight = ColorTitleBarLineHighlight;
    }


    DrawRect(window->total_rect, color_background);


    Rect2I combined_document_line_number = window->document_rect;
    if (window->draw_line_numbers) {
        combined_document_line_number.min.x = window->line_numbers_rect.min.x;
    }
    SetScissor(combined_document_line_number);
    BeginProfileScope(draw_caret_selection);
    Rect2I visible = GetVisibleCells(window);
    For(view->carets) {
        XY min = PosToXY(buffer, it.range.min);
        XY max = PosToXY(buffer, it.range.max);
        if (visible.min.y > max.line) continue;
        if (visible.max.y < min.line) continue;

        if (GetSize(it.range)) {
            //
            // Draw selection
            for (Int line = visible.min.y; line <= visible.max.y && line >= 0 && line < buffer->line_starts.len; line += 1) {
                String16 line_string = GetLineString(buffer, line);

                for (Int col = visible.min.x; col < visible.max.x && col >= 0 && col < line_string.len; col += 1) {
                    bool a = line > min.line && line < max.line;
                    bool b = min.line != max.line && (line == min.line && col >= min.col);
                    bool c = min.line != max.line && (line == max.line && col < max.col);
                    bool d = min.line == max.line && line == max.line && col >= min.col && col < max.col;
                    if (!(a || b || c || d)) continue;

                    Vec2I pos = {col * window->font->char_spacing, line * window->font->line_spacing};
                    pos -= view->scroll;
                    pos += window->document_rect.min;
                    Rect2 rect = Rect2FromSize({(float)pos.x, (float)pos.y}, {(float)window->font->char_spacing, (float)window->font->line_spacing});
                    DrawRect(rect, color_selection);
                }
            }

            // Draw the little artifacts signaling whitespace
            // @copypaste
            Glyph *xglyph = GetGlyph(window->font, 'x');
            for (Int line = visible.min.y; line <= visible.max.y && line >= 0 && line < buffer->line_starts.len; line += 1) {
                String16 line_string = GetLineString(buffer, line);

                for (Int col = visible.min.x; col < visible.max.x && col >= 0 && col < line_string.len; col += 1) {
                    bool a = line > min.line && line < max.line;
                    bool b = min.line != max.line && (line == min.line && col >= min.col);
                    bool c = min.line != max.line && (line == max.line && col < max.col);
                    bool d = min.line == max.line && line == max.line && col >= min.col && col < max.col;
                    if (!(a || b || c || d)) continue;
                    if (line_string[col] == ' ' || line_string[col] == '\t' || line_string[col] == '\n' || line_string[col] == '\r') {
                        Vec2I pos = {col * window->font->char_spacing, line * window->font->line_spacing};
                        Vec2I scrolled_pos = pos - view->scroll + window->document_rect.min;
                        Vec2 circle_pos = {scrolled_pos.x + (float)window->font->char_spacing / 2.f, (float)scrolled_pos.y + xglyph->size.y};
                        float circle_size = window->font->size / 16.f;
                        DrawCircle(circle_pos, circle_size, color_whitespace_during_selection);
                    }
                }
            }
        } else if (window->draw_line_highlight) {
            //
            // Draw highlight
            Int   front = GetFront(it);
            XY    fxy = PosToXY(buffer, front);
            Vec2I pos = XYToWorldPos(window, XYLine(fxy.line));
            Vec2I scrolled_pos = pos - view->scroll + window->document_rect.min;
            Rect2 rect = {
                {(float)window->total_rect.min.x, (float)scrolled_pos.y},
                {(float)window->total_rect.max.x, (float)scrolled_pos.y + (float)window->font->line_spacing}
            };
            DrawRect(rect, color_line_highlight);
        }
    }

    // Underline word under mouse cursor
    if (Ctrl()) {
        Caret caret             = view->carets[0];
        Vec2I mouse             = MouseVec2I();
        bool  mouse_in_document = AreOverlapping(mouse, window->document_rect);
        if (mouse_in_document) {
            View   *view   = GetView(window->active_view);
            Buffer *buffer = GetBuffer(view->active_buffer);
            Int     p      = ScreenSpaceToBufferPosErrorOutOfBounds(window, view, buffer, mouse);
            if (p != -1) {
                Range range = EncloseLoadWord(buffer, p);
                if (InBounds(caret.range, p)) range = caret.range;
                DrawUnderline(window, view, buffer, range, ColorMouseUnderline, 2);
            }
        }

        if (is_active) {
            if (GetSize(caret.range) == 0) {
                Range range = EncloseLoadWord(buffer, caret.range.min);
                DrawUnderline(window, view, buffer, range, ColorCaretUnderline);
            } else {
                DrawUnderline(window, view, buffer, caret.range, ColorCaretUnderline);
            }
        }
    }

    EndProfileScope();
    DrawVisibleText(window, color_text);

    // Draw caret "|" markings
    if (is_active) {
        ProfileScope(DrawCarets);
        For(view->carets) {
            Int front = GetFront(it);
            XY  fxy   = PosToXY(buffer, front);
            if (fxy.col >= visible.min.x && fxy.col < visible.max.x && fxy.line >= visible.min.y && fxy.line <= visible.max.y) {
                bool main_caret = &it == &view->carets.data[0];
                DrawCaret(window, fxy, 0.3f, main_caret ? color_main_caret : color_sub_caret);
            }
        }
    }

    // Draw line numbers
    if (window->draw_line_numbers) {
        ProfileScope(DrawLineNumbers);
        Scratch scratch;
        SetScissor(window->line_numbers_rect);

        Rect2I vlines = GetVisibleCells(window);
        for (Int line = vlines.min.y; line <= visible.max.y && line >= 0 && line < buffer->line_starts.len; line += 1) {
            Vec2I pos = {0, line * window->font->line_spacing};
            pos.y -= view->scroll.y;
            pos += window->line_numbers_rect.min;
            String   s      = Format(scratch, "%lld", (long long)line + 1ll);
            String16 string = ToString16(scratch, s);
            float    x      = window->font->char_spacing * (float)string.len;
            Vec2     p      = ToVec2(pos);
            float    rectx  = (float)GetSize(window->line_numbers_rect).x;
            p.x += (rectx - x) / 2.f;
            if (x > rectx) p.x = (float)window->line_numbers_rect.min.x;
            DrawString(window->font, string, p, color_text_line_numbers);
        }
    }

    if (SearchWindowID == window->id) {
        SetScissor(window->line_numbers_rect);
        DrawString(window->font, u"Find: ", ToVec2(window->line_numbers_rect.min), color_text_line_numbers);
    }

    // Draw scrollbar
    if (window->draw_scrollbar) {
        ProfileScope(DrawScrollbar);
        SetScissor(window->scrollbar_rect);
        DrawRect(window->scrollbar_rect, ColorScrollbarBackground);
        Scroller scroller = ComputeScrollerRect(window);
        Rect2    rect     = Shrink(scroller.rect, 2);
        Color    color    = ColorScrollbarScroller;
        if (ScrollbarSelected.id == window->id.id) color = ColorScrollbarScrollerSelected;
        DrawRect(rect, color);
    }

    // color the floating object to make it stand out
    if (window->z >= 1) {
        ProfileScope(ColorFloating);
        SetScissor(window->total_rect);
        DrawRect(window->total_rect, {255, 255, 255, 25});
    }

    if (window->draw_darker) {
        SetScissor(window->total_rect);
        Rect2I rect = window->total_rect;
        DrawRect(CutTop(&rect, 1), ColorTitleBarOutline);
    }

    // darken the inactive windows
    if (!is_active) {
        ProfileScope(DarkenInactiveRect);
        SetScissor(screen_rect);
        DrawRect(window->total_rect, ColorInactiveWindow);
    }

    // Draw resizer rect
    if (GetSize(window->resizer_rect).x > 0){
        Rect2I rect = window->resizer_rect;
        SetScissor(window->resizer_rect);
        DrawRect(rect, ColorResizerBackground);
        DrawRect(CutRight(&rect, 1), ColorResizerOutline);
    }
}
