// zig fmt: off
const std = @import("std");
const rl = @import("raylib");
const rect_utils = @import("./rect-utils.zig");
const FontFace = @import("./font-face.zig");
const builtin = @import("builtin");
const srcery = @import("./srcery.zig");
const rlgl_h = @cImport({
    @cInclude("rlgl.h");
});

const Rect = rl.Rectangle;
const Vec2 = rl.Vector2;
const assert = std.debug.assert;

const UI = @This();

pub const Interaction = struct {
    widget: Widget.Key,

    clicked: bool = false,

    hovering: bool = false,
    pressed: bool = false,
    released: bool = false,
    held_down: bool = false,
};

const SemanticSize = union(enum) {
    pixels: f32,
    percent: f32,
    fit_children,
    text
};

const SemanticVec2 = struct {
    x: SemanticSize,
    y: SemanticSize,
};

pub const Widget = struct {
    const Flag = enum {
        clickable,
        passthrough
    };

    const Flags = std.EnumSet(Flag);

    pub const Key = packed struct {
        hash: u16 = 0,
        extra: u16 = 0,

        pub fn fromUsize(number: usize) Key {
            return .{
                .hash = @truncate(number),
            };
        }

        pub fn eql(self: Key, other: Key) bool {
            return self.hash == other.hash and self.extra == other.extra;
        }

        pub fn indexOf(haystack: []const Key, needle: Key) ?usize {
            for (0.., haystack) |i, key| {
                if (key.eql(needle)) {
                    return i;
                }
            }
            return null;
        }
    };

    const Sides = struct {
        top: f32 = 0,
        bottom: f32 = 0,
        left: f32 = 0,
        right: f32 = 0,

        pub fn vertical(self: *Sides, amount: f32) void {
            self.top = amount;
            self.bottom = amount;
        }

        pub fn horizontal(self: *Sides, amount: f32) void {
            self.left = amount;
            self.right = amount;
        }

        pub fn all(self: *Sides, amount: f32) void {
            self.top = amount;
            self.bottom = amount;
            self.left = amount;
            self.right = amount;
        }
    };

    key: Key = .{},
    parent: Key = .{},
    flags: Flags = .{},
    layout: Layout = .{},

    padding: Sides = .{},
    corner_radius: f32 = 12,

    size: SemanticVec2 = .{
        .x = .{ .pixels = 0 },
        .y = .{ .pixels = 0 },
    },

    text: ?struct {
        content: []const u8,
        color: rl.Color = srcery.bright_white,
    } = null,

    background: ?union(enum) {
        color: rl.Color,
        blur_world,
    } = null,

    computed_relative_position: ?Vec2 = null,
    computed_content_size: ?Vec2 = null,
    computed_rect: ?Rect = null,

    fn computed_size(self: *const Widget) ?Vec2 {
        if (self.computed_content_size) |content_size| {
            return Vec2{
                .x = content_size.x + self.padding.left + self.padding.right,
                .y = content_size.y + self.padding.top + self.padding.bottom,
            };
        } else {
            return null;
        }
    }
};

pub const Layout = struct {
    pub const Kind = enum {
        vertical_column
    };

    kind: Kind = .vertical_column,
    gap: f32 = 0,
    key_extra: u16 = 0
};

const debug = false;

const max_widgets = 64;
const Widgets = std.BoundedArray(Widget, max_widgets);

const random_seed = 0;
const root_widget_key = Widget.Key{ .hash = 0 };

prev_widgets: Widgets = .{},
widgets: Widgets = .{},
widget_stack: std.BoundedArray(Widget.Key, max_widgets) = .{},
font_face: FontFace,
random: std.Random.DefaultPrng,
blur_background: ?rl.Texture = null,
disable_mouse_interaction: bool = false,

// Debug fields
duplicate_keys: std.BoundedArray(Widget.Key, max_widgets) = .{},

pub fn init(font_face: FontFace) UI {
    const random = std.Random.DefaultPrng.init(random_seed);
    return UI{
        .font_face = font_face,
        .random = random
    };
}

