const std = @import("std");
const vk = @import("vulkan");
const sdl = @import("sdl");
const zm = @import("zmath");

const VulkanRenderer = @import("vulkan_renderer.zig").VulkanRenderer;
const Vector3 = @import("utilities.zig").Vector3;

// 100 fps
const TARGET_MS_F32: f32 = 16.0;

const Delta = struct {
    current_time: u64 = 0,
    last_time: u64 = 0,

    delta_ms: u64 = 0,
    delta_f32: f32 = 0.0,

    delay_amount: u32 = 0,

    perf_frequency: f64,

    _counter: u32 = 0,

    pub fn new() Delta {
        return .{
            .perf_frequency = @as(f64, @floatFromInt(sdl.getPerformanceFrequency())),
        };
    }

    pub fn start(self: *Delta) void {
        self.last_time = sdl.getPerformanceCounter();
    }

    pub fn tick(self: *Delta) void {
        self.current_time = sdl.getPerformanceCounter();
        self.delta_ms = self.current_time - self.last_time;

        const delta_f64 = @as(f64, @floatFromInt(self.delta_ms)) / (self.perf_frequency * 1000.0);
        self.delta_f32 = @as(f32, @floatCast(delta_f64));

        self.delay_amount = @as(u32, @intFromFloat(@floor(TARGET_MS_F32 - self.delta_f32)));

        // Limit the output to the terminal
        if (self._counter % 100 == 0) {
            const fps = self.perf_frequency / @as(f64, @floatFromInt(self.delta_ms));
            std.debug.print("ms/f: {d}\tFPS: {d}\n", .{ delta_f64 * 1000.0, fps });
        }

        self.last_time = self.current_time;
        self._counter += 1;
    }
};

pub fn main() !void {
    const window = try initWindow();
    defer sdl.quit();
    defer sdl.vulkan.unloadLibrary();
    defer window.destroy();

    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    var vulkan_renderer = try VulkanRenderer.init(allocator, window);
    defer vulkan_renderer.deinit();

    var delta = Delta.new();
    delta.start();

    var angle: f32 = 0.0;
    const move_speed: f32 = 1000;

    const model_handle = try vulkan_renderer.createMeshModel("tescoPiwo.obj");

    mainLoop: while (true) {
        while (sdl.pollEvent()) |ev| {
            switch (ev) {
                .quit => break :mainLoop,
                .key_down => |key| {
                    if (key.keycode == sdl.Keycode.escape) {
                        break :mainLoop;
                    }
                },
                else => {},
            }
        }

        const keystate = sdl.getKeyboardState();
        if (keystate.isPressed(sdl.Scancode.left_shift)) {
            const move = zm.translation(0.0, -move_speed * delta.delta_f32, 0.0);
            vulkan_renderer.updateCamera(move);
        } else if (keystate.isPressed(sdl.Scancode.left_control)) {
            const move = zm.translation(0.0, move_speed * delta.delta_f32, 0.0);
            vulkan_renderer.updateCamera(move);
        } else if (keystate.isPressed(sdl.Scancode.w)) {
            const move = zm.translation(0.0, 0.0, move_speed * delta.delta_f32);
            vulkan_renderer.updateCamera(move);
        } else if (keystate.isPressed(sdl.Scancode.s)) {
            const move = zm.translation(0.0, 0.0, -move_speed * delta.delta_f32);
            vulkan_renderer.updateCamera(move);
        } else if (keystate.isPressed(sdl.Scancode.a)) {
            const move = zm.translation(move_speed * delta.delta_f32, 0.0, 0.0);
            vulkan_renderer.updateCamera(move);
        } else if (keystate.isPressed(sdl.Scancode.d)) {
            const move = zm.translation(-move_speed * delta.delta_f32, 0.0, 0.0);
            vulkan_renderer.updateCamera(move);
        }

        delta.tick();

        angle += 200.0 * delta.delta_f32;

        if (angle > 360.0) {
            angle -= 360.0;
        }

        const rotate = zm.mul(zm.identity(), zm.rotationY(angle));
        try vulkan_renderer.updateModel(model_handle, rotate);

        try vulkan_renderer.draw();

        sdl.delay(delta.delay_amount);
    }
}

fn initWindow() !sdl.Window {
    sdl.init(.{ .timer = true, .video = true, .events = true }) catch {
        std.log.err("Failed to initialize SDL2", .{});
    };

    return try sdl.createWindow(
        "Vulkan Test",
        .{ .centered = {} },
        .{ .centered = {} },
        800,
        600,
        .{ .vis = .shown, .context = .vulkan },
    );
}
