const std = @import("std");
const Io = std.Io;
const time = std.time;
const unicode = std.unicode;

const N = 1_000_000;

const KiB = 1024;
const MiB = 1024 * KiB;
const GiB = 1024 * MiB;

const ResultCount = struct {
    count: usize,
    throughput: u64,
};

fn benchTime(io: Io) i96 {
    return Io.Clock.awake.now(io).nanoseconds;
}

fn benchmarkCodepointCount(buf: []const u8, io: Io) !ResultCount {
    const bytes = N * buf.len;

    const start = benchTime(io);
    var i: usize = 0;
    var r: usize = undefined;
    while (i < N) : (i += 1) {
        r = try @call(
            .never_inline,
            std.unicode.utf8CountCodepoints,
            .{buf},
        );
    }
    const end = benchTime(io);

    const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s;
    const throughput = @as(u64, @intFromFloat(@as(f64, @floatFromInt(bytes)) / elapsed_s));

    return ResultCount{ .count = r, .throughput = throughput };
}

pub fn main(init: std.process.Init) !void {
    // Size of buffer is about size of printed message.
    const io = init.io;
    var stdout_buffer: [0x100]u8 = undefined;
    var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
    const stdout = &stdout_writer.interface;

    try stdout.print("short ASCII strings\n", .{});
    try stdout.flush();
    {
        const result = try benchmarkCodepointCount("abc", io);
        try stdout.print("  count: {:5} MiB/s [{d}]\n", .{ result.throughput / (1 * MiB), result.count });
    }

    try stdout.print("short Unicode strings\n", .{});
    try stdout.flush();
    {
        const result = try benchmarkCodepointCount("ŌŌŌ", io);
        try stdout.print("  count: {:5} MiB/s [{d}]\n", .{ result.throughput / (1 * MiB), result.count });
    }

    try stdout.print("pure ASCII strings\n", .{});
    try stdout.flush();
    {
        const part = "hello";
        const buf: [16][part.len]u8 = @splat(part.*);
        const result = try benchmarkCodepointCount(@ptrCast(&buf), io);
        try stdout.print("  count: {:5} MiB/s [{d}]\n", .{ result.throughput / (1 * MiB), result.count });
    }

    try stdout.print("pure Unicode strings\n", .{});
    try stdout.flush();
    {
        const part = "こんにちは";
        const buf: [16][part.len]u8 = @splat(part.*);
        const result = try benchmarkCodepointCount(@ptrCast(&buf), io);
        try stdout.print("  count: {:5} MiB/s [{d}]\n", .{ result.throughput / (1 * MiB), result.count });
    }

    try stdout.print("mixed ASCII/Unicode strings\n", .{});
    try stdout.flush();
    {
        const part = "Hyvää huomenta";
        const buf: [16][part.len]u8 = @splat(part.*);
        const result = try benchmarkCodepointCount(@ptrCast(&buf), io);
        try stdout.print("  count: {:5} MiB/s [{d}]\n", .{ result.throughput / (1 * MiB), result.count });
    }
    try stdout.flush();
}