pub fn begin(self: *UI) void {
    self.prev_widgets = self.widgets;
    self.widgets.len = 0;
    self.duplicate_keys.len = 0;
    self.random = std.Random.DefaultPrng.init(random_seed);

    self.widgets.appendAssumeCapacity(Widget{
        .key = root_widget_key,
        .size = .{
            .x = .{ .pixels = @floatFromInt(rl.getScreenWidth()) },
            .y = .{ .pixels = @floatFromInt(rl.getScreenHeight()) },
        },
        .flags = Widget.Flags.initMany(&.{ .passthrough })
    });
    self.pushWidget(root_widget_key);
}

pub fn end(self: *UI) void {
    self.popWidget();

    const widgets: []Widget = self.widgets.slice();

    for (widgets) |*widget| {
        widget.computed_content_size = Vec2{ .x = 0, .y = 0 };
        widget.computed_relative_position = Vec2{ .x = 0, .y = 0 };
    }

    // (1) Calculate stadalone sizes
    for (widgets) |*widget| {
        var text_size = Vec2{ .x = 0, .y = 0 };
        if (widget.text) |text| {
            text_size = self.font_face.measureText(text.content);
        }

        var content_size = &widget.computed_content_size.?;
        inline for (.{ "x", "y" }) |axis| {
            const semantic_size = @field(widget.size, axis);

            if (semantic_size == .pixels) {
                @field(content_size, axis) = semantic_size.pixels;
            } else if (semantic_size == .text) {
                @field(content_size, axis) = @field(text_size, axis);
            }
        }
    }

    // (2) Upward dependent sizes
    for (widgets) |*widget| {
        if (widget.key.eql(root_widget_key)) continue;

        const parent = self.getWidget(widget.parent);
        const parent_size = parent.computed_size().?;

        var content_size = &widget.computed_content_size.?;

        if (widget.size.x == .percent) {
            const percent = widget.size.x.percent;
            content_size.x = parent_size.x * percent;
        }

        if (widget.size.y == .percent) {
            const percent = widget.size.y.percent;
            content_size.y = parent_size.y * percent;
        }
    }

    for (0..widgets.len) |i| {
        const widget = &widgets[widgets.len - i - 1];
        var children = self.listChildren(widget);

        // (3) Calculate relative position of each widget
        switch (widget.layout.kind) {
            .vertical_column => {
                var y_offset: f32 = 0;
                const children_slice: []*Widget = children.slice();
                for (children_slice) |child| {
                    child.computed_relative_position.?.y += y_offset;
                    y_offset += child.computed_size().?.y;
                    y_offset += widget.layout.gap;
                }
            }
        }

        // (4) Downward dependent sizes
        inline for (.{ "x", "y" }) |axis| {
            if (@field(widget.size, axis) == .fit_children) {
                var min = std.math.floatMax(f32);
                var max = std.math.floatMin(f32);

                for (children.constSlice()) |child| {
                    const child_size = child.computed_size().?;
                    const child_position = child.computed_relative_position.?;

                    min = @min(min, @field(child_position, axis));
                    max = @max(max, @field(child_position, axis) + @field(child_size, axis));
                }

                if (children.len > 0) {
                    @field(widget.computed_content_size.?, axis) = max - min;
                }
            }
        }
    }

    for (widgets) |*widget| {
        const computed_size = widget.computed_size().?;
        const relative_position = widget.computed_relative_position.?;

        var computed_rect = Rect{
            .x = relative_position.x,
            .y = relative_position.y,
            .width = computed_size.x,
            .height = computed_size.y,
        };

        if (!widget.key.eql(root_widget_key)) {
            const parent = self.getWidget(widget.parent);
            computed_rect.x += parent.padding.left;
            computed_rect.y += parent.padding.top;

            if (parent.computed_rect) |parent_rect| {
                computed_rect.x += parent_rect.x;
                computed_rect.y += parent_rect.y;
            }
        }

        widget.computed_rect = computed_rect;
    }

    const duplicate_keys = self.duplicate_keys.constSlice();
    for (widgets) |*widget|{
        const rect = widget.computed_rect orelse continue;

        const padding = widget.padding;
        const content_rect = Rect{
            .x = rect.x + padding.left,
            .y = rect.y + padding.top,
            .width = rect.width - padding.left - padding.right,
            .height = rect.height - padding.top - padding.bottom,
        };

        if (widget.background) |background| {
            switch (background) {
                .color => |bg_color| {
                    drawRectangleRoundedUV(rect, widget.corner_radius, bg_color);
                },
                .blur_world => {
                    const bg_color = srcery.xgray10;

                    if (self.blur_background) |texture| {
                        const border = 2.5;

                        {
                            const previous_texture = rl.getShapesTexture();
                            const previous_rect = rl.getShapesTextureRectangle();
                            defer rl.setShapesTexture(previous_texture, previous_rect);

                            const texture_height: f32 = @floatFromInt(texture.height);
                            const shape_rect = rl.Rectangle{
                                .x = rect.x,
                                .y = texture_height - rect.y,
                                .width = rect.width,
                                .height = -rect.height,
                            };
                            rl.setShapesTexture(texture, shape_rect);

                            drawRectangleRoundedUV(rect, widget.corner_radius, bg_color);
                            rl.gl.rlDrawRenderBatchActive();
                        }

                        drawRectangleRoundedLinesEx(
                            rect,
                            widget.corner_radius,
                            border,
                            srcery.bright_white.alpha(0.25)
                        );
                        rl.gl.rlDrawRenderBatchActive();
                    }
                }
            }
        }

        if (widget.text) |text| {
            self.font_face.drawTextCenter(
                text.content,
                rect_utils.center(content_rect),
                text.color
            );
        }
    }

    if (builtin.mode == .Debug) {
        for (widgets) |widget|{
            const rect = widget.computed_rect orelse continue;

            if (Widget.Key.indexOf(duplicate_keys, widget.key) != null) {
                const time: f32 = @floatCast(rl.getTime());
                if (@rem(time, 0.4) < 0.2) {
                    rl.drawRectangleLinesEx(rect, 3, rl.Color.purple);
                } else {
                    rl.drawRectangleLinesEx(rect, 3, rl.Color.red);
                }
            }
        }
    }
}

