#pragma once
/**
 * @file src/vkcv/Core.hpp
 * @brief Handling of global states regarding dependencies
 */

#include <memory>
#include <vulkan/vulkan.hpp>

#include "Context.hpp"
#include "Swapchain.hpp"
#include "Window.hpp"
#include "PassConfig.hpp"
#include "Handles.hpp"
#include "Buffer.hpp"
#include "Image.hpp"
#include "PipelineConfig.hpp"
#include "CommandResources.hpp"
#include "SyncResources.hpp"
#include "Result.hpp"
#include "vkcv/DescriptorConfig.hpp"
#include "Sampler.hpp"
#include "DescriptorWrites.hpp"
#include "Event.hpp"
#include "DrawcallRecording.hpp"
#include "CommandRecordingFunctionTypes.hpp"

namespace vkcv
{

    // forward declarations
    class PassManager;
    class PipelineManager;
    class DescriptorManager;
    class BufferManager;
    class SamplerManager;
    class ImageManager;
	class CommandStreamManager;

	struct SubmitInfo {
		QueueType queueType;
		std::vector<vk::Semaphore> waitSemaphores;
		std::vector<vk::Semaphore> signalSemaphores;
	};

    class Core final
    {
    private:

        /**
         * Constructor of #Core requires an @p context.
         *
         * @param context encapsulates various Vulkan objects
         */
        Core(Context &&context, Window &window, const Swapchain& swapChain,  std::vector<vk::ImageView> imageViews,
			const CommandResources& commandResources, const SyncResources& syncResources) noexcept;
        // explicit destruction of default constructor
        Core() = delete;

		Result acquireSwapchainImage();

        Context m_Context;

        Swapchain                       m_swapchain;
        Window&                   		m_window;

        std::unique_ptr<PassManager>            m_PassManager;
        std::unique_ptr<PipelineManager>        m_PipelineManager;
        std::unique_ptr<DescriptorManager>      m_DescriptorManager;
        std::unique_ptr<BufferManager>          m_BufferManager;
        std::unique_ptr<SamplerManager>         m_SamplerManager;
        std::unique_ptr<ImageManager>           m_ImageManager;
        std::unique_ptr<CommandStreamManager>   m_CommandStreamManager;

		CommandResources    m_CommandResources;
		SyncResources       m_SyncResources;
		uint32_t            m_currentSwapchainImageIndex;

		event_handle<int,int> e_resizeHandle;

    public:
        /**
         * Destructor of #Core destroys the Vulkan objects contained in the core's context.
         */
        ~Core() noexcept;

        /**
         * Copy-constructor of #Core is deleted!
         *
         * @param other Other instance of #Context
         */
        Core(const Core& other) = delete;

        /**
         * Move-constructor of #Core uses default behavior!
         *
         * @param other Other instance of #Context
         */
        Core(Core &&other) = delete; // move-ctor

        /**
         * Copy assignment operator of #Core is deleted!
         *
         * @param other Other instance of #Context
         * @return Reference to itself
         */
        Core & operator=(const Core &other) = delete;

        /**
         * Move assignment operator of #Core uses default behavior!
         *
         * @param other Other instance of #Context
         * @return Reference to itself
         */
        Core & operator=(Core &&other) = delete;

        [[nodiscard]]
        const Context &getContext() const;
        
        [[nodiscard]]
        const Swapchain& getSwapchain() const;

        /**
             * Creates a #Core with given @p applicationName and @p applicationVersion for your application.
             *
             * It is also possible to require a specific amount of queues, ask for specific queue-flags or
             * extensions. This function will take care of the required arguments as best as possible.
             *
             * To pass a valid version for your application, you should use #VK_MAKE_VERSION().
             *
             * @param[in] applicationName Name of the application
             * @param[in] applicationVersion Version of the application
             * @param[in] queueFlags (optional) Requested flags of queues
             * @param[in] instanceExtensions (optional) Requested instance extensions
             * @param[in] deviceExtensions (optional) Requested device extensions
             * @return New instance of #Context
             */
        static Core create(Window &window,
                           const char *applicationName,
                           uint32_t applicationVersion,
                           const std::vector<vk::QueueFlagBits>& queueFlags    = {},
						   const Features& features = {},
						   const std::vector<const char *>& instanceExtensions = {});

