// #include "vendor/stb_truetype.h"
// #include "vendor/glad/glad.h"
#include <stdio.h>

extern MA_Arena *G_Frame;

struct Font_Glyph {
    // Scaling transform applied to these
    Vec2  size, offset;
    float x_advance;
    float left_side_bearing;

    Rect2 atlas_bounding_box;
};

struct Font {
    uint8_t   *atlas;
    Font_Glyph glyphs[96];
    int        glyph_count;

    uint32_t first_char, last_char;

    unsigned texture_id;

    // This is for oversampling
    // 0.5 = we draw the font as 2 times smaller then it is.
    // Should lead to better quality result.
    float scaling_transform;

    // scaling transform is applied to these
    float size;
    float ascent;
    float descent;
    float line_gap;

    // scaling factor not applied, not sure if these will be useful
    float scale;
    float em_scale;

    Rect2 white_texture_bounding_box;
};

struct R2_Atlas {
    uint8_t *bitmap;
    Vec2I    sizei;
    Vec2     size;
    Vec2     inverse_size;
    Vec2I    cursor;
    int      biggest_height;
    Rect2    white_texture_bounding_box;
    unsigned texture_id;
};

struct File {
    uint8_t *content;
    uint64_t size;
};

typedef struct R2_Vertex R2_Vertex;
struct R2_Vertex {
    Vec2 pos;
    Vec2 tex;
    Vec4 color;
};
#define R2_Vertex_GLSL                                       \
    "layout(location=0) uniform Vec2 U_InvHalfScreenSize;\n" \
    "layout(location=0) in Vec2 IN_Pos;\n"                   \
    "layout(location=1) in Vec2 IN_Tex;\n"                   \
    "layout(location=2) in Vec4 IN_Color;\n"

#define R2_STRINGIFY2(x) #x
#define R2_STRINGIFY(x) R2_STRINGIFY2(x)
#define R2_GLSL_HEADER(x)                          \
    "#version 450 core\n"                          \
    "#line " R2_STRINGIFY(x) "\n"                  \
                             "#define Vec2 vec2\n" \
                             "#define Vec3 vec3\n" \
                             "#define Vec4 vec4\n"

struct R2_VertexNode {
    R2_VertexNode *next;
    int            count;
    R2_Vertex      vertices[1024 * 64];
};

struct R2_VertexList {
    R2_VertexNode *first;
    R2_VertexNode *last;
};

struct R2_Shader {
    unsigned pipeline, fshader, vshader;
};

struct V_Circle {
    Vec2  pos;
    float color_index;
};
const int R2_MaxCircleCount   = 1024 * 64;
const int R2_CircleBufferSize = R2_MaxCircleCount * sizeof(V_Circle);

struct R2_Render {
    bool          initialized;
    R2_VertexList base_vertex_list;
    R2_VertexList ui_vertex_list;

    R2_VertexList *vertex_list;
    Font          *font;
    Font           font_small;
    Font           font_medium;
    Font           font_large;
    Font           font_very_large;
    R2_Atlas       atlas;
    unsigned       vbo, vao, pipeline, fshader, vshader;

    int64_t  vertex_count; //@stats
    MA_Arena arena;

    struct {
        MA_Arena  arena;
        R2_Shader shader;
        float     radius;

        Array<Vec4> colors;
        int         color_iter;

        int      instances_to_render;
        int      mesh_vertex_count;
        int      mesh_segment_count;
        unsigned vbo_positions;
        unsigned vbo_mesh;
        unsigned vao;
    } circle;
};
R2_Render R2;

struct R2_RectNode {
    R2_RectNode *next;
    Rect2        rect;
    int          utf8_codepoint_byte_size;
};

struct R2_StringMeasure {
    R2_RectNode *first_rect;
    R2_RectNode *last_rect;
    Rect2        rect;
};

// @todo: Add multiple font support
// Need to add a way to output multiple draw calls instead of 1
// So a command buffer of sorts
///

File ReadFile(MA_Arena *arena, S8_String filename) {
    IO_Assertf(filename.str[filename.len] == 0, "String was not null terminated");

    File  result = {};
    FILE *f      = fopen(filename.str, "r");
    if (f) {
        fseek(f, 0, SEEK_END);
        long fsize = ftell(f);
        fseek(f, 0, SEEK_SET);

        result.content = (uint8_t *)MA_PushSizeNonZeroed(arena, fsize + 1);
        fread(result.content, fsize, 1, f);

        result.size                 = fsize;
        result.content[result.size] = 0;

        fclose(f);
    }

    return result;
}

