#pragma once

#include <psemek/wgpu/pipeline_layout.hpp>
#include <psemek/wgpu/bind_group_layout.hpp>
#include <psemek/wgpu/shader_module.hpp>
#include <psemek/wgpu/sampler.hpp>
#include <psemek/wgpu/texture.hpp>
#include <psemek/wgpu/constant_entry.hpp>
#include <psemek/wgpu/detail/object.hpp>
#include <psemek/wgpu/chained_struct.hpp>

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

namespace psemek::wgpu
{

	enum class vertex_step_mode : std::uint32_t
	{
		vertex_buffer_not_used = 0x00000000,
		undefined              = 0x00000001,
		vertex                 = 0x00000002,
		instance               = 0x00000003,
	};

	enum class vertex_format : std::uint32_t
	{
		uint8           = 0x00000001,
		uint8x2         = 0x00000002,
		uint8x4         = 0x00000003,
		sint8           = 0x00000004,
		sint8x2         = 0x00000005,
		sint8x4         = 0x00000006,
		unorm8          = 0x00000007,
		unorm8x2        = 0x00000008,
		unorm8x4        = 0x00000009,
		snorm8          = 0x0000000a,
		snorm8x2        = 0x0000000b,
		snorm8x4        = 0x0000000c,
		uint16          = 0x0000000d,
		uint16x2        = 0x0000000e,
		uint16x4        = 0x0000000f,
		sint16          = 0x00000010,
		sint16x2        = 0x00000011,
		sint16x4        = 0x00000012,
		unorm16         = 0x00000013,
		unorm16x2       = 0x00000014,
		unorm16x4       = 0x00000015,
		snorm16         = 0x00000016,
		snorm16x2       = 0x00000017,
		snorm16x4       = 0x00000018,
		float16         = 0x00000019,
		float16x2       = 0x0000001a,
		float16x4       = 0x0000001b,
		float32         = 0x0000001c,
		float32x2       = 0x0000001d,
		float32x3       = 0x0000001e,
		float32x4       = 0x0000001f,
		uint32          = 0x00000020,
		uint32x2        = 0x00000021,
		uint32x3        = 0x00000022,
		uint32x4        = 0x00000023,
		sint32          = 0x00000024,
		sint32x2        = 0x00000025,
		sint32x3        = 0x00000026,
		sint32x4        = 0x00000027,
		unorm10_10_10_2 = 0x00000028,
		unorm8x4_bgra   = 0x00000029,
	};

	struct vertex_attribute
	{
		vertex_format format;
		std::uint64_t offset;
		std::uint32_t shader_location;
	};

	struct vertex_buffer_layout
	{
		std::uint64_t array_stride;
		vertex_step_mode step_mode;
		std::vector<vertex_attribute> attributes;
	};

	struct vertex_state
	{
		std::vector<chained_struct> chain = {};
		shader_module module;
		std::string entry_point;
		std::vector<constant_entry> constants = {};
		std::vector<vertex_buffer_layout> buffers;
	};

	enum class primitive_topology : std::uint32_t
	{
		undefined      = 0x00000000,
		point_list     = 0x00000001,
		line_list      = 0x00000002,
		line_strip     = 0x00000003,
		triangle_list  = 0x00000004,
		triangle_strip = 0x00000005,
	};

	enum class index_format : std::uint32_t
	{
		undefined = 0x00000000,
		uint16    = 0x00000001,
		uint32    = 0x00000002,
	};

	enum class front_face : std::uint32_t
	{
		undefined = 0x00000000,
		ccw       = 0x00000001,
		cw        = 0x00000002,
	};

	enum class cull_mode : std::uint32_t
	{
		undefined = 0x00000000,
		none      = 0x00000001,
		front     = 0x00000002,
		back      = 0x00000003,
	};

	enum class polygon_mode
	{
		fill = 0,
		line = 1,
		point = 2,
	};

	struct primitive_state
	{
		struct extras
		{
			enum polygon_mode polygon_mode;
			bool conservative;
		};

