// Copyright (C) 2015 Vincent Lejeune
// For conditions of distribution and use, see copyright notice in License.txt
#pragma once

#include <vector>
#include <tuple>
#include <array>
#include <memory>
#include <gsl/gsl>
#include <variant>
#include <optional>
#include "..\Core\SColor.h"

namespace irr
{
	namespace video
	{
		enum class E_INDEX_TYPE
		{
			EIT_16BIT,
			EIT_32BIT
		};

		enum class E_POLYGON_MODE
		{
			EPM_FILL,
			EPM_LINE,
			EPM_POINT
		};

		enum class E_CULL_MODE
		{
			ECM_NONE,
			ECM_FRONT,
			ECM_BACK,
			ECM_FRONT_AND_BACK
		};

		enum class E_FRONT_FACE
		{
			EFF_CCW,
			EFF_CW
		};

		enum class E_SAMPLE_COUNT
		{
			ESC_1,
			ESC_2,
			ESC_4,
			ESC_8
		};

		enum class E_PRIMITIVE_TYPE
		{
			EPT_POINTS,
			EPT_LINE_STRIP,
			EPT_LINES,
			EPT_TRIANGLE_STRIP,
			EPT_TRIANGLES,
		};

		enum class E_COMPARE_FUNCTION
		{
			ECF_LESS,
			ECF_LEQUAL,
			ECF_NEVER,
		};

		enum class E_STENCIL_OP
		{
			ESO_KEEP,
		};

		enum class E_ASPECT
		{
			EA_COLOR,
			EA_DEPTH,
			EA_STENCIL,
			EA_DEPTH_STENCIL
		};

		enum class E_MEMORY_POOL
		{
			EMP_CPU_WRITEABLE,
			EMP_GPU_LOCAL,
			EMP_CPU_READABLE,
		};

		enum class E_TEXTURE_TYPE
		{
			ETT_2D,
			ETT_CUBE,
		};
	}
}

enum class RESOURCE_USAGE
{
	PRESENT,
	COPY_DEST,
	COPY_SRC,
	RENDER_TARGET,
	READ_GENERIC,
	DEPTH_WRITE,
	undefined,
	uav,
};

enum image_flags
{
	usage_transfer_src = 0x1,
	usage_transfer_dst = 0x2,
	usage_sampled = 0x4,
	usage_render_target = 0x8,
	usage_depth_stencil = 0x10,
	usage_input_attachment = 0x20,
	usage_cube = 0x40,
};

enum buffer_flags
{
	none = 0,
	usage_uav = 0x1,
	usage_texel_buffer = 0x2,
    usage_buffer_transfer_src = 0x4,
	usage_uniform = 0x8,
	usage_index = 0x10,
	usage_vertex = 0x20,
};

enum class SAMPLER_TYPE
{
	NEAREST,
	BILINEAR_CLAMPED,
	TRILINEAR,
	ANISOTROPIC,
};

struct MipLevelData
{
	uint64_t Offset;
	uint32_t Width;
	uint32_t Height;
	uint32_t RowPitch;
};

enum class RESOURCE_VIEW
{
	CONSTANTS_BUFFER,
	INPUT_ATTACHMENT,
	SHADER_RESOURCE,
	TEXEL_BUFFER,
	SAMPLER,
	UAV_BUFFER,
	UAV_IMAGE,
};

enum class shader_stage
{
	vertex_shader,
	fragment_shader,
	all,
};

struct range_of_descriptors
{
	const RESOURCE_VIEW range_type;
	const uint32_t bind_point;
	const uint32_t count;

	constexpr range_of_descriptors(const RESOURCE_VIEW rt, const uint32_t bindpoint, const uint32_t range_size)
		: range_type(rt), bind_point(bindpoint), count(range_size)
	{}
};

struct descriptor_set
{
	const std::vector<range_of_descriptors> descriptors_ranges;
	const shader_stage stage;

	descriptor_set(const std::initializer_list<range_of_descriptors>& ranges, const shader_stage& st)
		: descriptors_ranges(ranges), stage(st)
	{ }
};

struct pipeline_vertex_attributes
{
	uint32_t location;
	irr::video::ECOLOR_FORMAT format;
	uint32_t binding;
	uint32_t stride;
	uint32_t offset;
};
/*	VkPipelineColorBlendAttachmentState blend_attachment_state{ true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE , VK_BLEND_OP_ADD, VK_BLEND_FACTOR_ONE , VK_BLEND_FACTOR_ONE , VK_BLEND_OP_ADD, -1 };
VkPipelineColorBlendStateCreateInfo blend_state{ VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, nullptr, 0, false, VK_LOGIC_OP_NO_OP, 1, &blend_attachment_state };*/

enum class blend_factor
{
	zero,
	one,
};

enum class blend_op
{
	add
};

struct color_output
{
	bool blend;
	blend_op op;
	blend_factor src_color;
	blend_factor src_alpha;
	blend_factor dst_color;
	blend_factor dst_alpha;
};

struct compute_pipeline_state_description
{
	std::vector<uint32_t> compute_binary;

	compute_pipeline_state_description set_compute_shader(gsl::span<const uint32_t> code)
	{
		std::copy(code.begin(), code.end(), std::back_inserter(compute_binary));
		return *this;
	}
};

struct graphic_pipeline_state_description
{
	std::vector<uint32_t> vertex_binary;
	std::vector<uint32_t> fragment_binary;

	std::vector<pipeline_vertex_attributes> attributes;
	std::vector<color_output> color_outputs;

	bool rasterization_depth_clamp_enable;
	bool rasterization_discard_enable;
	irr::video::E_POLYGON_MODE rasterization_polygon_mode;
	irr::video::E_CULL_MODE rasterization_cull_mode;
	irr::video::E_FRONT_FACE rasterization_front_face;
	bool rasterization_depth_bias_enable;
	float rasterization_depth_bias_constant_factor;
	float rasterization_depth_bias_clamp;
	float rasterization_depth_bias_slope_factor;
	float rasterization_line_width;
	bool rasterization_conservative_enable;