R2_Atlas R2_CreateAtlas(MA_Arena *arena, Vec2I size) {
    R2_Atlas result       = {};
    result.sizei          = size;
    result.size           = Vec2IToVec2(size);
    result.inverse_size.x = 1.f / result.size.x;
    result.inverse_size.y = 1.f / result.size.y;
    result.bitmap         = MA_PushArray(arena, uint8_t, size.x * size.y);

    // Add a whitebox first for rectangle rendering
    for (int y = 0; y < 16; y++) {
        for (int x = 0; x < 16; x++) {
            uint8_t *dst = result.bitmap + x + y * result.sizei.x;
            *dst         = 0xff;
        }
    }
    // Skipping some pixels to avoid linear interpolation on edges
    result.white_texture_bounding_box = {2.f * result.inverse_size.x, 2.f / result.size.y, 14.f * result.inverse_size.x, 14.f / result.size.y};
    result.cursor.x += 16;
    result.biggest_height += 16;
    return result;
}

Rect2 R2_PackBitmapInvertY(R2_Atlas *atlas, uint8_t *bitmap, int width, int height) {
    // Packing into a texture atlas
    // @Inefficient The algorithm is a simplest thing I had in mind, first we advance
    // through the atlas in X packing consecutive glyphs. After we get to the end of the row
    // we advance to the next row by the Y size of the biggest packed glyph. If we get to the
    // end of atlas and fail to pack everything the app panics.

    int spacing = 4;
    if (atlas->cursor.x + width > atlas->sizei.x) {
        if (atlas->cursor.y + height < atlas->sizei.y) {
            atlas->cursor.x = 0;
            atlas->cursor.y += atlas->biggest_height + spacing;
        } else {
            IO_Assert(!"Error while packing a font into atlas. Atlas size for this font scale is a bit too small");
        }
    }

    // Write the bitmap with inverted Y
    uint8_t *src = bitmap;
    for (int y = atlas->cursor.y + height - 1; y >= atlas->cursor.y; y--) {
        for (int x = atlas->cursor.x; x < atlas->cursor.x + width; x++) {
            uint8_t *dst = atlas->bitmap + x + y * atlas->sizei.x;
            *dst         = *src++;
        }
    }

    Vec2  size   = {(float)width * atlas->inverse_size.x, (float)height / atlas->size.y};
    Vec2  pos    = {(float)atlas->cursor.x * atlas->inverse_size.x, (float)atlas->cursor.y / atlas->size.y};
    Rect2 result = {pos.x, pos.y, pos.x + size.x, pos.y + size.y};

    atlas->cursor.x += width + spacing;
    if (height > atlas->biggest_height) {
        atlas->biggest_height = height;
    }

    return result;
}

Font Font_Create(R2_Atlas *atlas, float size, S8_String path, float oversampling) {
    MA_Scratch scratch;
    File       font_file = ReadFile(scratch, path);

    Font result              = {};
    result.scaling_transform = 1.f / oversampling;
    result.size              = oversampling * size;

    result.first_char = ' ';
    result.last_char  = '~';
    stbtt_fontinfo stb_font;
    if (font_file.content) {
        int success = stbtt_InitFont(&stb_font, font_file.content, 0);
        if (success) {
            int ascent, descent, gap;
            stbtt_GetFontVMetrics(&stb_font, &ascent, &descent, &gap);
            result.scale                      = stbtt_ScaleForPixelHeight(&stb_font, result.size);
            result.em_scale                   = stbtt_ScaleForMappingEmToPixels(&stb_font, result.size);
            result.ascent                     = (float)ascent * result.scale;
            result.descent                    = (float)descent * result.scale;
            result.line_gap                   = (float)gap * result.scale;
            result.white_texture_bounding_box = atlas->white_texture_bounding_box;

            for (uint32_t ascii_symbol = result.first_char; ascii_symbol <= result.last_char; ascii_symbol++) {
                int      width, height, xoff, yoff;
                uint8_t *bitmap = (uint8_t *)stbtt_GetCodepointBitmap(&stb_font, 0, result.scale, ascii_symbol, &width, &height, &xoff, &yoff);
                defer { stbtt_FreeBitmap(bitmap, 0); };

                int x_advance, left_side_bearing;
                stbtt_GetCodepointHMetrics(&stb_font, ascii_symbol, &x_advance, &left_side_bearing);

                Font_Glyph *g         = result.glyphs + result.glyph_count++;
                g->atlas_bounding_box = R2_PackBitmapInvertY(atlas, bitmap, width, height);
                g->size               = {(float)width, (float)height};

                // Offset y needs to be inverted cause bitmap has inverted Y
                g->offset            = {(float)xoff, -(g->size.y + (float)yoff)};
                g->x_advance         = x_advance * result.scale;
                g->left_side_bearing = left_side_bearing * result.scale;

                // Apply scaling transform
                g->offset *= result.scaling_transform;
                g->x_advance *= result.scaling_transform;
                g->left_side_bearing *= result.scaling_transform;
                g->size *= result.scaling_transform;
            }

            result.ascent *= result.scaling_transform;
            result.descent *= result.scaling_transform;
            result.size *= result.scaling_transform;
            result.line_gap *= result.scaling_transform;
        }
    }

    return result;
}

