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

#include <cstring>

namespace psemek::wgpu
{

	std::vector<feature> adapter::enumerate_features() const
	{
		WGPUSupportedFeatures supported_features;
		wgpuAdapterGetFeatures((WGPUAdapter)get(), &supported_features);
		return std::vector<feature>((feature const *)supported_features.features, (feature const *)(supported_features.features) + supported_features.featureCount);
	}

	limits adapter::get_limits() const
	{
		WGPULimits limits = {};
		detail::check_status("wgpuAdapterGetLimits", wgpuAdapterGetLimits((WGPUAdapter)get(), &limits));

		// TODO: support out chain
		wgpu::limits result;
		result.max_texture_dimension_1D = limits.maxTextureDimension1D;
		result.max_texture_dimension_2D = limits.maxTextureDimension2D;
		result.max_texture_dimension_3D = limits.maxTextureDimension3D;
		result.max_texture_array_layers = limits.maxTextureArrayLayers;
		result.max_bind_groups = limits.maxBindGroups;
		result.max_bind_groups_plus_vertex_buffers = limits.maxBindGroupsPlusVertexBuffers;
		result.max_bindings_per_bind_group = limits.maxBindingsPerBindGroup;
		result.max_dynamic_uniform_buffers_per_pipeline_layout = limits.maxDynamicUniformBuffersPerPipelineLayout;
		result.max_dynamic_storage_buffers_per_pipeline_layout = limits.maxDynamicStorageBuffersPerPipelineLayout;
		result.max_sampled_textures_per_shader_stage = limits.maxSampledTexturesPerShaderStage;
		result.max_samplers_per_shader_stage = limits.maxSamplersPerShaderStage;
		result.max_storage_buffers_per_shader_stage = limits.maxStorageBuffersPerShaderStage;
		result.max_storage_textures_per_shader_stage = limits.maxStorageTexturesPerShaderStage;
		result.max_uniform_buffers_per_shader_stage = limits.maxUniformBuffersPerShaderStage;
		result.max_uniform_buffer_binding_size = limits.maxUniformBufferBindingSize;
		result.max_storage_buffer_binding_size = limits.maxStorageBufferBindingSize;
		result.min_uniform_buffer_offset_alignment = limits.minUniformBufferOffsetAlignment;
		result.min_storage_buffer_offset_alignment = limits.minStorageBufferOffsetAlignment;
		result.max_vertex_buffers = limits.maxVertexBuffers;
		result.max_buffer_size = limits.maxBufferSize;
		result.max_vertex_attributes = limits.maxVertexAttributes;
		result.max_vertex_buffer_array_stride = limits.maxVertexBufferArrayStride;
		result.max_inter_stage_shader_variables = limits.maxInterStageShaderVariables;
		result.max_color_attachments = limits.maxColorAttachments;
		result.max_color_attachment_bytes_per_sample = limits.maxColorAttachmentBytesPerSample;
		result.max_compute_workgroup_storage_size = limits.maxComputeWorkgroupStorageSize;
		result.max_compute_invocations_per_workgroup = limits.maxComputeInvocationsPerWorkgroup;
		result.max_compute_workgroup_size_x = limits.maxComputeWorkgroupSizeX;
		result.max_compute_workgroup_size_y = limits.maxComputeWorkgroupSizeY;
		result.max_compute_workgroup_size_z = limits.maxComputeWorkgroupSizeZ;
		result.max_compute_workgroups_per_dimension = limits.maxComputeWorkgroupsPerDimension;

		return result;
	}

	adapter::info adapter::get_info() const
	{
		WGPUAdapterInfo info;
		detail::check_status("wgpuAdapterGetInfo", wgpuAdapterGetInfo((WGPUAdapter)get(), &info));

		return {
			.vendor = std::string_view(info.vendor.data, info.vendor.length),
			.architecture = std::string_view(info.architecture.data, info.architecture.length),
			.device = std::string_view(info.device.data, info.device.length),
			.description = std::string_view(info.description.data, info.description.length),
			.backend_type = (backend_type)info.backendType,
			.adapter_type = (type)info.adapterType,
			.vendor_id = info.vendorID,
			.device_id = info.deviceID,
		};
	}

	bool adapter::has_feature(feature feature) const
	{
		return wgpuAdapterHasFeature((WGPUAdapter)get(), (WGPUFeatureName)feature);
	}