	bool multisample_multisample_enable;
	irr::video::E_SAMPLE_COUNT multisample_sample_count;
	float multisample_min_sample_shading;
	// sample mask ?
	bool multisample_alpha_to_coverage;
	bool multisample_alpha_to_one;

	irr::video::E_PRIMITIVE_TYPE input_assembly_topology;
	bool input_assembly_primitive_restart;

	bool depth_stencil_depth_test;
	bool depth_stencil_depth_write;
	irr::video::E_COMPARE_FUNCTION depth_stencil_depth_compare_op;
	bool depth_stencil_depth_clip_enable;
	bool depth_stencil_stencil_test;
	irr::video::E_STENCIL_OP depth_stencil_front_stencil_fail_op;
	irr::video::E_STENCIL_OP depth_stencil_front_stencil_depth_fail_op;
	irr::video::E_STENCIL_OP depth_stencil_front_stencil_pass_op;
	irr::video::E_COMPARE_FUNCTION depth_stencil_front_stencil_compare_op;
	irr::video::E_STENCIL_OP depth_stencil_back_stencil_fail_op;
	irr::video::E_STENCIL_OP depth_stencil_back_stencil_depth_fail_op;
	irr::video::E_STENCIL_OP depth_stencil_back_stencil_pass_op;
	irr::video::E_COMPARE_FUNCTION depth_stencil_back_stencil_compare_op;
	float depth_stencil_min_depth_clip;
	float depth_stencil_max_depth_clip;

	static graphic_pipeline_state_description get()
	{
		return graphic_pipeline_state_description(false, false, irr::video::E_POLYGON_MODE::EPM_FILL, irr::video::E_CULL_MODE::ECM_BACK, irr::video::E_FRONT_FACE::EFF_CW, false, 0.f, 0.f, 0.f, 1.f, false,
			false, irr::video::E_SAMPLE_COUNT::ESC_1, 0.f, false, false, irr::video::E_PRIMITIVE_TYPE::EPT_TRIANGLES, false, true, true, irr::video::E_COMPARE_FUNCTION::ECF_LESS, false, false,
			irr::video::E_STENCIL_OP::ESO_KEEP, irr::video::E_STENCIL_OP::ESO_KEEP, irr::video::E_STENCIL_OP::ESO_KEEP, irr::video::E_COMPARE_FUNCTION::ECF_NEVER,
			irr::video::E_STENCIL_OP::ESO_KEEP, irr::video::E_STENCIL_OP::ESO_KEEP, irr::video::E_STENCIL_OP::ESO_KEEP, irr::video::E_COMPARE_FUNCTION::ECF_NEVER,
			0.f, 1.f);
	}

	graphic_pipeline_state_description set_depth_compare_function(irr::video::E_COMPARE_FUNCTION depth_compare) const
	{
		return graphic_pipeline_state_description(rasterization_depth_clamp_enable,
			rasterization_discard_enable,
			rasterization_polygon_mode,
			rasterization_cull_mode,
			rasterization_front_face,
			rasterization_depth_bias_enable,
			rasterization_depth_bias_constant_factor,
			rasterization_depth_bias_clamp,
			rasterization_depth_bias_slope_factor,
			rasterization_line_width,
			rasterization_conservative_enable,
			multisample_multisample_enable,
			multisample_sample_count,
			multisample_min_sample_shading,
			multisample_alpha_to_coverage,
			multisample_alpha_to_one,
			input_assembly_topology,
			input_assembly_primitive_restart,
			depth_stencil_depth_test,
			depth_stencil_depth_write,
			depth_compare,
			depth_stencil_depth_clip_enable,
			depth_stencil_stencil_test,
			depth_stencil_front_stencil_fail_op,
			depth_stencil_front_stencil_depth_fail_op,
			depth_stencil_front_stencil_pass_op,
			depth_stencil_front_stencil_compare_op,
			depth_stencil_back_stencil_fail_op,
			depth_stencil_back_stencil_depth_fail_op,
			depth_stencil_back_stencil_pass_op,
			depth_stencil_back_stencil_compare_op,
			depth_stencil_min_depth_clip,
			depth_stencil_max_depth_clip);
	}

	graphic_pipeline_state_description set_depth_write(bool depthwrite) const
	{
		return graphic_pipeline_state_description(rasterization_depth_clamp_enable,
			rasterization_discard_enable,
			rasterization_polygon_mode,
			rasterization_cull_mode,
			rasterization_front_face,
			rasterization_depth_bias_enable,
			rasterization_depth_bias_constant_factor,
			rasterization_depth_bias_clamp,
			rasterization_depth_bias_slope_factor,
			rasterization_line_width,
			rasterization_conservative_enable,
			multisample_multisample_enable,
			multisample_sample_count,
			multisample_min_sample_shading,
			multisample_alpha_to_coverage,
			multisample_alpha_to_one,
			input_assembly_topology,
			input_assembly_primitive_restart,
			depth_stencil_depth_test,
			depthwrite,
			depth_stencil_depth_compare_op,
			depth_stencil_depth_clip_enable,
			depth_stencil_stencil_test,
			depth_stencil_front_stencil_fail_op,
			depth_stencil_front_stencil_depth_fail_op,
			depth_stencil_front_stencil_pass_op,
			depth_stencil_front_stencil_compare_op,
			depth_stencil_back_stencil_fail_op,
			depth_stencil_back_stencil_depth_fail_op,
			depth_stencil_back_stencil_pass_op,
			depth_stencil_back_stencil_compare_op,
			depth_stencil_min_depth_clip,
			depth_stencil_max_depth_clip);
	}

