#pragma once 	

#include <uf/ext/vulkan/device.h>
#include <uf/ext/vulkan/swapchain.h>
#include <uf/ext/vulkan/initializers.h>
#include <uf/ext/vulkan/texture.h>
#include <uf/utils/mesh/mesh.h>

#define UF_GRAPHIC_POINTERED_USERDATA 1

#define IS_DYNAMIC(name) true
 // name.ends_with("_d")

namespace ext {
	namespace vulkan {
		#if UF_GRAPHIC_POINTERED_USERDATA
			typedef uf::PointeredUserdata userdata_t;
		#else
			typedef uf::Userdata userdata_t;
		#endif

		ext::json::Value definitionToJson(/*const*/ ext::json::Value& definition );
		ext::vulkan::userdata_t jsonToUserdata( const ext::json::Value& payload, const ext::json::Value& definition );

		struct RenderMode;

		struct UF_API Shader : public Buffers {
			bool aliased = false;

			Device* device = NULL;
			uf::stl::string filename = "";

			VkShaderModule module = VK_NULL_HANDLE;
			VkPipelineShaderStageCreateInfo descriptor = {};
			uf::stl::map<size_t, uf::stl::vector<VkDescriptorSetLayoutBinding>> descriptorSetLayoutBindings;
			uf::stl::vector<VkSpecializationMapEntry> specializationMapEntries;
			VkSpecializationInfo specializationInfo = {};

			struct Metadata {
				uf::Serializer json;
				
				uf::stl::string pipeline = "";
				uf::stl::string type = "";
				bool autoInitializeUniformBuffers = true;
				bool autoInitializeUniformUserdatas = false;

				struct Definition {
					uf::stl::string name = "";
					uint32_t index = 0;
					uint32_t set = 0;
					uint32_t binding = 0;
					uint32_t size = 0;
				//	 int32_t buffer = -1;
					ext::vulkan::enums::Image::viewType_t type{};
				};
				struct Definitions {
					// unused?
					typedef Metadata::Definition InOut;
					typedef Metadata::Definition Texture;
					typedef Metadata::Definition Uniform;
					typedef Metadata::Definition Storage;
					typedef Metadata::Definition AccelerationStructure;
					typedef Metadata::Definition PushConstant;

					struct SpecializationConstants {
						uf::stl::string name = "";
						uint32_t index = 0;
						uf::stl::string type = "";
						bool validate = false;
						union {
							int32_t i;
							uint32_t ui;
							float f;
						} value;
					};

					uf::stl::unordered_map<uf::stl::string, InOut> inputs;
					uf::stl::unordered_map<uf::stl::string, InOut> outputs;
					uf::stl::unordered_map<uf::stl::string, Texture> textures;
					uf::stl::unordered_map<uf::stl::string, Uniform> uniforms;
					uf::stl::unordered_map<uf::stl::string, Storage> storage;
					uf::stl::unordered_map<uf::stl::string, AccelerationStructure> accelerationStructure;
					uf::stl::unordered_map<uf::stl::string, PushConstant> pushConstants;
					uf::stl::unordered_map<uf::stl::string, SpecializationConstants> specializationConstants;

					pod::Vector3ui localSize{};
				} definitions;

				// to automatically grab attachments from the target renderMode
				struct AttachmentDescriptor {
					uf::stl::string name{};
					const ext::vulkan::RenderMode* renderMode{};
					VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
					VkFilter filter = VK_FILTER_NEAREST;
				};
				struct BufferDescriptor {
					uf::stl::string name{};
					ext::vulkan::Buffer fallback;
					const ext::vulkan::RenderMode* renderMode{};
					VkBufferUsageFlags flags{};
				};
				struct {
					uf::stl::vector<AttachmentDescriptor> attachments;
					uf::stl::vector<BufferDescriptor> buffers;
				} aliases;

				uf::stl::vector<uint32_t> dynamicRanges;
			} metadata;

			ext::vulkan::userdata_t specializationConstants;
			uf::stl::vector<ext::vulkan::userdata_t> pushConstants;
			uf::stl::vector<ext::vulkan::userdata_t> uniforms;

			// for per-shader texture allotment, needed for our bloom pipeline
			uf::stl::vector<Texture2D> textures;

		//	~Shader();
			void initialize( Device& device, const uf::stl::string&, VkShaderStageFlagBits );
			void destroy();
			bool validate();

			bool hasAttachment( const uf::stl::string& name );
			void aliasAttachment( const Metadata::AttachmentDescriptor& descriptor );
			void aliasAttachment( const uf::stl::string& name, const ext::vulkan::RenderMode* renderMode = NULL, VkImageLayout = VK_IMAGE_LAYOUT_UNDEFINED, VkFilter filter = VK_FILTER_NEAREST );

			bool hasBuffer( const uf::stl::string& name );
			void aliasBuffer( const Metadata::BufferDescriptor& descriptor );
			void aliasBuffer( const ext::vulkan::Buffer& buffer );
			void aliasBuffer( const uf::stl::string& name, const ext::vulkan::Buffer& = {}, const ext::vulkan::RenderMode* renderMode = NULL, VkBufferUsageFlags = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT );

			bool hasUniform( const uf::stl::string& name ) const;

			Buffer& getUniformBuffer( const uf::stl::string& name );
			const Buffer& getUniformBuffer( const uf::stl::string& name ) const;

			ext::vulkan::userdata_t& getUniform( const uf::stl::string& name );
			const ext::vulkan::userdata_t& getUniform( const uf::stl::string& name ) const ;
			
			bool updateUniform( const uf::stl::string& name, const void*, size_t ) const;
			inline bool updateUniform( const uf::stl::string& name ) const {
				if ( !hasUniform(name) ) return false;
				return updateUniform(name, getUniform(name));
			}
			inline bool updateUniform( const uf::stl::string& name, const ext::vulkan::userdata_t& userdata ) const {
				return updateUniform(name, (const void*) userdata, userdata.size() );
			}

			bool hasStorage( const uf::stl::string& name ) const;

			Buffer& getStorageBuffer( const uf::stl::string& name );
			const Buffer& getStorageBuffer( const uf::stl::string& name ) const;
			
			bool updateStorage( const uf::stl::string& name, const void*, size_t ) const;
			inline bool updateStorage( const uf::stl::string& name, const ext::vulkan::userdata_t& userdata ) const {
				return updateStorage(name, (const void*) userdata, userdata.size() );
			}

			void setSpecializationConstants( const uf::stl::unordered_map<uf::stl::string, uint32_t>& values );
			void setDescriptorCounts( const uf::stl::unordered_map<uf::stl::string, uint32_t>& values );

			Metadata::Definition getDefinition( const uf::stl::string& );

		/*
			uf::Serializer getUniformJson( const uf::stl::string& name, bool cache = true );
			bool updateUniform( const uf::stl::string& name, const ext::json::Value& payload );
			ext::vulkan::userdata_t getUniformUserdata( const uf::stl::string& name, const ext::json::Value& payload );

			uf::Serializer getStorageJson( const uf::stl::string& name, bool cache = true );
			ext::vulkan::userdata_t getStorageUserdata( const uf::stl::string& name, const ext::json::Value& payload );
		*/
		};
	}
}