		std::vector<chained_struct> chain = {};
		primitive_topology topology;
		index_format strip_index_format = index_format::undefined;
		enum front_face front_face;
		enum cull_mode cull_mode;
		bool clip_depth = true;
	};

	enum class stencil_operation : std::uint32_t
	{
		undefined       = 0x00000000,
		keep            = 0x00000001,
		zero            = 0x00000002,
		replace         = 0x00000003,
		invert          = 0x00000004,
		increment_clamp = 0x00000005,
		decrement_clamp = 0x00000006,
		increment_wrap  = 0x00000007,
		decrement_wrap  = 0x00000008,
	};

	struct stencil_face_state
	{
		compare_function compare = compare_function::always;
		stencil_operation fail_op = stencil_operation::keep;
		stencil_operation depth_fail_op = stencil_operation::keep;
		stencil_operation pass_op = stencil_operation::keep;
	};

	struct depth_stencil_state
	{
		std::vector<chained_struct> chain = {};
		texture::format format;
		std::optional<bool> depth_write = true;
		compare_function depth_compare = compare_function::always;
		stencil_face_state stencil_front = {};
		stencil_face_state stencil_back = {};
		std::uint32_t stencil_read_mask = 0;
		std::uint32_t stencil_write_mask = 0;
		std::int32_t depth_bias = 0;
		float depth_bias_slope_scale = 0.f;
		float depth_bias_clamp = 0.f;
	};

	struct multisample_state
	{
		std::vector<chained_struct> chain = {};
		std::uint32_t count = 1;
		std::uint32_t mask = -1;
		bool alpha_to_coverage = false;
	};

	enum class blend_operation : std::uint32_t
	{
		undefined        = 0x00000000,
		add              = 0x00000001,
		subtract         = 0x00000002,
		reverse_subtract = 0x00000003,
		min              = 0x00000004,
		max              = 0x00000005,
	};

	enum class blend_factor : std::uint32_t
	{
		undefined            = 0x00000000,
		zero                 = 0x00000001,
		one                  = 0x00000002,
		src                  = 0x00000003,
		one_minus_src        = 0x00000004,
		src_alpha            = 0x00000005,
		one_minus_src_alpha  = 0x00000006,
		dst                  = 0x00000007,
		one_minus_dst        = 0x00000008,
		dst_alpha            = 0x00000009,
		one_minus_dst_alpha  = 0x0000000a,
		src_alpha_saturated  = 0x0000000b,
		constant             = 0x0000000c,
		one_minus_constant   = 0x0000000d,
		src1                 = 0x0000000e,
		one_minus_src1       = 0x0000000f,
		src1_alpha           = 0x00000010,
		one_minus_src1_alpha = 0x00000011,
	};

	struct blend_component
	{
		blend_operation operation;
		blend_factor src_factor;
		blend_factor dst_factor;
	};

	struct blend_state
	{
		blend_component color;
		blend_component alpha;
	};

	enum color_write_mask : std::uint64_t
	{
		none  = 0x00000000,
		red   = 0x00000001,
		green = 0x00000002,
		blue  = 0x00000004,
		alpha = 0x00000008,
		all   = 0x0000000f,
	};

	struct color_target_state
	{
		std::vector<chained_struct> chain = {};
		texture::format format;
		std::optional<blend_state> blend = {};
		color_write_mask write_mask = color_write_mask::all;
	};

	struct fragment_state {
		std::vector<chained_struct> chain = {};
		shader_module module;
		std::string entry_point;
		std::vector<constant_entry> constants = {};
		std::vector<color_target_state> targets;
	};

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

		struct descriptor
		{
			std::vector<chained_struct> chain = {};
			std::string label = {};
			pipeline_layout layout;
			vertex_state vertex;
			primitive_state primitive;
			std::optional<depth_stencil_state> depth_stencil = {};
			multisample_state multisample = {};
			std::optional<fragment_state> fragment = {};
		};

		bind_group_layout get_bind_group_layout(std::uint32_t index) const;
		void set_label(std::string const & label);

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

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

		friend struct device;
	};

	detail::chained_struct_ptr to_chained_struct(primitive_state::extras const &);

}