	graphic_pipeline_state_description set_depth_test(bool depth_test) const
	{
		return graphic_pipeline_state_description(rasterization_depth_clamp_enable,
			rasterization_discard_enable,
			rasterization_polygon_mode,
			rasterization_cull_mode,
			rasterization_front_face,
			rasterization_depth_bias_enable,
			rasterization_depth_bias_constant_factor,
			rasterization_depth_bias_clamp,
			rasterization_depth_bias_slope_factor,
			rasterization_line_width,
			rasterization_conservative_enable,
			multisample_multisample_enable,
			multisample_sample_count,
			multisample_min_sample_shading,
			multisample_alpha_to_coverage,
			multisample_alpha_to_one,
			input_assembly_topology,
			input_assembly_primitive_restart,
			depth_test,
			depth_stencil_depth_write,
			depth_stencil_depth_compare_op,
			depth_stencil_depth_clip_enable,
			depth_stencil_stencil_test,
			depth_stencil_front_stencil_fail_op,
			depth_stencil_front_stencil_depth_fail_op,
			depth_stencil_front_stencil_pass_op,
			depth_stencil_front_stencil_compare_op,
			depth_stencil_back_stencil_fail_op,
			depth_stencil_back_stencil_depth_fail_op,
			depth_stencil_back_stencil_pass_op,
			depth_stencil_back_stencil_compare_op,
			depth_stencil_min_depth_clip,
			depth_stencil_max_depth_clip);
	}

	graphic_pipeline_state_description set_vertex_shader(gsl::span<const uint32_t> binary)
	{
		std::copy(binary.begin(), binary.end(), std::back_inserter(vertex_binary));
		return *this;
	}

	graphic_pipeline_state_description set_fragment_shader(gsl::span<const uint32_t> binary)
	{
		std::copy(binary.begin(), binary.end(), std::back_inserter(fragment_binary));
		return *this;
	}

	graphic_pipeline_state_description set_vertex_attributes(const gsl::span<pipeline_vertex_attributes> &attributes_)
	{
		attributes = std::vector<pipeline_vertex_attributes>(attributes_.begin(), attributes_.end());
		return *this;
	}

	graphic_pipeline_state_description set_color_outputs(const gsl::span<color_output> &color_outputs_)
	{
		color_outputs = std::vector<color_output>(color_outputs_.begin(), color_outputs_.end());
		return *this;
	}

	graphic_pipeline_state_description()
	{

	}

private:
	graphic_pipeline_state_description(
		bool depth_clamp_enable,
		bool rasterizer_discard_enable,
		irr::video::E_POLYGON_MODE polygon_mode,
		irr::video::E_CULL_MODE cull_mode,
		irr::video::E_FRONT_FACE front_face,
		bool depth_bias_enable,
		float depth_bias_constant_factor,
		float depth_bias_clamp,
		float depth_bias_slope_factor,
		float line_width,
		bool conservative_enable,
		bool multisample_enable,
		irr::video::E_SAMPLE_COUNT sample_count,
		float min_sample_shading,
		bool alpha_to_coverage,
		bool alpha_to_one,
		irr::video::E_PRIMITIVE_TYPE topology,
		bool primitive_restart,
		bool depth_test,
		bool depth_write,
		irr::video::E_COMPARE_FUNCTION depth_compare_op,
		bool depth_clip_enable,
		bool stencil_test,
		irr::video::E_STENCIL_OP front_stencil_fail_op,
		irr::video::E_STENCIL_OP front_stencil_depth_fail_op,
		irr::video::E_STENCIL_OP front_stencil_pass_op,
		irr::video::E_COMPARE_FUNCTION front_stencil_compare_op,
		irr::video::E_STENCIL_OP back_stencil_fail_op,
		irr::video::E_STENCIL_OP back_stencil_depth_fail_op,
		irr::video::E_STENCIL_OP back_stencil_pass_op,
		irr::video::E_COMPARE_FUNCTION back_stencil_compare_op,
		float min_depth_clip,
		float max_depth_clip
	)
		: rasterization_depth_clamp_enable(depth_clamp_enable),
		rasterization_discard_enable(rasterizer_discard_enable),
		rasterization_polygon_mode(polygon_mode),
		rasterization_cull_mode(cull_mode),
		rasterization_front_face(front_face),
		rasterization_depth_bias_enable(depth_bias_enable),
		rasterization_depth_bias_constant_factor(depth_bias_constant_factor),
		rasterization_depth_bias_clamp(depth_bias_clamp),
		rasterization_depth_bias_slope_factor(depth_bias_slope_factor),
		rasterization_line_width(line_width),
		rasterization_conservative_enable(conservative_enable),
		multisample_multisample_enable(multisample_enable),
		multisample_sample_count(sample_count),
		multisample_min_sample_shading(min_sample_shading),
		multisample_alpha_to_coverage(alpha_to_coverage),
		multisample_alpha_to_one(alpha_to_one),
		input_assembly_topology(topology),
		input_assembly_primitive_restart(primitive_restart),
		depth_stencil_depth_test(depth_test),
		depth_stencil_depth_write(depth_write),
		depth_stencil_depth_compare_op(depth_compare_op),
		depth_stencil_depth_clip_enable(depth_clip_enable),
		depth_stencil_stencil_test(stencil_test),
		depth_stencil_front_stencil_fail_op(front_stencil_fail_op),
		depth_stencil_front_stencil_depth_fail_op(front_stencil_depth_fail_op),
		depth_stencil_front_stencil_pass_op(front_stencil_pass_op),
		depth_stencil_front_stencil_compare_op(front_stencil_compare_op),
		depth_stencil_back_stencil_fail_op(back_stencil_fail_op),
		depth_stencil_back_stencil_depth_fail_op(back_stencil_depth_fail_op),
		depth_stencil_back_stencil_pass_op(back_stencil_pass_op),
		depth_stencil_back_stencil_compare_op(back_stencil_compare_op),
		depth_stencil_min_depth_clip(min_depth_clip),
		depth_stencil_max_depth_clip(max_depth_clip)
	{ }
};

struct framebuffer_t {
	virtual ~framebuffer_t() {}
};

struct pipeline_state_t {
	virtual ~pipeline_state_t() {}
};

struct compute_pipeline_state_t {
	virtual ~compute_pipeline_state_t() {}
};

struct pipeline_layout_t {
	virtual ~pipeline_layout_t() {}
};

struct render_pass_t {
	virtual ~render_pass_t() {}
};

using clear_value_t = std::variant<std::array<float, 4>, std::tuple<float, uint8_t> >;

struct image_view_t {
	virtual ~image_view_t() {}
};

struct sampler_t {
	virtual ~sampler_t() {}
};

