#include <psemek/wgpu/command_encoder.hpp>
#include <psemek/wgpu/detail/string_view.hpp>
#include <psemek/wgpu/external/webgpu.h>

namespace psemek::wgpu
{

	compute_pass_encoder command_encoder::begin_compute_pass(compute_pass_encoder::descriptor const & desc)
	{
		WGPUComputePassTimestampWrites timestamp_writes = {};
		if (desc.timestamp_writes)
		{
			timestamp_writes.querySet = (WGPUQuerySet)desc.timestamp_writes->query_set.get();
			timestamp_writes.beginningOfPassWriteIndex = desc.timestamp_writes->begin_index;
			timestamp_writes.endOfPassWriteIndex = desc.timestamp_writes->end_index;
		}

		WGPUComputePassDescriptor descriptor = {};
		descriptor.nextInChain = (WGPUChainedStruct const *)detail::fill_chain(desc.chain);
		descriptor.label = detail::to_string_view(desc.label);
		descriptor.timestampWrites = desc.timestamp_writes ? &timestamp_writes : nullptr;

		return compute_pass_encoder(wgpuCommandEncoderBeginComputePass((WGPUCommandEncoder)get(), &descriptor));
	}

	render_pass_encoder command_encoder::begin_render_pass(render_pass_encoder::descriptor const & desc)
	{
		std::vector<WGPURenderPassColorAttachment> color_attachments;
		for (auto const & src : desc.color_attachments)
		{
			auto & dst = color_attachments.emplace_back();
			dst.nextInChain = (WGPUChainedStruct const *)detail::fill_chain(src.chain);
			dst.view = (WGPUTextureView)src.view.get();
			dst.depthSlice = src.depth_slice.value_or(WGPU_DEPTH_SLICE_UNDEFINED);
			dst.resolveTarget = (WGPUTextureView)src.resolve_target.get();
			dst.loadOp = (WGPULoadOp)src.load_op;
			dst.storeOp = (WGPUStoreOp)src.store_op;
			dst.clearValue = {src.clear_value[0], src.clear_value[1], src.clear_value[2], src.clear_value[3]};
		}

		WGPURenderPassDepthStencilAttachment depth_stencil_attachment = {};
		if (desc.depth_stencil_attachment)
		{
			depth_stencil_attachment.view = (WGPUTextureView)desc.depth_stencil_attachment->view.get();
			depth_stencil_attachment.depthLoadOp = (WGPULoadOp)desc.depth_stencil_attachment->depth_load_op;
			depth_stencil_attachment.depthStoreOp = (WGPUStoreOp)desc.depth_stencil_attachment->depth_store_op;
			depth_stencil_attachment.depthClearValue = desc.depth_stencil_attachment->depth_clear_value;
			depth_stencil_attachment.depthReadOnly = desc.depth_stencil_attachment->depth_read_only ? 1 : 0;
			depth_stencil_attachment.stencilLoadOp = (WGPULoadOp)desc.depth_stencil_attachment->stencil_load_op;
			depth_stencil_attachment.stencilStoreOp = (WGPUStoreOp)desc.depth_stencil_attachment->stencil_store_op;
			depth_stencil_attachment.stencilClearValue = desc.depth_stencil_attachment->stencil_clear_value;
			depth_stencil_attachment.stencilReadOnly = desc.depth_stencil_attachment->stencil_read_only ? 1 : 0;
		}

		WGPURenderPassTimestampWrites timestamp_writes = {};
		if (desc.timestamp_writes)
		{
			timestamp_writes.querySet = (WGPUQuerySet)desc.timestamp_writes->query_set.get();
			timestamp_writes.beginningOfPassWriteIndex = desc.timestamp_writes->begin_index;
			timestamp_writes.endOfPassWriteIndex = desc.timestamp_writes->end_index;
		}

		WGPURenderPassDescriptor descriptor = {};
		descriptor.nextInChain = (WGPUChainedStruct const *)detail::fill_chain(desc.chain);
		descriptor.label = detail::to_string_view(desc.label);
		descriptor.colorAttachmentCount = color_attachments.size();
		descriptor.colorAttachments = color_attachments.data();
		descriptor.depthStencilAttachment = desc.depth_stencil_attachment ? &depth_stencil_attachment : nullptr;
		descriptor.occlusionQuerySet = (WGPUQuerySet)desc.occlusion_query_set.get();
		descriptor.timestampWrites = desc.timestamp_writes ? &timestamp_writes : nullptr;

		return render_pass_encoder(wgpuCommandEncoderBeginRenderPass((WGPUCommandEncoder)get(), &descriptor));
	}

	void command_encoder::clear_buffer(buffer const & buffer, std::uint64_t offset, std::uint64_t size)
	{
		wgpuCommandEncoderClearBuffer((WGPUCommandEncoder)get(), (WGPUBuffer)buffer.get(), offset, size);
	}

	void command_encoder::copy_buffer_to_buffer(buffer const & source, std::uint64_t source_offset, buffer const & destination, std::uint64_t destination_offset, std::uint64_t size)
	{
		wgpuCommandEncoderCopyBufferToBuffer((WGPUCommandEncoder)get(), (WGPUBuffer)source.get(), source_offset, (WGPUBuffer)destination.get(), destination_offset, size);
	}

