const std = @import("std");
const rl = @import("raylib");

font: rl.Font,
spacing: ?f32 = null,
line_height: f32 = 1.4,

pub fn getSpacing(self: @This()) f32 {
    if (self.spacing) |spacing| {
        return spacing;
    } else {
        return self.getSize() / 10;
    }
}

pub fn getSize(self: @This()) f32 {
    return @floatFromInt(self.font.baseSize);
}

pub fn drawTextLines(self: @This(), lines: []const []const u8, position: rl.Vector2, tint: rl.Color) void {
    var offset_y: f32 = 0;

    const font_size = self.getSize();

    for (lines) |line| {
        self.drawText(line, position.add(.{ .x = 0, .y = offset_y }), tint);

        const line_size = self.measureText(line);
        offset_y += line_size.y + font_size * (self.line_height - 1);
    }
}

pub fn measureTextLines(self: @This(), lines: []const []const u8) rl.Vector2 {
    var text_size = rl.Vector2.zero();

    const font_size = self.getSize();

    for (lines) |line| {
        const line_size = self.measureText(line);

        text_size.x = @max(text_size.x, line_size.x);
        text_size.y += line_size.y;
    }

    text_size.y += (self.line_height - 1) * font_size * @as(f32, @floatFromInt(@max(lines.len - 1, 0)));

    return text_size;
}

pub fn drawTextEx(self: @This(), text: []const u8, position: rl.Vector2, tint: rl.Color, offset: *rl.Vector2) void {
    if (self.font.texture.id == 0) return;

    const font_size = self.getSize();
    const spacing = self.getSpacing();

    var iter = std.unicode.Utf8Iterator{ .bytes = text, .i = 0 };
    while (iter.nextCodepoint()) |codepoint| {
        if (codepoint == '\n') {
            offset.x = 0;
            offset.y += font_size * self.line_height;
        } else {
            if (!(codepoint <= 255 and std.ascii.isWhitespace(@intCast(codepoint)))) {
                var codepoint_position = position.add(offset.*);
                codepoint_position.x = @round(codepoint_position.x);
                codepoint_position.y = @round(codepoint_position.y);
                rl.drawTextCodepoint(self.font, codepoint, codepoint_position, font_size, tint);
            }

            const index: u32 = @intCast(rl.getGlyphIndex(self.font, codepoint));
            if (self.font.glyphs[index].advanceX != 0) {
                offset.x += @floatFromInt(self.font.glyphs[index].advanceX);
            } else {
                offset.x += self.font.recs[index].width;
                offset.x += @floatFromInt(self.font.glyphs[index].offsetX);
            }
            offset.x += spacing;
        }
    }
}

pub fn drawText(self: @This(), text: []const u8, position: rl.Vector2, tint: rl.Color) void {
    var offset = rl.Vector2.init(0, 0);
    self.drawTextEx(text, position, tint, &offset);
}

pub fn drawTextAlloc(self: @This(), allocator: std.mem.Allocator, comptime fmt: []const u8, args: anytype, position: rl.Vector2, tint: rl.Color) !void {
    const text = try std.fmt.allocPrint(allocator, fmt, args);
    defer allocator.free(text);

    self.drawText(text, position, tint);
}

pub fn measureText(self: @This(), text: []const u8) rl.Vector2 {
    var text_size = rl.Vector2.zero();

    if (self.font.texture.id == 0) return text_size; // Security check
    if (text.len == 0) return text_size;

    const font_size = self.getSize();
    const spacing = self.getSpacing();

    var line_width: f32 = 0;
    text_size.y = font_size;

    var iter = std.unicode.Utf8Iterator{ .bytes = text, .i = 0 };
    while (iter.nextCodepoint()) |codepoint| {
        if (codepoint == '\n') {
            text_size.y += font_size * self.line_height;

            line_width = 0;
        } else {
            if (line_width > 0) {
                line_width += spacing;
            }

            const index: u32 = @intCast(rl.getGlyphIndex(self.font, codepoint));
            if (self.font.glyphs[index].advanceX != 0) {
                line_width += @floatFromInt(self.font.glyphs[index].advanceX);
            } else {
                line_width += self.font.recs[index].width;
                line_width += @floatFromInt(self.font.glyphs[index].offsetX);
            }

            text_size.x = @max(text_size.x, line_width);
        }
    }

    return text_size;
}

pub fn drawTextCenter(self: @This(), text: []const u8, position: rl.Vector2, tint: rl.Color) void {
    const text_size = self.measureText(text);
    const adjusted_position = rl.Vector2{
        .x = position.x - text_size.x / 2,
        .y = position.y - text_size.y / 2,
    };
    self.drawText(text, adjusted_position, tint);
}
