ftlib : FT_Library;

ATLAS_SIZE :: 4096;

window_proj : Matrix4;

vertex_p2_st2_col4 :: struct {
    pos : Vector2;
    st  : Vector2;
    col : Vector4;
}

Glyph :: struct {
    utf32 : u32;
    index : u32;

    x, y : s16;
    width, height : s16;
    rwidth, rheight : s16;

    bearing_x, bearing_y : s16;

    y_max, y_min : s16;

    ascent : s16;
    descent : s16;
    advance : s16;

    st0, st1 : Vector2;

    bitmap : *u8;
}

Font :: struct {
    face : FT_Face;

    glyphs : Table(u32, Glyph);

    //hb : *hb_font_t;

    kb : kbts_font;

    texture : Texture;
}

init_font :: (using font: *Font, filename: string, size: s32) {

    pixel_size := size * 96.0 / 72.0;

    font_data, success := read_entire_file(filename);

    if !success {
        log("Could not read the font file.");
    }

    
    error := FT_New_Memory_Face(ftlib, font_data.data, xx font_data.count, 0, *face);
    if error {
        log("%", get_working_directory());
        log("FT_New_Face failed with error: %", to_string(FT_Error_String(error)));
        return;
    }

    //FT_Set_Pixel_Sizes(face, 0, size);
    error = FT_Set_Char_Size(face, 0, size * 64, 0, 96);
    if error {
        log("%", to_string(FT_Error_String(error)));
    }

    rects : [..]stbrp_rect;
    defer array_free(rects);

    nodes : [ATLAS_SIZE] stbrp_node;

    stbrpcontext : stbrp_context;
    stbrp_init_target(*stbrpcontext, ATLAS_SIZE, ATLAS_SIZE, nodes.data, nodes.count);

    atlas : *u8 = alloc(ATLAS_SIZE * ATLAS_SIZE);
    defer free(atlas);

    charcode : FT_ULong;
    gindex   : FT_UInt;

    charcode = FT_Get_First_Char(face, *gindex);
    while gindex != 0 {
        error = FT_Load_Glyph(face, gindex, FT_LOAD_RENDER);
        if error
            log("%", to_string(FT_Error_String(error)));

        array_add(*rects, stbrp_rect.{cast(s32) face.glyph.glyph_index, xx (face.glyph.bitmap.width + 2),
                    xx (face.glyph.bitmap.rows + 2), 0, 0, 0});

        glyph : Glyph;
        glyph.utf32 = xx charcode;
        glyph.index = gindex;
        glyph.bearing_x = cast,trunc(s16) face.glyph.bitmap_left;
        glyph.bearing_y = cast,trunc(s16) face.glyph.bitmap_top;
        glyph.width = xx face.glyph.bitmap.width;
        glyph.height = xx face.glyph.bitmap.rows;
        glyph.rwidth = xx face.glyph.metrics.width >> 6;
        glyph.rheight = xx face.glyph.metrics.height >> 6;
        glyph.descent = xx (cast(float) face.glyph.bitmap.rows - cast(float) face.glyph.bitmap_top);
        glyph.ascent  = xx (cast(float) face.glyph.bitmap.rows - glyph.descent);
        glyph.advance = cast(s16) face.glyph.advance.x >> 6;
        glyph.bitmap = alloc(glyph.height * glyph.width);
        memcpy(glyph.bitmap, face.glyph.bitmap.buffer, glyph.height * glyph.width);

        table_set(*font.glyphs, glyph.index, glyph);

        charcode = FT_Get_Next_Char(face, charcode, *gindex);
    }

    // for 0..face.num_glyphs {
    // //for 65..90 {
    //     //index := FT_Get_Char_Index(face, xx it);
    //     error = FT_Load_Glyph(face, cast(u32) it, FT_LOAD_RENDER);
    //     if error
    //         logd("%", to_string(FT_Error_String(error)));

    //     array_add(*rects, stbrp_rect.{cast(s32) face.glyph.glyph_index, xx (face.glyph.bitmap.width + 2),
    //                 xx (face.glyph.bitmap.rows + 2), 0, 0, 0});

    //     glyph : Glyph;
    //     glyph.utf32 = cast(u32) it;
    //     glyph.index = face.glyph.glyph_index;
    //     glyph.bearing_x = cast,trunc(s16) face.glyph.bitmap_left;
    //     glyph.bearing_y = cast,trunc(s16) face.glyph.bitmap_top;
    //     glyph.width = xx face.glyph.bitmap.width;
    //     glyph.height = xx face.glyph.bitmap.rows;
    //     glyph.rwidth = xx face.glyph.metrics.width >> 6;
    //     glyph.rheight = xx face.glyph.metrics.height >> 6;
    //     glyph.descent = xx (cast(float) face.glyph.bitmap.rows - cast(float) face.glyph.bitmap_top);
    //     glyph.ascent  = xx (cast(float) face.glyph.bitmap.rows - glyph.descent);
    //     glyph.advance = cast(s16) face.glyph.advance.x >> 6;
    //     glyph.bitmap = alloc(glyph.height * glyph.width);
    //     memcpy(glyph.bitmap, face.glyph.bitmap.buffer, glyph.height * glyph.width);

    //     table_set(*font.glyphs, glyph.index, glyph);
    // }

    stbrp_pack_rects(*stbrpcontext, rects.data, cast(s32) rects.count);


    copybitmaptoatlas :: (bitmap : *u8, width: s32, height: s32, atlas: *u8, destx: s32, desty: s32) {
        if (destx >= ATLAS_SIZE)
            destx = ATLAS_SIZE - 1;

        if (desty >= ATLAS_SIZE)
            desty = ATLAS_SIZE - 1;

        for j: 0..height - 1 {
            for i: 0..width - 1 {
                atlas[(desty + j) * ATLAS_SIZE + (destx + i)] = bitmap[j * width + i];
            }
        }
    }

    flip :: (atlas: *u8) {
        for j : 0..ATLAS_SIZE / 2 - 1 {
            for i : 0..ATLAS_SIZE - 1 {
                byte := atlas[j * ATLAS_SIZE + i];
                atlas[j * ATLAS_SIZE + i] = atlas[(ATLAS_SIZE - j - 1) * ATLAS_SIZE + i];
                atlas[(ATLAS_SIZE - j - 1) * ATLAS_SIZE + i] = byte;
                
            }
        }
    }

    for rect : rects {
        glyph : *Glyph = table_find_pointer(*font.glyphs, cast(u32) rect.id);
        if (glyph.bitmap) {
            glyph.x = cast,trunc(s16, rect.x);
            glyph.y = cast,trunc(s16, rect.y) + xx glyph.height;
            glyph.st0 = .{(cast(float, glyph.x + 1)) / cast(float, ATLAS_SIZE), cast(float, glyph.y + 1) / cast(float, ATLAS_SIZE)};
            glyph.st1 = .{(cast(float, glyph.x + glyph.width + 1)) / cast(float, ATLAS_SIZE), (cast(float, glyph.y - glyph.height + 1)) / cast(float, ATLAS_SIZE)};
            copybitmaptoatlas(glyph.bitmap, cast(s32) glyph.width, cast(s32) glyph.height, atlas, xx (rect.x + 1), xx (rect.y + 1));
        }
    }

    flip(atlas);
    
    // for rect : rects {
    //     glyph : *Glyph = table_find_pointer(*font.glyphs, cast(u32) rect.id);
    //     if (glyph.bitmap) {
    //         glyph.x = cast,trunc(s16) rect.x;
    //         glyph.y = ATLAS_SIZE - 1 - cast,trunc(s16) rect.y - xx glyph.height;
    //         glyph.st0 = .{cast(float, glyph.x) / ATLAS_SIZE, cast(float, glyph.y) / ATLAS_SIZE};
    //         glyph.st1 = glyph.st0 + .{cast(float, glyph.width) / ATLAS_SIZE, cast(float, glyph.height) / ATLAS_SIZE};
    //     }
    // }

    texture = make_texture_from_data(atlas, ATLAS_SIZE, ATLAS_SIZE);

    // blob : *hb_blob_t = hb_blob_create_or_fail(font_data.data, xx font_data.count, .HB_MEMORY_MODE_DUPLICATE, null, null);
    // if blob == null {
    //     loge("Could not create the HarfBuzz blob.");
    // }

    // hb_face : *hb_face_t = hb_face_create_or_fail(blob, 0);
    // if hb_face == null {
    //     loge("Could not create the HarfBuzz face.");
    // }

    // hb = hb_font_create(hb_face);
    // hb_font_set_ppem(hb, xx pixel_size, xx pixel_size);
    // hb_font_set_scale(hb, cast(s32) pixel_size * 64, cast(s32) pixel_size * 64);

    ScratchSize : u64 = kbts_ReadFontHeader(*font.kb, font_data.data, xx font_data.count);
    PermanentMemorySize : u64 = kbts_ReadFontData(*font.kb, alloc(xx ScratchSize), ScratchSize);
    kbts_PostReadFontInitialize(*font.kb, alloc(xx PermanentMemorySize), PermanentMemorySize);
    if !kbts_FontIsValid(*font.kb) {
        log("[Error] [kb_text_shape] failed to load the font.");
    }
}