Font_Glyph &Font_GetGlyph(Font *font, uint32_t codepoint) {
    bool is_in_range = codepoint >= font->first_char && codepoint <= font->last_char;
    if (is_in_range) {
        uint32_t index = codepoint - font->first_char;
        return font->glyphs[index];
    } else {
        uint32_t index = '?' - font->first_char;
        return font->glyphs[index];
    }
}

void GL_DebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *user) {
    IO_Printf("%s\n", message);
    if (severity == GL_DEBUG_SEVERITY_HIGH || severity == GL_DEBUG_SEVERITY_MEDIUM) {
        IO_Assert(!"OpenGL API usage error!");
    }
}

void GL_Init(MU_Context *mu) {
    int success = gladLoadGLLoader(mu->gl_get_proc_address);
    IO_Assert(success);
    glDebugMessageCallback(&GL_DebugCallback, NULL);
    glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
    void R2_Init(R2_Render * R);
    R2_Init(&R2);
}

void GL_Clear(Vec2I window_size, Vec4 clear_color = {0, 0, 0, 0}) {
    glViewport(0, 0, window_size.x, window_size.y);
    glClearColor(clear_color.r, clear_color.g, clear_color.b, clear_color.a);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}

R2_Shader R2_CreateShader(char *glsl_vshader, char *glsl_fshader) {
    R2_Shader result = {};
    result.vshader   = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &glsl_vshader);
    result.fshader   = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &glsl_fshader);

    GLint linked;
    glGetProgramiv(result.vshader, GL_LINK_STATUS, &linked);
    if (!linked) {
        char message[1024];
        glGetProgramInfoLog(result.vshader, sizeof(message), NULL, message);
        IO_FatalErrorf("[GL] Failed to create vertex shader! %s", message);
    }

    glGetProgramiv(result.fshader, GL_LINK_STATUS, &linked);
    if (!linked) {
        char message[1024];
        glGetProgramInfoLog(result.fshader, sizeof(message), NULL, message);
        IO_FatalErrorf("[GL] Failed to create fragment shader! %s", message);
    }

    glGenProgramPipelines(1, &result.pipeline);
    glUseProgramStages(result.pipeline, GL_VERTEX_SHADER_BIT, result.vshader);
    glUseProgramStages(result.pipeline, GL_FRAGMENT_SHADER_BIT, result.fshader);
    return result;
}

Vec2 *MESH_GenerateCircle(MA_Arena *arena, float radius, int segment_count) {
    int   vertex_count = segment_count * 3;
    Vec2 *circle_pos   = MA_PushArray(arena, Vec2, vertex_count);
    Vec2 *points       = MA_PushArray(G_Frame, Vec2, segment_count + 1);
    for (int i = 0; i < segment_count; i += 1) {
        float radians = 2.0f * PI32 * float(i) / float(segment_count);
        float x       = radius * cosf(radians);
        float y       = radius * sinf(radians);
        points[i]     = {x, y};
    }
    points[segment_count] = points[0]; // wrap around

    int point_i   = 0;
    int segment_i = 0;
    for (; segment_i < vertex_count; segment_i += 3, point_i += 1) {
        Vec2 *it = circle_pos + segment_i;
        it[0]    = {points[point_i]};
        it[1]    = {points[point_i + 1]};
        it[2]    = {};
    }
    return circle_pos;
}

