const std = @import("std");

const c = @cImport({
    @cInclude("errno.h");
    @cInclude("string.h");
    @cInclude("libevdev/libevdev.h");
});

const log = std.log.scoped(.input_listener);

const Self = @This();

pub const Error = error{
    Evdev,
};

const FdEvdevPair = struct {
    path: []const u8,
    fd: std.posix.fd_t,
    dev: *c.libevdev,
};

pub const EventCallback = struct {
    ptr: *anyopaque,
    key_event_callback_fn: *const fn (ptr: *anyopaque, device: []const u8, key_code: u32, key_name: []const u8, value: u32) anyerror!void,

    pub fn init(ptr: anytype) EventCallback {
        const T = @TypeOf(ptr);
        const ptr_info = @typeInfo(T);
        if (ptr_info != .pointer) @compileError("ptr must be a pointer");
        if (ptr_info.pointer.size != .one) @compileError("ptr must be a single item pointer");

        const gen = struct {
            pub fn keyEventCallback(pointer: *anyopaque, device: []const u8, key_code: u32, key_name: []const u8, value: u32) anyerror!void {
                const self: T = @ptrCast(@alignCast(pointer));
                try @call(
                    .always_inline,
                    ptr_info.pointer.child.keyEventCallback,
                    .{ self, device, key_code, key_name, value },
                );
            }
        };

        return .{
            .ptr = ptr,
            .key_event_callback_fn = gen.keyEventCallback,
        };
    }

    pub fn keyEventCallback(self: EventCallback, device: []const u8, key_code: u32, key_name: []const u8, value: u32) !void {
        return self.key_event_callback_fn(self.ptr, device, key_code, key_name, value);
    }
};

allocator: std.mem.Allocator,
devs: []FdEvdevPair,

event_callback: EventCallback,

pub fn init(allocator: std.mem.Allocator, inputs: []const []const u8, event_callback: EventCallback) !Self {
    const devs = try allocator.alloc(FdEvdevPair, inputs.len);
    var max_i: usize = 0;
    errdefer {
        for (devs[0 .. max_i + 1]) |pair| {
            c.libevdev_free(pair.dev);
            std.posix.close(pair.fd);
        }
        allocator.free(devs);
    }

    for (inputs, 0..) |path, i| {
        const fd = try std.posix.open(path, .{ .ACCMODE = .RDONLY, .NONBLOCK = false }, 0);
        errdefer std.posix.close(fd);

        var dev: *c.libevdev = undefined;
        if (c.libevdev_new_from_fd(fd, @ptrCast(&dev)) < 0)
            return error.Evdev;

        devs[i] = .{
            .path = try allocator.dupe(u8, path),
            .fd = fd,
            .dev = dev,
        };
        max_i = i;
    }

    return .{
        .allocator = allocator,
        .devs = devs,
        .event_callback = event_callback,
    };
}

pub fn deinit(self: *Self) void {
    for (self.devs) |pair| {
        self.allocator.free(pair.path);
        c.libevdev_free(pair.dev);
        std.posix.close(pair.fd);
    }
    self.allocator.free(self.devs);
}

fn printEvent(pair: *const FdEvdevPair, ev: *const c.input_event) void {
    log.info("Event ({s}): {s} ({}) {s} ({}) => {}", .{
        pair.path,
        c.libevdev_event_type_get_name(ev.type),
        ev.type,
        c.libevdev_event_code_get_name(ev.type, ev.code),
        ev.code,
        ev.value,
    });
}

fn handleEvent(self: *const Self, pair: *const FdEvdevPair, ev: *const c.input_event) !void {
    if (c.libevdev_event_is_type(ev, c.EV_KEY) == 0) return;

    const key_name: []const u8 = std.mem.span(c.libevdev_event_code_get_name(ev.type, ev.code));
    try self.event_callback.keyEventCallback(pair.path, ev.code, key_name, @intCast(ev.value));
}

fn eventListener(self: *const Self, pair: *const FdEvdevPair) !void {
    var rc: c_int = c.LIBEVDEV_READ_STATUS_SUCCESS;

    while (rc == c.LIBEVDEV_READ_FLAG_SYNC or rc == c.LIBEVDEV_READ_STATUS_SUCCESS or rc == -c.EAGAIN) {
        var ev: c.input_event = undefined;
        rc = c.libevdev_next_event(pair.dev, c.LIBEVDEV_READ_FLAG_NORMAL | c.LIBEVDEV_READ_FLAG_BLOCKING, &ev);
        switch (rc) {
            c.LIBEVDEV_READ_STATUS_SYNC => {
                log.err("{s}: events dropped", .{pair.path});
                while (rc == c.LIBEVDEV_READ_STATUS_SYNC) {
                    log.err("Sync event ({s})", .{pair.path});
                    printEvent(pair, &ev);
                    try self.handleEvent(pair, &ev);
                    rc = c.libevdev_next_event(pair.dev, c.LIBEVDEV_READ_FLAG_SYNC, &ev);
                }
                log.err("{s}: re-synced", .{pair.path});
            },
            c.LIBEVDEV_READ_STATUS_SUCCESS => {
                printEvent(pair, &ev);
                try self.handleEvent(pair, &ev);
            },
            else => {},
        }
    }

    if (rc != c.LIBEVDEV_READ_STATUS_SYNC and rc != -c.EAGAIN) {
        log.err("{s}: failed to handle events: {s}", .{ pair.path, c.strerror(-rc) });
    }
}

fn eventListenerThread(self: *const Self, pair: *const FdEvdevPair) void {
    self.eventListener(pair) catch unreachable;
    unreachable;
}

pub fn start(self: *Self) !void {
    for (self.devs) |*pair| {
        _ = try std.Thread.spawn(.{}, eventListenerThread, .{ self, pair });
    }
}