struct buffer_view_t {
	virtual ~buffer_view_t() {}
};

struct allocated_descriptor_set {
};

struct descriptor_set_layout {
	virtual ~descriptor_set_layout() {}
};

struct buffer_t {
	virtual void* map_buffer() = 0;
	virtual void unmap_buffer() = 0;

	virtual ~buffer_t() {}
};

struct image_t {
	virtual ~image_t() {}
};

struct descriptor_storage_t {
	virtual std::unique_ptr<allocated_descriptor_set> allocate_descriptor_set_from_cbv_srv_uav_heap(uint32_t starting_index, const std::vector<descriptor_set_layout*> layouts, uint32_t descriptors_count) = 0;
	virtual std::unique_ptr<allocated_descriptor_set> allocate_descriptor_set_from_sampler_heap(uint32_t starting_index, const std::vector<descriptor_set_layout*> layouts, uint32_t descriptors_count) = 0;
	virtual ~descriptor_storage_t() {};
};

struct command_list_t {
	virtual void bind_graphic_descriptor(uint32_t bindpoint, const allocated_descriptor_set& descriptor_set, pipeline_layout_t& sig) = 0;
	virtual void bind_compute_descriptor(uint32_t bindpoint, const allocated_descriptor_set& descriptor_set, pipeline_layout_t& sig) = 0;
	virtual void copy_buffer_to_image_subresource(image_t& destination_image, uint32_t destination_subresource, buffer_t& source, uint64_t offset_in_buffer,
		uint32_t width, uint32_t height, uint32_t row_pitch, irr::video::ECOLOR_FORMAT format) = 0;
	virtual void set_pipeline_barrier(image_t& resource, RESOURCE_USAGE before, RESOURCE_USAGE after, uint32_t subresource, irr::video::E_ASPECT) = 0;
	virtual void set_uav_flush(image_t& resource) = 0;

	virtual void set_viewport(float x, float width, float y, float height, float min_depth, float max_depth) = 0;
	virtual void set_scissor(uint32_t left, uint32_t right, uint32_t top, uint32_t bottom) = 0;
	virtual void set_graphic_pipeline(pipeline_state_t& pipeline) = 0;
	virtual void set_graphic_pipeline_layout(pipeline_layout_t& sig) = 0;
	virtual void set_compute_pipeline(compute_pipeline_state_t& pipeline) = 0;
	virtual void set_compute_pipeline_layout(pipeline_layout_t& sig) = 0;
	virtual void set_descriptor_storage_referenced(descriptor_storage_t& main_heap, descriptor_storage_t* sampler_heap = nullptr) = 0;
	virtual void bind_index_buffer(buffer_t& buffer, uint64_t offset, uint32_t size, irr::video::E_INDEX_TYPE type) = 0;
	virtual void bind_vertex_buffers(uint32_t first_bind, const std::vector<std::tuple<buffer_t&, uint64_t, uint32_t, uint32_t> > &buffer_offset_stride_size) = 0;
	virtual void draw_indexed(uint32_t index_count, uint32_t instance_count, uint32_t base_index, int32_t base_vertex, uint32_t base_instance) = 0;
	virtual void draw_non_indexed(uint32_t vertex_count, uint32_t instance_count, int32_t base_vertex, uint32_t base_instance) = 0;
	virtual void dispatch(uint32_t x, uint32_t y, uint32_t z) = 0;
	virtual void copy_buffer(buffer_t& src, uint64_t src_offset, buffer_t& dst, uint64_t dst_offset, uint64_t size) = 0;

	virtual void clear_depth_stencil(image_t &img, float depth) = 0;
	virtual void clear_depth_stencil(image_t &img, uint8_t stencil) = 0;
	virtual void clear_depth_stencil(image_t &img, float depth, uint8_t stencil) = 0;
	virtual void clear_color(image_t &img, const std::array<float, 4> &clear_colors) = 0;

	virtual void begin_renderpass(render_pass_t& rp, framebuffer_t& fbo,
		gsl::span<clear_value_t> clear_values,
		uint32_t width, uint32_t height) = 0;
	virtual void next_subpass() = 0;
	virtual void end_renderpass() = 0;

	virtual void make_command_list_executable() = 0;
	virtual void start_command_list_recording(struct command_list_storage_t& storage) = 0;
};

struct semaphore_t {
	virtual ~semaphore_t() {};
};

struct fence_t {
	virtual ~fence_t() {};
};

struct command_list_storage_t {
	virtual std::unique_ptr<command_list_t> create_command_list() = 0;
	virtual void reset_command_list_storage() = 0;
	virtual ~command_list_storage_t() {}
};

struct command_queue_t {
	virtual void submit_executable_command_list(command_list_t& command_list, semaphore_t* wait_sem) = 0;
	virtual void wait_for_command_queue_idle() = 0;
};

struct swap_chain_t {
	virtual ~swap_chain_t() {}
	virtual uint32_t get_next_backbuffer_id(semaphore_t& semaphore) = 0;
	virtual std::vector<std::unique_ptr<image_t>> get_image_view_from_swap_chain() = 0;
	virtual void present(command_queue_t& cmdqueue, uint32_t backbuffer_index) = 0;
};

