const std = @import("std");
const core = @import("core");
const platform = @import("platform.zig");

const RingQueue = core.RingQueue;
const sdl3 = @import("sdl3");
const tracy = core.tracy;

pub const InputListenerError = error{
    UnknownError,
};

pub const RawInputListenerInterface = struct {

    // required functions
    OnIoEvent: *const fn (*anyopaque, event: IOEvent) InputListenerError!void,

    pub fn from(comptime TargetType: type) @This() {
        const Wrapped = struct {
            pub fn OnIoEvent(pointer: *anyopaque, event: IOEvent) InputListenerError!void {
                var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer)));
                try ptr.OnIoEvent(event);
            }
        };

        return .{
            .OnIoEvent = Wrapped.OnIoEvent,
        };
    }
};

pub const RawInputObjectRef = struct {
    ptr: *anyopaque,
    vtable: *const RawInputListenerInterface,

    pub fn from(target: anytype) @This() {
        const vtable = &(@TypeOf(target.*)).RawInputListenerVTable;

        return .{
            .ptr = target,
            .vtable = vtable,
        };
    }
};

// We are having some serious issues with this events pump.

pub const PlatformParams = struct {
    extent: core.Vector2c = .{ .x = 1600, .y = 900 },
    windowName: []const u8 = "sample window",
    icon: []const u8 = "textures/icon.png",
    hasVideo: bool = true,
};

pub var gPlatformSettings: struct {
    pollLockoutTime: ?f32 = null,
    decoratedWindow: bool = true,
    transparentFrameBuffer: bool = false,
} = .{};

// For windows and linux computers this is a glfw instance
pub const PlatformInstance = struct {
    allocator: std.mem.Allocator,

    exitSignal: bool = false,
    cursorEnabled: bool = true,
    newCursorEnabled: bool = true,
    hasVideo: bool = false,

    windowExtent: core.Vector2c,

    windowName: []u8,
    iconPath: []u8,

    pub fn deinit(self: *@This()) void {
        self.allocator.free(self.windowName);
        self.allocator.free(self.iconPath);
    }

    pub fn init(
        allocator: std.mem.Allocator,
        params: PlatformParams,
    ) !@This() {
        const self: @This() = .{
            .allocator = allocator,
            .windowName = try allocator.dupeZ(u8, params.windowName),
            .iconPath = try allocator.dupeZ(u8, params.icon),
            .windowExtent = params.extent,
            .hasVideo = params.hasVideo,
        };

        return self;
    }

    pub fn setup(self: *@This()) !void {
        core.setupEnginePlatform(self, enginePoll, processEvents);
    }

    pub fn enginePoll(opaqueSelf: *anyopaque) core.EngineDataEventError!void {
        var self: *@This() = @alignCast(@ptrCast(opaqueSelf));

        self.pollEvents();
    }

    pub fn installListener(self: *@This(), listener: anytype) !void {
        try self.listeners.append(RawInputObjectRef.from(listener));
    }

    pub fn pollEvents(self: *@This()) void {
        if (self.shouldExit()) {
            core.gEngine.exit();
        }
    }

    fn convertEvent(event: *const sdl3.Event) ?IOEvent {
        _ = event;
        return null;
    }

    pub fn processEvents(ptr: *anyopaque, frameNumber: u64) core.EngineDataEventError!void {
        _ = ptr;
        _ = frameNumber;
        var t1 = tracy.ZoneN(@src(), "Pumping Events");
        defer t1.End();

        const inputStack = core.getInputStack();
        inputStack.updatePreviousInputs();
        while (sdl3.pollEvent()) |event| {
            if (convertEvent(event)) |converted| {
                inputStack.routeEvent(converted);
            }
        }
    }

    pub fn getCursorPosition(self: *@This()) core.Vector2f {
        return self.cursorPos;
    }

    pub fn shouldExit(self: @This()) bool {
        if (self.exitSignal)
            return true;

        return false;
    }

    pub fn setCursorEnabled(self: *@This(), cursorEnabled: bool) void {
        self.newCursorEnabled = cursorEnabled;
    }

    pub fn isCursorEnabled(self: @This()) bool {
        return self.cursorEnabled;
    }
};

pub const IOEvent = core.IOEvent;
