#pragma once

#include <psemek/wgpu/chained_struct.hpp>
#include <psemek/wgpu/queue.hpp>
#include <psemek/wgpu/bind_group.hpp>
#include <psemek/wgpu/bind_group_layout.hpp>
#include <psemek/wgpu/buffer.hpp>
#include <psemek/wgpu/callback_mode.hpp>
#include <psemek/wgpu/command_encoder.hpp>
#include <psemek/wgpu/compute_pipeline.hpp>
#include <psemek/wgpu/pipeline_layout.hpp>
#include <psemek/wgpu/query_set.hpp>
#include <psemek/wgpu/render_bundle_encoder.hpp>
#include <psemek/wgpu/render_pipeline.hpp>
#include <psemek/wgpu/sampler.hpp>
#include <psemek/wgpu/shader_module.hpp>
#include <psemek/wgpu/texture.hpp>
#include <psemek/wgpu/detail/object.hpp>

#include <cstdint>
#include <optional>
#include <functional>

namespace psemek::wgpu
{

	enum class feature : std::uint32_t {
		undefined = 0x00000000,
		depth_clip_control = 0x00000001,
		depth32_float_stencil8 = 0x00000002,
		timestamp_query = 0x00000003,
		texture_compression_BC = 0x00000004,
		texture_compression_BC_sliced_3D = 0x00000005,
		texture_compression_ETC2 = 0x00000006,
		texture_compression_ASTC = 0x00000007,
		texture_compression_ASTC_sliced_3D = 0x00000008,
		indirect_first_instance = 0x00000009,
		shader_f16 = 0x0000000A,
		rg11b10_ufloat_renderable = 0x0000000B,
		bgra8_unorm_storage = 0x0000000C,
		float32_filterable = 0x0000000D,
		float32_blendable = 0x0000000E,
		clip_distances = 0x0000000F,
		dual_source_blending = 0x00000010,


		push_constants = 0x00030001,
		texture_adapter_specific_format_features = 0x00030002,
		multi_draw_indirect_count = 0x00030004,
		vertex_writable_storage = 0x00030005,
		texture_binding_array = 0x00030006,
		sampled_texture_and_storage_buffer_array_non_uniform_indexing = 0x00030007,
		pipeline_statistics_query = 0x00030008,
		storage_resource_binding_array = 0x00030009,
		partially_bound_binding_array = 0x0003000a,
		texture_format16bit_norm = 0x0003000b,
		texture_compression_astc_hdr = 0x0003000c,
		mappable_primary_buffers = 0x0003000e,
		buffer_binding_array = 0x0003000f,
		uniform_buffer_and_storage_texture_array_non_uniform_indexing = 0x00030010,
		polygon_mode_line = 0x00030013,
		polygon_mode_point = 0x00030014,
		conservative_rasterization = 0x00030015,
		spirv_shader_passthrough = 0x00030017,
		vertex_attribute64bit = 0x00030019,
		texture_format_nv12 = 0x0003001a,
		ray_query = 0x0003001c,
		shader_f64 = 0x0003001d,
		shader_i16 = 0x0003001e,
		shader_primitive_index = 0x0003001f,
		shader_early_depth_test = 0x00030020,
		subgroup = 0x00030021,
		subgroup_vertex = 0x00030022,
		subgroup_barrier = 0x00030023,
		timestamp_query_inside_encoders = 0x00030024,
		timestamp_query_inside_passes = 0x00030025,
		shader_int64 = 0x00030026,
	};

	struct limits
	{
		std::uint32_t max_texture_dimension_1D = 8192;
		std::uint32_t max_texture_dimension_2D = 8192;
		std::uint32_t max_texture_dimension_3D = 2048;
		std::uint32_t max_texture_array_layers = 256;
		std::uint32_t max_bind_groups = 4;
		std::uint32_t max_bind_groups_plus_vertex_buffers = 8;
		std::uint32_t max_bindings_per_bind_group = 1000;
		std::uint32_t max_dynamic_uniform_buffers_per_pipeline_layout = 8;
		std::uint32_t max_dynamic_storage_buffers_per_pipeline_layout = 4;
		std::uint32_t max_sampled_textures_per_shader_stage = 16;
		std::uint32_t max_samplers_per_shader_stage = 16;
		std::uint32_t max_storage_buffers_per_shader_stage = 8;
		std::uint32_t max_storage_textures_per_shader_stage = 4;
		std::uint32_t max_uniform_buffers_per_shader_stage = 12;
		std::uint64_t max_uniform_buffer_binding_size = 64 * 1024;
		std::uint64_t max_storage_buffer_binding_size = 128 * 1024 * 1024;
		std::uint32_t min_uniform_buffer_offset_alignment = 256;
		std::uint32_t min_storage_buffer_offset_alignment = 256;
		std::uint32_t max_vertex_buffers = 8;
		std::uint64_t max_buffer_size = 256 * 1024 * 1024;
		std::uint32_t max_vertex_attributes = 16;
		std::uint32_t max_vertex_buffer_array_stride = 2048;
		std::uint32_t max_inter_stage_shader_variables = 15;
		std::uint32_t max_color_attachments = 1;
		std::uint32_t max_color_attachment_bytes_per_sample = 4;
		std::uint32_t max_compute_workgroup_storage_size = 16384;
		std::uint32_t max_compute_invocations_per_workgroup = 256;
		std::uint32_t max_compute_workgroup_size_x = 256;
		std::uint32_t max_compute_workgroup_size_y = 256;
		std::uint32_t max_compute_workgroup_size_z = 64;
		std::uint32_t max_compute_workgroups_per_dimension = 65535;
	};

