#pragma once

#include <psemek/wgpu/chained_struct.hpp>
#include <psemek/wgpu/texture_view.hpp>
#include <psemek/wgpu/query_set.hpp>
#include <psemek/wgpu/buffer.hpp>
#include <psemek/wgpu/bind_group.hpp>
#include <psemek/wgpu/render_pipeline.hpp>
#include <psemek/wgpu/render_bundle.hpp>
#include <psemek/wgpu/detail/object.hpp>
#include <psemek/wgpu/timestamp_writes.hpp>
#include <psemek/math/vector.hpp>
#include <psemek/math/box.hpp>

#include <cstdint>
#include <vector>
#include <optional>

namespace psemek::wgpu
{

	enum class load_op : std::uint32_t
	{
		undefined = 0x00000000,
		load      = 0x00000001,
		clear     = 0x00000002,
	};

	enum class store_op : std::uint32_t
	{
		undefined = 0x00000000,
		store     = 0x00000001,
		discard   = 0x00000002,
	};

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

		struct color_attachment
		{
			std::vector<chained_struct> chain = {};
			texture_view view;
			std::optional<std::uint32_t> depth_slice = std::nullopt;
			texture_view resolve_target = {};
			enum load_op load_op;
			enum store_op store_op;
			math::vector<double, 4> clear_value;
		};

		struct depth_stencil_attachment
		{
			texture_view view;
			load_op depth_load_op;
			store_op depth_store_op;
			float depth_clear_value;
			bool depth_read_only;
			load_op stencil_load_op;
			store_op stencil_store_op;
			std::uint32_t stencil_clear_value;
			bool stencil_read_only;
		};

		struct descriptor
		{
			std::vector<chained_struct> chain = {};
			std::string label = {};
			std::vector<color_attachment> color_attachments = {};
			std::optional<struct depth_stencil_attachment> depth_stencil_attachment = {};
			query_set occlusion_query_set = {};
			std::optional<struct timestamp_writes> timestamp_writes = {};
		};

		void set_pipeline(render_pipeline const & pipeline);
		void set_bind_group(std::uint32_t group_index, bind_group const & group, std::vector<std::uint32_t> const & dynamic_offsets);
		void set_vertex_buffer(std::uint32_t slot, buffer const & buffer, std::uint64_t offset, std::uint64_t size);
		void set_index_buffer(buffer const & buffer, index_format format, std::uint64_t offset, std::uint64_t size);
		void set_viewport(math::box<float, 3> const & viewport);
		void set_scissor_rect(math::box<std::uint32_t, 2> const & rect);
		void set_blend_constant(math::vector<double, 4> const & color);
		void set_stencil_reference(std::uint32_t reference);
		void set_push_constants(shader_stage stages, std::uint32_t offset, util::span<char const> data);
		void draw(std::uint32_t vertex_count, std::uint32_t instance_count, std::uint32_t first_vertex, std::uint32_t first_instance);
		void draw_indexed(std::uint32_t index_count, std::uint32_t instance_count, std::uint32_t first_index, std::uint32_t base_vertex, std::uint32_t first_instance);
		void draw_indirect(buffer const & indirect_buffer, std::uint64_t offset);
		void draw_indexed_indirect(buffer const & indirect_buffer, std::uint64_t offset);
		void multi_draw_indirect(buffer const & indirect_buffer, std::uint64_t offset, std::uint32_t count);
		void multi_draw_indexed_indirect(buffer const & indirect_buffer, std::uint64_t offset, std::uint32_t count);
		void multi_draw_indirect_count(buffer const & indirect_buffer, std::uint64_t offset, buffer const & count_buffer, std::uint64_t count_offset, std::uint32_t max_count);
		void multi_draw_indexed_indirect_count(buffer const & indirect_buffer, std::uint64_t offset, buffer const & count_buffer, std::uint64_t count_offset, std::uint32_t max_count);
		void execute_bundles(std::vector<render_bundle> const & bundles);
		void begin_occlusion_query(std::uint32_t query_index);
		void end_occlusion_query();
		void write_timestamp(query_set const & query_set, std::uint32_t query_index);
		void insert_debug_marker(std::string_view marker_label);
		void push_debug_group(std::string_view group_label);
		void pop_debug_group();
		void end();
		void set_label(std::string const & label);

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

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

		friend struct command_encoder;
	};

}