pub fn pushWidget(self: *UI, key: Widget.Key) void {
    self.widget_stack.appendAssumeCapacity(key);
}

pub fn popWidget(self: *UI) void {
    assert(self.widget_stack.len >= 1);

    _ = self.widget_stack.pop();
}

pub fn topWidget(self: *UI) *Widget {
    const top_key = self.widget_stack.buffer[self.widget_stack.len - 1];
    return self.getWidget(top_key);
}

pub fn layout(self: *UI) *Layout {
    return &self.topWidget().layout;
}

pub fn getOrAppendWidget(self: *UI, key_hash: u16) *Widget {
    const parent = self.topWidget();

    const key = Widget.Key{
        .hash = key_hash,
        .extra = parent.layout.key_extra
    };

    var found_prev_widget: ?Widget = null;
    for (self.prev_widgets.constSlice()) |widget| {
        if (widget.key.eql(key)) {
            found_prev_widget = widget;
            break;
        }
    }

    for (self.widgets.constSlice()) |widget| {
        if (widget.key.eql(key)) {
            self.duplicate_keys.appendAssumeCapacity(widget.key);
            break;
        }
    }

    if (found_prev_widget) |prev_widget| {
        self.widgets.appendAssumeCapacity(prev_widget);
    } else {
        self.widgets.appendAssumeCapacity(Widget{
            .key = key
        });
    }

    const widget = &self.widgets.buffer[self.widgets.len - 1];

    assert(self.widget_stack.len >= 1);
    widget.parent = self.widget_stack.buffer[self.widget_stack.len-1];

    return widget;
}

