#pragma once

#include <uf/utils/math/vector.h>
#include <uf/utils/math/matrix.h>

#include <functional>
#include <uf/utils/memory/unordered_map.h>

#if UF_USE_VULKAN
#include <uf/ext/vulkan/enums.h>
#define RENDERER vulkan
#elif UF_USE_OPENGL
#include <uf/ext/opengl/enums.h>
#define RENDERER opengl
#endif

namespace ext {
	namespace RENDERER {
	#if UF_ENV_DREAMCAST && !UF_USE_OPENGL_GLDC
		typedef uint16_t index_t;
	#else
		typedef uint32_t index_t;
	#endif
		struct UF_API AttributeDescriptor {
			// essential for vertex input
			size_t offset = 0;
			size_t size = 0;
			ext::RENDERER::enums::Format::type_t format = ext::RENDERER::enums::Format::UNDEFINED;
			// not as essential
			uf::stl::string name = "";
			ext::RENDERER::enums::Type::type_t type = 0;
			size_t components = 0;
			
			bool operator==( const AttributeDescriptor& right ) const { return name == right.name;
			/*
				offset == right.offset && 
				size == right.size && 
				format == right.format &&
				name == right.name &&
				type == right.type && 
				components == right.components;
			*/
			}
			bool operator!=( const AttributeDescriptor& right ) const { return !(*this == right); };
		};
	}
}

namespace pod {
	struct UF_API DrawCommand {
		alignas(4) uint32_t indices = 0; // triangle count
		alignas(4) uint32_t instances = 0; // instance count
		alignas(4) uint32_t indexID = 0; // starting triangle position
		alignas(4)  int32_t vertexID = 0; // starting vertex position
		alignas(4) uint32_t instanceID = 0; // starting instance position
		// extra data
		alignas(4) uint32_t auxID = 0; //
		alignas(4) uint32_t materialID = 0; // 
		alignas(4) uint32_t vertices = 0; //
	};


	struct UF_API Instance {		
		pod::Matrix4f model;
		pod::Matrix4f previous;

		pod::Vector4f color = {1,1,1,1};

		alignas(4) uint32_t materialID = 0;
		alignas(4) uint32_t primitiveID = 0;
		alignas(4) uint32_t meshID = 0;
		alignas(4) uint32_t objectID = 0;

		alignas(4)  int32_t jointID = -1;
		alignas(4)  int32_t lightmapID = -1;
		alignas(4) uint32_t imageID = 0;
		alignas(4) uint32_t auxID = 0;

		struct Bounds {
			pod::Vector3f min = { std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max() };
			alignas(4) float padding1 = 0;
			pod::Vector3f max = { -std::numeric_limits<float>::max(), -std::numeric_limits<float>::max(), -std::numeric_limits<float>::max() };
			alignas(4) float padding2 = 0;
		} bounds;

		struct UF_API Addresses {
			alignas(8) uint64_t vertex{};
			alignas(8) uint64_t index{};
			
			alignas(8) uint64_t indirect{};
			alignas(4) uint32_t drawID{};
			alignas(4) uint32_t padding0{};
			
			alignas(8) uint64_t position{};
			alignas(8) uint64_t uv{};

			alignas(8) uint64_t color{};
			alignas(8) uint64_t st{};

			alignas(8) uint64_t normal{};
			alignas(8) uint64_t tangent{};

			alignas(8) uint64_t joints{};
			alignas(8) uint64_t weights{};

			alignas(8) uint64_t id{};
			alignas(8) uint64_t padding1{};
		};
	//	Addresses addresses = {};
	};


	struct Primitive {
		pod::DrawCommand drawCommand;
		pod::Instance instance;
	};
}

namespace uf {
	struct UF_API Mesh {
	public:
		static bool defaultInterleaved;
		typedef uf::stl::vector<uint8_t> buffer_t;
		struct Attribute {
			ext::RENDERER::AttributeDescriptor descriptor;
			 int32_t buffer = -1;
			size_t offset = 0;

