const std = @import("std");
const c = @import("c.zig").c;
const config = @import("config.zig");
const util = @import("util.zig");
const atoms_mod = @import("atoms.zig");
const client_mod = @import("client.zig");
const WM = client_mod.WM;
const bar_mod = @import("bar.zig");
const event_mod = @import("event.zig");

var wm_global: ?*WM = null;

fn xerrorStrict(dpy: ?*c.Display, ev: [*c]c.XErrorEvent) callconv(.c) c_int {
    _ = dpy;
    _ = ev;
    util.die("wm: another window manager is already running\n", .{});
    return 0;
}

fn xerrorNormal(dpy: ?*c.Display, ev: [*c]c.XErrorEvent) callconv(.c) c_int {
    const ec = ev.*.error_code;
    if (ec == c.BadWindow or ec == c.BadMatch or ec == c.BadDrawable)
        return 0;
    var buf: [128]u8 = undefined;
    _ = c.XGetErrorText(dpy, ec, &buf, buf.len);
    util.log("wm: xerror: {s} ({d})\n", .{ std.mem.sliceTo(&buf, 0), ec });
    return 0;
}

fn sigTerm(sig: c_int) callconv(.c) void {
    _ = sig;
    if (wm_global) |wm| wm.running = false;
}

fn sigChld(sig: c_int) callconv(.c) void {
    _ = sig;
    while (true) {
        var status: c_int = 0;
        const pid = std.c.waitpid(-1, &status, 1);
        if (pid <= 0) break;
    }
}

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

    var args = std.process.args();
    _ = args.next();
    while (args.next()) |arg| {
        if (std.mem.eql(u8, arg, "-v")) {
            util.log("wm 0.1.0\n", .{});
            std.process.exit(0);
        } else {
            util.die("usage: wm [-v]\n", .{});
        }
    }

    if (c.XOpenDisplay(null)) |dpy| {
        var wm = WM{
            .display = dpy,
            .screen = c.DefaultScreen(dpy),
            .root = c.RootWindow(dpy, c.DefaultScreen(dpy)),
            .sw = @intCast(c.DisplayWidth(dpy, c.DefaultScreen(dpy))),
            .sh = @intCast(c.DisplayHeight(dpy, c.DefaultScreen(dpy))),
            .atoms = undefined,
            .bar = undefined,
            .clients = null,
            .focused = null,
            .running = true,
            .allocator = alloc,
            .support_win = 0,
            .grab = null,
            .visual = c.DefaultVisual(dpy, c.DefaultScreen(dpy)),
            .colormap = c.DefaultColormap(dpy, c.DefaultScreen(dpy)),
        };
        wm_global = &wm;

        _ = c.XSetErrorHandler(xerrorStrict);
        _ = c.XSelectInput(dpy, wm.root,
            c.SubstructureRedirectMask | c.SubstructureNotifyMask |
            c.ButtonPressMask | c.StructureNotifyMask |
            c.PropertyChangeMask | c.EnterWindowMask |
            c.FocusChangeMask | c.KeymapStateMask);
        _ = c.XSync(dpy, 0);
        _ = c.XSetErrorHandler(xerrorNormal);

        const cursor = c.XCreateFontCursor(dpy, 68);
        _ = c.XDefineCursor(dpy, wm.root, cursor);

        wm.atoms = atoms_mod.Atoms.init(dpy);

        var wm_sn_buf: [32]u8 = undefined;
        const wm_sn_name = std.fmt.bufPrintZ(&wm_sn_buf, "WM_S{d}", .{wm.screen}) catch "WM_S0";
        const wm_sn = c.XInternAtom(dpy, wm_sn_name, 0);

        wm.support_win = c.XCreateSimpleWindow(dpy, wm.root, -1, -1, 1, 1, 0, 0, 0);
        _ = c.XChangeProperty(dpy, wm.support_win, wm.atoms.net_supporting_wm_check,
            c.XA_WINDOW, 32, c.PropModeReplace,
            @ptrCast(&wm.support_win), 1);
        _ = c.XChangeProperty(dpy, wm.root, wm.atoms.net_supporting_wm_check,
            c.XA_WINDOW, 32, c.PropModeReplace,
            @ptrCast(&wm.support_win), 1);
        _ = c.XSetSelectionOwner(dpy, wm_sn, wm.support_win, c.CurrentTime);

        const pid_val: c_long = @intCast(std.os.linux.getpid());
        _ = c.XChangeProperty(dpy, wm.support_win, wm.atoms.net_wm_pid,
            c.XA_CARDINAL, 32, c.PropModeReplace,
            @ptrCast(@constCast(&pid_val)), 1);

        const supported = wm.atoms.supportedList();
        _ = c.XChangeProperty(dpy, wm.root, wm.atoms.net_supported,
            c.XA_ATOM, 32, c.PropModeReplace,
            @ptrCast(@constCast(supported.ptr)), @intCast(supported.len));

        var btn_init: [128]client_mod.ButtonEntry = undefined;
        for (&btn_init) |*b| b.* = .{ .x = 0, .w = 0, .win = 0 };
        wm.bar = .{
            .window = 0,
            .pixmap = 0,
            .draw = null,
            .fonts = .{null} ** config.font_names.len,
            .colors = undefined,
            .buttons = btn_init,
            .button_count = 0,
        };
        bar_mod.create(&wm) catch |err| util.die("bar init: {}\n", .{err});

        installSignals();

        _ = c.XGrabServer(dpy);
        scanWindows(&wm);
        _ = c.XUngrabServer(dpy);

        event_mod.grabKeys(&wm);
        bar_mod.draw(&wm);

        for (&config.autostart) |cmd| {
            util.spawn(alloc, cmd);
        }

        var ev: c.XEvent = undefined;
        while (wm.running) {
            _ = c.XNextEvent(dpy, &ev);
            if (wm.running)
                event_mod.dispatch(&wm, &ev);
        }

        cleanup(&wm);
    } else {
        util.die("wm: cannot open display\n", .{});
    }
}