render_text :: (font: *Font, text: string, pos: Vector2, window_space: bool = true, colour: Vector4 = .{1.0, 1.0, 1.0, 1.0},
                  background: Vector4 = .{0.0, 0.0, 0.0, 0.0}, nice_background := false, count_descent: bool = false) {

    if !text
        return;

    // Backup GL state
    // opengl_state : OpenGLState;
    // save_opengl_state(*opengl_state);

    glEnable(GL_BLEND);
    glDisable(GL_MULTISAMPLE);

    if nice_background {
        render_text(font, text, pos + .{3.0, -3.0}, window_space, v4(v3(0.0), 1.0), v4(0.0), false);
    }

    text_utf32 : [..]u32;
    defer array_free(text_utf32);

    StringAt : u64;
    while StringAt < xx text.count {
        Decode : kbts_decode = kbts_DecodeUtf8(text.data, xx text.count);
        StringAt += Decode.SourceCharactersConsumed;
        if Decode.Valid {
            array_add(*text_utf32, Decode.Codepoint);
        }
    }

    Cursor : kbts_cursor;
    Direction : kbts_direction = .KBTS_DIRECTION_NONE;
    Script : kbts_script = .KBTS_SCRIPT_DONT_KNOW;
    size, max_descent := ShapeText(font, *Cursor, text_utf32.data, xx text_utf32.count, Direction, Direction, Script);

    // buf : *hb_buffer_t = hb_buffer_create();
    // hb_buffer_add_utf8(buf, text.data, xx text.count, 0, -1);

    // hb_buffer_set_direction(buf, hb_direction_t.LTR);
    // hb_buffer_set_script(buf, hb_script_t.HB_SCRIPT_LATIN);
    // hb_buffer_set_language(buf, hb_language_from_string("en", -1));

    // features : [1]hb_feature_t;
    // features[0].tag = HB_TAG(#char "c", #char "a", #char "l", #char "t");
    // features[0].value = 1;
    // features[0].start = HB_FEATURE_GLOBAL_START;
    // features[0].end = HB_FEATURE_GLOBAL_END;

    // hb_shape(font.hb, buf, features.data, features.count);

    // glyph_count : u32;
    // glyph_info : *hb_glyph_info_t     = hb_buffer_get_glyph_infos(buf, *glyph_count);
    // glyph_pos  : *hb_glyph_position_t = hb_buffer_get_glyph_positions(buf, *glyph_count);

    // vertices : [..]vertex_p2_st2_col4;
    // defer array_free(vertices);

    // render_pos := pos;

    // draw_size, max_ascent, max_descent := calculate_string_draw_size(font, text);

    // for i : 0..glyph_count - 1 {
    //     glyphid   : hb_codepoint_t = glyph_info[i].codepoint;
    //     x_offset  : hb_position_t  = glyph_pos[i].x_offset;
    //     y_offset  : hb_position_t  = glyph_pos[i].y_offset;
    //     x_advance : hb_position_t  = glyph_pos[i].x_advance;
    //     y_advance : hb_position_t  = glyph_pos[i].y_advance;

    //     glyph : *Glyph = table_find_pointer(*font.glyphs, glyphid);

    //     v0 : Vector2;
    //     v1 : Vector2;
    //     if count_descent {
    //         v0 = render_pos + .{cast(float) x_offset + glyph.bearing_x,
    //                             cast(float) y_offset - glyph.bearing_y + draw_size.y - max_descent};
    //     } else {
    //         v0 = render_pos + .{cast(float) x_offset + glyph.bearing_x,
    //                             cast(float) y_offset - (xx glyph.height - glyph.bearing_y)/* - glyph.height + draw_size.y*/};
    //     }

    //     v1 = v0 + Vector2.{cast(float) glyph.width, cast(float) glyph.height};

    //     // #if Y_IS_UP {
    //         // t0 := Vector2.{cast(float, glyph.x) / cast(float, ATLAS_SIZE), cast(float,  glyph.y) / cast(float, ATLAS_SIZE)};
    //         // t1 := t0 + Vector2.{cast(float, glyph.width) / cast(float, ATLAS_SIZE), -cast(float, glyph.height) / cast(float, ATLAS_SIZE)};
    //         t0 := glyph.st0;
    //         t1 := glyph.st1;
    //     // } else {
    //     //     t0 := Vector2.{cast(float, glyph.x / ATLAS_SIZE), cast(float, glyph.y / ATLAS_SIZE)};
    //     //     t1 := t0 + .{cast(float, glyph.width / ATLAS_SIZE), cast(float, glyph.height / ATLAS_SIZE)};
    //     // }

    //     array_add(*vertices, .{v0, t0, colour});
    //     array_add(*vertices, .{.{v0.x, v1.y}, .{t0.x, t1.y}, colour});
    //     array_add(*vertices, .{.{v1.x, v0.y}, .{t1.x, t0.y}, colour});

    //     array_add(*vertices, .{.{v1.x, v0.y}, .{t1.x, t0.y}, colour});
    //     array_add(*vertices, .{.{v0.x, v1.y}, .{t0.x, t1.y}, colour});
    //     array_add(*vertices, .{v1, t1, colour});

    //     render_pos += Vector2.{x_advance / 64.0, y_advance / 64.0};
    // }
    // hb_buffer_destroy(buf);

    // view := identity_of(Matrix4);
    // proj := window_proj;

    // render_batch(.TRIANGLES, vertices, *text_shader, *font.texture, view, proj);

    // Restore modified GL state
    // restore_opengl_state(*opengl_state);
}

