const std = @import("std");
const xinerama = @import("xinerama.zig");
const Walker = @import("Walker.zig");

pub const std_options = std.Options{
    .log_level = .debug,
};

pub fn main() !u8 {
    const alloc = std.heap.c_allocator;
    const home_s = std.os.getenv("HOME") orelse return error.HomeNotSet;
    const runtime_dir = std.os.getenv("XDG_RUNTIME_DIR") orelse return error.MissingRuntimeDir;

    var walker = Walker.init(alloc);
    defer walker.deinit();

    try walker.walk(
        try std.fs.openDirAbsolute(
            "/usr/share/backgrounds/",
            .{ .iterate = true },
        ),
    );

    try walkLocalWps(&walker, home_s);

    const swww_socket_path = try std.fs.path.join(alloc, &.{ runtime_dir, "swww.socket" });
    defer alloc.free(swww_socket_path);

    const has_swww = if (std.fs.cwd().statFile(swww_socket_path)) |_| true else |e| switch (e) {
        error.FileNotFound => false,
        else => return e,
    };

    if (has_swww) {
        std.log.info("found running swww daemon, using swww backend", .{});
        return try setWallpapersSwww(alloc, walker.files.items);
    } else {
        std.log.info("using X/feh backend", .{});
        return try setWallpapersX(alloc, walker.files.items);
    }
}

fn walkLocalWps(walker: *Walker, home_s: []const u8) !void {
    const wp_path = try std.fs.path.join(walker.files.allocator, &.{ home_s, ".local/share/backgrounds" });
    defer walker.files.allocator.free(wp_path);

    var local_wp = std.fs.cwd().openDir(wp_path, .{ .iterate = true }) catch |e| switch (e) {
        error.FileNotFound => {
            std.log.warn(
                "No local wallpaper directory @ {s}, skipping local wallpapers",
                .{wp_path},
            );
            return;
        },
        else => return e,
    };
    defer local_wp.close();

    try walker.walk(local_wp);
}

fn setWallpapersSwww(alloc: std.mem.Allocator, wps: []const []const u8) !u8 {
    const exec_res = try std.process.Child.run(.{
        .allocator = alloc,
        .argv = &.{ "swww", "query" },
    });
    defer alloc.free(exec_res.stdout);
    defer alloc.free(exec_res.stderr);

    if (!std.meta.eql(exec_res.term, .{ .Exited = 0 }))
        return error.SwwwQuery;

    var prng = std.rand.DefaultPrng.init(std.crypto.random.int(u64));
    const rand = prng.random();

    var output_iter = std.mem.tokenizeScalar(u8, exec_res.stdout, '\n');
    while (output_iter.next()) |line| {
        if (line.len == 0) continue;
        const output = std.mem.sliceTo(line, ':');

        const argv = [_][]const u8{
            "swww",
            "img",
            wps[rand.uintAtMost(usize, wps.len - 1)],
            "--outputs",
            output,
            "--transition-type",
            "wipe",
            "--transition-angle",
            "30",
            "--transition-bezier",
            "0.8,0.0,0.2,1.0",
            "--transition-fps",
            "60",
            "--transition-duration",
            "2",
        };

        var child = std.process.Child.init(&argv, alloc);
        const term = try child.spawnAndWait();
        const code = switch (term) {
            .Exited => |n| n,
            .Signal,
            .Stopped,
            .Unknown,
            => |n| b: {
                std.log.err("Child borked with code {}", .{n});
                break :b 1;
            },
        };

        if (code != 0) return code;
    }

    return 0;
}

fn setWallpapersX(alloc: std.mem.Allocator, wps: []const []const u8) !u8 {
    const screens = try xinerama.getHeadCount();

    const feh_baseargs = [_][]const u8{
        "feh",
        "--no-fehbg",
        "--bg-fill",
    };

    var feh_argv = try alloc.alloc(
        []const u8,
        feh_baseargs.len + @as(usize, @intCast(screens)),
    );
    defer alloc.free(feh_argv);
    @memcpy(feh_argv[0..feh_baseargs.len], &feh_baseargs);

    var prng = std.rand.DefaultPrng.init(std.crypto.random.int(u64));
    const rand = prng.random();

    var i: u31 = 0;
    while (i < screens) : (i += 1) {
        const idx = rand.uintAtMost(usize, wps.len - 1);
        feh_argv[feh_baseargs.len + i] = wps[idx];
    }

    std.log.info("feh argv: {s}", .{feh_argv});
    var child = std.process.Child.init(feh_argv, alloc);
    const term = try child.spawnAndWait();

    return switch (term) {
        .Exited => |n| n,
        .Signal,
        .Stopped,
        .Unknown,
        => |n| b: {
            std.log.err("Child borked with code {}", .{n});
            break :b 1;
        },
    };
}