	struct native_limits
	{
		std::uint32_t max_push_constant_size = 0;
		std::uint32_t max_non_sampler_bindings = 0;
	};

	enum class create_pipeline_async_status : std::uint32_t
	{
		success          = 0x00000001,
		instance_dropped = 0x00000002,
		validation_error = 0x00000003,
		internal_error   = 0x00000004,
		unknown          = 0x00000005,
	};

	enum class error_filter : std::uint32_t
	{
		validation    = 0x00000001,
		out_of_memory = 0x00000002,
		internal      = 0x00000003,
	};

	enum class error_type : std::uint32_t
	{
		no_error      = 0x00000001,
		validation    = 0x00000002,
		out_of_memory = 0x00000003,
		internal      = 0x00000004,
		unknown       = 0x00000005,
	};

	enum class pop_error_scope_status : std::uint32_t
	{
		success          = 0x00000001,
		instance_dropped = 0x00000002,
		empty_stack      = 0x00000003,
	};

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

		enum class lost_reason : std::uint32_t
		{
			unknown          = 0x00000001,
			destroyed        = 0x00000002,
			instance_dropped = 0x00000003,
			failed_creation  = 0x00000004,
		};

		using lost_callback = std::function<void(lost_reason, std::string_view)>;
		using uncaptured_error_callback = std::function<void(error_type, std::string_view)>;

		struct required_limits
		{
			std::vector<chained_struct> chain = {};
			wgpu::limits limits = {};
		};

		struct descriptor
		{
			std::vector<chained_struct> chain = {};
			std::string label = {};
			std::vector<feature> required_features = {};
			std::optional<struct required_limits> required_limits = {};
			queue::descriptor default_queue = {};
			device::lost_callback lost_callback = {};
			callback_mode lost_callback_mode = {};
			device::uncaptured_error_callback uncaptured_error_callback = {};
		};

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

		using request_callback = std::function<void(request_status, device, std::string const & message)>;
		using create_compute_pipeline_async_callback = std::function<void(create_pipeline_async_status, compute_pipeline, std::string_view)>;
		using create_render_pipeline_async_callback = std::function<void(create_pipeline_async_status, render_pipeline, std::string_view)>;
		using pop_error_callback = std::function<void(pop_error_scope_status, error_type, std::string_view)>;
		using error_callback = std::function<void(error_type, std::string const &)>;

		queue get_queue() const;
		bind_group create_bind_group(bind_group::descriptor const & desc);
		bind_group_layout create_bind_group_layout(bind_group_layout::descriptor const & desc);
		buffer create_buffer(buffer::descriptor const & desc);
		command_encoder create_command_encoder(command_encoder::descriptor const & desc);
		compute_pipeline create_compute_pipeline(compute_pipeline::descriptor const & desc);
		void create_compute_pipeline_async(callback_mode mode, compute_pipeline::descriptor const & desc, create_compute_pipeline_async_callback const & callback);
		pipeline_layout create_pipeline_layout(pipeline_layout::descriptor const & desc);
		query_set create_query_set(query_set::descriptor const & desc);
		render_pipeline create_render_pipeline(render_pipeline::descriptor const & desc);
		void create_render_pipeline_async(callback_mode mode, render_pipeline::descriptor const & desc, create_render_pipeline_async_callback const & callback);
		render_bundle_encoder create_render_bundle_encoder(render_bundle_encoder::descriptor const & desc);
		sampler create_sampler(sampler::descriptor const & desc);
		shader_module create_shader_module(shader_module::descriptor const & desc);
		texture create_texture(texture::descriptor const & desc);
		void destroy();
		std::vector<feature> enumerate_features() const;
		limits get_limits() const;
		bool has_feature(feature feature) const;
		void push_error_scope(error_filter filter);
		void pop_error_scope(callback_mode mode, pop_error_callback const & callback);
		void set_label(std::string const & label);

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

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

		friend struct adapter;
	};

	detail::chained_struct_ptr to_chained_struct(native_limits && value);

}