pub fn getWidget(self: *UI, key: Widget.Key) *Widget {
    for (self.widgets.slice()) |*widget| {
        if (widget.key.eql(key)) {
            return widget;
        }
    }

    @panic("Failed to find widget");
}

pub fn getInteraction(self: *UI, widget: *const Widget) Interaction {
    var interaction = Interaction{
        .widget = widget.key
    };

    const rect = widget.computed_rect orelse return interaction;

    if (!self.disable_mouse_interaction) {
        const mouse = rl.getMousePosition();
        if (!widget.flags.contains(.passthrough) and rect_utils.isInsideVec2(rect, mouse)) {
            interaction.hovering = true;

            if (rl.isMouseButtonPressed(.mouse_button_left)) {
                interaction.pressed = true;
            }

            if (rl.isMouseButtonReleased(.mouse_button_left)) {
                interaction.released = true;
                if (widget.flags.contains(.clickable)) {
                    interaction.clicked = true;
                }
            }

            if (rl.isMouseButtonDown(.mouse_button_left)) {
                interaction.held_down = true;
            }
        }
    }

    return interaction;
}

pub fn isHoveringAnything(self: *UI) bool {
    const widgets: []Widget = self.widgets.slice();
    for (widgets) |*widget|{
        const interaction = self.getInteraction(widget);
        if (interaction.hovering) {
            return true;
        }
    }

    return false;
}

pub fn randomWidgetHash(self: *UI) u16 {
    const rng = self.random.random();
    return rng.int(u16);
}

fn listChildren(self: *UI, parent: *Widget) std.BoundedArray(*Widget, max_widgets) {
    var children: std.BoundedArray(*Widget, max_widgets) = .{};

    const widgets: []Widget = self.widgets.slice();
    for (widgets) |*child| {
        if (child == parent) {
            continue;
        }

        if (child.parent.eql(parent.key)) {
            children.appendAssumeCapacity(child);
        }
    }

    return children;
}

