const std = @import("std");
const io = std.io;
const heap = std.heap;
const json = std.json;
const posix = std.posix;

const battery = @import("modules/battery.zig");
const calendar = @import("modules/calendar.zig");
const display = @import("modules/display.zig");
const uptime = @import("modules/uptime.zig");
const volume = @import("modules/volume.zig");
const memory = @import("modules/memory.zig");
const loadavg = @import("modules/loadavg.zig");
const Module = @import("module.zig");

const bg_left_color = Color{ .r = 0, .g = 0, .b = 0 };
const bg_right_color = Color{ .r = 0.2, .g = 0.2, .b = 0.2 };

pub fn main() !void {
    var gpa = heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    var arena = heap.ArenaAllocator.init(gpa.allocator());
    defer arena.deinit();

    var backlight_count: usize = 0;
    var powersupply_count: usize = 0;

    var backlight_dir = try std.fs.openDirAbsolute("/sys/class/backlight", .{ .iterate = true });
    defer backlight_dir.close();
    var backlight_it = backlight_dir.iterate();
    while (try backlight_it.next()) |_| backlight_count += 1;

    var powersupply_dir = try std.fs.openDirAbsolute("/sys/class/power_supply", .{ .iterate = true });
    defer powersupply_dir.close();
    var powersupply_it = powersupply_dir.iterate();
    while (try powersupply_it.next()) |_| powersupply_count += 1;

    const stdout_file = std.io.getStdOut().writer();
    var bw = std.io.bufferedWriter(stdout_file);
    const stdout = bw.writer();

    try stdout.print("{{\"version\":1}}\n[\n", .{});
    try bw.flush();

    var _uptime = try uptime.init(gpa.allocator());
    var _loadavg = try loadavg.init(gpa.allocator());
    var _memory = try memory.init(gpa.allocator());
    var _volume = try volume.init(gpa.allocator());
    var _display = try display.init(gpa.allocator());
    var _battery = try battery.init(gpa.allocator());
    var _calendar = try calendar.init(gpa.allocator());

    var module_arr = std.ArrayList(*Module).init(gpa.allocator());
    defer module_arr.deinit();
    try module_arr.append(&_uptime.module);
    try module_arr.append(&_loadavg.module);
    try module_arr.append(&_memory.module);
    try module_arr.append(&_volume.module);
    if (backlight_count > 0) try module_arr.append(&_display.module);
    if (powersupply_count > 0) try module_arr.append(&_battery.module);
    try module_arr.append(&_calendar.module);
    const modules = try module_arr.toOwnedSlice();
    const modules_len = 1 / @as(f32, @floatFromInt(modules.len));

    var act = posix.Sigaction{
        .handler = .{ .handler = sigHandler },
        .mask = posix.empty_sigset,
        .flags = 0,
    };
    try posix.sigaction(posix.SIG.USR1, &act, null);

    while (true) {
        const start_time = std.time.milliTimestamp();

        try stdout.print("[", .{});
        var bg = Color{ .r = 0.0, .g = 0.0, .b = 0.0 };
        for (modules, 0..) |module, idx| {
            const module_start_time = std.time.nanoTimestamp();

            const output = module.getBlock(arena.allocator()) catch {
                const output = Module.JSON{
                    .full_text = " [error] ",
                    .background = try bg.getString(arena.allocator()),
                    .color = try bg.add(Color{ .r = 1, .g = 0.1, .b = 0.1 }).getString(arena.allocator()),
                    .separator = false,
                    .separator_block_width = 0,
                };
                try json.stringify(output, .{ .emit_null_optional_fields = false }, stdout);
                try stdout.print(",", .{});
                const progress = @as(f32, @floatFromInt(idx + 1)) * modules_len;
                bg = bg_left_color.mix(bg_right_color, progress);
                continue;
            };
            std.debug.print("output: {any}\n", .{output});

            const bg_color = bg.mix(Color{ .r = 0.25, .g = 0.1, .b = 0.1 }, @max(0.0, output.severity - 1.0));
            const fg_color = bg_color.add(Color{ .r = 0.6, .g = 0.6, .b = 0.6 })
                .mix(Color{ .r = 1, .g = 0.1, .b = 0.1 }, output.severity)
                .mix(Color{ .r = 1, .g = 0.3, .b = 0.54 }, @max(0.0, output.severity - 1.0));
            const jsonBlock = Module.JSON{
                .full_text = try std.fmt.allocPrint(arena.allocator(), " {s} ", .{output.full_text}),
                .background = try bg_color.getString(arena.allocator()),
                .color = try fg_color.getString(arena.allocator()),
                .separator = false,
                .separator_block_width = 0,
            };
            try json.stringify(jsonBlock, .{ .emit_null_optional_fields = false }, stdout);
            try stdout.print(",", .{});

            const progress = @as(f32, @floatFromInt(idx + 1)) * modules_len;
            bg = bg_left_color.mix(bg_right_color, progress);

            const module_end_time = std.time.nanoTimestamp();
            std.debug.print("module {d} finished in {d}ns\n", .{ idx, module_end_time - module_start_time });
        }
        try stdout.print("],\n", .{});

        std.debug.print("\n", .{});
        try bw.flush();
        _ = arena.reset(.retain_capacity);

        const end_time = std.time.milliTimestamp();
        std.debug.print("\nFinished in {d}ms\n\n", .{end_time - start_time});
        var i: usize = 0;
        while (!refresh and i < 100) : (i += 1) std.time.sleep(10_000_000);
        refresh = false;
    }
}

