#pragma once

#include <psemek/wgpu/callback_mode.hpp>
#include <psemek/wgpu/chained_struct.hpp>
#include <psemek/wgpu/detail/object.hpp>

#include <cstdint>
#include <string>
#include <functional>

namespace psemek::wgpu
{

	struct buffer
		: detail::object<buffer>
	{
		using detail::object<buffer>::object;

		enum class usage : std::uint64_t
		{
			none          = 0x00000000,
			map_read      = 0x00000001,
			map_write     = 0x00000002,
			copy_src      = 0x00000004,
			copy_dst      = 0x00000008,
			index         = 0x00000010,
			vertex        = 0x00000020,
			uniform       = 0x00000040,
			storage       = 0x00000080,
			indirect      = 0x00000100,
			query_resolve = 0x00000200,
		};

		enum class map_mode : std::uint64_t
		{
			none  = 0x00000000,
			read  = 0x00000001,
			write = 0x00000002,
		};

		enum class map_state : std::uint32_t
		{
			unmapped = 0x00000001,
			pending  = 0x00000002,
			mapped   = 0x00000003,
		};

		enum class map_async_status : std::uint32_t
		{
			success          = 0x00000001,
			instance_dropped = 0x00000002,
			error            = 0x00000003,
			aborted          = 0x00000004,
			unknown          = 0x00000005,
		};

		struct descriptor {
			std::vector<chained_struct> chain = {};
			std::string label = {};
			enum usage usage;
			uint64_t size;
			bool mapped_at_creation = false;
		};

		using map_callback = std::function<void(map_async_status, std::string_view)>;

		void destroy();
		std::uint64_t get_size() const;
		usage get_usage() const;
		map_state get_map_state() const;
		void const * get_const_mapped_range(std::size_t offset, std::size_t size) const;
		void * get_mapped_range(std::size_t offset, std::size_t size);
		void map_async(callback_mode mode, map_mode flags, std::size_t offset, std::size_t size, map_callback const & callback);
		void unmap();
		void set_label(std::string const & label);

		static void reference(void * ptr);
		static void release(void * ptr);

	private:
		explicit buffer(void * ptr)
			: detail::object<buffer>(ptr)
		{}

		friend struct device;
	};

	inline buffer::usage operator | (buffer::usage u1, buffer::usage u2)
	{
		return static_cast<buffer::usage>(static_cast<std::uint32_t>(u1) | static_cast<std::uint32_t>(u2));
	}

}