// Modified version of `DrawRectangleRounded` where the UV texture coordiantes are consistent and align
fn drawRectangleRoundedUV(rec: rl.Rectangle, radius: f32, color: rl.Color) void {
    if (radius <= 0 or rec.width <= 1 or rec.height <= 1) {
        rl.drawRectangleRec(rec, color);
        return;
    }

    if (radius <= 0.0) return;

    // Calculate the maximum angle between segments based on the error rate (usually 0.5f)
    const smooth_circle_error_rate = 0.5;
    const th: f32 = std.math.acos(2 * std.math.pow(f32, 1 - smooth_circle_error_rate / radius, 2) - 1);
    var segments: i32 = @intFromFloat(@ceil(2 * std.math.pi / th) / 4.0);
    segments = @max(segments, 4);

    const step_length = 90.0 / @as(f32, @floatFromInt(segments));

    // Quick sketch to make sense of all of this,
    // there are 9 parts to draw, also mark the 12 points we'll use
    //
    //       P0____________________P1
    //       /|                    |\
    //      /1|          2         |3\
    //  P7 /__|____________________|__\ P2
    //    |   |P8                P9|   |
    //    | 8 |          9         | 4 |
    //    | __|____________________|__ |
    //  P6 \  |P11              P10|  / P3
    //      \7|          6         |5/
    //       \|____________________|/
    //       P5                    P4

    // Coordinates of the 12 points that define the rounded rect
    const radius_u = radius / rec.width;
    const radius_v = radius / rec.height;
    const points = [_]rl.Vector2{
        .{ .x = radius_u     , .y = 0            }, // P0
        .{ .x = 1 - radius_u , .y = 0            }, // P1
        .{ .x = 1            , .y = radius_v     }, // P2
        .{ .x = 1            , .y = 1 - radius_v }, // P3
        .{ .x = 1 - radius_u , .y = 1            }, // P4
        .{ .x = radius_u     , .y = 1            }, // P5
        .{ .x = 0            , .y = 1 - radius_v }, // P6
        .{ .x = 0            , .y = radius_v     }, // P7
        .{ .x = radius_u     , .y = radius_v     }, // P8
        .{ .x = 1 - radius_u , .y = radius_v     }, // P9
        .{ .x = 1 - radius_u , .y = 1 - radius_v }, // P10
        .{ .x = radius_u     , .y = 1 - radius_v }, // P11
    };

    const texture = rl.getShapesTexture();
    const shape_rect = rl.getShapesTextureRectangle();

    const texture_width: f32 = @floatFromInt(texture.width);
    const texture_height: f32 = @floatFromInt(texture.height);

    rl.gl.rlBegin(rlgl_h.RL_TRIANGLES);
    defer rl.gl.rlEnd();

    rl.gl.rlSetTexture(texture.id);
    defer rl.gl.rlSetTexture(0);

    // Draw all of the 4 corners: [1] Upper Left Corner, [3] Upper Right Corner, [5] Lower Right Corner, [7] Lower Left Corner
    const centers = [_]rl.Vector2{ points[8], points[9], points[10], points[11] };
    const angles = [_]f32{ 180.0, 270.0, 0.0, 90.0 };
    for (0..4) |k| {
        var angle = angles[k];
        const center = centers[k];
        for (0..@intCast(segments)) |_| {
            rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);

            const rad_per_deg = std.math.rad_per_deg;

            const triangle = .{
                center,
                .{
                    .x = center.x + @cos(rad_per_deg*(angle + step_length))*radius_u,
                    .y = center.y + @sin(rad_per_deg*(angle + step_length))*radius_v
                },
                .{
                    .x = center.x + @cos(rad_per_deg * angle)*radius_u,
                    .y = center.y + @sin(rad_per_deg * angle)*radius_v
                }
            };

            inline for (triangle) |point| {
                rl.gl.rlTexCoord2f(
                    (shape_rect.x + shape_rect.width  * point.x) / texture_width,
                    (shape_rect.y + shape_rect.height * point.y) / texture_height
                );
                rl.gl.rlVertex2f(
                    rec.x + rec.width * point.x,
                    rec.y + rec.height * point.y
                );
            }

            angle += step_length;
        }
    }

    // [2] Upper Rectangle
    rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
    inline for (.{ 0, 8, 9, 1, 0, 9 }) |index| {
        const point = points[index];
        rl.gl.rlTexCoord2f(
            (shape_rect.x + shape_rect.width  * point.x) / texture_width,
            (shape_rect.y + shape_rect.height * point.y) / texture_height
        );
        rl.gl.rlVertex2f(
            rec.x + rec.width * point.x,
            rec.y + rec.height * point.y
        );
    }

    // [4] Right Rectangle
    rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
    inline for (.{ 9, 10, 3, 2, 9, 3 }) |index| {
        const point = points[index];
        rl.gl.rlTexCoord2f(
            (shape_rect.x + shape_rect.width  * point.x) / texture_width,
            (shape_rect.y + shape_rect.height * point.y) / texture_height
        );
        rl.gl.rlVertex2f(
            rec.x + rec.width * point.x,
            rec.y + rec.height * point.y
        );
    }

    // [6] Bottom Rectangle
    rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
    inline for (.{ 11, 5, 4, 10, 11, 4 }) |index| {
        const point = points[index];
        rl.gl.rlTexCoord2f(
            (shape_rect.x + shape_rect.width  * point.x) / texture_width,
            (shape_rect.y + shape_rect.height * point.y) / texture_height
        );
        rl.gl.rlVertex2f(
            rec.x + rec.width * point.x,
            rec.y + rec.height * point.y
        );
    }

    // [8] Left Rectangle
    rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
    inline for (.{ 7, 6, 11, 8, 7, 11 }) |index| {
        const point = points[index];
        rl.gl.rlTexCoord2f(
            (shape_rect.x + shape_rect.width  * point.x) / texture_width,
            (shape_rect.y + shape_rect.height * point.y) / texture_height
        );
        rl.gl.rlVertex2f(
            rec.x + rec.width * point.x,
            rec.y + rec.height * point.y
        );
    }

    // [9] Middle Rectangle
    rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
    inline for (.{ 8, 11, 10, 9, 8, 10 }) |index| {
        const point = points[index];
        rl.gl.rlTexCoord2f(
            (shape_rect.x + shape_rect.width  * point.x) / texture_width,
            (shape_rect.y + shape_rect.height * point.y) / texture_height
        );
        rl.gl.rlVertex2f(
            rec.x + rec.width * point.x,
            rec.y + rec.height * point.y
        );
    }
}