struct device_t {
	virtual std::unique_ptr<command_list_storage_t> create_command_storage() = 0;
	virtual std::unique_ptr<buffer_t> create_buffer(size_t size, irr::video::E_MEMORY_POOL memory_pool, uint32_t flags) = 0;
	virtual std::unique_ptr<buffer_view_t> create_buffer_view(buffer_t&, irr::video::ECOLOR_FORMAT, uint64_t offset, uint32_t size) = 0;
	virtual void set_constant_buffer_view(const allocated_descriptor_set& descriptor_set, uint32_t offset_in_set, uint32_t binding_location, buffer_t& buffer, uint32_t buffer_size, uint64_t offset_in_buffer = 0) = 0;
	virtual void set_uniform_texel_buffer_view(const allocated_descriptor_set& descriptor_set, uint32_t offset_in_set, uint32_t binding_location, buffer_view_t& buffer_view) = 0;
	virtual void set_uav_buffer_view(const allocated_descriptor_set& descriptor_set, uint32_t offset_in_set, uint32_t binding_location, buffer_t& buffer, uint64_t offset, uint32_t size) = 0;
	virtual std::unique_ptr<image_t> create_image(irr::video::ECOLOR_FORMAT format, uint32_t width, uint32_t height, uint16_t mipmap, uint32_t layers, uint32_t flags, clear_value_t *clear_value) = 0;
	virtual std::unique_ptr<image_view_t> create_image_view(image_t& img, irr::video::ECOLOR_FORMAT fmt, uint16_t base_mipmap, uint16_t mipmap_count, uint16_t base_layer, uint16_t layer_count, irr::video::E_TEXTURE_TYPE texture_type, irr::video::E_ASPECT aspect = irr::video::E_ASPECT::EA_COLOR) = 0;
	virtual void set_image_view(const allocated_descriptor_set& descriptor_set, uint32_t offset, uint32_t binding_location, image_view_t& img_view) = 0;
	virtual void set_input_attachment(const allocated_descriptor_set& descriptor_set, uint32_t offset, uint32_t binding_location, image_view_t& img_view) = 0;
	virtual void set_uav_image_view(const allocated_descriptor_set& descriptor_set, uint32_t offset, uint32_t binding_location, image_view_t& img_view) = 0;
	virtual void set_sampler(const allocated_descriptor_set& descriptor_set, uint32_t offset, uint32_t binding_location, sampler_t& sampler) = 0;
	virtual std::unique_ptr<sampler_t> create_sampler(SAMPLER_TYPE sampler_type) = 0;
	virtual std::unique_ptr<descriptor_storage_t> create_descriptor_storage(uint32_t num_sets, const std::vector<std::tuple<RESOURCE_VIEW, uint32_t> > &num_descriptors) = 0;
	virtual std::unique_ptr<framebuffer_t> create_frame_buffer(gsl::span<const image_view_t*> render_targets, uint32_t width, uint32_t height, render_pass_t* render_pass) = 0;
	virtual std::unique_ptr<framebuffer_t> create_frame_buffer(gsl::span<const image_view_t*> render_targets, const image_view_t& depth_stencil_texture, uint32_t width, uint32_t height, render_pass_t* render_pass) = 0;
	virtual std::unique_ptr<descriptor_set_layout> get_object_descriptor_set(const descriptor_set &ds) = 0;
	virtual std::unique_ptr<pipeline_state_t> create_graphic_pso(const graphic_pipeline_state_description&, const render_pass_t&, const pipeline_layout_t&, const uint32_t& subpass) = 0;
	virtual std::unique_ptr<compute_pipeline_state_t> create_compute_pso(const compute_pipeline_state_description&, const pipeline_layout_t&) = 0;
	virtual std::unique_ptr<pipeline_layout_t> create_pipeline_layout(gsl::span<const descriptor_set_layout *>) = 0;
	virtual std::unique_ptr<fence_t> create_fence() = 0;
	virtual std::unique_ptr<semaphore_t> create_semaphore() = 0;

	virtual std::unique_ptr<render_pass_t> create_ibl_sky_pass(const irr::video::ECOLOR_FORMAT&) = 0;
	virtual std::unique_ptr<render_pass_t> create_object_sunlight_pass(const irr::video::ECOLOR_FORMAT&) = 0;
	virtual std::unique_ptr<render_pass_t> create_ssao_pass() = 0;
	virtual std::unique_ptr<render_pass_t> create_blit_pass(const irr::video::ECOLOR_FORMAT& color_format) = 0;

	virtual ~device_t() {};
};

clear_value_t get_clear_value(irr::video::ECOLOR_FORMAT format, float depth, uint8_t stencil);
clear_value_t get_clear_value(irr::video::ECOLOR_FORMAT format, const std::array<float,4> &color);


#ifndef QDM_PARAMETERS_H
#define QDM_PARAMETERS_H 1

#include <gsl/gsl_vector.h>

typedef struct {
  unsigned long int rng_seed;

  int acc_check;
  int burn;
  int iter;
  int knot_try;
  int spline_df;
  int thin;

  double month;
  double knot;

  double tau_high;
  double tau_low;

  double theta_min;
  double theta_tune_sd;

  double xi_high;
  double xi_low;
  double xi_prior_mean;
  double xi_prior_var;
  double xi_tune_sd;

  double bound;

  int debug;
  int tau_table;

  bool truncate;
} qdm_parameters;

void
qdm_parameters_fprint(
    FILE *f,
    const qdm_parameters *p
);

int
qdm_parameters_write(
    hid_t id,
    const qdm_parameters *p
);

int
qdm_parameters_read(
    hid_t id,
    qdm_parameters *p
);

#endif /* QDM_PARAMETERS_H */


// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

#pragma once

#include <type_traits>
#include <algorithm>
#include <limits>
#include <iostream>
#include <cstring>
#include "seal/memorymanager.h"
#include "seal/serialization.h"
#include "seal/util/pointer.h"
#include "seal/util/defines.h"
#include "seal/util/common.h"
#ifdef SEAL_USE_MSGSL_SPAN
#include <gsl/span>
#endif

namespace seal
{
    class Ciphertext;

    // Forward-declaring the deflate_size_bound function
    namespace util::ztools
    {
        SEAL_NODISCARD std::size_t deflate_size_bound(std::size_t in_size) noexcept;
    }

    /**
    A resizable container for storing an array of arithmetic data types or
    SEAL_BYTE types (e.g., std::byte). The allocations are done from a memory
    pool. The IntArray class is mainly intended for internal use and provides
    the underlying data structure for Plaintext and Ciphertext classes.

    @par Size and Capacity
    IntArray allows the user to pre-allocate memory (capacity) for the array
    in cases where the array is known to be resized in the future and memory
    moves are to be avoided at the time of resizing. The size of the IntArray
    can never exceed its capacity. The capacity and size can be changed using
    the reserve and resize functions, respectively.

    @par Thread Safety
    In general, reading from IntArray is thread-safe as long as no other thread
    is concurrently mutating it.
    */
    template<typename T_,
        typename = std::enable_if_t<std::is_arithmetic<T_>::value ||
            std::is_same<typename std::decay<T_>::type, SEAL_BYTE>::value>>
    class IntArray
    {
        friend class Ciphertext;