// string_size :: (font: *Font, Cursor: *kbts_cursor, text: string, MainDirection: kbts_direction, Direction: kbts_direction, Script: kbts_script) -> Vector2 {
//     text_utf32 : [..]u32;
//     defer array_free(text_utf32);

//     StringAt : u64;
//     while StringAt < xx text.count {
//         Decode : kbts_decode = kbts_DecodeUtf8(text.data, xx text.count - StringAt);
//         StringAt += Decode.SourceCharactersConsumed;
//         if Decode.Valid {
//             array_add(*text_utf32, Decode.Codepoint);
//         }
//     }

//     Glyphs : *kbts_glyph = cast(*kbts_glyph, alloc(size_of(kbts_glyph) * text_utf32.count));

//     CodepointIndex: u64;
//     while CodepointIndex < xx text_utf32.count {
//         Glyphs[CodepointIndex] = kbts_CodepointToGlyph(*font.kb, text_utf32[CodepointIndex]);

//         CodepointIndex += 1;
//     }

//     State : *kbts_shape_state = kbts_CreateShapeState(*font.kb);
//     Config : kbts_shape_config = kbts_ShapeConfig(*font.kb, Script, xx kbts_language_enum.KBTS_LANGUAGE_DONT_KNOW);

//     size : Vector2;

