#pragma once

#include <memory>
#include <vector>

namespace psemek::wgpu
{

	struct chained_struct;

	namespace detail
	{

		struct chained_struct_base
		{
			virtual ~chained_struct_base() {}
			virtual void * ptr() = 0;
		};

		struct empty_storage
		{};

		template <typename ChainedType, typename StorageType>
		struct chained_struct_impl
			: chained_struct_base
		{
			chained_struct_impl(ChainedType const & chained, StorageType const & storage)
				: chained_(chained)
				, storage_(std::move(storage))
			{}

			chained_struct_impl(ChainedType const & chained, StorageType && storage)
				: chained_(chained)
				, storage_(std::move(storage))
			{}

			void * ptr() override
			{
				return &chained_;
			}

		private:
			ChainedType chained_;
			StorageType storage_;
		};

		void * fill_chain(std::vector<chained_struct> const & chain);
		void * fill_chain_out(std::vector<chained_struct> const & chain);

		using chained_struct_ptr = std::shared_ptr<chained_struct_base>;

		template <typename ChainedType>
		chained_struct_ptr make_chained_struct(ChainedType const & chained)
		{
			return std::make_shared<chained_struct_impl<ChainedType, empty_storage>>(chained, empty_storage{});
		}

		template <typename ChainedType, typename StorageType>
		chained_struct_ptr make_chained_struct(ChainedType const & chained, StorageType && storage)
		{
			return std::make_shared<chained_struct_impl<ChainedType, std::remove_cvref_t<StorageType>>>(chained, std::forward<StorageType>(storage));
		}

	}

	struct chained_struct
	{
		template <typename T>
		requires(!std::is_same_v<std::remove_reference_t<T>, chained_struct>)
		chained_struct(T && value)
			: impl_(to_chained_struct(std::move(value)))
		{}

		chained_struct(chained_struct const &) = default;
		chained_struct(chained_struct &&) = default;

		chained_struct & operator = (chained_struct const &) = default;
		chained_struct & operator = (chained_struct &&) = default;

		void * ptr() const
		{
			return impl_->ptr();
		}

	private:
		detail::chained_struct_ptr impl_;
	};

}