    public:
        using T = typename std::decay<T_>::type;

        /**
        Creates a new IntArray. No memory is allocated by this constructor.

        @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
        @throws std::invalid_argument if pool is uninitialized
        */
        IntArray(MemoryPoolHandle pool = MemoryManager::GetPool()) :
            pool_(std::move(pool))
        {
            if (!pool_)
            {
                throw std::invalid_argument("pool is uninitialized");
            }
        }

        /**
        Creates a new IntArray with given size.

        @param[in] size The size of the array
        @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
        @throws std::invalid_argument if pool is uninitialized
        */
        explicit IntArray(std::size_t size,
            MemoryPoolHandle pool = MemoryManager::GetPool()) :
            pool_(std::move(pool))
        {
            if (!pool_)
            {
                throw std::invalid_argument("pool is uninitialized");
            }

            // Reserve memory, resize, and set to zero
            resize(size);
        }

        /**
        Creates a new IntArray with given capacity and size.

        @param[in] capacity The capacity of the array
        @param[in] size The size of the array
        @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
        @throws std::invalid_argument if capacity is less than size
        @throws std::invalid_argument if pool is uninitialized
        */
        explicit IntArray(std::size_t capacity, std::size_t size,
            MemoryPoolHandle pool = MemoryManager::GetPool()) :
            pool_(std::move(pool))
        {
            if (!pool_)
            {
                throw std::invalid_argument("pool is uninitialized");
            }
            if (capacity < size)
            {
                throw std::invalid_argument("capacity cannot be smaller than size");
            }

            // Reserve memory, resize, and set to zero
            reserve(capacity);
            resize(size);
        }

        /**
        Creates a new IntArray with given size wrapping a given pointer. This
        constructor allocates no memory. If the IntArray goes out of scope, the
        Pointer object given here is destroyed. On resizing the IntArray to larger
        size, the data will be copied over to a new allocation from the memory pool
        pointer to by the given MemoryPoolHandle and the Pointer object given here
        will subsequently be destroyed. Unlike the other constructors, this one
        exposes the option of not automatically zero-filling the allocated memory.

        @param[in] ptr An initial Pointer object to wrap
        @param[in] capacity The capacity of the array
        @param[in] size The size of the array
        @param[in] fill_zero If true, fills ptr with zeros
        @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
        @throws std::invalid_argument if ptr is null and capacity is positive
        @throws std::invalid_argument if capacity is less than size
        @throws std::invalid_argument if pool is uninitialized
        */
        explicit IntArray(util::Pointer<T> &&ptr,
            std::size_t capacity, std::size_t size, bool fill_zero,
            MemoryPoolHandle pool = MemoryManager::GetPool()) :
            pool_(std::move(pool)),
            capacity_(capacity)
        {
            if (!ptr && capacity)
            {
                throw std::invalid_argument("ptr cannot be null");
            }
            if (!pool_)
            {
                throw std::invalid_argument("pool is uninitialized");
            }
            if (capacity < size)
            {
                throw std::invalid_argument("capacity cannot be smaller than size");
            }

            // Grab the given Pointer
            data_ = std::move(ptr);

            // Resize, and optionally set to zero
            resize(size, fill_zero);
        }

        /**
        Creates a new IntArray with given size wrapping a given pointer. This
        constructor allocates no memory. If the IntArray goes out of scope, the
        Pointer object given here is destroyed. On resizing the IntArray to larger
        size, the data will be copied over to a new allocation from the memory pool
        pointer to by the given MemoryPoolHandle and the Pointer object given here
        will subsequently be destroyed. Unlike the other constructors, this one
        exposes the option of not automatically zero-filling the allocated memory.

        @param[in] ptr An initial Pointer object to wrap
        @param[in] size The size of the array
        @param[in] fill_zero If true, fills ptr with zeros
        @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
        @throws std::invalid_argument if ptr is null and size is positive
        @throws std::invalid_argument if pool is uninitialized
        */
        explicit IntArray(util::Pointer<T> &&ptr, std::size_t size, bool fill_zero,
            MemoryPoolHandle pool = MemoryManager::GetPool()) :
            IntArray(std::move(ptr), size, size, fill_zero, std::move(pool))
        {
        }

        /**
        Constructs a new IntArray by copying a given one.

        @param[in] copy The IntArray to copy from
        */
        IntArray(const IntArray<T> &copy) :
            pool_(MemoryManager::GetPool()),
            capacity_(copy.size_),
            size_(copy.size_),
            data_(util::allocate<T>(copy.size_, pool_))
        {
            // Copy over value
            std::copy_n(copy.cbegin(), copy.size_, begin());
        }

        /**
        Constructs a new IntArray by moving a given one.

        @param[in] source The IntArray to move from
        */
        IntArray(IntArray<T> &&source) noexcept :
            pool_(std::move(source.pool_)),
            capacity_(source.capacity_),
            size_(source.size_),
            data_(std::move(source.data_))
        {
        }

        /**
        Destroys the IntArray.
        */
        ~IntArray()
        {
            release();
        }

        /**
        Returns a pointer to the beginning of the array data.
        */
        SEAL_NODISCARD inline T *begin() noexcept
        {
            return data_.get();
        }

        /**
        Returns a constant pointer to the beginning of the array data.
        */
        SEAL_NODISCARD inline const T *cbegin() const noexcept
        {
            return data_.get();
        }

        /**
        Returns a pointer to the end of the array data.
        */
        SEAL_NODISCARD inline T *end() noexcept
        {
            return size_ ? begin() + size_ : begin();
        }

        /**
        Returns a constant pointer to the end of the array data.
        */
        SEAL_NODISCARD inline const T *cend() const noexcept
        {
            return size_ ? cbegin() + size_ : cbegin();
        }
#ifdef SEAL_USE_MSGSL_SPAN
        /**
        Returns a span pointing to the beginning of the IntArray.
        */
        SEAL_NODISCARD inline gsl::span<T> span()
        {
            return gsl::span<T>(
                begin(), static_cast<std::ptrdiff_t>(size_));
        }