fn installSignals() void {
    const act = std.posix.Sigaction{
        .handler = .{ .handler = sigTerm },
        .mask = std.posix.empty_sigset,
        .flags = 0,
    };
    std.posix.sigaction(std.posix.SIG.TERM, &act, null);
    std.posix.sigaction(std.posix.SIG.INT, &act, null);

    const chld_act = std.posix.Sigaction{
        .handler = .{ .handler = sigChld },
        .mask = std.posix.empty_sigset,
        .flags = 0,
    };
    std.posix.sigaction(std.posix.SIG.CHLD, &chld_act, null);
}

fn scanWindows(wm: *WM) void {
    var root_ret: c.Window = 0;
    var parent_ret: c.Window = 0;
    var children: [*c]c.Window = null;
    var nchildren: c_uint = 0;
    if (c.XQueryTree(wm.display, wm.root, &root_ret, &parent_ret, &children, &nchildren) != 0) {
        if (children != null) {
            var i: c_uint = 0;
            while (i < nchildren) : (i += 1) {
                var wa: c.XWindowAttributes = undefined;
                if (c.XGetWindowAttributes(wm.display, children[@intCast(i)], &wa) != 0) {
                    if (wa.override_redirect == 0 and wa.map_state == c.IsViewable) {
                        client_mod.manage(wm, children[@intCast(i)]) catch {};
                    }
                }
            }
            _ = c.XFree(children);
        }
    }
}

fn cleanup(wm: *WM) void {
    var it = wm.clients;
    while (it) |cl| {
        const next = cl.next;
        client_mod.unmanage(wm, cl);
        it = next;
    }
    bar_mod.destroy(wm);
    if (wm.support_win != 0)
        _ = c.XDestroyWindow(wm.display, wm.support_win);
    _ = c.XCloseDisplay(wm.display);
}
