const std = @import("std");

const Self = @This();

const RawStats = struct {
    user: u32,
    nice: u32,
    system: u32,
    idle: u32,
    iowait: u32,
    irq: u32,
    softirq: u32,
    steal: u32,
    guest: u32,
    guest_nice: u32,
};

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

    pub fn active_ratio(self: Stats) f32 {
        return @as(f32, @floatFromInt(self.all_active)) / @as(f32, @floatFromInt(self.all));
    }

    pub fn active_perc(self: Stats) u8 {
        return @intCast(100 * self.all_active / self.all);
    }
};

fh: std.fs.File,
buf: []u8,

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

    const buf = try alloc.alloc(u8, 512);
    errdefer alloc.free(buf);

    return .{ .fh = fh, .buf = buf };
}

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

pub 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: RawStats = undefined;

    inline for (std.meta.fields(RawStats)) |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 };
}

pub fn get_delta(self: Self, prev: *Stats) !Stats {
    const next = try self.get();
    const delta: Stats = .{
        .all = next.all - prev.all,
        .all_idle = next.all_idle - prev.all_idle,
        .all_active = next.all_active - prev.all_active,
    };
    prev.* = next;
    return delta;
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const alloc = gpa.allocator();

    const monitor = try Self.init(alloc);
    defer monitor.deinit(alloc);

    var stats = try monitor.get();
    std.time.sleep(std.time.ns_per_s);
    const delta = try monitor.get_delta(&stats);

    std.debug.print("{any}\n", .{delta});
    std.debug.print("{d:.6} {d}\n", .{ delta.active_ratio(), delta.active_perc() });
}