        /**
        Returns a span pointing to the beginning of the IntArray.
        */
        SEAL_NODISCARD inline gsl::span<const T> span() const
        {
            return gsl::span<const T>(
                cbegin(), static_cast<std::ptrdiff_t>(size_));
        }
#endif
        /**
        Returns a constant reference to the array element at a given index.
        This function performs bounds checking and will throw an error if
        the index is out of range.

        @param[in] index The index of the array element
        @throws std::out_of_range if index is out of range
        */
        SEAL_NODISCARD inline const T &at(std::size_t index) const
        {
            if (index >= size_)
            {
                throw std::out_of_range("index must be within [0, size)");
            }
            return data_[index];
        }

        /**
        Returns a reference to the array element at a given index. This
        function performs bounds checking and will throw an error if the
        index is out of range.

        @param[in] index The index of the array element
        @throws std::out_of_range if index is out of range
        */
        SEAL_NODISCARD inline T &at(std::size_t index)
        {
            if (index >= size_)
            {
                throw std::out_of_range("index must be within [0, size)");
            }
            return data_[index];
        }

        /**
        Returns a constant reference to the array element at a given index.
        This function does not perform bounds checking.

        @param[in] index The index of the array element
        */
        SEAL_NODISCARD inline const T &operator [](std::size_t index) const
        {
            return data_[index];
        }

        /**
        Returns a reference to the array element at a given index. This
        function does not perform bounds checking.

        @param[in] index The index of the array element
        */
        SEAL_NODISCARD inline T &operator [](std::size_t index)
        {
            return data_[index];
        }

        /**
        Returns whether the array has size zero.
        */
        SEAL_NODISCARD inline bool empty() const noexcept
        {
            return (size_ == 0);
        }

        /**
        Returns the largest possible array size.
        */
        SEAL_NODISCARD inline std::size_t max_size() const noexcept
        {
            return std::numeric_limits<std::size_t>::max();
        }

        /**
        Returns the size of the array.
        */
        SEAL_NODISCARD inline std::size_t size() const noexcept
        {
            return size_;
        }

        /**
        Returns the capacity of the array.
        */
        SEAL_NODISCARD inline std::size_t capacity() const noexcept
        {
            return capacity_;
        }

        /**
        Returns the currently used MemoryPoolHandle.
        */
        SEAL_NODISCARD inline MemoryPoolHandle pool() const noexcept
        {
            return pool_;
        }

        /**
        Releases any allocated memory to the memory pool and sets the size
        and capacity of the array to zero.
        */
        inline void release() noexcept
        {
            capacity_ = 0;
            size_ = 0;
            data_.release();
        }

        /**
        Sets the size of the array to zero. The capacity is not changed.
        */
        inline void clear() noexcept
        {
            size_ = 0;
        }

        /**
        Allocates enough memory for storing a given number of elements without
        changing the size of the array. If the given capacity is smaller than
        the current size, the size is automatically set to equal the new capacity.

        @param[in] capacity The capacity of the array
        */
        inline void reserve(std::size_t capacity)
        {
            std::size_t copy_size = std::min(capacity, size_);

            // Create new allocation and copy over value
            auto new_data(util::allocate<T>(capacity, pool_));
            std::copy_n(cbegin(), copy_size, new_data.get());
            std::swap(data_, new_data);

            // Set the coeff_count and capacity
            capacity_ = capacity;
            size_ = copy_size;
        }

        /**
        Reallocates the array so that its capacity exactly matches its size.
        */
        inline void shrink_to_fit()
        {
            reserve(size_);
        }

        /**
        Resizes the array to given size. When resizing to larger size the data
        in the array remains unchanged and any new space is initialized to zero
        if fill_zero is set to true; when resizing to smaller size the last
        elements of the array are dropped. If the capacity is not already large
        enough to hold the new size, the array is also reallocated.

        @param[in] size The size of the array
        @param[in] fill_zero If true, fills expanded space with zeros
        */
        inline void resize(std::size_t size, bool fill_zero = true)
        {
            if (size <= capacity_)
            {
                // Are we changing size to bigger within current capacity?
                // If so, need to set top terms to zero
                if (size > size_ && fill_zero)
                {
                    std::fill(end(), begin() + size, T(0));
                }

                // Set the size
                size_ = size;

                return;
            }

            // At this point we know for sure that size_ <= capacity_ < size so need
            // to reallocate to bigger
            auto new_data(util::allocate<T>(size, pool_));
            std::copy_n(cbegin(), size_, new_data.get());
            if (fill_zero)
            {
                std::fill(new_data.get() + size_, new_data.get() + size, T(0));
            }
            std::swap(data_, new_data);

            // Set the coeff_count and capacity
            capacity_ = size;
            size_ = size;
        }

        /**
        Copies a given IntArray to the current one.

        @param[in] assign The IntArray to copy from
        */
        inline IntArray<T> &operator =(const IntArray<T> &assign)
        {
            // Check for self-assignment
            if (this == &assign)
            {
                return *this;
            }

            // First resize to correct size
            resize(assign.size_);

            // Size is guaranteed to be OK now so copy over
            std::copy_n(assign.cbegin(), assign.size_, begin());

            return *this;
        }

        /**
        Moves a given IntArray to the current one.

        @param[in] assign The IntArray to move from
        */
        IntArray<T> &operator =(IntArray<T> &&assign) noexcept
        {
            capacity_ = assign.capacity_;
            size_ = assign.size_;
            data_ = std::move(assign.data_);
            pool_ = std::move(assign.pool_);

            return *this;
        }

        /**
        Returns an upper bound on the size of the IntArray, as if it was written
        to an output stream.

        @param[in] compr_mode The compression mode
        @throws std::invalid_argument if the compression mode is not supported
        @throws std::logic_error if the size does not fit in the return type
        */
        SEAL_NODISCARD inline std::streamoff save_size(
            compr_mode_type compr_mode) const
        {
            std::size_t members_size = Serialization::ComprSizeEstimate(
                util::add_safe(
                    sizeof(std::uint64_t), // size_
                    util::mul_safe(size_, sizeof(T))), // data_
                compr_mode);

            return util::safe_cast<std::streamoff>(util::add_safe(
                sizeof(Serialization::SEALHeader),
                members_size
            ));
        }

