const std = @import("std");

const rl = @import("raylib");
const kb = @import("kb");
const ft = @import("freetype");
const rp = @import("stb_rect_pack");

pub var ft_lib : ft.Library = undefined;
pub var shader : rl.Shader = undefined;

const ATLAS_SIZE = 4096;

const Glyph = struct {
    utf32 : u32,
    index : u32,

    x : i16,
    y : i16,
    width : u32,
    height : u32,
    rwidth : i16,
    rheight : i16,

    bearing_x : i32,
    bearing_y : i32,

    // y_max : i16,
    // y_min : i16,

    ascent : i32,
    descent : i32,
    advance : i16,

    st0 : rl.Vector2,
    st1 : rl.Vector2,
};

pub const Font = struct {
    face : ft.Face,
    glyphs : std.AutoHashMap(u32, Glyph),
    kb : kb.kbts_font,
    texture : rl.Texture2D,
    font_data : []u8,

    pub fn init(filename : []const u8, size : i32, allocator: std.mem.Allocator) !Font {

        // const pixel_size = size * 96.0 / 72.0;

        const font_data = try std.fs.cwd().readFileAlloc(allocator, filename, 10 * 1024 * 1024);

        var face = try ft_lib.createFaceMemory(font_data, 0);
        var glyphs = std.AutoHashMap(u32, Glyph).init(allocator);
        try face.setCharSize(0, size * 64, 0, 96);
        try face.selectCharmap(.unicode);

        var rects = try std.ArrayList(rp.stbrp_rect).initCapacity(allocator, 1024);
        defer rects.deinit(allocator);

        var nodes : [ATLAS_SIZE]rp.stbrp_node = std.mem.zeroes([ATLAS_SIZE]rp.stbrp_node);

        var stbrpcontext : rp.stbrp_context = .{};
        rp.stbrp_init_target(&stbrpcontext, ATLAS_SIZE, ATLAS_SIZE, &nodes, nodes.len);

        const _atlas : []u8 = try allocator.alloc(u8, ATLAS_SIZE * ATLAS_SIZE);
        defer allocator.free(_atlas);
        @memset(_atlas, 0);

        const atlas : rl.Image = .{
            .data = _atlas.ptr,
            .width = ATLAS_SIZE,
            .height = ATLAS_SIZE,
            .mipmaps = 1,
            .format = .uncompressed_grayscale
        };

        var iter = face.iterateCharmap();
        while (iter.next()) |char| {
            const i = iter.index;

        // for (0..face.numGlyphs()) |i| {
            try face.loadGlyph(@intCast(i), .{});
            const bmp = face.glyph().bitmap();

            try rects.append(allocator, .{
                .id = @intCast(char),//@intCast(face.glyph().glyphIndex()),
                .w = @intCast(bmp.width()),
                .h = @intCast(bmp.rows()),
                .x = 0,
                .y = 0,
                .was_packed = 0,
            });
        }

        _ = rp.stbrp_pack_rects(&stbrpcontext, rects.items.ptr, @intCast(rects.items.len));

        for (rects.items) |rect| {
            if (rect.was_packed == 0) continue;
            const char : u32 = @intCast(rect.id);
            const index = face.getCharIndex(char) orelse return error.NoGlyph;// FT_Get_Char_Index(face, xx it);
            try face.loadGlyph(index, .{ .render = true });

            const bmp = face.glyph().bitmap();
            const buf = bmp.buffer() orelse continue;

            const pitch: usize = @intCast(bmp.pitch());
            const w: usize = @intCast(bmp.width());
            const h: usize = @intCast(bmp.rows());

            const dst_x: usize = @intCast(rect.x);
            const dst_y: usize = @intCast(rect.y);

            for (0..h) |y| {
                const src = buf[y * pitch .. y * pitch + w];

                const dst_index = (dst_y + y) * ATLAS_SIZE + dst_x;
                const dst = _atlas[dst_index .. dst_index + w];

                @memcpy(dst, src);
            }

            const width  = bmp.width();
            const height = bmp.rows();
            const bearing_y = face.glyph().bitmapTop();
            const descent = @as(i32, @intCast(height)) - bearing_y;
            const ascent  = @as(i32, @intCast(height)) - descent;

            const x : i16 = @intCast(rect.x);
            const y : i16 = @as(i16, @intCast(rect.y)) + @as(i16, @intCast(height));

            const fx = @as(f32, @floatFromInt(x));
            const fy = @as(f32, @floatFromInt(y));
            const fw = @as(f32, @floatFromInt(width));
            const fh = @as(f32, @floatFromInt(height));
            const fs = @as(f32, ATLAS_SIZE);

            const glyph = Glyph{
                .x = x,
                .y = y,
                .utf32 = char,
                .index = index,
                .bearing_x = face.glyph().bitmapLeft(),
                .bearing_y = face.glyph().bitmapTop(),
                .width = width,
                .height = height,
                .rwidth = @intCast(face.glyph().metrics().width >> 6),
                .rheight = @intCast(face.glyph().metrics().height >> 6),
                .descent = @as(i32, @intCast(face.glyph().bitmap().rows())) - face.glyph().bitmapTop(),
                .ascent = ascent,
                .advance = @intCast(face.glyph().advance().x >> 6),
                .st0 = .{.x = (fx + 0.5) / fs, .y = (fy - fh) / fs},
                .st1 = .{.x = (fx + fw) / fs,  .y = (fy - 0.5) / fs},
            };

            try glyphs.put(glyph.index, glyph);
        }

        const texture = try rl.Texture.fromImage(atlas);

        var _kb = kb.kbts_FontFromMemory(font_data.ptr, @intCast(font_data.len), 0, null, null);

        if (kb.kbts_FontIsValid(&_kb) != 0) {
            std.log.info("[Error] [kb_text_shape] Failed to load the font.", .{});
        }

        return .{
            .face = face,
            .glyphs = glyphs,
            .kb = _kb,
            .texture = texture,
            .font_data = font_data,
        };
    }

    pub fn deinit(self: *Font, allocator: std.mem.Allocator) void {
        // var it = self.glyphs.valueIterator();
        // while (it.next()) |g| {
        //     allocator.free(g.bitmap);
        // }
        self.glyphs.deinit();
        self.face.deinit();
        kb.kbts_FreeFont(&self.kb);
        self.texture.unload();
        allocator.free(self.font_data);
    }

    pub fn render_text(
        self: *Font,
        text: []const u8,
        pos: rl.Vector2,
        window_space: bool,
        colour: rl.Color,
        background: rl.Color,
        nice_background: bool,
        count_descent: bool
    ) void {
        if (text.len == 0)
            return;

        if (nice_background) {
            self.render_text(
                text,
                pos.add(.{.x = 3.0, .y = -3.0}),
                window_space,
                rl.Color.init(0, 0, 0, 255),
                rl.Color.init(0, 0, 0, 0),
                false,
                false
            );
        }

        var render_pos = pos;
        //_ = count_descent;
        //_ = colour;
        _ = background;

        const draw_size, const max_ascent, const max_descent = self.size_row(text, 0, 0);
        _ = max_ascent;
        _ = max_descent;

        const Context = kb.kbts_CreateShapeContext(null, null);
        const kb_font = kb.kbts_ShapePushFont(Context, &self.kb);
        if (kb_font == null) {
            std.log.info("Could not open font!", .{});
            return;
        }

        kb.kbts_ShapeBegin(Context, kb.KBTS_DIRECTION_DONT_KNOW, kb.KBTS_LANGUAGE_DONT_KNOW);
        kb.kbts_ShapeUtf8(Context, text.ptr, @intCast(text.len), kb.KBTS_USER_ID_GENERATION_MODE_CODEPOINT_INDEX);
        kb.kbts_ShapeEnd(Context);

        var x_offset : f32 = 0;
        var y_offset : f32 = 0;

        rl.beginShaderMode(shader);
        rl.gl.rlSetTexture(self.texture.id);
        rl.gl.rlBegin(rl.gl.rl_quads);

        // Layout runs naively left to right.
        var Run = kb.kbts_run{};
        while (kb.kbts_ShapeRun(Context, &Run) != 0) {
            if ((Run.Flags & kb.KBTS_BREAK_FLAG_LINE_HARD) != 0) {

            }

            var g : [*c]kb.kbts_glyph = null;
            while (kb.kbts_GlyphIteratorNext(&Run.Glyphs, &g) != 0) {
                const RunGlyph : *kb.kbts_glyph = g.?; // assert non-null
                const CodepointIndex : i32 = RunGlyph.UserIdOrCodepointIndex;
                var ShapeCodepoint : kb.kbts_shape_codepoint = undefined;
                if (kb.kbts_ShapeGetShapeCodepoint(Context, CodepointIndex, &ShapeCodepoint) == 0) {
                    std.log.info("aah it's wrong!! idk how to handle the error rn!!", .{});
                }

                // glyph : *Glyph = table_find_pointer(*text.font.glyphs, RunGlyph.Id);
                if (self.glyphs.getPtr(RunGlyph.Id)) |glyph| {
                    var v0 = rl.Vector2.zero();
                    var v1 = rl.Vector2.zero();
                    if (count_descent) {
                        v0 = render_pos.add(.{ .x = x_offset,// + glyph.bearing_x,
                                               .y = y_offset - @as(f32, @floatFromInt(glyph.bearing_y)) + draw_size.y });// /*- max_descent*/};
                    } else {
                        v0 = render_pos.add(.{
                            .x = x_offset,// + glyph.bearing_x,
                            .y = y_offset - @as(f32, @floatFromInt(@as(i32, @intCast(glyph.height)) - glyph.bearing_y))
                        });//* - glyph.height + draw_size.y*/};
                    }

                    v1 = v0.add(rl.Vector2{ .x = @floatFromInt(glyph.width), .y = @floatFromInt(glyph.height) });
                    const p0 : rl.Vector4 = .{ .x = v0.x, .y = v0.y, .z = 0.0, .w = 1.0 };
                    const p1 : rl.Vector4 = .{ .x = v1.x, .y = v1.y, .z = 0.0, .w = 1.0 };
                    x_offset += @floatFromInt(ft.mulFix(RunGlyph.AdvanceX, @intCast(self.face.size().metrics().x_scale)) >> 6);
                    y_offset += @floatFromInt(ft.mulFix(RunGlyph.AdvanceY, @intCast(self.face.size().metrics().y_scale)) >> 6);

                    // #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)
                        // };
                        const st0 = glyph.st0;
                        const st1 = 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)};
                    // }

                    rl.gl.rlColor4ub(colour.r, colour.g, colour.b, colour.a);
                    rl.gl.rlNormal3f(0.0, 0.0, 1.0);

                    rl.gl.rlTexCoord2f(st0.x, st0.y); rl.gl.rlVertex2f(p0.x, p0.y);
                    rl.gl.rlTexCoord2f(st0.x, st1.y); rl.gl.rlVertex2f(p0.x, p1.y);
                    rl.gl.rlTexCoord2f(st1.x, st1.y); rl.gl.rlVertex2f(p1.x, p1.y);
                    rl.gl.rlTexCoord2f(st1.x, st0.y); rl.gl.rlVertex2f(p1.x, p0.y);
                } else {
                    x_offset += @floatFromInt(ft.mulFix(RunGlyph.AdvanceX, @intCast(self.face.size().metrics().x_scale)) >> 6);
                    y_offset += @floatFromInt(ft.mulFix(RunGlyph.AdvanceY, @intCast(self.face.size().metrics().y_scale)) >> 6);
                    continue;
                }
            }
        }

        rl.gl.rlEnd();
        rl.gl.rlSetTexture(0);
        // rl.endShaderMode();
    }

    pub fn size_row(self: *Font, str: []const u8, n: i32, max_width: f32) struct {rl.Vector2, f32, i32} {
        _ = max_width;
        _ = n;
        const Context = kb.kbts_CreateShapeContext(null, null);
        const kb_font = kb.kbts_ShapePushFont(Context, &self.kb);
        if (kb_font == null) {
            std.log.info("Could not open font!", .{});
            return .{ rl.Vector2.zero(), 0, 0 };
        }

        kb.kbts_ShapeBegin(Context, kb.KBTS_DIRECTION_DONT_KNOW, kb.KBTS_LANGUAGE_DONT_KNOW);
        kb.kbts_ShapeUtf8(Context, str.ptr, @intCast(str.len), kb.KBTS_USER_ID_GENERATION_MODE_CODEPOINT_INDEX);
        kb.kbts_ShapeEnd(Context);

        var size = rl.Vector2.zero();
        var max_descent : f32 = 0;

        // Layout runs naively left to right.
        var Run = kb.kbts_run{};
        while(kb.kbts_ShapeRun(Context, &Run) != 0) {
            if ((Run.Flags & kb.KBTS_BREAK_FLAG_LINE_HARD) != 0) {

            }

            var g : [*c]kb.kbts_glyph = null;
            while (kb.kbts_GlyphIteratorNext(&Run.Glyphs, &g) != 0) {
                const RunGlyph : *kb.kbts_glyph = g.?; // assert non-null
                const CodepointIndex : i32 = RunGlyph.UserIdOrCodepointIndex;
                var ShapeCodepoint : kb.kbts_shape_codepoint = undefined;
                if (kb.kbts_ShapeGetShapeCodepoint(Context, CodepointIndex, &ShapeCodepoint) == 0) {
                    std.log.info("aah it's wrong!! idk how to handle the error rn!!", .{});
                }

                if (self.glyphs.getPtr(@intCast(RunGlyph.Id))) |glyph| {
                    size.y = @max(size.y, @as(f32, @floatFromInt(glyph.height)));
                    // size.x += (ft.mulFix(RunGlyph.AdvanceX, self.face.size().metrics().x_scale) >> 6);
                    size.x += @floatFromInt(ft.mulFix(RunGlyph.AdvanceX, @intCast(self.face.size().metrics().x_scale)) >> 6);
                    max_descent = @max(max_descent, @as(f32, @floatFromInt(glyph.descent)));
                }
            }
        }

        return .{ size, max_descent,  0};
    }
};
