const std = @import("std");
// const vk = @import("vk");
// const c = @import("../c.zig");
// const Window = @import("../au.zig").Window;
const Self = @This();

const Window = @import("Window.zig");
const c = Window.c;

alloc: std.mem.Allocator,
events: std.ArrayListUnmanaged(Event) = .{}, // todo bounded array?
drops: std.ArrayListUnmanaged([][]const u8) = .{}, // todo bounded array?

pub fn init(alloc: std.mem.Allocator) Self {
    return .{
        .alloc = alloc,
    };
}

pub fn deinit(self: *Self) void {
    self.clear();

    self.events.deinit(self.alloc);
    self.drops.deinit(self.alloc);
}

pub fn connect(self: *Self, handle: *c.GLFWwindow) void {
    // todo somehow prevent double-connect?
    c.glfwSetWindowUserPointer(handle, self);
    _ = c.glfwSetWindowPosCallback(handle, onWindowPos);
    _ = c.glfwSetWindowSizeCallback(handle, onWindowSize);
    _ = c.glfwSetWindowCloseCallback(handle, onWindowClose);
    _ = c.glfwSetWindowRefreshCallback(handle, onWindowRefresh);
    _ = c.glfwSetWindowFocusCallback(handle, onWindowFocus);
    _ = c.glfwSetWindowIconifyCallback(handle, onWindowIconify);
    _ = c.glfwSetWindowMaximizeCallback(handle, onWindowMaximize);
    _ = c.glfwSetFramebufferSizeCallback(handle, onFramebufferSize);
    _ = c.glfwSetWindowContentScaleCallback(handle, onWindowContentScale);
    _ = c.glfwSetMouseButtonCallback(handle, onMouseButton);
    _ = c.glfwSetCursorPosCallback(handle, onCursorPos);
    _ = c.glfwSetCursorEnterCallback(handle, onCursorEnter);
    _ = c.glfwSetScrollCallback(handle, onScroll);
    _ = c.glfwSetKeyCallback(handle, onKey);
    _ = c.glfwSetCharModsCallback(handle, onCharMods);
    _ = c.glfwSetDropCallback(handle, onDrop);
}

pub fn disconnect(_: *Self, handle: *c.GLFWwindow) void {
    // todo somehow prevent double-disconnect?
    c.glfwSetWindowUserPointer(handle, null);
    _ = c.glfwSetWindowPosCallback(handle, null);
    _ = c.glfwSetWindowSizeCallback(handle, null);
    _ = c.glfwSetWindowCloseCallback(handle, null);
    _ = c.glfwSetWindowRefreshCallback(handle, null);
    _ = c.glfwSetWindowFocusCallback(handle, null);
    _ = c.glfwSetWindowIconifyCallback(handle, null);
    _ = c.glfwSetWindowMaximizeCallback(handle, null);
    _ = c.glfwSetFramebufferSizeCallback(handle, null);
    _ = c.glfwSetWindowContentScaleCallback(handle, null);
    _ = c.glfwSetMouseButtonCallback(handle, null);
    _ = c.glfwSetCursorPosCallback(handle, null);
    _ = c.glfwSetCursorEnterCallback(handle, null);
    _ = c.glfwSetScrollCallback(handle, null);
    _ = c.glfwSetKeyCallback(handle, null);
    _ = c.glfwSetCharModsCallback(handle, null);
    _ = c.glfwSetDropCallback(handle, null);
}

pub fn clear(self: *Self) void {
    for (self.drops.items) |drop| {
        for (drop) |path| {
            self.alloc.free(path);
        }
        self.alloc.free(drop);
    }
    self.drops.clearAndFree(self.alloc);

    self.events.clearRetainingCapacity();
}

fn getBus(handle: ?*c.GLFWwindow) *Self {
    return @alignCast(@ptrCast(c.glfwGetWindowUserPointer(handle)));
}

pub const Event = union(enum) {
    const WindowPos = struct { x: i32, y: i32 };
    const WindowSize = struct { x: i32, y: i32 };
    const WindowClose = struct {};
    const WindowRefresh = struct {};
    const WindowFocus = struct { focused: bool };
    const WindowIconify = struct { iconified: bool };
    const WindowMaximize = struct { maximized: bool };
    const FramebufferSize = struct { width: u32, height: u32 };
    const WindowContentScale = struct { x: f32, y: f32 };
    const MouseButton = struct {
        button: c_int, // todo enum
        action: c_int, // todo enum
        mods: c_int, // todo bitmask
    };
    const CursorPos = struct { x: f64, y: f64 };
    const CursorEnter = struct { entered: bool };
    const Scroll = struct { dx: f64, dy: f64 };
    const Key = struct {
        key: c_int, // todo enum
        scan: c_int, // todo ???
        action: c_int, // todo enum
        mods: c_int, // todo bitmask
    };
    const Char = struct {
        code: u21,
    };
    const CharMods = struct {
        code: u21,
        mods: c_int, // todo bitmask
    };
    const Drop = struct {
        paths: []const []const u8,
    };

    windowPos: WindowPos,
    windowSize: WindowSize,
    windowClose: WindowClose,
    windowRefresh: WindowRefresh,
    windowFocus: WindowFocus,
    windowIconify: WindowIconify,
    windowMaximize: WindowMaximize,
    framebufferSize: FramebufferSize,
    windowContentScale: WindowContentScale,
    mouseButton: MouseButton,
    cursorPos: CursorPos,
    cursorEnter: CursorEnter,
    scroll: Scroll,
    key: Key,
    char: Char,
    charMods: CharMods,
    drop: Drop,
};