//     GlyphCount : u32 = xx text_utf32.count;
//     GlyphCapacity : u32 = GlyphCount;
//     while kbts_Shape(State, *Config, MainDirection, Direction, Glyphs, *GlyphCount, GlyphCapacity) {
//         Glyphs = cast(*kbts_glyph, realloc(Glyphs, size_of(kbts_glyph) * State.RequiredGlyphCapacity, size_of(kbts_glyph) * GlyphCapacity));
//         GlyphCapacity = State.RequiredGlyphCapacity;
//     }

//     GlyphIndex : u64;
//     while GlyphIndex < GlyphCount {
//         Glyph : *kbts_glyph = *Glyphs[GlyphIndex];

//         X : s32;
//         Y : s32;
//         kbts_PositionGlyph(Cursor, Glyph, *X, *Y);

//         //RenderGlyph(Glyph, X, Y);

//         GlyphIndex += 1;
//     }

//     free(Glyphs);

//     return size;
// }

ShapeText :: (font: *Font, Cursor: *kbts_cursor, Codepoints: *u32, CodepointCount: u64, MainDirection: kbts_direction, Direction: kbts_direction, Script: kbts_script) -> Vector2, float {
    kglyphs : *kbts_glyph = cast(*kbts_glyph, alloc(xx (size_of(kbts_glyph) * CodepointCount)));

    CodepointIndex: u64;
    while CodepointIndex < CodepointCount {
        kglyphs[CodepointIndex] = kbts_CodepointToGlyph(*font.kb, Codepoints[CodepointIndex]);

        CodepointIndex += 1;
    }

    State : *kbts_shape_state = kbts_CreateShapeState(*font.kb);
    Config : kbts_shape_config = kbts_ShapeConfig(*font.kb, Script, .KBTS_LANGUAGE_DONT_KNOW);

    size : Vector2;
    max_descent : float;

    GlyphCount : u32 = xx CodepointCount;
    GlyphCapacity : u32 = GlyphCount;
    while kbts_Shape(State, *Config, MainDirection, Direction, kglyphs, *GlyphCount, GlyphCapacity) {
        kglyphs = cast(*kbts_glyph, realloc(kglyphs, size_of(kbts_glyph) * State.RequiredGlyphCapacity, size_of(kbts_glyph) * GlyphCapacity));
        GlyphCapacity = State.RequiredGlyphCapacity;
    }

    GlyphIndex : u64;
    while GlyphIndex < GlyphCount {
        kglyph : *kbts_glyph = *kglyphs[GlyphIndex];

        X : s32;
        Y : s32;
        kbts_PositionGlyph(Cursor, kglyph, *X, *Y);

        glyph : *Glyph = table_find_pointer(*font.glyphs, kglyph.Id);
        if glyph {
            size.y = max(size.y, xx glyph.height);
            size.x += xx FT_MulFix(Cursor.X, font.face.size.metrics.x_scale) >> 6;
            max_descent = max(max_descent, xx glyph.descent);
            //line.max_descent = max(line.max_descent, cast(float) glyph.descent);
            //line.max_ascent = max(line.max_ascent, cast(float) glyph.ascent);
        }

        //RenderGlyph(Glyph, X, Y);

        GlyphIndex += 1;
    }

    free(kglyphs);

    return size, max_descent;
}