void R2_Init(R2_Render *R) {
    if (R2.initialized) return;
    R2.vertex_list = &R2.base_vertex_list;

    R2.initialized             = true;
    MA_Checkpoint atlas_memory = MA_Save(&R2.arena);

    R2_Atlas atlas = R2_CreateAtlas(atlas_memory.arena, {2048, 2048});
    R2.atlas       = atlas;

    R2.font_small      = Font_Create(&atlas, 32.f, "C:/windows/fonts/Calibri.ttf"_s, 1.0f);
    R2.font_medium     = Font_Create(&atlas, 32.f, "C:/windows/fonts/Calibri.ttf"_s, 2.0f);
    R2.font_large      = Font_Create(&atlas, 32.f, "C:/windows/fonts/Calibri.ttf"_s, 4.0f);
    R2.font_very_large = Font_Create(&atlas, 32.f, "C:/windows/fonts/Calibri.ttf"_s, 8.0f);
    // Upload to GPU
    {
        glCreateTextures(GL_TEXTURE_2D, 1, &atlas.texture_id);
        glTextureParameteri(atlas.texture_id, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTextureParameteri(atlas.texture_id, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTextureParameteri(atlas.texture_id, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTextureParameteri(atlas.texture_id, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTextureStorage2D(atlas.texture_id, 1, GL_R8, R2.atlas.sizei.x, R2.atlas.sizei.y);
        glTextureSubImage2D(atlas.texture_id, 0, 0, 0, R2.atlas.sizei.x, R2.atlas.sizei.y, GL_RED, GL_UNSIGNED_BYTE, atlas.bitmap);
    }
    R2.font_medium.texture_id     = atlas.texture_id;
    R2.font_small.texture_id      = atlas.texture_id;
    R2.font_large.texture_id      = atlas.texture_id;
    R2.font_very_large.texture_id = atlas.texture_id;
    R2.font                       = &R2.font_medium;
    MA_Load(atlas_memory);

    {
        glCreateBuffers(1, &R2.vbo);
        glNamedBufferStorage(R2.vbo, lengthof(R2_VertexNode::vertices) * sizeof(R2_Vertex), 0, GL_DYNAMIC_STORAGE_BIT);
    }

    {
        glCreateVertexArrays(1, &R2.vao);

        GLint vbuf_index = 0;
        glVertexArrayVertexBuffer(R2.vao, vbuf_index, R2.vbo, 0, sizeof(struct R2_Vertex));

        GLint a_pos = 0;
        glVertexArrayAttribFormat(R2.vao, a_pos, 2, GL_FLOAT, GL_FALSE, offsetof(struct R2_Vertex, pos));
        glVertexArrayAttribBinding(R2.vao, a_pos, vbuf_index);
        glEnableVertexArrayAttrib(R2.vao, a_pos);

        GLint a_tex = 1;
        glVertexArrayAttribFormat(R2.vao, a_tex, 2, GL_FLOAT, GL_FALSE, offsetof(struct R2_Vertex, tex));
        glVertexArrayAttribBinding(R2.vao, a_tex, vbuf_index);
        glEnableVertexArrayAttrib(R2.vao, a_tex);

        GLint a_color = 2;
        glVertexArrayAttribFormat(R2.vao, a_color, 4, GL_FLOAT, GL_FALSE, offsetof(struct R2_Vertex, color));
        glVertexArrayAttribBinding(R2.vao, a_color, vbuf_index);
        glEnableVertexArrayAttrib(R2.vao, a_color);
    }

    {
        char *glsl_vshader = R2_GLSL_HEADER(__LINE__) R2_Vertex_GLSL R"==(
            out gl_PerVertex { vec4 gl_Position; }; // required because of ARB_separate_shader_objects
            out vec2 OUT_UV;
            out vec4 OUT_Color;

            void main() {
                vec2 pos = IN_Pos * U_InvHalfScreenSize;
                pos -= vec2(1, 1);

                gl_Position = vec4(pos, 0, 1);
                OUT_UV = IN_Tex;
                OUT_Color = IN_Color;
            }
        )==";

        char     *glsl_fshader = R2_GLSL_HEADER(__LINE__) R"==(
            in vec2 IN_UV;
            in vec4 IN_Color;

            layout (binding=0) uniform sampler2D S_Texture;
            layout (location=0) out vec4 OUT_Color;

            vec3 lerp(vec3 v0, vec3 v1, float t) {
                return (1 - t) * v0 + t * v1;
            }

            void main() {
                vec4 c = IN_Color;
                c.a *= texture(S_Texture, IN_UV).r;
                OUT_Color = c;
            }
        )==";
        R2_Shader shader       = R2_CreateShader(glsl_vshader, glsl_fshader);
        R2.vshader             = shader.vshader;
        R2.fshader             = shader.fshader;
        R2.pipeline            = shader.pipeline;
    }

    //
    // Prepare circle rendering
    //
    // @note: Circle gets later updated before rendering
    {
        const int segment_count      = 16;
        const int vertex_count       = segment_count * 3;
        R2.circle.mesh_vertex_count  = vertex_count;
        R2.circle.mesh_segment_count = segment_count;
        R2.circle.radius             = 2.0f;
        Vec2 *circle_pos             = MESH_GenerateCircle(G_Frame, R2.circle.radius, segment_count);

        glCreateBuffers(1, &R2.circle.vbo_mesh);
        glNamedBufferStorage(R2.circle.vbo_mesh, sizeof(Vec2) * vertex_count, circle_pos, GL_DYNAMIC_STORAGE_BIT);

        glCreateBuffers(1, &R2.circle.vbo_positions);
        glNamedBufferStorage(R2.circle.vbo_positions, R2_CircleBufferSize, 0, GL_DYNAMIC_STORAGE_BIT);

        {
            unsigned vao = 0;
            glCreateVertexArrays(1, &vao);

            glVertexArrayVertexBuffer(vao, 0 /*vbuf_index*/, R2.circle.vbo_mesh, 0, sizeof(Vec2));
            glVertexArrayVertexBuffer(vao, 1 /*vbuf_index*/, R2.circle.vbo_positions, 0, sizeof(V_Circle));

            GLint IN_Pos = 0;
            glVertexArrayAttribFormat(vao, IN_Pos, 2, GL_FLOAT, GL_FALSE, 0);
            glVertexArrayAttribBinding(vao, IN_Pos, 0 /*vbuf_index*/);
            glEnableVertexArrayAttrib(vao, IN_Pos);

            GLint IN_Offset = 1;
            glVertexArrayAttribFormat(vao, IN_Offset, 2, GL_FLOAT, GL_FALSE, 0);
            glVertexArrayAttribBinding(vao, IN_Offset, 1 /*vbuf_index*/);
            glEnableVertexArrayAttrib(vao, IN_Offset);
            glVertexArrayBindingDivisor(vao, IN_Offset, 1);

            GLint IN_ColorIndex = 2;
            glVertexArrayAttribFormat(vao, IN_ColorIndex, 1, GL_FLOAT, GL_FALSE, offsetof(V_Circle, color_index));
            glVertexArrayAttribBinding(vao, IN_ColorIndex, 1 /*vbuf_index*/);
            glEnableVertexArrayAttrib(vao, IN_ColorIndex);
            glVertexArrayBindingDivisor(vao, IN_ColorIndex, 1);

            R2.circle.vao = vao;
        }

        char *glsl_vshader = R2_GLSL_HEADER(__LINE__) R"==(
        layout(location=0) uniform Vec2 U_InvHalfScreenSize;
        layout(location=1) uniform Vec4 U_Colors[256]; // :Colors

        layout(location=0) in Vec2 IN_Pos;
        layout(location=1) in Vec2 IN_Offset;
        layout(location=2) in float IN_ColorIndex;

        out gl_PerVertex { vec4 gl_Position; }; // required because of ARB_separate_shader_objects
        out vec4 OUT_Color;

        void main() {
            vec2 pos = (IN_Pos+IN_Offset)*U_InvHalfScreenSize;
            pos -= vec2(1, 1);

            gl_Position = vec4(pos, 0, 1);
            OUT_Color = U_Colors[int(IN_ColorIndex)];
        }
    )==";

        char *glsl_fshader = R2_GLSL_HEADER(__LINE__) R"==(
        in vec4 IN_Color;

        layout (location=0) out vec4 OUT_Color;

        void main() {
            vec4 c = IN_Color;
            OUT_Color = c;
        }
    )==";
        R2.circle.shader   = R2_CreateShader(glsl_vshader, glsl_fshader);
        MA_Init(&R2.circle.arena);
        R2.circle.arena.alignment = 0;

        // :Colors
        RandomSeed seed = {193};
        {
            R2.circle.colors.reserve(256);
            for (int i = 0; i < 256; i += 1) {
                Vec4 hsl = {0, 0.4f, 0.60f, 1.0f};
                hsl.r    = (float)GetRandomNormal(&seed);
                Vec4 rgb = HSLToRGB(hsl);
                R2.circle.colors.add(rgb);
            }
        }
    }
}