	void adapter::request_device(callback_mode mode, device::descriptor const & descriptor, device::request_callback const & callback)
	{
		WGPULimits limits = {};
		if (descriptor.required_limits)
		{
			limits.nextInChain = (WGPUChainedStructOut *)detail::fill_chain_out(descriptor.required_limits->chain);

			limits.maxTextureDimension1D = descriptor.required_limits->limits.max_texture_dimension_1D;
			limits.maxTextureDimension2D = descriptor.required_limits->limits.max_texture_dimension_2D;
			limits.maxTextureDimension3D = descriptor.required_limits->limits.max_texture_dimension_3D;
			limits.maxTextureArrayLayers = descriptor.required_limits->limits.max_texture_array_layers;
			limits.maxBindGroups = descriptor.required_limits->limits.max_bind_groups;
			limits.maxBindGroupsPlusVertexBuffers = descriptor.required_limits->limits.max_bind_groups_plus_vertex_buffers;
			limits.maxBindingsPerBindGroup = descriptor.required_limits->limits.max_bindings_per_bind_group;
			limits.maxDynamicUniformBuffersPerPipelineLayout = descriptor.required_limits->limits.max_dynamic_uniform_buffers_per_pipeline_layout;
			limits.maxDynamicStorageBuffersPerPipelineLayout = descriptor.required_limits->limits.max_dynamic_storage_buffers_per_pipeline_layout;
			limits.maxSampledTexturesPerShaderStage = descriptor.required_limits->limits.max_sampled_textures_per_shader_stage;
			limits.maxSamplersPerShaderStage = descriptor.required_limits->limits.max_samplers_per_shader_stage;
			limits.maxStorageBuffersPerShaderStage = descriptor.required_limits->limits.max_storage_buffers_per_shader_stage;
			limits.maxStorageTexturesPerShaderStage = descriptor.required_limits->limits.max_storage_textures_per_shader_stage;
			limits.maxUniformBuffersPerShaderStage = descriptor.required_limits->limits.max_uniform_buffers_per_shader_stage;
			limits.maxUniformBufferBindingSize = descriptor.required_limits->limits.max_uniform_buffer_binding_size;
			limits.maxStorageBufferBindingSize = descriptor.required_limits->limits.max_storage_buffer_binding_size;
			limits.minUniformBufferOffsetAlignment = descriptor.required_limits->limits.min_uniform_buffer_offset_alignment;
			limits.minStorageBufferOffsetAlignment = descriptor.required_limits->limits.min_storage_buffer_offset_alignment;
			limits.maxVertexBuffers = descriptor.required_limits->limits.max_vertex_buffers;
			limits.maxBufferSize = descriptor.required_limits->limits.max_buffer_size;
			limits.maxVertexAttributes = descriptor.required_limits->limits.max_vertex_attributes;
			limits.maxVertexBufferArrayStride = descriptor.required_limits->limits.max_vertex_buffer_array_stride;
			limits.maxInterStageShaderVariables = descriptor.required_limits->limits.max_inter_stage_shader_variables;
			limits.maxColorAttachments = descriptor.required_limits->limits.max_color_attachments;
			limits.maxColorAttachmentBytesPerSample = descriptor.required_limits->limits.max_color_attachment_bytes_per_sample;
			limits.maxComputeWorkgroupStorageSize = descriptor.required_limits->limits.max_compute_workgroup_storage_size;
			limits.maxComputeInvocationsPerWorkgroup = descriptor.required_limits->limits.max_compute_invocations_per_workgroup;
			limits.maxComputeWorkgroupSizeX = descriptor.required_limits->limits.max_compute_workgroup_size_x;
			limits.maxComputeWorkgroupSizeY = descriptor.required_limits->limits.max_compute_workgroup_size_y;
			limits.maxComputeWorkgroupSizeZ = descriptor.required_limits->limits.max_compute_workgroup_size_z;
			limits.maxComputeWorkgroupsPerDimension = descriptor.required_limits->limits.max_compute_workgroups_per_dimension;
		}

		WGPUDeviceDescriptor device_desc = {};
		device_desc.nextInChain = (WGPUChainedStruct const *)detail::fill_chain(descriptor.chain);
		device_desc.label = detail::to_string_view(descriptor.label);
		device_desc.requiredFeatureCount = descriptor.required_features.size();
		device_desc.requiredFeatures = (WGPUFeatureName const *)descriptor.required_features.data();
		device_desc.requiredLimits = descriptor.required_limits ? &limits : nullptr;
		device_desc.defaultQueue.nextInChain = (WGPUChainedStruct const *)detail::fill_chain(descriptor.default_queue.chain);
		device_desc.defaultQueue.label = detail::to_string_view(descriptor.default_queue.label);
		device_desc.deviceLostCallbackInfo.mode = (WGPUCallbackMode)descriptor.lost_callback_mode;
		device_desc.deviceLostCallbackInfo.callback = [](WGPUDevice const *, WGPUDeviceLostReason reason, WGPUStringView message, void * userdata, void *)
		{
			std::unique_ptr<device::lost_callback> callback{(device::lost_callback *)userdata};
			if (*callback) (*callback)((device::lost_reason)reason, std::string(message.data, message.length));
		};
		device_desc.deviceLostCallbackInfo.userdata1 = new device::lost_callback(descriptor.lost_callback);
		device_desc.uncapturedErrorCallbackInfo.callback = [](WGPUDevice const *, WGPUErrorType type, WGPUStringView message, void * userdata, void *)
		{
			auto callback = (device::uncaptured_error_callback *)userdata;
			if (*callback) (*callback)((error_type)type, std::string(message.data, message.length));
		};
		device_desc.uncapturedErrorCallbackInfo.userdata1 = new device::uncaptured_error_callback(descriptor.uncaptured_error_callback);

		WGPURequestDeviceCallbackInfo callback_info = {};
		callback_info.mode = (WGPUCallbackMode)mode;
		callback_info.callback = [](WGPURequestDeviceStatus status, WGPUDevice device, WGPUStringView message, void * userdata, void *)
		{
			std::unique_ptr<device::request_callback> callback{(device::request_callback *)userdata};
			if (*callback) (*callback)((device::request_status)status, wgpu::device(device), std::string(message.data, message.length));
		};
		callback_info.userdata1 = new device::request_callback(callback);

		wgpuAdapterRequestDevice((WGPUAdapter)get(), &device_desc, callback_info);
	}

	void adapter::reference(void * ptr)
	{
		wgpuAdapterAddRef((WGPUAdapter)ptr);
	}

	void adapter::release(void * ptr)
	{
		wgpuAdapterRelease((WGPUAdapter)ptr);
	}

}