// Modified version of `DrawRectangleRoundedLinesEx` where the corner radius is provided
fn drawRectangleRoundedLinesEx(rec: rl.Rectangle, radius: f32, _line_thick: f32, color: rl.Color) void {
    var line_thick = _line_thick;
    if (line_thick < 0) line_thick = 0;

    // Not a rounded rectangle
    if (radius <= 0.0) {
        rl.drawRectangleLinesEx(rl.Rectangle{
            .x = rec.x,
            .y = rec.y,
            .width = rec.width,
            .height = rec.height
        }, line_thick, color);
        return;
    }

    // Calculate number of segments to use for the corners
    const smooth_circle_error_rate = 0.5;
    const th: f32 = std.math.acos(2 * std.math.pow(f32, 1 - smooth_circle_error_rate / radius, 2) - 1);
    var segments: i32 = @intFromFloat(@ceil(2 * std.math.pi / th) / 4.0);
    segments = @max(segments, 4);

    const step_length = 90.0/@as(f32, @floatFromInt(segments));
    const outer_radius = radius;
    const inner_radius = radius - line_thick;

    // Quick sketch to make sense of all of this,
    // marks the 16 + 4(corner centers P16-19) points we'll use
    //
    //        P0 ================== P1
    //       // P8                P9 \\
    //      //                        \\
    //  P7 // P15                  P10 \\ P2
    //    ||   *P16             P17*    ||
    //    ||                            ||
    //    || P14                   P11  ||
    //  P6 \\  *P19             P18*   // P3
    //      \\                        //
    //       \\ P13              P12 //
    //        P5 ================== P4
    const points = [_]rl.Vector2{
        .{ .x = outer_radius              , .y = 0                          }, // P0
        .{ .x = rec.width - outer_radius  , .y = 0                          }, // P1
        .{ .x = rec.width                 , .y = outer_radius               }, // P2
        .{ .x = rec.width                 , .y = rec.height - outer_radius  }, // P3
        .{ .x = rec.width - outer_radius  , .y = rec.height                 }, // P4
        .{ .x = outer_radius              , .y = rec.height                 }, // P5
        .{ .x = 0                         , .y = rec.height - outer_radius  }, // P6
        .{ .x = 0                         , .y = outer_radius               }, // P7

        .{ .x = outer_radius              , .y = line_thick                 }, // P8
        .{ .x = rec.width - outer_radius  , .y = line_thick                 }, // P9
        .{ .x = rec.width - line_thick    , .y = outer_radius               }, // P10
        .{ .x = rec.width - line_thick    , .y = rec.height - outer_radius  }, // P11
        .{ .x = rec.width - outer_radius  , .y = rec.height - line_thick    }, // P12
        .{ .x = outer_radius              , .y = rec.height - line_thick    }, // P13
        .{ .x = line_thick                , .y = rec.height - outer_radius  }, // P14
        .{ .x = line_thick                , .y = outer_radius               }, // P15
    };

    const centers = [_]rl.Vector2{
        .{ .x = outer_radius             , .y = outer_radius              }, // P16
        .{ .x = rec.width - outer_radius , .y = outer_radius              }, // P17
        .{ .x = rec.width - outer_radius , .y = rec.height - outer_radius }, // P18
        .{ .x = outer_radius             , .y = rec.height - outer_radius }, // P18
    };

    const angles = [_]f32{ 180.0, 270.0, 0.0, 90.0 };

    if (line_thick > 1) {
        rl.gl.rlBegin(rlgl_h.RL_TRIANGLES);
        defer rl.gl.rlEnd();

        // Draw all of the 4 corners first: Upper Left Corner, Upper Right Corner, Lower Right Corner, Lower Left Corner
        for (centers, angles) |center, initialAngle| {
            var angle = initialAngle;
            for (0..@intCast(segments)) |_| {

                const rad_per_deg = std.math.rad_per_deg;
                const next_angle = angle + step_length;

                const rect_points = .{
                    rl.Vector2{
                        .x = @cos(rad_per_deg * angle) * inner_radius,
                        .y = @sin(rad_per_deg * angle) * inner_radius
                    },
                    rl.Vector2{
                        .x = @cos(rad_per_deg * next_angle) * inner_radius,
                        .y = @sin(rad_per_deg * next_angle) * inner_radius
                    },
                    rl.Vector2{
                        .x = @cos(rad_per_deg * angle) * outer_radius,
                        .y = @sin(rad_per_deg * angle) * outer_radius
                    },
                    rl.Vector2{
                        .x = @cos(rad_per_deg * next_angle) * outer_radius,
                        .y = @sin(rad_per_deg * next_angle) * outer_radius
                    }
                };

                rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
                inline for (.{ 0, 1, 2, 1, 3, 2 }) |i| {
                    rl.gl.rlVertex2f(
                        rec.x + center.x + rect_points[i].x,
                        rec.y + center.y + rect_points[i].y
                    );
                }

                angle = next_angle;
            }
        }

        // Upper rectangle
        rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
        inline for (.{ 0, 8, 9, 1, 0, 9 }) |i| {
            rl.gl.rlVertex2f(rec.x + points[i].x, rec.y + points[i].y);
        }

        // Right rectangle
        rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
        inline for (.{ 10, 11, 3, 2, 10, 3 }) |i| {
            rl.gl.rlVertex2f(rec.x + points[i].x, rec.y + points[i].y);
        }

        // Lower rectangle
        rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
        inline for (.{ 13, 5, 4, 12, 13, 4 }) |i| {
            rl.gl.rlVertex2f(rec.x + points[i].x, rec.y + points[i].y);
        }

        // Left rectangle
        rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
        inline for (.{ 7, 6, 14, 15, 7, 14 }) |i| {
            rl.gl.rlVertex2f(rec.x + points[i].x, rec.y + points[i].y);
        }
    } else {
        // Use LINES to draw the outline
        rl.gl.rlBegin(rlgl_h.RL_LINES);
        defer rl.gl.rlEnd();

        // Draw all the 4 corners first: Upper Left Corner, Upper Right Corner, Lower Right Corner, Lower Left Corner
        for (centers, angles) |center, initialAngle| {
            var angle = initialAngle;

            for (0..@intCast(segments)) |_| {
                const rad_per_deg = std.math.rad_per_deg;
                const next_angle = angle + step_length;

                rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
                rl.gl.rlVertex2f(
                    rec.x + center.x + @cos(rad_per_deg*angle)*outer_radius,
                    rec.y + center.y + @sin(rad_per_deg*angle)*outer_radius
                );
                rl.gl.rlVertex2f(
                    rec.x + center.x + @cos(rad_per_deg*next_angle)*outer_radius,
                    rec.y + center.y + @sin(rad_per_deg*next_angle)*outer_radius
                );

                angle = next_angle;
            }
        }

        // And now the remaining 4 lines
        inline for (.{ 0, 2, 4, 6 }) |i| {
            rl.gl.rlColor4ub(color.r, color.g, color.b, color.a);
            rl.gl.rlVertex2f(rec.x + points[i + 0].x, rec.y + points[i + 0].y);
            rl.gl.rlVertex2f(rec.x + points[i + 1].x, rec.y + points[i + 1].y);
        }
    }
}
