#pragma once

#include <mutex>

#include <uf/ext/opengl/ogl.h>

#include <uf/ext/opengl/uniform.h>
#include <uf/ext/opengl/buffer.h>
#include <uf/ext/opengl/texture.h>

#include <uf/utils/thread/thread.h>
#include <uf/utils/thread/perthread.h>
#include <uf/utils/userdata/userdata.h>
#include <uf/utils/mesh/mesh.h>

#define UF_COMMAND_BUFFER_USERDATA 1
#define UF_COMMAND_BUFFER_POINTERED_USERDATA 1

namespace ext {
	namespace opengl {
		struct Device;
		struct Graphic;
		
		struct UF_API CommandBuffer {
		public:
			struct UF_API Info {
				ext::opengl::enums::Command::type_t type = 0;
				Info* next = NULL;
			};
			struct UF_API InfoClear : public Info {
			//	ext::opengl::enums::Command::type_t type = 0;
				pod::Vector4f color = {};
				float depth = 0;
				uint32_t bits = 0;
			};
			struct UF_API InfoViewport : public Info {
			//	ext::opengl::enums::Command::type_t type = 0;
				pod::Vector2ui corner = {};
				pod::Vector2ui size = {};
			};
			struct UF_API InfoVariant : public Info {
			//	ext::opengl::enums::Command::type_t type = 0;
				typedef std::function<void()> function_t;
				function_t lambda;
			};
			struct UF_API InfoDraw : public Info {
				GraphicDescriptor descriptor = {};

				struct {
					uf::Mesh::Attribute index = {};
					uf::Mesh::Attribute position = {};
					uf::Mesh::Attribute uv = {};
					uf::Mesh::Attribute st = {};
					uf::Mesh::Attribute normal = {};
					uf::Mesh::Attribute color = {};
					uf::Mesh::Attribute instance = {};
					uf::Mesh::Attribute indirect = {};
				} attributes;
				
				struct Matrices {
					const pod::Matrix4f* model = NULL;
					const pod::Matrix4f* view = NULL;
					const pod::Matrix4f* projection = NULL;
				} matrices;
				
				struct {
					ext::opengl::Texture::Descriptor primary = {};
					ext::opengl::Texture::Descriptor secondary = {};
				} textures;

				struct {
					int8_t modeAlpha{};
					float alphaCutoff = 0.001f;
				} blend;

				struct {
					bool enabled = false;
					pod::Vector4f value = { 1, 1, 1, 1 };
					const pod::Vector4f* pointer = NULL;
				} color;
			};

			#if UF_COMMAND_BUFFER_USERDATA
			#if UF_COMMAND_BUFFER_POINTERED_USERDATA
				typedef uf::PointeredUserdata userdata_t;
			#else
				typedef uf::Userdata userdata_t;
			#endif
			#else
				typedef Info* userdata_t;
			#endif
			typedef std::function<void()> function_t;
			static size_t preallocate;

			Device* device;
			size_t state = 0;
			uf::stl::vector<CommandBuffer::userdata_t> infos;
			std::mutex* mutex;
			
			void initialize( Device& device );
			void destroy();

			void record( const CommandBuffer::Info& );
			void record( const CommandBuffer::InfoVariant::function_t& );
			void record( const CommandBuffer& );

			void submit();
			void flush();
			void start();
			void end();
			size_t size() const;
		protected:
			pod::Matrix4f bindUniform( const Buffer::Descriptor& descriptor );
			void drawIndexed( const InfoDraw& );
		//	void draw( const InfoDraw&, const Info&, const uf::stl::vector<InfoTexture*>& = {} );
		//	void drawIndexed( const InfoDraw&, const Info&, const Info&, const uf::stl::vector<InfoTexture*>& = {} );
		};
	}
}