SegmentText :: (font: *Font, Codepoints: *u32, CodepointCount: u64) -> Vector2, float, u32 {
    Cursor : kbts_cursor;
    Direction : kbts_direction = .NONE;
    Script : kbts_script = .DONT_KNOW;
    RunStart : u64 = 0;
    BreakState : kbts_break_state;
    kbts_BeginBreak(*BreakState, .NONE, .NORMAL);

    // size : Vector2;
    // max_descent : float;

    CodepointIndex : u64;
    while CodepointIndex < xx CodepointCount {
        kbts_BreakAddCodepoint(*BreakState, Codepoints[CodepointIndex], 1, xx ((CodepointIndex + 1) == xx CodepointCount));
        Break : kbts_break;
        while kbts_Break(*BreakState, *Break) {

            // if (Break.Position > RunStart) && (Break.Flags & (.DIRECTION | .SCRIPT | .LINE_HARD)) {
            //     RunLength : u64 = Break.Position - RunStart;
            //     size, max_descent := ShapeText(font, *Cursor, Codepoints + RunStart, RunLength, BreakState.MainDirection, Direction, Script);

            //     return size, max_descent, Break.Position;
            // }

            // if (Break.Position > RunStart) && (Break.Flags & (.DIRECTION | .SCRIPT | .LINE_HARD)) {
            //     RunLength : u64 = Break.Position - RunStart;
            //     size, max_descent := ShapeText(font, *Cursor, Codepoints + RunStart, RunLength, BreakState.MainDirection, Direction, Script);

            //     return size, max_descent, xx CodepointIndex;
            // }

            if Break.Flags & .DIRECTION {
                Direction = Break.Direction;
                if(!Cursor.Direction) Cursor = kbts_Cursor(BreakState.MainDirection);
            }

            if Break.Flags & .SCRIPT {
                Script = Break.Script;
            }

            if (Break.Position > RunStart) && (Break.Flags & .WORD) {
                RunLength : u64 = Break.Position - RunStart;
                size, max_descent := ShapeText(font, *Cursor, Codepoints + RunStart, RunLength, BreakState.MainDirection, Direction, Script);

                //if max_width > 0.0 && size.x + word_size.x > max_width {
                    return size, max_descent, Break.Position;
                //}

                // size.x += word_size.x;
                // size.y = max(word_size.y);

                // max_descent = max(max_descent, word_max_descent);
            }

            // if (Break.Position > RunStart) && Break.Flags & .LINE_HARD {
            //     RunLength : u64 = Break.Position - RunStart;
            //     word_size, word_max_descent := ShapeText(font, *Cursor, Codepoints + RunStart, RunLength, BreakState.MainDirection, Direction, Script);                
            //     return size, max_descent, Break.Position;
            // }
        }

        CodepointIndex += 1;
    }

    return .{}, 0, 0;
}

// get_next_row :: (font: *Font, max_width: float, text: string, index: s32, length: s32) -> s32 {
//     Cursor : kbts_cursor;
//     Direction : kbts_direction = xx kbts_direction_enum.KBTS_DIRECTION_NONE;
//     Script : kbts_script = xx kbts_script_enum.KBTS_SCRIPT_DONT_KNOW;
//     RunStart : u64;
//     BreakState : kbts_break_state;
//     kbts_BeginBreak(*BreakState, xx kbts_direction_enum.KBTS_DIRECTION_NONE, xx kbts_japanese_line_break_style_enum.KBTS_JAPANESE_LINE_BREAK_STYLE_NORMAL);

//     text_utf32 : [..]u32;
//     defer array_free(text_utf32);

//     StringAt : u64;
//     while StringAt < xx text.count {
//         Decode : kbts_decode = kbts_DecodeUtf8(text.data, xx text.count - StringAt);
//         StringAt += Decode.SourceCharactersConsumed;
//         if Decode.Valid {
//             array_add(*text_utf32, Decode.Codepoint);
//         }
//     }

//     width : float;

//     CodepointIndex : u64;
//     while CodepointIndex < xx text_utf32.count {

