#ifndef VULKAN_PIPELINE_HPP_
#define VULKAN_PIPELINE_HPP_

#include <vector>
#include <list>

#include <Utilities/Extern.hpp>
#include <Vk/ShaderModule/ShaderModule.hpp>
#include <Vk/Device/Device.hpp>
#include <Vk/RenderPass/RenderPass.hpp>
#include <Vk/DescriptorSetLayout/DescriptorSetLayout.hpp>

namespace Vk
{
    /**
     * @brief Represents a Vulkan pipeline.
     */
    class Pipeline
    {
    private:
        std::shared_ptr<Vk::Device> device;
        VkPipeline pipeline;
        VkPipelineLayout layout;
        std::shared_ptr<Vk::RenderPass> renderPass;
    
    public:
        /**
         * @brief Represents a graphics pipeline builder.
         */
        class GraphicsBuilder
        {
        private:
            std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
            std::vector<VkDynamicState> dynamicStates;
            std::shared_ptr<Vk::RenderPass> renderPass;
            std::vector<VkPushConstantRange> pushConstantRanges;
            std::vector<std::shared_ptr<Vk::DescriptorSetLayout>> descriptorSetLayoutHandles;
            std::vector<VkDescriptorSetLayout> descriptorSetLayouts;
            std::vector<VkPipelineColorBlendAttachmentState> blendModes;
            std::vector<VkVertexInputBindingDescription> vertexInputBindings;
            std::vector<VkVertexInputAttributeDescription> vertexAttrs;
            bool m_depthTest = false;
            bool m_depthWrite = false;
            VkFrontFace m_frontFace = VK_FRONT_FACE_CLOCKWISE;
            VkCullModeFlags m_cullMode = VK_CULL_MODE_BACK_BIT;

        public:            
            /**
             * @brief Add a shader stage.
             * 
             * @param stage The shader stage bit.
             * @param module The module to add.
             */
            GraphicsBuilder& addShaderStage(VkShaderStageFlagBits stage, Vk::ShaderModule &shaderModule);

            /**
             * @brief Add a dynamic state.
             * 
             * @param state The dynamic state to add.
             */
            GraphicsBuilder& addDynamicState(VkDynamicState state);

            /**
             * @brief Add a push constant range.
             */
            GraphicsBuilder& addPushConstantRange(uint32_t offset, uint32_t size, VkShaderStageFlags stages);

            /**
             * @brief Add a descriptor set layout.
             */
            GraphicsBuilder& addDescriptorSetLayout(std::shared_ptr<Vk::DescriptorSetLayout> layout);

            /**
             * @brief Enable depth testing in this pipeline (requires a depth attachment).
             */
            GraphicsBuilder& enableDepthTest();

            /**
             * @brief Enable writing to the depth buffer.
             */
            GraphicsBuilder& enableDepthWrite();

            /**
             * @brief Add an alpha-blended attachment.
             */
            GraphicsBuilder& addAlphaBlendedAttachment();

            /**
             * @brief Add an additive-blended attachment.
             */
            GraphicsBuilder& addAdditiveBlendedAttachment();

            /**
             * @brief Add a invert-blended attachment.
             */
            GraphicsBuilder& addInvertBlendedAttachment();

            /**
             * @brief Add a non-blended attachment.
             */
            GraphicsBuilder& addNonBlendedAttachment();

            /**
             * @brief Add a vertex input binding.
             */
            GraphicsBuilder& addVertexInputBinding(int binding, size_t stride, VkVertexInputRate inputRate);
            
            /**
             * @brief Add a vertex attribute.
             */
            GraphicsBuilder& addVertexAttr(int binding, int location, VkFormat format, size_t offset);

            /**
             * @brief Set which face is considered front in this pipeline.
             */
            GraphicsBuilder& setFrontFace(VkFrontFace frontFace);

            /**
             * @brief Set the cull mode for this pipeline.
             */
            GraphicsBuilder& setCullMode(VkCullModeFlags cullMode);

            /**
             * @brief Build this pipeline for the specified device.
             * 
             * @param device The device to create on.
             * @param renderPass The render pass for this pipeline.
             * @return The pipeline.
             */
            std::shared_ptr<Pipeline> build(
                std::shared_ptr<Vk::Device> device,
                std::shared_ptr<Vk::RenderPass> renderPass
            );
        };

        /**
         * @brief Represents a compute pipeline builder.
         */
        class ComputeBuilder
        {
        private:
            std::vector<std::shared_ptr<Vk::DescriptorSetLayout>> descriptorSetLayoutHandles;
            std::vector<VkDescriptorSetLayout> descriptorSetLayouts;
            std::vector<VkPushConstantRange> pushConstantRanges;
        
        public:
            /**
             * @brief Add a descriptor set layout.
             */
            ComputeBuilder& addDescriptorSetLayout(std::shared_ptr<Vk::DescriptorSetLayout> layout);

            /**
             * @brief Add a push constant range.
             */
            ComputeBuilder& addPushConstantRange(uint32_t offset, uint32_t size, VkShaderStageFlags stages);

            /**
             * @brief Build this compute pipeline.
             */
            std::shared_ptr<Pipeline> build(std::shared_ptr<Device> device, Vk::ShaderModule &shaderModule);
        };

        Pipeline(
            std::shared_ptr<Vk::Device> device,
            VkPipeline pipeline,
            VkPipelineLayout layout,
            std::shared_ptr<Vk::RenderPass> renderPass
        );
        Pipeline(const Pipeline &) = delete;
        ~Pipeline();

        /**
         * @brief Get the wrapped pipeline.
         */
        VkPipeline inner();

        /**
         * @brief Get the pipeline layout.
         */
        VkPipelineLayout getInnerLayout();

        friend class Vk::Pipeline::GraphicsBuilder;
    };
}

#endif