        /**
        Saves the IntArray to an output stream. The output is in binary format
        and not human-readable. The output stream must have the "binary" flag set.

        @param[out] stream The stream to save the IntArray to
        @param[in] compr_mode The desired compression mode
        @throws std::logic_error if the data to be saved is invalid, if compression
        mode is not supported, or if compression failed
        @throws std::runtime_error if I/O operations failed
        */
        inline std::streamoff save(
            std::ostream &stream,
            compr_mode_type compr_mode = Serialization::compr_mode_default) const
        {
            using namespace std::placeholders;
            return Serialization::Save(
                std::bind(&IntArray<T_>::save_members, this, _1),
                save_size(compr_mode_type::none),
                stream, compr_mode);
        }

        /**
        Loads a IntArray from an input stream overwriting the current IntArray.
        This function takes optionally a bound on the size for the loaded IntArray
        and throws an exception if the size indicated by the loaded metadata exceeds
        the provided value. The check is omitted if in_size_bound is zero.

        @param[in] stream The stream to load the IntArray from
        @param[in] in_size_bound A bound on the size of the loaded IntArray
        @throws std::logic_error if the loaded data is invalid, if the loaded size
        exceeds in_size_bound, or if decompression failed
        @throws std::runtime_error if I/O operations failed
        */
        inline std::streamoff load(
            std::istream &stream, std::size_t in_size_bound = 0)
        {
            using namespace std::placeholders;
            return Serialization::Load(
                std::bind(&IntArray<T_>::load_members, this, _1, in_size_bound),
                stream);
        }

        /**
        Saves the IntArray to a given memory location. The output is in binary
        format and not human-readable.

        @param[out] out The memory location to write the SmallModulus to
        @param[in] size The number of bytes available in the given memory location
        @param[in] compr_mode The desired compression mode
        @throws std::invalid_argument if out is null or if size is too small to
        contain a SEALHeader
        @throws std::logic_error if the data to be saved is invalid, if compression
        mode is not supported, or if compression failed
        @throws std::runtime_error if I/O operations failed
        */
        inline std::streamoff save(
            SEAL_BYTE *out,
            std::size_t size,
            compr_mode_type compr_mode = Serialization::compr_mode_default) const
        {
            using namespace std::placeholders;
            return Serialization::Save(
                std::bind(&IntArray<T_>::save_members, this, _1),
                save_size(compr_mode_type::none),
                out, size, compr_mode);
        }

        /**
        Loads a IntArray from a given memory location overwriting the current
        IntArray. This function takes optionally a bound on the size for the loaded
        IntArray and throws an exception if the size indicated by the loaded
        metadata exceeds the provided value. The check is omitted if in_size_bound
        is zero.

        @param[in] in The memory location to load the SmallModulus from
        @param[in] size The number of bytes available in the given memory location
        @param[in] in_size_bound A bound on the size of the loaded IntArray
        @throws std::invalid_argument if in is null or if size is too small to
        contain a SEALHeader
        @throws std::logic_error if the loaded data is invalid, if the loaded size
        exceeds in_size_bound, or if decompression failed
        @throws std::runtime_error if I/O operations failed
        */
        inline std::streamoff load(
            const SEAL_BYTE *in, std::size_t size, std::size_t in_size_bound = 0)
        {
            using namespace std::placeholders;
            return Serialization::Load(
                std::bind(&IntArray<T_>::load_members, this, _1, in_size_bound),
                in, size);
        }

    private:
        void save_members(std::ostream &stream) const
        {
            auto old_except_mask = stream.exceptions();
            try
            {
                // Throw exceptions on std::ios_base::badbit and std::ios_base::failbit
                stream.exceptions(std::ios_base::badbit | std::ios_base::failbit);

                std::uint64_t size64 = size_;
                stream.write(reinterpret_cast<const char*>(&size64), sizeof(std::uint64_t));
                if (size_)
                {
                    stream.write(reinterpret_cast<const char*>(cbegin()),
                        util::safe_cast<std::streamsize>(util::mul_safe(size_, sizeof(T))));
                }
            }
            catch (const std::ios_base::failure &)
            {
                stream.exceptions(old_except_mask);
                throw std::runtime_error("I/O error");
            }
            catch (...)
            {
                stream.exceptions(old_except_mask);
                throw;
            }
            stream.exceptions(old_except_mask);
        }

        void load_members(std::istream &stream, std::size_t in_size_bound)
        {
            auto old_except_mask = stream.exceptions();
            try
            {
                // Throw exceptions on std::ios_base::badbit and std::ios_base::failbit
                stream.exceptions(std::ios_base::badbit | std::ios_base::failbit);

                std::uint64_t size64 = 0;
                stream.read(reinterpret_cast<char*>(&size64), sizeof(std::uint64_t));

                // Check (optionally) that the size in the metadata does not exceed
                // in_size_bound
                if (in_size_bound && util::unsigned_gt(size64, in_size_bound))
                {
                    throw std::logic_error("unexpected size");
                }

                // Set new size; this is potentially unsafe if size64 was not checked
                // against expected_size
                resize(util::safe_cast<std::size_t>(size64));

                // Read data
                if (size_)
                {
                    stream.read(reinterpret_cast<char*>(begin()),
                        util::safe_cast<std::streamsize>(util::mul_safe(size_, sizeof(T))));
                }
            }
            catch (const std::ios_base::failure &)
            {
                stream.exceptions(old_except_mask);
                throw std::runtime_error("I/O error");
            }
            catch (...)
            {
                stream.exceptions(old_except_mask);
                throw;
            }
            stream.exceptions(old_except_mask);
        }

        MemoryPoolHandle pool_;

        std::size_t capacity_ = 0;

        std::size_t size_ = 0;

        util::Pointer<T> data_;
    };
}