struct Glyph {
    Vec2  size;
    Vec2  offset;
    float xadvance;
    float left_side_bearing;
    Rect2 atlas_bounding_box;
};

struct Font {
    Array<Glyph> glyphs;
    uint32_t     first_char, last_char;
    uint32_t     texture_id;

    float size;
    float descent;
    float ascent;
    float line_gap;

    Int line_spacing;
    Int char_spacing;

    Rect2 white_texture_bounding_box;
};

struct Atlas {
    uint8_t *bitmap;
    Vec2I    size;
    Vec2     inverse_size;
    int      xcursor;
    int      ycursor;

    int32_t  biggest_height;
    Rect2    white_texture_bounding_box;
    uint32_t texture_id;
};

Atlas CreateAtlas(Allocator allocator, Vec2I size) {
    Atlas result          = {};
    result.size           = size;
    result.inverse_size.x = 1.f / (float)result.size.x;
    result.inverse_size.y = 1.f / (float)result.size.y;
    result.bitmap         = AllocArray(allocator, 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.size.x;
            *dst         = 0xff;
        }
    }

    result.white_texture_bounding_box = {
        { 2.f * result.inverse_size.x,  2.f / (float)result.size.y},
        {14.f * result.inverse_size.x, 14.f / (float)result.size.y}
    };
    result.xcursor += 16;
    result.biggest_height += 16;
    return result;
}

Rect2 PackBitmap(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->xcursor + width > atlas->size.x) {
        if (atlas->ycursor + height < atlas->size.y) {
            atlas->xcursor = 0;
            atlas->ycursor += atlas->biggest_height + spacing;
        } else {
            SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Font loading error!", "Error while packing a font into atlas. Atlas size for this font scale is a bit too small", NULL);
        }
    }

    uint8_t *src = bitmap;
    for (int y = atlas->ycursor; y < atlas->ycursor + height; y += 1) {
        for (int x = atlas->xcursor; x < atlas->xcursor + width; x += 1) {
            uint8_t *dst = atlas->bitmap + x + y * atlas->size.x;
            *dst         = *src++;
        }
    }

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

    atlas->xcursor += width + spacing;
    if (height > atlas->biggest_height) {
        atlas->biggest_height = height;
    }

    return result;
}

Glyph *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];
    }
}
Font CreateFont(Atlas *atlas, int32_t size, String path) {
    Allocator allocator = GetSystemAllocator();
    Scratch   scratch;

    Font result             = {};
    result.glyphs.allocator = allocator;
    String file             = ReadFile(scratch, path);
    if (file.len == 0) {
        file = BakedInFont;
        Assert(file.len != 0);
    }

    stbtt_fontinfo stb_font;
    int            success = stbtt_InitFont(&stb_font, (const unsigned char *)file.data, 0);
    if (!success) {
        return result;
    }

    float scale = stbtt_ScaleForPixelHeight(&stb_font, (float)size);
    // float em_scale = stbtt_ScaleForMappingEmToPixels(&stb_font, (float)size);

    int ascent, descent, line_gap;
    stbtt_GetFontVMetrics(&stb_font, &ascent, &descent, &line_gap);
    result.ascent                     = (float)ascent * scale;
    result.descent                    = (float)descent * scale;
    result.line_gap                   = (float)line_gap * scale;
    result.size                       = (float)size;
    result.first_char                 = ' ';
    result.last_char                  = '~';
    result.white_texture_bounding_box = atlas->white_texture_bounding_box;

    int glyph_count = result.last_char - result.first_char + 1; // + 1 because it's '<=' not '<'
    Reserve(&result.glyphs, glyph_count);

    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, scale, ascii_symbol, &width, &height, &xoff, &yoff);
        defer { stbtt_FreeBitmap(bitmap, 0); };

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

        Glyph glyph              = {};
        glyph.atlas_bounding_box = PackBitmap(atlas, bitmap, width, height);
        glyph.size               = {(float)width, (float)height};
        glyph.offset             = {(float)xoff, (float)yoff};
        glyph.xadvance           = (float)xadvance * scale;
        glyph.left_side_bearing  = (float)left_side_bearing * scale;
        Add(&result.glyphs, glyph);
    }

    Glyph *g = GetGlyph(&result, '_');
    result.char_spacing = (Int)g->xadvance ? (Int)g->xadvance : (Int)g->size.x;
    result.line_spacing = (Int)(result.ascent - result.descent + result.line_gap);

    return result;
}