var refresh = false;

fn sigHandler(_: i32) callconv(.C) void {
    refresh = true;
}

pub const Color = struct {
    r: f32,
    g: f32,
    b: f32,

    // creates a color from an html hexcode string
    pub fn init(string: []const u8) !Color {
        const r = try std.fmt.parseInt(u8, string[1..2], 16);
        const g = try std.fmt.parseInt(u8, string[3..4], 16);
        const b = try std.fmt.parseInt(u8, string[5..6], 16);
        return .{
            .r = @as(f32, @floatFromInt(r)) / 255,
            .g = @as(f32, @floatFromInt(g)) / 255,
            .b = @as(f32, @floatFromInt(b)) / 255,
        };
    }

    // returns the color as an html hexcode string
    pub fn getString(self: *const Color, allocator: std.mem.Allocator) ![]const u8 {
        const v = self.getVector();
        const x = @max(VecRGB{ 0.0, 0.0, 0.0 }, @min(v, VecRGB{ 1.0, 1.0, 1.0 }));
        const y = x * VecRGB{ 255.0, 255.0, 255.0 };
        const r = @as(u8, @intFromFloat(y[0]));
        const g = @as(u8, @intFromFloat(y[1]));
        const b = @as(u8, @intFromFloat(y[2]));
        return try std.fmt.allocPrint(allocator, "#{s}{s}{s}", .{
            std.fmt.fmtSliceHexLower(&[_]u8{r}),
            std.fmt.fmtSliceHexLower(&[_]u8{g}),
            std.fmt.fmtSliceHexLower(&[_]u8{b}),
        });
    }

    // returns the result of `a` plus `b`
    pub fn add(a: *const Color, b: Color) Color {
        const va = a.getVector();
        const vb = b.getVector();
        return Color.fromVector(va + vb);
    }

    // returns the result of the linear interpolation between `a` and `b` by amount `t`
    pub fn mix(a: *const Color, b: Color, t: f32) Color {
        const va = a.getVector();
        const vb = b.getVector();
        const vt = VecRGB{ t, t, t };
        return Color.fromVector(@mulAdd(VecRGB, vb - va, vt, va));
    }

    // returns the color with its rgb values clamped to those in `min` and `max`
    pub fn clamp(self: *const Color, min: Color, max: Color) Color {
        return .{
            .r = @max(max.r, @min(min.r, self.r)),
            .g = @max(max.g, @min(min.g, self.g)),
            .b = @max(max.b, @min(min.b, self.b)),
        };
    }

    fn getVector(self: *const Color) VecRGB {
        return VecRGB{
            self.r,
            self.g,
            self.b,
        };
    }

    fn fromVector(v: VecRGB) Color {
        return .{
            .r = v[0],
            .g = v[1],
            .b = v[2],
        };
    }

    // why did i use vectors?? this was probably a bad idea,
    // but now i am too lazy to go back.
    // it could have worked if i had made them chainable somehow,
    // so it doesn't get converted to and from a vector constantly.
    pub const VecRGB = @Vector(3, f32);
};

// the hell were these for??
pub fn getBgColor(idx: usize, override: ?[]const u8) []const u8 {
    if (override) |o| return o;
    return switch (idx % 2) {
        0 => "#002b36",
        1 => "#0e1b15",
        else => "#002b36",
    };
}

pub fn getFgColor(idx: usize, override: ?[]const u8) []const u8 {
    if (override) |o| return o;
    return switch (idx % 2) {
        // 0 => "#93a1a1",
        // 1 => "#d6c2d6",
        else => "#",
    };
}