			size_t stride = 0;
			size_t length = 0;
			void* pointer = NULL;
		};
		struct Input {
			uf::stl::vector<Attribute> attributes;
			size_t count = 0; // how many elements is the input using
			size_t first = 0; // base index to start from
			size_t size = 0; // size of one element in the input's buffer
			size_t offset = 0; // bytes to offset from within the associated buffer
			 int32_t interleaved = -1; // index to interleaved buffer if in bounds
		} vertex, index, instance, indirect;
	/*
		struct Bounds {
			pod::Vector3f min = { std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max() };
			pod::Vector3f max = { -std::numeric_limits<float>::max(), -std::numeric_limits<float>::max(), -std::numeric_limits<float>::max() };
		} bounds;
	*/
		uf::stl::vector<buffer_t> buffers;
	protected:
		void _destroy( uf::Mesh::Input& input );
		void _bind( bool interleaved = uf::Mesh::defaultInterleaved );
		void _updateDescriptor( uf::Mesh::Input& input );
		uf::Mesh::Attribute _remapAttribute( const uf::Mesh::Input& input, const uf::Mesh::Attribute& attribute, size_t i = 0 ) const;

		bool _hasV( const uf::Mesh::Input& input, const uf::stl::vector<ext::RENDERER::AttributeDescriptor>& descriptors ) const;
		bool _hasV( const uf::Mesh::Input& input, const uf::Mesh::Input& src ) const;
		void _bindV( uf::Mesh::Input& input, const uf::stl::vector<ext::RENDERER::AttributeDescriptor>& descriptors );
		void _resizeVs( uf::Mesh::Input& input, size_t count );
		void _reserveVs( uf::Mesh::Input& input, size_t count );
		void _insertV( uf::Mesh::Input& input, const void* data );
		void _insertVs( uf::Mesh::Input& input, const void* data, size_t size );
		void _insertVs( uf::Mesh::Input& input, const uf::Mesh& mesh, const uf::Mesh::Input& srcInput );
		
		template<typename T> inline bool _hasV( const uf::Mesh::Input& input ) const { return _hasV( input, T::descriptor ); }
		template<typename T> inline void _bindV( uf::Mesh::Input& input ) { return _bindV( input, T::descriptor ); }
		template<typename T> inline void _insertV( uf::Mesh::Input& input, const T& vertex ) { return _insertV( input, (const void*) &vertex ); }
		template<typename T> inline void _insertVs( uf::Mesh::Input& input, const uf::stl::vector<T>& vs ) { return _insertVs( input, (const void*) vs.data(), vs.size() ); }

		void _bindI( uf::Mesh::Input& input, size_t size, ext::RENDERER::enums::Type::type_t type, size_t count = 1 );
		void _reserveIs( uf::Mesh::Input& input, size_t count, size_t i = 0 );
		void _resizeIs( uf::Mesh::Input& input, size_t count, size_t i = 0 );
		void _insertI( uf::Mesh::Input& input, const void* data, size_t i );
		void _insertIs( uf::Mesh::Input& input, const void* data, size_t size, size_t i );
		void _insertIs( uf::Mesh::Input& input, const uf::Mesh& mesh, const uf::Mesh::Input& srcInput );
		
		template<typename U> inline void _bindI( uf::Mesh::Input& input, size_t indices = 1 ) { return _bindI( input, sizeof(U), ext::RENDERER::typeToEnum<U>(), indices ); }
		template<typename U> inline void _insertI( uf::Mesh::Input& input, U index, size_t i = 0 ) { return _insertI( input, (const void*) &index, i ); }
		template<typename U> inline void _insertIs( uf::Mesh::Input& input, const uf::stl::vector<U>& is, size_t i = 0 ) { return _insertIs( input, (const void*) is.data(), is.size(), i ); }
	public:
		void initialize();
		void destroy();

		uf::Mesh convert() const;
		uf::Mesh copy() const;
		uf::Mesh copy(bool) const;
		uf::Mesh interleave() const;
		uf::Mesh deinterleave() const;

		uf::Mesh expand();
		uf::Mesh expand(bool);

		void updateDescriptor();
		
		void bind( const uf::Mesh& );
		void bind( const uf::Mesh&, bool );
		void insert( const uf::Mesh& );
		
		void generateIndices();
		void generateIndirect();

		bool isInterleaved() const;
		bool isInterleaved( const uf::Mesh::Input& ) const;
		bool isInterleaved( size_t ) const;

		buffer_t& getBuffer( const uf::Mesh::Input&, size_t = 0 );
		buffer_t& getBuffer( const uf::Mesh::Input&, const uf::Mesh::Attribute& );
		