        /**
         * Creates a basic vulkan graphics pipeline using @p config from the pipeline config class and returns it using the @p handle.
         * Fixed Functions for pipeline are set with standard values.
         *
         * @param config a pipeline config object from the pipeline config class
         * @param handle a handle to return the created vulkan handle
         * @return True if pipeline creation was successful, False if not
         */
        [[nodiscard]]
        PipelineHandle createGraphicsPipeline(const PipelineConfig &config);

        /**
         * Creates a basic vulkan compute pipeline using @p shader program and returns it using the @p handle.
         * Fixed Functions for pipeline are set with standard values.
         *
         * @param shader program that hold the compiles compute shader
         * @param handle a handle to return the created vulkan handle
         * @return True if pipeline creation was successful, False if not
         */
        [[nodiscard]]
        PipelineHandle createComputePipeline(
            const ShaderProgram &shaderProgram,
            const std::vector<vk::DescriptorSetLayout> &descriptorSetLayouts);

        /**
         * Creates a basic vulkan render pass using @p config from the render pass config class and returns it using the @p handle.
         * Fixed Functions for pipeline are set with standard values.
         *
         * @param config a render pass config object from the render pass config class
         * @param handle a handle to return the created vulkan handle
         * @return True if render pass creation was successful, False if not
         */
        [[nodiscard]]
        PassHandle createPass(const PassConfig &config);

        /**
            * Creates a #Buffer with data-type T and @p bufferType
            * @param type Type of Buffer created
            * @param count Count of elements of type T
            * @param memoryType Type of Buffers memory
            * return Buffer-Object
            */
        template<typename T>
        Buffer<T> createBuffer(vkcv::BufferType type, size_t count, BufferMemoryType memoryType = BufferMemoryType::DEVICE_LOCAL, bool supportIndirect = false) {
        	return Buffer<T>::create(m_BufferManager.get(), type, count, memoryType, supportIndirect);
        }
        
        /**
         * Creates a Sampler with given attributes.
         *
         * @param magFilter Magnifying filter
         * @param minFilter Minimizing filter
         * @param mipmapMode Mipmapping filter
         * @param addressMode Address mode
         * @param mipLodBias Mip level of detail bias
         * @return Sampler handle
         */
        [[nodiscard]]
        SamplerHandle createSampler(SamplerFilterType magFilter, SamplerFilterType minFilter,
									SamplerMipmapMode mipmapMode, SamplerAddressMode addressMode,
									float mipLodBias = 0.0f);

        /**
         * Creates an #Image with a given format, width, height and depth.
         *
         * @param format Image format
         * @param width Image width
         * @param height Image height
         * @param depth Image depth
         * @return Image-Object
         */
        [[nodiscard]]
        Image createImage(
			vk::Format      format,
			uint32_t        width,
			uint32_t        height,
			uint32_t        depth = 1,
			bool            createMipChain = false,
			bool            supportStorage = false,
			bool            supportColorAttachment = false,
			Multisampling   multisampling = Multisampling::None);

        [[nodiscard]]
        uint32_t getImageWidth(const ImageHandle& image);
        
        [[nodiscard]]
        uint32_t getImageHeight(const ImageHandle& image);
	
		[[nodiscard]]
		vk::Format getImageFormat(const ImageHandle& image);

        /** TODO:
         *   @param setDescriptions
         *   @return
         */
        [[nodiscard]]
        DescriptorSetHandle createDescriptorSet(const std::vector<DescriptorBinding> &bindings);
		void writeDescriptorSet(DescriptorSetHandle handle, const DescriptorWrites& writes);

		DescriptorSet getDescriptorSet(const DescriptorSetHandle handle) const;

		/**
		 * @brief start recording command buffers and increment frame index
		*/
		bool beginFrame(uint32_t& width, uint32_t& height);