Vec4 R2_GetColor(int *idx) {
    int index = R2.circle.color_iter % 256;
    R2.circle.color_iter += 1;
    Vec4 result = R2.circle.colors[index];
    *idx        = index;
    return result;
}

R2_Vertex *R2_AllocVertex(MA_Arena *arena, R2_VertexList *list, int count) {
    R2_VertexNode *node = list->last;
    if (node == 0 || node->count + count > lengthof(node->vertices)) {
        node = MA_PushStruct(arena, R2_VertexNode);
        SLL_QUEUE_ADD(list->first, list->last, node);
    }

    R2.vertex_count += count;
    R2_Vertex *result = node->vertices + node->count;
    node->count += count;
    return result;
}

void R2_PushVertex(MA_Arena *arena, R2_VertexList *list, R2_Vertex *vertices, int count) {
    R2_VertexNode *node = list->last;
    if (node == 0 || node->count + count > lengthof(node->vertices)) {
        node = MA_PushStruct(arena, R2_VertexNode);
        SLL_QUEUE_ADD(list->first, list->last, node);
    }

    R2.vertex_count += count;
    for (int i = 0; i < count; i += 1) {
        node->vertices[node->count++] = vertices[i];
    }
}

void R2_DrawCircle(Vec2 pos, float radius, Vec4 color) {
    const int segment_count = 16;
    const int vertex_count  = segment_count * 3;
    Vec2      points[segment_count + 1];
    for (int i = 0; i < lengthof(points); i += 1) {
        float radians = 2.0f * PI32 * float(i) / float(segment_count);
        float x       = radius * cosf(radians);
        float y       = radius * sinf(radians);
        points[i]     = {x, y};
    }
    points[segment_count] = points[0]; // wrap around

    R2_Vertex *vertices  = R2_AllocVertex(&R2.arena, R2.vertex_list, vertex_count);
    int        point_i   = 0;
    int        segment_i = 0;
    for (; segment_i < vertex_count; segment_i += 3, point_i += 1) {
        Rect2      tex = R2.font->white_texture_bounding_box;
        R2_Vertex *it  = vertices + segment_i;
        it[0].color    = color;
        it[1].color    = color;
        it[2].color    = color;
        it[0].tex      = {tex.min.x, tex.max.y};
        it[1].tex      = {tex.max.x, tex.max.y};
        it[2].tex      = {tex.min.x, tex.min.y};

        it[0].pos = {pos + points[point_i]};
        it[1].pos = {pos + points[point_i + 1]};
        it[2].pos = {pos};
    }
}