//         kbts_BreakAddCodepoint(*BreakState, text_utf32[CodepointIndex], 1, xx ((CodepointIndex + 1) == xx text_utf32.count));
//         Break : kbts_break;
//         while kbts_Break(*BreakState, *Break) {
//             if (Break.Position > RunStart) && (Break.Flags & xx kbts_break_flags_enum.KBTS_BREAK_FLAG_DIRECTION | xx kbts_break_flags_enum.KBTS_BREAK_FLAG_SCRIPT | xx kbts_break_flags_enum.KBTS_BREAK_FLAG_LINE_HARD | xx kbts_break_flags_enum.KBTS_BREAK_FLAG_GRAPHEME) {
//                 RunLength : u64 = Break.Position - RunStart;

//                 // Glyph : kbts_glyph = kbts_CodepointToGlyph(*font.kb, Decode.Codepoint);

//                 // // Shape
//                 // GlyphCapacity : u32 = Length;
//                 // kbts_shape_state *State = kbts_CreateShapeState(*font.kb);
//                 // // A shape_config is immutable once created. You can freely share/hash it, etc.
//                 // Config : kbts_shape_config = kbts_ShapeConfig(*font.kb, Script, xx kbts_language_enum.KBTS_LANGUAGE_DONT_KNOW);

//                 // while kbts_Shape(State, *Config, Direction, Direction, Glyphs, *GlyphCount, GlyphCapacity) {
//                 //     Glyphs = realloc(Glyphs, sizeof(kbts_glyph) * State.RequiredGlyphCapacity);
//                 //     GlyphCapacity = State.RequiredGlyphCapacity;
//                 // }

//                 // // Get final positions
//                 // Cursor : kbts_cursor = kbts_Cursor(Direction);
//                 // GlyphIndex : u64;

//                 // while GlyphIndex < GlyphCount {
//                 //     X : s32;
//                 //     Y : s32;
//                 //     kbts_PositionGlyph(*Cursor, *Glyphs[GlyphIndex], *X, *Y);
//                 //     GlyphIndex += 1;
//                 // }

//                 //width += 
//                 //ShapeText(&Cursor, Codepoints + RunStart, RunLength, BreakState.MainDirection, Direction, Script);
//             }

//             if Break.Flags & xx kbts_break_flags_enum.KBTS_BREAK_FLAG_DIRECTION {
//                 Direction = Break.Direction;
//                 if(!Cursor.Direction) Cursor = kbts_Cursor(BreakState.MainDirection);
//             }

//             if Break.Flags & xx kbts_break_flags_enum.KBTS_BREAK_FLAG_SCRIPT {
//                 Script = Break.Script;
//             }
//         }

//         CodepointIndex += 1;
//     }

//     return 0;
// }

