const c = @import("c.zig");
const std = @import("std");
const cfg = @import("config.zig");
const gl = @import("gl.zig");

pub const Output = struct {
    update: *const fn (*Output, c.GLuint) bool,
    destroy: *const fn (*Output) void,

    fn init(update: *const fn (*Output, c.GLuint) bool, destroy: *const fn (*Output) void) Output {
        return Output{
            .update = update,
            .destroy = destroy,
        };
    }

    pub fn create(
        allocator: std.mem.Allocator,
        config: cfg.OutputConfig,
        constants: *gl.Constants,
    ) *Output {
        return switch (config.type) {
            .window => WindowOutput.create(allocator, config, constants),
        };
    }
};

pub const WindowOutput = struct {
    output: Output,
    window: *c.GLFWwindow,
    constants: *gl.Constants,

    resized: bool = false,

    pub fn create(
        allocator: std.mem.Allocator,
        config: cfg.OutputConfig,
        constants: *gl.Constants,
    ) *Output {
        const self = allocator.create(WindowOutput) catch unreachable;

        c.glfwDefaultWindowHints();

        self.* = WindowOutput{
            .constants = constants,
            .output = Output.init(update, destroy),
            .window = c.glfwCreateWindow(
                config.width,
                config.height,
                "glsl-view output",
                null,
                constants.main_window,
            ) orelse {
                std.debug.panic("unable to create output window\n", .{});
            },
        };

        c.glfwSetWindowUserPointer(self.window, @as(*anyopaque, @ptrCast(self)));
        _ = c.glfwSetKeyCallback(self.window, keyCallback);
        _ = c.glfwSetFramebufferSizeCallback(self.window, sizeCallback);

        c.glfwMakeContextCurrent(self.window);
        constants.normalized_quad.bind(0);

        return &self.output;
    }

    fn update(output: *Output, texture_id: c.GLuint) bool {
        const self: *WindowOutput = @fieldParentPtr("output", output);

        if (c.glfwWindowShouldClose(self.window) == c.GL_TRUE)
            return true;

        c.glfwMakeContextCurrent(self.window);
        c.glClear(c.GL_COLOR_BUFFER_BIT);

        if (self.resized) {
            var width: c_int = undefined;
            var height: c_int = undefined;
            var scaled_width: c_int = undefined;
            var scaled_height: c_int = undefined;

            c.glfwGetFramebufferSize(self.window, &width, &height);
            const window_aspect = @as(f32, @floatFromInt(width)) / @as(f32, @floatFromInt(height));
            if (window_aspect >= self.constants.aspect) {
                scaled_height = height;
                scaled_width = @as(c_int, @intFromFloat(@as(f32, @floatFromInt(height)) * self.constants.aspect));
            } else {
                scaled_width = width;
                scaled_height = @as(c_int, @intFromFloat(@as(f32, @floatFromInt(width)) / self.constants.aspect));
            }

            c.glViewport(
                @divFloor(width - scaled_width, 2),
                @divFloor(height - scaled_height, 2),
                scaled_width,
                scaled_height,
            );
        }

        c.glBindTexture(c.GL_TEXTURE_2D, texture_id);
        c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);

        self.constants.texture_shader.bind();
        self.constants.normalized_quad.draw();

        c.glfwSwapBuffers(self.window);
        return false;
    }

    fn destroy(output: *Output) void {
        const self: *WindowOutput = @fieldParentPtr("output", output);

        c.glfwDestroyWindow(self.window);
    }

    fn keyCallback(
        win: ?*c.GLFWwindow,
        key: c_int,
        scancode: c_int,
        action: c_int,
        mods: c_int,
    ) callconv(.C) void {
        if (action != c.GLFW_PRESS) return;
        const userptr = c.glfwGetWindowUserPointer(win).?;
        const self = @as(*WindowOutput, @ptrCast(@alignCast(userptr)));

        _ = self;
        _ = scancode;
        _ = mods;

        switch (key) {
            //        c.GLFW_KEY_F => // toggle fullscreen
            //        c.GLFW_KEY_LEFT => // cycle through monitors
            //        c.GLFW_KEY_RIGHT => // cycle through monitors
            else => {},
        }
    }

    fn sizeCallback(win: ?*c.GLFWwindow, width: c_int, height: c_int) callconv(.C) void {
        const userptr = c.glfwGetWindowUserPointer(win).?;
        const self = @as(*WindowOutput, @ptrCast(@alignCast(userptr)));
        self.resized = true;

        _ = width;
        _ = height;
    }
};