		void recordDrawcallsToCmdStream(
			const CommandStreamHandle&      cmdStreamHandle,
			const PassHandle&               renderpassHandle,
			const PipelineHandle            pipelineHandle,
			const PushConstants             &pushConstants,
			const std::vector<DrawcallInfo> &drawcalls,
			const std::vector<ImageHandle>  &renderTargets);

		void recordMeshShaderDrawcalls(
			const CommandStreamHandle&              cmdStreamHandle,
			const PassHandle&                       renderpassHandle,
			const PipelineHandle                    pipelineHandle,
			const PushConstants&                    pushConstantData,
            const std::vector<MeshShaderDrawcall>&  drawcalls,
			const std::vector<ImageHandle>&         renderTargets);

		void recordComputeDispatchToCmdStream(
			CommandStreamHandle cmdStream,
			PipelineHandle computePipeline,
			const uint32_t dispatchCount[3],
			const std::vector<DescriptorSetUsage> &descriptorSetUsages,
			const PushConstants& pushConstants);
		
		void recordBeginDebugLabel(const CommandStreamHandle &cmdStream,
								   const std::string& label,
								   const std::array<float, 4>& color);
		
		void recordEndDebugLabel(const CommandStreamHandle &cmdStream);

		void recordComputeIndirectDispatchToCmdStream(
			const CommandStreamHandle               cmdStream,
			const PipelineHandle                    computePipeline,
			const vkcv::BufferHandle                buffer,
			const size_t                            bufferArgOffset,
			const std::vector<DescriptorSetUsage>&  descriptorSetUsages,
			const PushConstants&                    pushConstants);

		/**
		 * @brief end recording and present image
		*/
		void endFrame();

		/**
		 * Submit a command buffer to any queue of selected type. The recording can be customized by a
		 * custom record-command-function. If the command submission has finished, an optional finish-function
		 * will be called.
		 *
		 * @param submitInfo Submit information
		 * @param record Record-command-function
		 * @param finish Finish-command-function or nullptr
		 */
		void recordAndSubmitCommandsImmediate(
			const SubmitInfo            &submitInfo, 
			const RecordCommandFunction &record, 
			const FinishCommandFunction &finish);

		CommandStreamHandle createCommandStream(QueueType queueType);

		void recordCommandsToStream(
			const CommandStreamHandle   cmdStreamHandle,
			const RecordCommandFunction &record,
			const FinishCommandFunction &finish);

		void submitCommandStream(const CommandStreamHandle& handle);
		void prepareSwapchainImageForPresent(const CommandStreamHandle& handle);
		void prepareImageForSampling(const CommandStreamHandle& cmdStream, const ImageHandle& image);
		void prepareImageForStorage(const CommandStreamHandle& cmdStream, const ImageHandle& image);
		void recordImageMemoryBarrier(const CommandStreamHandle& cmdStream, const ImageHandle& image);
		void recordBufferMemoryBarrier(const CommandStreamHandle& cmdStream, const BufferHandle& buffer);
		void resolveMSAAImage(const CommandStreamHandle& cmdStream, const ImageHandle& src, const ImageHandle& dst);

		[[nodiscard]]
		vk::ImageView getSwapchainImageView() const;
	
		void recordMemoryBarrier(const CommandStreamHandle& cmdStream);
		
		void recordBlitImage(const CommandStreamHandle& cmdStream, const ImageHandle& src, const ImageHandle& dst,
							 SamplerFilterType filterType);
	
		void setDebugLabel(const BufferHandle &handle, const std::string &label);
		void setDebugLabel(const PassHandle &handle, const std::string &label);
		void setDebugLabel(const PipelineHandle &handle, const std::string &label);
		void setDebugLabel(const DescriptorSetHandle &handle, const std::string &label);
		void setDebugLabel(const SamplerHandle &handle, const std::string &label);
		void setDebugLabel(const ImageHandle &handle, const std::string &label);
		void setDebugLabel(const CommandStreamHandle &handle, const std::string &label);
		
    };
}