calculate_string_draw_size :: (font: *Font, text: string, n: u32, max_width: float = 0.0) -> Vector2, float, u32 {

    textp : *u8 = text.data;
    line_start : *u8 = textp + n;
    len := text.count - (line_start - textp);

    text_utf32 : [..]u32;
    defer array_free(text_utf32);

    StringAt : u64;
    while StringAt < xx len {
        Decode : kbts_decode = kbts_DecodeUtf8(line_start + StringAt, xx len - StringAt);
        StringAt += Decode.SourceCharactersConsumed;
        if Decode.Valid {
            array_add(*text_utf32, Decode.Codepoint);
        }
    }

    size, max_descent, read := SegmentText(font, text_utf32.data, xx text_utf32.count);

    return size, max_descent, read;

    // words := split(text, " ");

    // for *word : words {
    //     if word.* != "" && word.data - text.data + word.count < text.count {
    //         if text[word.data - text.data + word.count] == " " {
    //             word.count += 1;
    //         }
    //     }
    // }

    total_size : Vector2;
    // lines      : [..]Line;

    // if !text {
    //     return .{}, 0.0, 0.0, lines;
    // }

    // if words.count == 0 {
    //     return .{}, 0.0, 0.0, lines;
    // }

    // text_utf32 : [..]u32;
    // defer array_free(text_utf32);
    
    // StringAt : u64;
    // while StringAt < text.count {
    //     Decode : kbts_decode = kbts_DecodeUtf8(text.data, text.count - StringAt);
    //     StringAt += Decode.SourceCharactersConsumed;
    //     if Decode.Valid {
    //         array_add(*text_utf32, Decode.Codepoint);
    //     }
    // }

    // String : *u8 = text_utf32.data;
    // Length : u64 = text_utf32.count;

    // // Make some glyphs
    // kbts_glyph *Glyphs = cast(*kbts_glyph, alloc(size_of(kbts_glyph) * Length));
    // GlyphCount : u32 = 0;
    // Script : kbts_script = KBTS_SCRIPT_DONT_KNOW;
    // Direction : kbts_direction = KBTS_DIRECTION_NONE;
    // StringAt : u64;
    // while StringAt < Length {
    //     Decode : kbts_decode = kbts_DecodeUtf8(String, Length - StringAt);
    //     StringAt += Decode.SourceCharactersConsumed;
    //     if(Decode.Valid) {
    //         Glyph : kbts_glyph = kbts_CodepointToGlyph(*Font, Decode.Codepoint);
    //         // Easy script inference for simple cases. (This is similar to hb_buffer_guess_segment_properties.)
    //         // If you have already segmented String with our API, you already have a script!
    //         // So no need to pass it in that case.
    //         // Only use this as a shorthand, when you are pretty sure String is a single
    //         // script.
    //         kbts_InferScript(*Direction, *Script, Glyph.Script);
    //         Glyphs[GlyphCount] = Glyph;
    //         GlyphCount += 1;
    //     }
    // }


    // void SegmentText(uint32_t *Codepoints, size_t CodepointCount)

    // line : Line;

    // for word : words {
    //     started_a_line : bool;
    //     if line.text.count == 0 {
    //         line.text.data = word.data;
    //         line.text.count = word.count;
    //         started_a_line = true;
    //     }

    //     buf : *hb_buffer_t = hb_buffer_create();
    //     hb_buffer_add_utf8(buf, word.data, xx word.count, 0, -1);

    //     hb_buffer_set_direction(buf, hb_direction_t.LTR);
    //     hb_buffer_set_script(buf, hb_script_t.HB_SCRIPT_LATIN);
    //     hb_buffer_set_language(buf, hb_language_from_string("en", -1));

    //     features : [1]hb_feature_t;
    //     features[0].tag = HB_TAG(#char "c", #char "a", #char "l", #char "t");
    //     features[0].value = 1;
    //     features[0].start = HB_FEATURE_GLOBAL_START;
    //     features[0].end = HB_FEATURE_GLOBAL_END;

    //     hb_shape(font.hb, buf, features.data, features.count);
    //     glyph_count_u : u32;
    //     glyph_info : *hb_glyph_info_t     = hb_buffer_get_glyph_infos(buf, *glyph_count_u);
    //     glyph_pos  : *hb_glyph_position_t = hb_buffer_get_glyph_positions(buf, *glyph_count_u);

    //     glyph_count : s32 = xx glyph_count_u;

    //     word_size : Vector2;

    //     for i : 0..glyph_count - 1 {
    //         glyphid : hb_codepoint_t = glyph_info[i].codepoint;
    //         x_offset  : hb_position_t = glyph_pos[i].x_offset;
    //         y_offset  : hb_position_t = glyph_pos[i].y_offset;
    //         x_advance : hb_position_t = glyph_pos[i].x_advance;
    //         y_advance : hb_position_t = glyph_pos[i].y_advance;

    //         glyph : *Glyph = table_find_pointer(*font.glyphs, glyphid);

    //         if glyph {
    //             word_size.y = max(word_size.y, xx (glyph.height + glyph.descent));
    //             word_size.x += x_advance / 64.0;
    //             line.max_descent = max(line.max_descent, cast(float) glyph.descent);
    //             line.max_ascent = max(line.max_ascent, cast(float) glyph.ascent);
    //         }
    //     }

    //     if !started_a_line {
    //         line.text.count += word.count;
    //     }

    //     if max_width > 0.0 {
    //         if line.size.x + word_size.x > max_width {
    //             line.size.x = max_width;
    //             line.size.y = max(line.size.y, word_size.y);
    //             line.size.y = max(line.size.y, line.max_ascent + line.max_descent);
    //             array_add(*lines, line);
    //             line = .{};
    //         } else {
    //             line.size.y = max(total_size.y, word_size.y);
    //             line.size.y = max(line.size.y, line.max_ascent + line.max_descent);
    //             line.size.x += word_size.x;
    //             // } else {
    //             //     line.text.data = word.data;
    //             //     line.text.count = word.count;
    //             // }
    //         }
    //     } else if max_width == 0.0 {
    //         line.size.y = max(total_size.y, word_size.y);
    //         line.size.y = max(line.size.y, line.max_ascent + line.max_descent);
    //         line.size.x += word_size.x;
    //         // if include_descent
    //         //     size.y = max_ascent + max_descent;
    //     }

    //     hb_buffer_destroy(buf);
    // }

    // if line.text.count > 0 {
    //     line.size.y = max(line.size.y, line.max_ascent + line.max_descent);
    //     array_add(*lines, line);
    //     line = .{};
    // }

    // for line : lines {
    //     total_size.x = max(total_size.x, line.size.x);
    //     total_size.y += line.size.y;
    // }


    return total_size, 0, 0;
}

