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

const nu = @import("nu.zig");
const Self = @This();

const ctx = nu.Render.ctx;

set_layout: vk.DescriptorSetLayout,
layout: vk.PipelineLayout,
pipeline: vk.Pipeline,

pub const Index = u16;

pub const Uniform = extern struct {
    mat: [16]f32,

    const Bindings = [_]vk.DescriptorSetLayoutBinding{.{
        .binding = 0,
        .descriptor_type = .uniform_buffer,
        .descriptor_count = 1,
        .stage_flags = .{ .vertex_bit = true },
    }};
};

pub const Vertex = extern struct {
    pos: [4]f32,
    color: [3]f32,

    const Bindings = [_]vk.VertexInputBindingDescription{
        .{
            .binding = 0,
            .stride = @sizeOf(Vertex),
            .input_rate = .vertex,
        },
    };

    const Attributes = [_]vk.VertexInputAttributeDescription{
        .{
            .binding = 0,
            .location = 0,
            .format = .r32g32b32a32_sfloat,
            .offset = @offsetOf(Vertex, "pos"),
        },
        .{
            .binding = 0,
            .location = 1,
            .format = .r32g32b32_sfloat,
            .offset = @offsetOf(Vertex, "color"),
        },
    };
};

pub fn init(cache: vk.PipelineCache) !Self {
    const vert = try ctx.D.createShaderModule(&.{
        .code_size = shaders.triangle_vert.len,
        .p_code = @ptrCast(&shaders.triangle_vert),
    }, null);
    defer ctx.D.destroyShaderModule(vert, null);

    const frag = try ctx.D.createShaderModule(&.{
        .code_size = shaders.triangle_frag.len,
        .p_code = @ptrCast(&shaders.triangle_frag),
    }, null);
    defer ctx.D.destroyShaderModule(frag, null);

    const set_layout = try ctx.D.createDescriptorSetLayout(&vk.DescriptorSetLayoutCreateInfo{
        .flags = .{},
        .binding_count = @intCast(Uniform.Bindings.len),
        .p_bindings = &Uniform.Bindings,
    }, null);
    errdefer ctx.D.destroyDescriptorSetLayout(set_layout, null);

    const layout = try ctx.D.createPipelineLayout(&vk.PipelineLayoutCreateInfo{
        .push_constant_range_count = 0,
        .set_layout_count = 1,
        .p_set_layouts = &.{set_layout},
    }, null);
    errdefer ctx.D.destroyPipelineLayout(layout, null);

    var pipeline: vk.Pipeline = .null_handle;
    _ = try ctx.D.createGraphicsPipelines(cache, 1, &[1]vk.GraphicsPipelineCreateInfo{
        vk.GraphicsPipelineCreateInfo{
            .stage_count = 2,
            .p_stages = &.{
                vk.PipelineShaderStageCreateInfo{ .stage = .{ .vertex_bit = true }, .module = vert, .p_name = "main" },
                vk.PipelineShaderStageCreateInfo{ .stage = .{ .fragment_bit = true }, .module = frag, .p_name = "main" },
            },
            .layout = layout,
            .render_pass = .null_handle,
            .subpass = 0,
            .base_pipeline_handle = .null_handle,
            .base_pipeline_index = -1,
            .p_vertex_input_state = &vk.PipelineVertexInputStateCreateInfo{
                .vertex_binding_description_count = @intCast(Vertex.Bindings.len),
                .p_vertex_binding_descriptions = &Vertex.Bindings,
                .vertex_attribute_description_count = @intCast(Vertex.Attributes.len),
                .p_vertex_attribute_descriptions = &Vertex.Attributes,
            },
            .p_input_assembly_state = &vk.PipelineInputAssemblyStateCreateInfo{
                .topology = .triangle_list,
                .primitive_restart_enable = vk.FALSE,
            },
            .p_tessellation_state = null,
            .p_viewport_state = &vk.PipelineViewportStateCreateInfo{
                .viewport_count = 1,
                .scissor_count = 1,
            },
            .p_rasterization_state = &vk.PipelineRasterizationStateCreateInfo{
                .depth_clamp_enable = vk.FALSE,
                .rasterizer_discard_enable = vk.FALSE,
                .polygon_mode = .fill,
                .cull_mode = .{ .back_bit = true },
                .front_face = .counter_clockwise,
                .depth_bias_enable = vk.FALSE,
                .depth_bias_constant_factor = 0.0,
                .depth_bias_clamp = 0.0,
                .depth_bias_slope_factor = 0.0,
                .line_width = 1.0,
            },
            .p_multisample_state = &vk.PipelineMultisampleStateCreateInfo{
                .rasterization_samples = .{ .@"1_bit" = true },
                .sample_shading_enable = vk.FALSE,
                .min_sample_shading = 1,
                .alpha_to_coverage_enable = vk.FALSE,
                .alpha_to_one_enable = vk.FALSE,
            },
            .p_depth_stencil_state = null,
            .p_color_blend_state = &vk.PipelineColorBlendStateCreateInfo{
                .logic_op_enable = vk.FALSE,
                .logic_op = .copy,
                .blend_constants = [_]f32{ 0, 0, 0, 0 },
                .attachment_count = 1,
                .p_attachments = &.{
                    vk.PipelineColorBlendAttachmentState{
                        .blend_enable = vk.FALSE,
                        .color_blend_op = .add,
                        .src_color_blend_factor = .one,
                        .dst_color_blend_factor = .zero,
                        .alpha_blend_op = .add,
                        .src_alpha_blend_factor = .one,
                        .dst_alpha_blend_factor = .zero,
                        .color_write_mask = .{ .r_bit = true, .g_bit = true, .b_bit = true, .a_bit = true },
                    },
                },
            },
            .p_dynamic_state = &vk.PipelineDynamicStateCreateInfo{
                .flags = .{},
                .dynamic_state_count = 2,
                .p_dynamic_states = &.{
                    .viewport,
                    .scissor,
                },
            },
            .p_next = &vk.PipelineRenderingCreateInfo{
                .color_attachment_count = 1,
                .p_color_attachment_formats = &.{nu.Render.sc.cinfo.image_format},
                .depth_attachment_format = .undefined,
                .stencil_attachment_format = .undefined,
                .view_mask = 0,
            },
        },
    }, null, @ptrCast(&pipeline));
    errdefer ctx.D.destroyPipeline(pipeline, null);

    return .{ .pipeline = pipeline, .layout = layout, .set_layout = set_layout };
}

pub fn deinit(self: Self) void {
    ctx.D.destroyPipeline(self.pipeline, null);
    ctx.D.destroyPipelineLayout(self.layout, null);
    ctx.D.destroyDescriptorSetLayout(self.set_layout, null);
}