fn onWindowPos(handle: ?*c.GLFWwindow, x: c_int, y: c_int) callconv(.C) void {
    const bus = getBus(handle);
    bus.events.append(bus.alloc, .{
        .windowPos = .{
            .x = @intCast(x),
            .y = @intCast(y),
        },
    }) catch unreachable; // todo circular queue; warn
}

fn onWindowSize(handle: ?*c.GLFWwindow, x: c_int, y: c_int) callconv(.C) void {
    const bus = getBus(handle);
    bus.events.append(bus.alloc, .{
        .windowSize = .{
            .x = @intCast(x),
            .y = @intCast(y),
        },
    }) catch unreachable; // todo circular queue; warn
}

fn onWindowClose(handle: ?*c.GLFWwindow) callconv(.C) void {
    const bus = getBus(handle);
    bus.events.append(bus.alloc, .{
        .windowClose = .{},
    }) catch unreachable; // todo circular queue; warn
}

fn onWindowRefresh(handle: ?*c.GLFWwindow) callconv(.C) void {
    const bus = getBus(handle);
    bus.events.append(bus.alloc, .{
        .windowRefresh = .{},
    }) catch unreachable; // todo circular queue; warn
}

fn onWindowFocus(handle: ?*c.GLFWwindow, focused: c_int) callconv(.C) void {
    const bus = getBus(handle);
    bus.events.append(bus.alloc, .{
        .windowFocus = .{
            .focused = focused == c.GLFW_TRUE,
        },
    }) catch unreachable; // todo circular queue; warn
}

fn onWindowIconify(handle: ?*c.GLFWwindow, iconified: c_int) callconv(.C) void {
    const bus = getBus(handle);
    bus.events.append(bus.alloc, .{
        .windowIconify = .{
            .iconified = iconified == c.GLFW_TRUE,
        },
    }) catch unreachable; // todo circular queue; warn
}

fn onWindowMaximize(handle: ?*c.GLFWwindow, maximized: c_int) callconv(.C) void {
    const bus = getBus(handle);
    bus.events.append(bus.alloc, .{
        .windowMaximize = .{
            .maximized = maximized == c.GLFW_TRUE,
        },
    }) catch unreachable; // todo circular queue; warn
}

fn onFramebufferSize(handle: ?*c.GLFWwindow, width: c_int, height: c_int) callconv(.C) void {
    const bus = getBus(handle);
    bus.events.append(bus.alloc, .{
        .framebufferSize = .{
            .width = @intCast(width),
            .height = @intCast(height),
        },
    }) catch unreachable; // todo circular queue; warn
}

fn onWindowContentScale(handle: ?*c.GLFWwindow, x: f32, y: f32) callconv(.C) void {
    const bus = getBus(handle);
    bus.events.append(bus.alloc, .{
        .windowContentScale = .{
            .x = x,
            .y = y,
        },
    }) catch unreachable; // todo circular queue; warn
}

fn onMouseButton(handle: ?*c.GLFWwindow, button: c_int, action: c_int, mods: c_int) callconv(.C) void {
    const bus = getBus(handle);
    bus.events.append(bus.alloc, .{
        .mouseButton = .{
            .button = button,
            .action = action,
            .mods = mods,
        },
    }) catch unreachable; // todo circular queue; warn
}

fn onCursorPos(handle: ?*c.GLFWwindow, x: f64, y: f64) callconv(.C) void {
    const bus = getBus(handle);
    bus.events.append(bus.alloc, .{
        .cursorPos = .{
            .x = x,
            .y = y,
        },
    }) catch unreachable; // todo circular queue; warn
}

fn onCursorEnter(handle: ?*c.GLFWwindow, entered: c_int) callconv(.C) void {
    const bus = getBus(handle);
    bus.events.append(bus.alloc, .{
        .cursorEnter = .{
            .entered = entered == c.GLFW_TRUE,
        },
    }) catch unreachable; // todo circular queue; warn
}

fn onScroll(handle: ?*c.GLFWwindow, dx: f64, dy: f64) callconv(.C) void {
    const bus = getBus(handle);
    bus.events.append(bus.alloc, .{
        .scroll = .{
            .dx = dx,
            .dy = dy,
        },
    }) catch unreachable; // todo circular queue; warn
}

fn onKey(handle: ?*c.GLFWwindow, key: c_int, scan: c_int, action: c_int, mods: c_int) callconv(.C) void {
    const bus = getBus(handle);
    bus.events.append(bus.alloc, .{
        .key = .{
            .key = key,
            .scan = scan,
            .action = action,
            .mods = mods,
        },
    }) catch unreachable; // todo circular queue; warn
}

fn onCharMods(handle: ?*c.GLFWwindow, code: c_uint, mods: c_int) callconv(.C) void {
    const bus = getBus(handle);
    bus.events.append(bus.alloc, .{
        .charMods = .{
            .code = @intCast(code),
            .mods = mods,
        },
    }) catch unreachable; // todo circular queue; warn
}

fn onDrop(handle: ?*c.GLFWwindow, count: c_int, paths: [*c][*c]const u8) callconv(.C) void {
    const bus = getBus(handle);

    const drops = bus.alloc.alloc([]const u8, @intCast(count)) catch unreachable; // todo warn
    for (drops, paths) |*dst, src| {
        dst.* = bus.alloc.dupe(u8, std.mem.sliceTo(src, 0)) catch unreachable; // todo warn
    }
    bus.drops.append(bus.alloc, drops) catch unreachable; // todo warn

    bus.events.append(bus.alloc, .{
        .drop = .{ .paths = drops },
    }) catch unreachable; // todo circular queue; warn
}