		const buffer_t& getBuffer( const uf::Mesh::Input&, size_t = 0 ) const;
		const buffer_t& getBuffer( const uf::Mesh::Input&, const uf::Mesh::Attribute& ) const;

		void eraseAttribute( uf::Mesh::Input&, const uf::Mesh::Attribute& );
		void eraseAttribute( uf::Mesh::Input&, size_t );

		uf::Mesh::Input remapInput( const uf::Mesh::Input&, size_t i = 0 ) const;
		uf::Mesh::Input remapVertexInput( size_t i = 0 ) const;
		uf::Mesh::Input remapIndexInput( size_t i = 0 ) const;

		void print( bool = true ) const;

		std::string printVertices( bool = true ) const;
		std::string printIndices( bool = true ) const;
		std::string printInstances( bool = true ) const;
		std::string printIndirects( bool = true ) const;

		inline bool hasVertex( const uf::stl::vector<ext::RENDERER::AttributeDescriptor>& descriptors ) const { return _hasV( vertex, descriptors ); }
		inline bool hasVertex( const uf::Mesh& mesh ) const { return _hasV( vertex, mesh.vertex ); }
		inline void bindVertex( const uf::stl::vector<ext::RENDERER::AttributeDescriptor>& descriptors ) { return _bindV( vertex, descriptors ); }
		inline void resizeVertices( size_t count ) { return _resizeVs( vertex, count ); }
		inline void reserveVertices( size_t count ) { return _reserveVs( vertex, count ); }
		inline void insertVertex( const void* data ) { return _insertV( vertex, data ); }
		inline void insertVertices( const void* data, size_t size ) { return _insertVs( vertex, data, size ); }
		inline void insertVertices( const uf::Mesh& mesh ) { return _insertVs( vertex, mesh, mesh.vertex ); }
		inline void updateVertexDescriptor() { return _updateDescriptor( vertex ); }
		inline uf::Mesh::Attribute remapVertexAttribute( const uf::Mesh::Attribute& attribute, size_t i = 0 ) const { return _remapAttribute( vertex, attribute, i ); }

		template<typename T> inline bool hasVertex() const { return _hasV( vertex, T::descriptor ); }
		template<typename T> inline void bindVertex() { return _bindV( vertex, T::descriptor ); }
		template<typename T> inline void insertVertex( const T& v ) { return _insertV( vertex, (const void*) &v ); }
		template<typename T> inline void insertVertices( const uf::stl::vector<T>& vertices ) { return _insertVs( vertex, (const void*) vertices.data(), vertices.size() ); }

		inline void bindIndex( size_t size, ext::RENDERER::enums::Type::type_t type, size_t count = 1 ) { return _bindI( index, size, type, count ); }
		inline void reserveIndices( size_t count, size_t i = 0 ) { return _reserveIs( index, count, i ); }
		inline void resizeIndices( size_t count, size_t i = 0 ) { return _resizeIs( index, count, i ); }
		inline void insertIndex( const void* data, size_t i = 0 ) { return _insertI( index, data, i ); }
		inline void insertIndices( const void* data, size_t size, size_t i = 0 ) { return _insertIs( index, data, size, i ); }
		inline void insertIndices( const uf::Mesh& mesh ) { return _insertIs( index, mesh, mesh.index ); }
		inline void updateIndexDescriptor() { return _updateDescriptor( index ); }
		inline uf::Mesh::Attribute remapIndexAttribute( const uf::Mesh::Attribute& attribute, size_t i = 0 ) const { return _remapAttribute( index, attribute, i ); }

		template<typename U> inline void bindIndex( size_t count = 1 ) { return _bindI( index, sizeof(U), ext::RENDERER::typeToEnum<U>(), count ); }
		template<typename U> inline void insertIndex( U I, size_t i = 0 ) { return _insertI( index, (const void*) &I, i ); }
		template<typename U> inline void insertIndices( const uf::stl::vector<U>& indices, size_t i = 0 ) { return _insertIs( index, (const void*) indices.data(), indices.size(), i ); }

