//! Isolate vulkan code (except for ImGui) through this module.
//!
//! Requires that Window module already be initialized.

const std = @import("std");
const builtin = @import("builtin");
const vk = @import("vk");

const nu = @import("../nu.zig");

pub const ctx = @import("Render/ctx.zig");

const SwapChain = @import("Render/SwapChain.zig");

pub const Config = struct {
    app_name: [*:0]const u8 = "nu-au-app",
    app_version: struct {
        variant: u3 = 0,
        major: u7 = 0,
        minor: u10 = 0,
        patch: u12 = 0,
    } = .{},
    engine_name: [*:0]const u8 = "nu-au",
    engine_version: struct {
        variant: u3 = 0,
        major: u7 = 0,
        minor: u10 = 0,
        patch: u12 = 0,
    } = .{},
    use_debug_messenger: bool = switch (builtin.mode) {
        .Debug, .ReleaseSafe => true,
        .ReleaseSmall, .ReleaseFast => false,
    },
    frames_in_flight: u8 = 2,
};
const config = nu.config.render;

pub const depends = .{nu.Window};

const Flight = struct {
    pool: vk.CommandPool = .null_handle,
    cmd: vk.CommandBuffer = .null_handle,

    pub fn init() !Flight {
        const pool = try ctx.D.createCommandPool(
            &.{ .queue_family_index = ctx.family.* },
            null,
        );
        errdefer ctx.D.destroyCommandPool(pool, null);

        var cmds: [1]vk.CommandBuffer = undefined;
        try ctx.D.allocateCommandBuffers(
            &vk.CommandBufferAllocateInfo{
                .command_buffer_count = 1,
                .command_pool = pool,
                .level = .primary,
            },
            &cmds,
        );
        errdefer ctx.D.freeCommandBuffers(pool, 1, &cmds);

        return .{
            .pool = pool,
            .cmd = cmds[0],
        };
    }

    pub fn deinit(self: Flight) void {
        const cmds: [1]vk.CommandBuffer = .{self.cmd};
        ctx.D.freeCommandBuffers(self.pool, 1, &cmds);
        ctx.D.destroyCommandPool(self.pool, null);
    }
};

pub const sc: *const SwapChain = &_sc;

var _sc: SwapChain = undefined;
var _flights: [nu.config.render.frames_in_flight]Flight = undefined;

pub fn setup(alloc: std.mem.Allocator) !void {
    try ctx.init(alloc);
    errdefer ctx.deinit();

    errdefer for (_flights) |flight| flight.deinit();
    for (&_flights) |*flight| flight.* = try Flight.init();

    _sc = try SwapChain.init(alloc, _flights.len);
    errdefer _sc.deinit();

    nu.Window.add_resize_callback(&on_resize);
}

fn on_resize(_: u32, _: u32) void {
    _sc.rebuild() catch @panic("rebuild on resize failed");
}

pub fn teardown() void {
    _sc.deinit();
    for (_flights) |flight| flight.deinit();
    ctx.deinit();
}

pub fn render() !void {
    const target = try _sc.acquire() orelse return;
    const flight = &_flights[target.flight_index];

    const render_area: vk.Rect2D = .{
        .offset = .{ .x = 0, .y = 0 },
        .extent = _sc.cinfo.image_extent,
    };

    try ctx.D.resetCommandPool(flight.pool, .{});
    var cmd = ctx.CommandBufferProxy.init(flight.cmd, ctx.dw);

    try cmd.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } });
    {
        cmd.pipelineBarrier(
            .{ .top_of_pipe_bit = true },
            .{ .color_attachment_output_bit = true },
            .{},
            0,
            null,
            0,
            null,
            1,
            &.{target.top_of_pipe_barrier()},
        );

        cmd.beginRendering(&vk.RenderingInfo{
            .render_area = render_area,
            .layer_count = 1,
            .view_mask = 0,
            .color_attachment_count = 1,
            .p_color_attachments = &.{
                vk.RenderingAttachmentInfo{
                    .image_view = target.view,
                    .image_layout = .color_attachment_optimal,
                    .resolve_mode = .{},
                    .resolve_image_view = .null_handle,
                    .resolve_image_layout = .undefined,
                    .load_op = .clear,
                    .store_op = .store,
                    .clear_value = .{ .color = .{ .float_32 = .{ 0.1, 0.1, 0.1, 1 } } },
                },
            },
        });

        nu.engine.invoke("present", .{cmd});
        nu.engine.rinvoke("rpresent", .{cmd});

        cmd.endRendering();

        cmd.pipelineBarrier(
            .{ .color_attachment_output_bit = true },
            .{ .bottom_of_pipe_bit = true },
            .{},
            0,
            null,
            0,
            null,
            1,
            &.{target.bottom_of_pipe_barrier()},
        );
    }
    try cmd.endCommandBuffer();

    try ctx.Q.submit(
        1,
        &.{
            vk.SubmitInfo{
                .wait_semaphore_count = 1,
                // don't start writing to color attachment until the swapchain image has been acquired.
                .p_wait_semaphores = &.{
                    target.acquired,
                },
                .p_wait_dst_stage_mask = &.{
                    vk.PipelineStageFlags{ .color_attachment_output_bit = true },
                },
                .command_buffer_count = 1,
                .p_command_buffers = &.{flight.cmd},
                .signal_semaphore_count = 1,
                .p_signal_semaphores = &.{target.complete},
            },
        },
        target.available, // target will become available again once these finish
    );

    try _sc.present(target);
}
