const std = @import("std");
const refAllDeclsRecursive = std.testing.refAllDeclsRecursive;

const zgl = @import("zgl");
const zm = @import("zmath");
const glfw = @import("zglfw");
const Window = glfw.Window;

const gl_helper = @import("../gl_helper/root.zig");

const callbacks = @import("callbacks.zig");
const Scene = @import("scene.zig").Scene;

pub const AppState = struct {
    height: i32,
    width: i32,
    camera: gl_helper.Camera,
    scene: Scene,
    light_pos: zm.Vec,
    render_gui: bool,

    pub fn init(height: i32, width: i32, scene: Scene, camera_pos: zm.Vec, light_pos: zm.Vec, initial_speed: f32) AppState {
        return .{
            .height = height,
            .width = width,
            .scene = scene,
            .camera = .init(camera_pos, @floatFromInt(width), @floatFromInt(height), initial_speed),
            .light_pos = light_pos,
            .render_gui = true,
        };
    }
};

pub const GlfwContext = struct {
    window: *Window,

    pub fn init(app_state: *AppState, title: [:0]const u8) !GlfwContext {
        // glfw: initialize and configure
        try glfw.init();

        glfw.windowHint(.client_api, .opengl_api);
        glfw.windowHint(.context_version_major, 4);
        glfw.windowHint(.context_version_minor, 6);
        glfw.windowHint(.opengl_profile, .opengl_core_profile);
        glfw.windowHint(.opengl_forward_compat, true);
        glfw.windowHint(.doublebuffer, true);

        glfw.windowHint(.focused, true);
        glfw.windowHint(.resizable, false);

        // glfw window creation
        const window = try Window.create(app_state.width, app_state.height, title, null, null);
        glfw.makeContextCurrent(window);
        glfw.swapInterval(1);

        if (glfw.rawMouseMotionSupported()) {
            try window.setInputMode(.raw_mouse_motion, true);
        }

        _ = window.setKeyCallback(callbacks.keyCallback);
        _ = window.setCursorPosCallback(callbacks.cursorPosCallback);

        // zgl: load all OpenGL function pointers
        try zgl.loadExtensions(@as(glfw.GlProc, undefined), getProcAddress);

        zgl.enable(.debug_output);
        zgl.enable(.debug_output_synchronous);
        zgl.debugMessageCallback({}, callbacks.messageCallback);

        window.setUserPointer(app_state);

        return .{ .window = window };
    }

    pub fn postRender(self: GlfwContext) void {
        self.window.swapBuffers();
        glfw.pollEvents();
    }

    pub fn cleanUp(self: GlfwContext) void {
        self.window.destroy();
        glfw.terminate();
    }
};

pub fn getTime() f64 {
    return glfw.getTime();
}

fn getProcAddress(_: glfw.GlProc, proc: [:0]const u8) ?zgl.binding.FunctionPointer {
    return @alignCast(glfw.getProcAddress(proc));
}

comptime {
    refAllDeclsRecursive(@This());
}