		inline bool hasInstance( const uf::stl::vector<ext::RENDERER::AttributeDescriptor>& descriptors ) const { return _hasV( instance, descriptors ); }
		inline bool hasInstance( const uf::Mesh& mesh ) const { return _hasV( instance, mesh.instance ); }
		inline void bindInstance( const uf::stl::vector<ext::RENDERER::AttributeDescriptor>& descriptors ) { return _bindV( instance, descriptors ); }
		inline void resizeInstances( size_t count ) { return _resizeVs( instance, count ); }
		inline void reserveInstances( size_t count ) { return _reserveVs( instance, count ); }
		inline void insertInstance( const void* data ) { return _insertV( instance, data ); }
		inline void insertInstances( const void* data, size_t size ) { return _insertVs( instance, data, size ); }
		inline void insertInstances( const uf::Mesh& mesh ) { return _insertVs( instance, mesh, mesh.instance ); }
		inline void updateInstanceDescriptor() { return _updateDescriptor( instance ); }

		template<typename T> inline bool hasInstance() const { return _hasV( instance, T::descriptor ); }
		template<typename T> inline void bindInstance() { return _bindV( instance, T::descriptor ); }
		template<typename T> inline void insertInstance( const T& v ) { return _insertV( instance, (const void*) &v ); }
		template<typename T> inline void insertInstances( const uf::stl::vector<T>& instances ) { return _insertVs( instance, (const void*) instances.data(), instances.size() ); }

		inline void bindIndirect( size_t size, ext::RENDERER::enums::Type::type_t type, size_t count = 1 ) { return _bindI( indirect, size, type, count ); }
		inline void reserveIndirects( size_t count, size_t i = 0 ) { return _reserveIs( indirect, count, i ); }
		inline void resizeIndirects( size_t count, size_t i = 0 ) { return _resizeIs( indirect, count, i ); }
		inline void insertIndirect( const void* data, size_t i = 0 ) { return _insertI( indirect, data, i ); }
		inline void insertIndirects( const void* data, size_t size, size_t i = 0 ) { return _insertIs( indirect, data, size, i ); }
		inline void insertIndirects( const uf::Mesh& mesh ) { return _insertIs( indirect, mesh, mesh.indirect ); }
		inline void updateIndirectDescriptor() { return _updateDescriptor( indirect ); }

		template<typename U> inline void bindIndirect( size_t i = 1 ) { return _bindI( indirect, sizeof(U), ext::RENDERER::typeToEnum<U>(), i ); }
		template<typename U> inline void insertIndirect( U v, size_t i = 0 ) { return _insertI( indirect, (const void*) &v, i ); }
		template<typename U> inline void insertIndirects( const uf::stl::vector<U>& indirects, size_t i = 0 ) { return _insertIs( indirect, (const void*) indirects.data(), indirects.size(), i ); }

		template<typename T, typename U = ext::RENDERER::index_t>
		void bind( bool interleave = uf::Mesh::defaultInterleaved, size_t indices = 1 ) {
			bindVertex<T>();
			bindIndex<U>( indices );
			_bind( interleave );
		}
	};
}

namespace ext {
	namespace RENDERER {
		struct UF_API GraphicDescriptor {
			typedef size_t hash_t;
			
			uf::stl::string renderMode = "";
			uf::stl::string pipeline = "";

			uint32_t renderTarget = 0;
			uint32_t subpass = 0;
			uint32_t aux = 0;
			
			struct {
				size_t width = 1;
				size_t height = 1;
				size_t depth = 1;
				uint32_t point = 0;
			} bind;

			struct {
				uf::Mesh::Input vertex, index, instance, indirect;
				size_t bufferOffset = 0;
			} inputs;

			ext::RENDERER::enums::PrimitiveTopology::type_t topology = ext::RENDERER::enums::PrimitiveTopology::TRIANGLE_LIST;
			ext::RENDERER::enums::PolygonMode::type_t fill = ext::RENDERER::enums::PolygonMode::FILL;
			ext::RENDERER::enums::CullMode::type_t cullMode = ext::RENDERER::enums::CullMode::BACK;
			ext::RENDERER::enums::Face::type_t frontFace = ext::RENDERER::enums::Face::CW;
			float lineWidth = 1.0f;

			struct {
				bool test = true;
				bool write = true;
				ext::RENDERER::enums::Compare::type_t operation = ext::RENDERER::enums::Compare::GREATER_OR_EQUAL;
				struct {
					bool enable = false;
					float constant = 0;
					float slope = 0;
					float clamp = 0;
				} bias;
			} depth;
			
			bool invalidated = false;