void R2_PushQuad(MA_Arena *arena, R2_VertexList *list, Rect2 rect, Rect2 tex, Vec4 color, float rotation = 0.f, Vec2 rotation_point = {}) {
    R2_Vertex *v = R2_AllocVertex(arena, list, 6);
    v[0]         = {
        {rect.min.x, rect.max.y},
        { tex.min.x,  tex.max.y},
        color
    };
    v[1] = {
        {rect.max.x, rect.max.y},
        { tex.max.x,  tex.max.y},
        color
    };
    v[2] = {
        {rect.min.x, rect.min.y},
        { tex.min.x,  tex.min.y},
        color
    };
    v[3] = {
        {rect.min.x, rect.min.y},
        { tex.min.x,  tex.min.y},
        color
    };
    v[4] = {
        {rect.max.x, rect.max.y},
        { tex.max.x,  tex.max.y},
        color
    };
    v[5] = {
        {rect.max.x, rect.min.y},
        { tex.max.x,  tex.min.y},
        color
    };
    if (rotation != 0.f) {
        float s = sinf(rotation);
        float c = cosf(rotation);
        for (int i = 0; i < 6; i += 1) {
            v[i].pos -= rotation_point;
            v[i].pos = {v[i].pos.x * c + v[i].pos.y * (-s), v[i].pos.x * s + v[i].pos.y * c};
            v[i].pos += rotation_point;
        }
    }
}

void R2_DrawRectRounded(Rect2 rect, Vec4 color, float roundness = 0.3f) {
    // Draw 5 rectangles, one in the middle, and for on sides, something like a cross
    Vec2  size     = CalcSize(rect);
    float max_size = Minimum(size.x, size.y);

    float shrink_by  = max_size * roundness;
    Rect2 inner_rect = ShrinkByHalfSize(rect, {shrink_by, shrink_by});
    Rect2 left_rect  = {rect.min.x, inner_rect.min.y, inner_rect.min.x, inner_rect.max.y};
    Rect2 right_rect = {inner_rect.max.x, inner_rect.min.y, rect.max.x, inner_rect.max.y};
    Rect2 up_rect    = {inner_rect.min.x, inner_rect.max.y, inner_rect.max.x, rect.max.y};
    Rect2 down_rect  = {inner_rect.min.x, inner_rect.min.y, inner_rect.max.x, rect.min.y};
    R2_PushQuad(&R2.arena, R2.vertex_list, inner_rect, R2.font->white_texture_bounding_box, color);
    R2_PushQuad(&R2.arena, R2.vertex_list, left_rect, R2.font->white_texture_bounding_box, color);
    R2_PushQuad(&R2.arena, R2.vertex_list, right_rect, R2.font->white_texture_bounding_box, color);
    R2_PushQuad(&R2.arena, R2.vertex_list, up_rect, R2.font->white_texture_bounding_box, color);
    R2_PushQuad(&R2.arena, R2.vertex_list, down_rect, R2.font->white_texture_bounding_box, color);

    // @optimize: we dont want to draw an entire circle lmao, just a 4th of it
    Vec2 top_left     = {inner_rect.min.x, inner_rect.max.y};
    Vec2 top_right    = {inner_rect.max.x, inner_rect.max.y};
    Vec2 bottom_left  = {inner_rect.min.x, inner_rect.min.y};
    Vec2 bottom_right = {inner_rect.max.x, inner_rect.min.y};

    R2_DrawCircle(top_left, shrink_by, color);
    R2_DrawCircle(top_right, shrink_by, color);
    R2_DrawCircle(bottom_left, shrink_by, color);
    R2_DrawCircle(bottom_right, shrink_by, color);
}

void R2_DrawRect(Rect2 rect, Vec4 color) {
    R2_PushQuad(&R2.arena, R2.vertex_list, rect, R2.font->white_texture_bounding_box, color);
}

void R2_DrawLine(Vec2 p0, Vec2 p1, float thickness, Vec4 color) {
    Rect2 tex            = R2.font->white_texture_bounding_box;
    float half_thickness = thickness / 2.f;

    Vec2 from_p1_to_p0 = Normalize(p1 - p0);
    Vec2 perp          = {-from_p1_to_p0.y, from_p1_to_p0.x};
    Vec2 offset        = perp * thickness;

    Vec2       p00 = p0 - offset;
    Vec2       p01 = p0 + offset;
    Vec2       p10 = p1 - offset;
    Vec2       p11 = p1 + offset;
    R2_Vertex *v   = R2_AllocVertex(&R2.arena, R2.vertex_list, 6);
    v[0].pos       = {p00.x, p00.y};
    v[0].tex       = {tex.min.x, tex.max.y};
    v[0].color     = color;
    v[1].pos       = {p11.x, p11.y};
    v[1].tex       = {tex.max.x, tex.max.y};
    v[1].color     = color;
    v[2].pos       = {p10.x, p10.y};
    v[2].tex       = {tex.min.x, tex.min.y};
    v[2].color     = color;
    v[3].pos       = {p00.x, p00.y};
    v[3].tex       = {tex.min.x, tex.min.y};
    v[3].color     = color;
    v[4].pos       = {p11.x, p11.y};
    v[4].tex       = {tex.max.x, tex.max.y};
    v[4].color     = color;
    v[5].pos       = {p01.x, p01.y};
    v[5].tex       = {tex.max.x, tex.min.y};
    v[5].color     = color;
}