make_texture_from_data :: (data: *u8, width: s32, height: s32) -> Texture {
    if !data {
        log("[Error] Could not load the texture.");
        return .{};
    }

    texture : Texture;

    texture.internal_format = GL_R8;
    texture.format = GL_RED;

    glGenTextures(1, *texture.id);
    glBindTexture(GL_TEXTURE_2D, texture.id);
    glTexImage2D(GL_TEXTURE_2D, 0, texture.internal_format, xx width, xx height, 0, texture.format, GL_UNSIGNED_BYTE, data);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    texture.width = width;
    texture.height = height;
    texture.channels = 1;
    texture.srgb = true;

    return texture;
}

PRIMITIVE_TYPE :: enum u8 {
    NONE;
    TRIANGLE_STRIP :: 4;
    TRIANGLES :: 6;
}

render_batch :: (primitive_type: PRIMITIVE_TYPE, vertices: []$T, shader: *_Shader, texture: *Texture, view: Matrix4, proj: Matrix4) {

    if !vertices {
        logd("[Error] The batch has no vertices.");
        return;
    }

    use(shader);

    if texture {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture.id);
    }

    vao : GLuint;
    glGenVertexArrays(1, *vao);
    glBindVertexArray(vao);

    vbo : GLuint;
    glGenBuffers(1, *vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, cast(*Type_Info, T).runtime_size * vertices.count, vertices.data, GL_STATIC_DRAW);

    if T == {
        // case vertex_p2_st2_col4_a1;
        //     glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, size_of(vertex_p2_st2_col4_a1), xx offset_of(vertex_p2_st2_col4_a1, #code pos));
        //     glEnableVertexAttribArray(0);

        //     glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, size_of(vertex_p2_st2_col4_a1), xx offset_of(vertex_p2_st2_col4_a1, #code st));
        //     glEnableVertexAttribArray(1);

        //     glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, size_of(vertex_p2_st2_col4_a1), xx offset_of(vertex_p2_st2_col4_a1, #code col));
        //     glEnableVertexAttribArray(2);

        //     glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, size_of(vertex_p2_st2_col4_a1), xx offset_of(vertex_p2_st2_col4_a1, #code alpha));
        //     glEnableVertexAttribArray(3);

        // case vertex_p2_scale2_rot1_st2;
        //     glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, size_of(vertex_p2_scale2_rot1_st2), xx offset_of(vertex_p2_scale2_rot1_st2, #code pos));
        //     glEnableVertexAttribArray(0);

        //     glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, size_of(vertex_p2_scale2_rot1_st2), xx offset_of(vertex_p2_scale2_rot1_st2, #code scale));
        //     glEnableVertexAttribArray(1);

        //     glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, size_of(vertex_p2_scale2_rot1_st2), xx offset_of(vertex_p2_scale2_rot1_st2, #code rot));
        //     glEnableVertexAttribArray(2);

        //     glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, size_of(vertex_p2_scale2_rot1_st2), xx offset_of(vertex_p2_scale2_rot1_st2, #code st));
        //     glEnableVertexAttribArray(3);

        case vertex_p2_st2_col4;
            glEnableVertexAttribArray(0);
            glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, size_of(vertex_p2_st2_col4), xx (offset_of(vertex_p2_st2_col4, #code pos)));

            glEnableVertexAttribArray(1);
            glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, size_of(vertex_p2_st2_col4), xx (offset_of(vertex_p2_st2_col4, #code st)));

            glEnableVertexAttribArray(2);
            glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, size_of(vertex_p2_st2_col4), xx (offset_of(vertex_p2_st2_col4, #code col)));

        // case vertex_p2_col4;
        //     glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, size_of(vertex_p2_col4), xx offset_of(vertex_p2_col4, #code pos));
        //     glEnableVertexAttribArray(0);

        //     glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, size_of(vertex_p2_col4), xx offset_of(vertex_p2_col4, #code col));
        //     glEnableVertexAttribArray(1);
    }

    glUniformMatrix4fv(glGetUniformLocation(shader.id, "uni_view"), 1, GL_TRUE, xx *view);
    glUniformMatrix4fv(glGetUniformLocation(shader.id, "uni_proj"), 1, GL_TRUE, xx *proj);

    if primitive_type == {
        case .TRIANGLE_STRIP;
            glDrawArrays(GL_TRIANGLE_STRIP, 0, xx vertices.count);

        case .TRIANGLES;
            glDrawArrays(GL_TRIANGLES, 0, xx vertices.count);
    }

    glBindVertexArray(0);

    glDeleteBuffers(1, *vbo);
    glDeleteVertexArrays(1, *vao);
}
