package com.github.hydos.ginger.engine.vulkan.render.pipelines;

import static org.lwjgl.system.MemoryStack.stackPush;
import static org.lwjgl.vulkan.VK10.*;

import java.nio.*;

import org.lwjgl.system.MemoryStack;
import org.lwjgl.vulkan.*;

import com.github.hydos.ginger.VulkanExample;
import com.github.hydos.ginger.VulkanExample.VulkanDemoGinger2.Vertex;
import com.github.hydos.ginger.engine.vulkan.shaders.*;
import com.github.hydos.ginger.engine.vulkan.shaders.VKShaderUtils.SPIRV;

public class VKPipelineManager
{
	public static void createGraphicsPipeline()
	{
		try (MemoryStack stack = stackPush())
		{
			SPIRV vertShaderSPIRV = VKShaderUtils.compileShaderFile("vulkan/shaders/entity.vert", VKShaderUtils.ShaderType.VERTEX_SHADER);
			SPIRV fragShaderSPIRV = VKShaderUtils.compileShaderFile("vulkan/shaders/entity.frag", VKShaderUtils.ShaderType.FRAGMENT_SHADER);
			long vertShaderModule = VKShaderManager.createShaderModule(vertShaderSPIRV.bytecode());
			long fragShaderModule = VKShaderManager.createShaderModule(fragShaderSPIRV.bytecode());
			ByteBuffer entryPoint = stack.UTF8("main");
			VkPipelineShaderStageCreateInfo.Buffer shaderStages = VkPipelineShaderStageCreateInfo.callocStack(2, stack);
			VkPipelineShaderStageCreateInfo vertShaderStageInfo = shaderStages.get(0);
			vertShaderStageInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO);
			vertShaderStageInfo.stage(VK_SHADER_STAGE_VERTEX_BIT);
			vertShaderStageInfo.module(vertShaderModule);
			vertShaderStageInfo.pName(entryPoint);
			VkPipelineShaderStageCreateInfo fragShaderStageInfo = shaderStages.get(1);
			fragShaderStageInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO);
			fragShaderStageInfo.stage(VK_SHADER_STAGE_FRAGMENT_BIT);
			fragShaderStageInfo.module(fragShaderModule);
			fragShaderStageInfo.pName(entryPoint);
			// VERTEX STAGE
			VkPipelineVertexInputStateCreateInfo vertexInputInfo = VkPipelineVertexInputStateCreateInfo.callocStack(stack);
			vertexInputInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO);
			vertexInputInfo.pVertexBindingDescriptions(Vertex.getBindingDescription());
			vertexInputInfo.pVertexAttributeDescriptions(Vertex.getAttributeDescriptions());
			// ASSEMBLY STAGE
			VkPipelineInputAssemblyStateCreateInfo inputAssembly = VkPipelineInputAssemblyStateCreateInfo.callocStack(stack);
			inputAssembly.sType(VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO);
			inputAssembly.topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
			inputAssembly.primitiveRestartEnable(false);
			// VIEWPORT & SCISSOR
			VkViewport.Buffer viewport = VkViewport.callocStack(1, stack);
			viewport.x(0.0f);
			viewport.y(0.0f);
			viewport.width(VulkanExample.VulkanDemoGinger2.swapChainExtent.width());
			viewport.height(VulkanExample.VulkanDemoGinger2.swapChainExtent.height());
			viewport.minDepth(0.0f);
			viewport.maxDepth(1.0f);
			VkRect2D.Buffer scissor = VkRect2D.callocStack(1, stack);
			scissor.offset(VkOffset2D.callocStack(stack).set(0, 0));
			scissor.extent(VulkanExample.VulkanDemoGinger2.swapChainExtent);
			VkPipelineViewportStateCreateInfo viewportState = VkPipelineViewportStateCreateInfo.callocStack(stack);
			viewportState.sType(VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO);
			viewportState.pViewports(viewport);
			viewportState.pScissors(scissor);
			// RASTERIZATION STAGE
			VkPipelineRasterizationStateCreateInfo rasterizer = VkPipelineRasterizationStateCreateInfo.callocStack(stack);
			rasterizer.sType(VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO);
			rasterizer.depthClampEnable(false);
			rasterizer.rasterizerDiscardEnable(false);
			rasterizer.polygonMode(VK_POLYGON_MODE_FILL);
			rasterizer.lineWidth(1.0f);
			rasterizer.cullMode(VK_CULL_MODE_BACK_BIT);
			rasterizer.frontFace(VK_FRONT_FACE_COUNTER_CLOCKWISE);
			rasterizer.depthBiasEnable(false);
			// MULTISAMPLING
			VkPipelineMultisampleStateCreateInfo multisampling = VkPipelineMultisampleStateCreateInfo.callocStack(stack);
			multisampling.sType(VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO);
			multisampling.sampleShadingEnable(true);
			multisampling.minSampleShading(0.2f); // Enable sample shading in the pipeline
			multisampling.rasterizationSamples(VulkanExample.VulkanDemoGinger2.msaaSamples); // Min fraction for sample shading; closer to one is smoother
			VkPipelineDepthStencilStateCreateInfo depthStencil = VkPipelineDepthStencilStateCreateInfo.callocStack(stack);
			depthStencil.sType(VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO);
			depthStencil.depthTestEnable(true);
			depthStencil.depthWriteEnable(true);
			depthStencil.depthCompareOp(VK_COMPARE_OP_LESS);
			depthStencil.depthBoundsTestEnable(false);
			depthStencil.minDepthBounds(0.0f);
			depthStencil.maxDepthBounds(1.0f);
			depthStencil.stencilTestEnable(false);
			// COLOR BLENDING
			VkPipelineColorBlendAttachmentState.Buffer colorBlendAttachment = VkPipelineColorBlendAttachmentState.callocStack(1, stack);
			colorBlendAttachment.colorWriteMask(VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT);
			colorBlendAttachment.blendEnable(false);
			VkPipelineColorBlendStateCreateInfo colorBlending = VkPipelineColorBlendStateCreateInfo.callocStack(stack);
			colorBlending.sType(VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO);
			colorBlending.logicOpEnable(false);
			colorBlending.logicOp(VK_LOGIC_OP_COPY);
			colorBlending.pAttachments(colorBlendAttachment);
			colorBlending.blendConstants(stack.floats(0.0f, 0.0f, 0.0f, 0.0f));
			// PIPELINE LAYOUT CREATION
			VkPipelineLayoutCreateInfo pipelineLayoutInfo = VkPipelineLayoutCreateInfo.callocStack(stack);
			pipelineLayoutInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO);
			pipelineLayoutInfo.pSetLayouts(stack.longs(VulkanExample.VulkanDemoGinger2.descriptorSetLayout));
			LongBuffer pPipelineLayout = stack.longs(VK_NULL_HANDLE);
			if (vkCreatePipelineLayout(VulkanExample.VulkanDemoGinger2.device, pipelineLayoutInfo, null, pPipelineLayout) != VK_SUCCESS)
			{ throw new RuntimeException("Failed to create pipeline layout"); }
			VulkanExample.VulkanDemoGinger2.pipelineLayout = pPipelineLayout.get(0);
			VkGraphicsPipelineCreateInfo.Buffer pipelineInfo = VkGraphicsPipelineCreateInfo.callocStack(1, stack);
			pipelineInfo.sType(VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO);
			pipelineInfo.pStages(shaderStages);
			pipelineInfo.pVertexInputState(vertexInputInfo);
			pipelineInfo.pInputAssemblyState(inputAssembly);
			pipelineInfo.pViewportState(viewportState);
			pipelineInfo.pRasterizationState(rasterizer);
			pipelineInfo.pMultisampleState(multisampling);
			pipelineInfo.pDepthStencilState(depthStencil);
			pipelineInfo.pColorBlendState(colorBlending);
			pipelineInfo.layout(VulkanExample.VulkanDemoGinger2.pipelineLayout);
			pipelineInfo.renderPass(VulkanExample.VulkanDemoGinger2.renderPass);
			pipelineInfo.subpass(0);
			pipelineInfo.basePipelineHandle(VK_NULL_HANDLE);
			pipelineInfo.basePipelineIndex(-1);
			LongBuffer pGraphicsPipeline = stack.mallocLong(1);
			if (vkCreateGraphicsPipelines(VulkanExample.VulkanDemoGinger2.device, VK_NULL_HANDLE, pipelineInfo, null, pGraphicsPipeline) != VK_SUCCESS)
			{ throw new RuntimeException("Failed to create graphics pipeline"); }
			VulkanExample.VulkanDemoGinger2.graphicsPipeline = pGraphicsPipeline.get(0);
			// Cleanup
			vkDestroyShaderModule(VulkanExample.VulkanDemoGinger2.device, vertShaderModule, null);
			vkDestroyShaderModule(VulkanExample.VulkanDemoGinger2.device, fragShaderModule, null);
			vertShaderSPIRV.free();
			fragShaderSPIRV.free();
		}
	}
}