void R2_DrawRectOutline(Rect2 rect, Vec4 color, float thickness = 1.0f) {
    Rect2 up    = {rect.min.x, rect.max.y - thickness, rect.max.x, rect.max.y};
    Rect2 down  = {rect.min.x, rect.min.y, rect.max.x, rect.min.y + thickness};
    Rect2 left  = {rect.min.x, rect.min.y, rect.min.x + thickness, rect.max.y};
    Rect2 right = {rect.max.x - thickness, rect.min.y, rect.max.x, rect.max.y};
    R2_PushQuad(&R2.arena, R2.vertex_list, up, R2.font->white_texture_bounding_box, color);
    R2_PushQuad(&R2.arena, R2.vertex_list, down, R2.font->white_texture_bounding_box, color);
    R2_PushQuad(&R2.arena, R2.vertex_list, left, R2.font->white_texture_bounding_box, color);
    R2_PushQuad(&R2.arena, R2.vertex_list, right, R2.font->white_texture_bounding_box, color);
}

struct R2_StringParams {
    Vec2      pos;
    S8_String string;
    Vec4      color;

    float     rotation;
    float     scale;
    bool      do_draw;
    MA_Arena *rects_arena;
};

R2_StringMeasure R2_BaseDrawString(R2_StringParams params) {
    R2_StringMeasure result = {};
    // if (params.color.a < 0.00001f) params.do_draw = false;

    S8_String string       = params.string;
    Vec2      original_pos = params.pos;
    Vec2      max_pos      = params.pos;
    Vec2      pos          = params.pos;
    float     scale        = params.scale;
    for (UTF8_Iter iter = UTF8_IterateEx(params.string.str, (int)params.string.len); iter.item; UTF8_Advance(&iter)) {
        uint32_t it = iter.item;
        if (it == '\n') {
            pos.x = original_pos.x;
            pos.y -= R2.font->size * scale;
            if (pos.x > max_pos.x) max_pos.x = pos.x;
            if (pos.y < max_pos.y) max_pos.y = pos.y; // @warning: min position y actually
            continue;
        }
        Font_Glyph &g = Font_GetGlyph(R2.font, it);

        Vec2 sym_pos = pos;
        pos.x += g.x_advance * scale;
        if (pos.x > max_pos.x) max_pos.x = pos.x;
        if (pos.y < max_pos.y) max_pos.y = pos.y; // @warning: min position y actually

        sym_pos.x += g.offset.x * scale;
        sym_pos.y += g.offset.y * scale;

        Vec2  minp = {sym_pos.x, sym_pos.y};
        Rect2 rect = Rect2_Size(minp, g.size * scale);
        if (params.do_draw) {
            R2_PushQuad(&R2.arena, R2.vertex_list, rect, g.atlas_bounding_box, params.color, params.rotation, original_pos);
        }
        if (params.rects_arena) {
            R2_RectNode *node              = MA_PushStruct(params.rects_arena, R2_RectNode);
            node->rect                     = rect;
            node->utf8_codepoint_byte_size = iter.utf8_codepoint_byte_size;
            SLL_QUEUE_ADD(result.first_rect, result.last_rect, node);
        }
    }

    result.rect = {
        original_pos.x, max_pos.y + R2.font->descent * scale, // @warning: min position y actually
        max_pos.x, original_pos.y + R2.font->ascent * scale};

    return result;
}

R2_StringParams R2_MakeStringParams(S8_String string, Vec2 pos = {}, Vec4 color = {}, bool do_draw = false) {
    R2_StringParams params = {};
    params.pos             = pos;
    params.string          = string;
    params.color           = color;
    params.scale           = 1.0f;
    params.do_draw         = do_draw;
    return params;
}

Rect2 R2_DrawString(Vec2 pos, S8_String string, Vec4 color = {1, 1, 1, 1}, float scale = 1.0f, float rotation = 0.0f) {
    R2_StringParams params  = R2_MakeStringParams(string, pos, color, true);
    params.scale            = scale;
    params.rotation         = rotation;
    R2_StringMeasure result = R2_BaseDrawString(params);
    return result.rect;
}

Rect2 R2_GetStringRect(Vec2 pos, S8_String string) {
    R2_StringParams  params = R2_MakeStringParams(string, pos);
    R2_StringMeasure result = R2_BaseDrawString(params);
    return result.rect;
}