	void command_encoder::copy_buffer_to_texture(texel_copy_buffer_info const & source, texel_copy_texture_info const & destination, math::vector<std::uint32_t, 3> const & extent)
	{
		WGPUTexelCopyBufferInfo texel_copy_buffer_info_src = {};
		texel_copy_buffer_info_src.layout.offset = source.layout.offset;
		texel_copy_buffer_info_src.layout.bytesPerRow = source.layout.bytes_per_row;
		texel_copy_buffer_info_src.layout.rowsPerImage = source.layout.rows_per_image;
		texel_copy_buffer_info_src.buffer = (WGPUBuffer)source.buffer.get();

		WGPUTexelCopyTextureInfo texel_copy_texture_info_src_dst = {};
		texel_copy_texture_info_src_dst.texture = (WGPUTexture)destination.texture.get();
		texel_copy_texture_info_src_dst.mipLevel = destination.mip_level;
		texel_copy_texture_info_src_dst.origin = {destination.origin[0], destination.origin[1], destination.origin[2]};
		texel_copy_texture_info_src_dst.aspect = (WGPUTextureAspect)destination.aspect;

		wgpuCommandEncoderCopyBufferToTexture((WGPUCommandEncoder)get(), &texel_copy_buffer_info_src, &texel_copy_texture_info_src_dst, (WGPUExtent3D const *)(&extent));
	}

	void command_encoder::copy_texture_to_buffer(texel_copy_texture_info const & source, texel_copy_buffer_info const & destination, math::vector<std::uint32_t, 3> const & extent)
	{
		WGPUTexelCopyTextureInfo texel_copy_texture_info_src = {};
		texel_copy_texture_info_src.texture = (WGPUTexture)source.texture.get();
		texel_copy_texture_info_src.mipLevel = source.mip_level;
		texel_copy_texture_info_src.origin = {source.origin[0], source.origin[1], source.origin[2]};
		texel_copy_texture_info_src.aspect = (WGPUTextureAspect)source.aspect;

		WGPUTexelCopyBufferInfo texel_copy_buffer_info_dst = {};
		texel_copy_buffer_info_dst.layout.offset = destination.layout.offset;
		texel_copy_buffer_info_dst.layout.bytesPerRow = destination.layout.bytes_per_row;
		texel_copy_buffer_info_dst.layout.rowsPerImage = destination.layout.rows_per_image;
		texel_copy_buffer_info_dst.buffer = (WGPUBuffer)destination.buffer.get();

		wgpuCommandEncoderCopyTextureToBuffer((WGPUCommandEncoder)get(), &texel_copy_texture_info_src, &texel_copy_buffer_info_dst, (WGPUExtent3D const *)(&extent));
	}

	void command_encoder::copy_texture_to_texture(texel_copy_texture_info const & source, texel_copy_texture_info const & destination, math::vector<std::uint32_t, 3> const & extent)
	{
		WGPUTexelCopyTextureInfo texel_copy_texture_info_src = {};
		texel_copy_texture_info_src.texture = (WGPUTexture)source.texture.get();
		texel_copy_texture_info_src.mipLevel = source.mip_level;
		texel_copy_texture_info_src.origin = {source.origin[0], source.origin[1], source.origin[2]};
		texel_copy_texture_info_src.aspect = (WGPUTextureAspect)source.aspect;

		WGPUTexelCopyTextureInfo texel_copy_texture_info_dst = {};
		texel_copy_texture_info_dst.texture = (WGPUTexture)destination.texture.get();
		texel_copy_texture_info_dst.mipLevel = destination.mip_level;
		texel_copy_texture_info_dst.origin = {destination.origin[0], destination.origin[1], destination.origin[2]};
		texel_copy_texture_info_dst.aspect = (WGPUTextureAspect)destination.aspect;

		wgpuCommandEncoderCopyTextureToTexture((WGPUCommandEncoder)get(), &texel_copy_texture_info_src, &texel_copy_texture_info_dst, (WGPUExtent3D const *)(&extent));
	}

	void command_encoder::insert_debug_marker(std::string const & marker_label)
	{
		wgpuCommandEncoderInsertDebugMarker((WGPUCommandEncoder)get(), detail::to_string_view(marker_label));
	}

	void command_encoder::push_debug_group(std::string const & group_label)
	{
		wgpuCommandEncoderPushDebugGroup((WGPUCommandEncoder)get(), detail::to_string_view(group_label));
	}

	void command_encoder::pop_debug_group()
	{
		wgpuCommandEncoderPopDebugGroup((WGPUCommandEncoder)get());
	}

	void command_encoder::resolve_query_set(query_set const & query_set, std::uint32_t first_query, std::uint32_t query_count, buffer const & destination, std::uint64_t destination_offset)
	{
		wgpuCommandEncoderResolveQuerySet((WGPUCommandEncoder)get(), (WGPUQuerySet)query_set.get(), first_query, query_count, (WGPUBuffer)destination.get(), destination_offset);
	}

	void command_encoder::write_timestamp(query_set const & query_set, std::uint32_t query_index)
	{
		wgpuCommandEncoderWriteTimestamp((WGPUCommandEncoder)get(), (WGPUQuerySet)query_set.get(), query_index);
	}

	command_buffer command_encoder::finish(command_buffer::descriptor const & desc)
	{
		WGPUCommandBufferDescriptor descriptor = {};
		descriptor.nextInChain = (WGPUChainedStruct const *)detail::fill_chain(desc.chain);
		descriptor.label = detail::to_string_view(desc.label);
		return command_buffer(wgpuCommandEncoderFinish((WGPUCommandEncoder)get(), &descriptor));
	}

	void command_encoder::set_label(std::string const & label)
	{
		wgpuCommandEncoderSetLabel((WGPUCommandEncoder)get(), detail::to_string_view(label));
	}

	void command_encoder::reference(void * ptr)
	{
		wgpuCommandEncoderAddRef((WGPUCommandEncoder)ptr);
	}

	void command_encoder::release(void * ptr)
	{
		wgpuCommandEncoderRelease((WGPUCommandEncoder)ptr);
	}

}
