const std = @import("std");
const Self = @This();

fh: std.fs.File,
buf: []u8,
stats: Stats,
perc: u8 = 0,

pub fn init(self: *Self, alloc: std.mem.Allocator) !void {
    self.fh = try std.fs.openFileAbsolute("/proc/stat", .{ .mode = .read_only });
    errdefer self.fh.close();

    self.buf = try alloc.alloc(u8, 256);
    errdefer alloc.free(self.buf);

    self.stats = try self.get();
}

pub const Stats = struct {
    all: u32,
    all_idle: u32,
    all_active: u32,
};

pub fn update(self: *Self) ![]const u8 {
    const delta = try self.get_delta();
    if (delta.all != 0) {
        self.perc = @intCast(@divFloor(1 + 2 * 100 * delta.all_active, 2 * delta.all));
    }
    return try std.fmt.bufPrint(self.buf, "cpu: {d:0>2}%", .{self.perc});
}

pub fn deinit(self: Self, alloc: std.mem.Allocator) void {
    self.fh.close();
    alloc.free(self.buf);
}

fn get(self: Self) !Stats {
    try self.fh.seekTo(0);
    const line = try self.fh.reader().readUntilDelimiter(self.buf, '\n');

    var tokens = std.mem.tokenizeScalar(u8, line, ' ');
    _ = tokens.next(); // skip "cpu"

    var raw: struct {
        user: u32,
        nice: u32,
        system: u32,
        idle: u32,
        iowait: u32,
        irq: u32,
        softirq: u32,
        steal: u32,
        guest: u32,
        guest_nice: u32,
    } = undefined;

    inline for (std.meta.fields(@TypeOf(raw))) |f| {
        const token = tokens.next() orelse return error.MissingStat;
        @field(raw, f.name) = try std.fmt.parseInt(f.type, token, 10);
    }

    const idle = raw.idle + raw.iowait;
    const active = raw.user + raw.nice + raw.system + raw.irq + raw.softirq + raw.steal;

    return .{ .all = idle + active, .all_idle = idle, .all_active = active };
}

fn get_delta(self: *Self) !Stats {
    const prev = self.stats;
    const next = try self.get();
    self.stats = next;

    return .{
        .all = next.all - prev.all,
        .all_idle = next.all_idle - prev.all_idle,
        .all_active = next.all_active - prev.all_active,
    };
}