Vec2 R2_GetStringSize(S8_String string, float scale = 1.0f) {
    R2_StringParams params  = R2_MakeStringParams(string);
    params.scale            = scale;
    R2_StringMeasure result = R2_BaseDrawString(params);
    return CalcSize(result.rect);
}

R2_StringMeasure R2_GetStringMeasure(MA_Arena *arena, Vec2 pos, S8_String string) {
    R2_StringParams params  = R2_MakeStringParams(string, pos);
    params.rects_arena      = arena;
    R2_StringMeasure result = R2_BaseDrawString(params);
    return result;
}

float R2_DebugStringYValue;
Rect2 R2_DebugString(char *string, ...) {
    S8_FORMAT(G_Frame, string, result);

    R2.vertex_list = &R2.ui_vertex_list;
    Vec2 pos       = {0, R2_DebugStringYValue};
    R2_DrawString(pos - 2, result, {0, 0, 0, 1});
    Rect2 rect = R2_DrawString(pos, result, {1, 1, 1, 1});
    R2_DebugStringYValue += R2.font->size;
    R2.vertex_list = &R2.base_vertex_list;
    return rect;
}

void R2_ClearFrameData() {
    MA_Reset(&R2.arena);
    R2.ui_vertex_list.first   = 0;
    R2.ui_vertex_list.last    = 0;
    R2.base_vertex_list.first = 0;
    R2.base_vertex_list.last  = 0;
    R2.vertex_count           = 0;

    MA_Reset(&R2.circle.arena);
    R2.circle.instances_to_render = 0;
}

void R2_EndFrame(Vec2 window_size) {
    R2_DebugStringYValue = 0;
    R2.circle.color_iter = 0;

    // Update the circle size
    {
        Vec2 *circle_pos = MESH_GenerateCircle(G_Frame, R2.circle.radius, R2.circle.mesh_segment_count);
        glNamedBufferSubData(R2.circle.vbo_mesh, 0, R2.circle.mesh_vertex_count * sizeof(Vec2), circle_pos);
    }

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);

    float x = 1.f / (window_size.x / 2.f);
    float y = 1.f / (window_size.y / 2.f);

    // Default draw using the font texture
    {
        glBindProgramPipeline(R2.pipeline);
        glProgramUniform2f(R2.vshader, 0, x, y);
        for (R2_VertexNode *it = R2.base_vertex_list.first; it; it = it->next) {
            glNamedBufferSubData(R2.vbo, 0, it->count * sizeof(R2_Vertex), it->vertices);
            glBindVertexArray(R2.vao);
            GLint s_texture = 0; // texture unit that sampler2D will use in GLSL code
            glBindTextureUnit(s_texture, R2.font->texture_id);
            glDrawArrays(GL_TRIANGLES, 0, it->count);
        }
    }

    // Draw circles
    {
        glBindProgramPipeline(R2.circle.shader.pipeline);
        glProgramUniform2f(R2.circle.shader.vshader, 0, x, y);
        // :Colors
        glProgramUniform4fv(R2.circle.shader.vshader, 1, R2.circle.colors.len, (float *)R2.circle.colors.data);
        glBindVertexArray(R2.circle.vao);

        V_Circle *circles           = (V_Circle *)R2.circle.arena.memory.data;
        int       offset            = 0;
        int       allocated_batches = (int)ceilf((float)R2.circle.instances_to_render / (float)R2_MaxCircleCount);
        for (int batch_i = 0; batch_i < allocated_batches; batch_i += 1) {
            int batch_size = ClampTop(R2.circle.instances_to_render - offset, R2_MaxCircleCount);
            IO_Assert(batch_size >= 0);

            glNamedBufferSubData(R2.circle.vbo_positions, 0, batch_size * sizeof(V_Circle), circles + offset);
            glDrawArraysInstanced(GL_TRIANGLES, 0, R2.circle.mesh_vertex_count, batch_size);

            offset += batch_size;
        }
    }

    // Draw UI
    {
        glBindProgramPipeline(R2.pipeline);
        glProgramUniform2f(R2.vshader, 0, x, y);
        for (R2_VertexNode *it = R2.ui_vertex_list.first; it; it = it->next) {
            glNamedBufferSubData(R2.vbo, 0, it->count * sizeof(R2_Vertex), it->vertices);
            glBindVertexArray(R2.vao);
            GLint s_texture = 0; // texture unit that sampler2D will use in GLSL code
            glBindTextureUnit(s_texture, R2.font->texture_id);
            glDrawArrays(GL_TRIANGLES, 0, it->count);
        }
    }

    R2_ClearFrameData();
}

uint64_t G_PointCount;

void R2_DrawCircleFast(Vec2 p, int index) {
    V_Circle *result    = MA_PushStruct(&R2.circle.arena, V_Circle);
    result->pos         = p;
    result->color_index = (float)index;
    R2.circle.instances_to_render += 1;
    G_PointCount += 1;
}