			hash_t hash() const;
			void parse( ext::json::Value& );
			bool operator==( const GraphicDescriptor& right ) const { return this->hash() == right.hash(); }
			bool operator!=( const GraphicDescriptor& right ) const { return this->hash() != right.hash(); }
		};
	}
}

namespace std {
    template <>
    struct hash<ext::RENDERER::GraphicDescriptor> {
        size_t operator()(const ext::RENDERER::GraphicDescriptor& descriptor) const { return descriptor.hash(); }
    };
}

#undef UF_RENDERER
#define UF_VERTEX_DESCRIPTION( TYPE, FORMAT, ATTRIBUTE ) {\
		.offset = offsetof(TYPE, ATTRIBUTE),\
		.size = sizeof(decltype(TYPE::ATTRIBUTE)),\
		.format = uf::renderer::enums::Format::FORMAT,\
		.name = #ATTRIBUTE,\
		.type = ext::RENDERER::typeToEnum<decltype(TYPE::ATTRIBUTE)::type_t>(),\
		.components = decltype(TYPE::ATTRIBUTE)::size,\
	},

#define UF_VERTEX_DESCRIPTOR( TYPE, ... )\
	uf::stl::vector<uf::renderer::AttributeDescriptor> TYPE::descriptor = { __VA_ARGS__ };


#if UF_USE_VULKAN
	namespace uf {
		namespace renderer = ext::vulkan;
	}
#elif UF_USE_OPENGL
	namespace uf {
		namespace renderer = ext::opengl;
	}
#endif

namespace pod {
	struct /*UF_API*/ Vertex_3F2F3F4F {
		pod::Vector3f position;
		pod::Vector2f uv;
		pod::Vector3f normal;
		pod::Vector4f color;

		static UF_API uf::stl::vector<uf::renderer::AttributeDescriptor> descriptor;
	};
	struct /*UF_API*/ Vertex_3F2F3F32B {
		pod::Vector3f position;
		pod::Vector2f uv;
		pod::Vector3f normal;
		pod::Vector4t<uint8_t> color;

		static UF_API uf::stl::vector<uf::renderer::AttributeDescriptor> descriptor;
	};
	struct /*UF_API*/ Vertex_3F3F3F {
		pod::Vector3f position;
		pod::Vector3f uv;
		pod::Vector3f normal;

		static UF_API uf::stl::vector<uf::renderer::AttributeDescriptor> descriptor;
	};
	struct /*UF_API*/ Vertex_3F2F3F1UI {
		pod::Vector3f position;
		pod::Vector2f uv;
		pod::Vector3f normal;
		pod::Vector1ui id;

		static UF_API uf::stl::vector<uf::renderer::AttributeDescriptor> descriptor;
	};
	struct /*UF_API*/ Vertex_3F2F3F {
		pod::Vector3f position;
		pod::Vector2f uv;
		pod::Vector3f normal;

		static UF_API uf::stl::vector<uf::renderer::AttributeDescriptor> descriptor;
	};
	struct /*UF_API*/ Vertex_3F2F {
		pod::Vector3f position;
		pod::Vector2f uv;

		static UF_API uf::stl::vector<uf::renderer::AttributeDescriptor> descriptor;
	};
	struct /*UF_API*/ Vertex_2F2F {
		pod::Vector2f position;
		pod::Vector2f uv;

		static UF_API uf::stl::vector<uf::renderer::AttributeDescriptor> descriptor;
	};
	struct /*UF_API*/ Vertex_3F {
		pod::Vector3f position;

		static UF_API uf::stl::vector<uf::renderer::AttributeDescriptor> descriptor;
	};
}

namespace uf {
	template<typename T = pod::Vertex_3F, typename U = ext::RENDERER::index_t>
	struct UF_API Mesh_T {
		typedef T vertex_t;
		typedef U index_t;

		uf::stl::vector<vertex_t> vertices;
		uf::stl::vector<index_t> indices;
		uf::stl::vector<pod::Primitive> primitives;
	};

	template<typename T = pod::Vertex_3F, typename U = ext::RENDERER::index_t>
	struct UF_API Meshlet_T {
		typedef T vertex_t;
		typedef U index_t;

		uf::stl::vector<vertex_t> vertices;
		uf::stl::vector<index_t> indices;
		pod::Primitive primitive;
	};
}