#pragma once

#include <type_traits>
#include <memory>

#define psemek_declare_pimpl \
	struct impl; \
	std::unique_ptr<struct impl> pimpl_; \
	struct impl & impl() { return *pimpl_; } \
	struct impl const & impl() const { return *pimpl_; } \
	template <typename ... Args> static auto make_impl(Args && ... args) { return std::make_unique<struct impl>(std::forward<Args>(args)...); }

#define psemek_declare_shared_pimpl \
	struct impl; \
	std::shared_ptr<struct impl> pimpl_; \
	struct impl & impl() { return *pimpl_; } \
	struct impl const & impl() const { return *pimpl_; } \
	template <typename ... Args> static auto make_impl(Args && ... args) { return std::make_shared<struct impl>(std::forward<Args>(args)...); }

namespace psemek::util::pimpl
{

	template <typename Parent>
	struct impl;

	template <typename Parent, std::size_t Size>
	struct in_place
	{
	protected:

		using impl_type = pimpl::impl<Parent>;

		impl_type * pimpl() { return reinterpret_cast<impl_type *>(std::addressof(storage_)); }
		impl_type & impl() { return *pimpl(); }

		impl_type const * pimpl() const { return reinterpret_cast<impl_type const *>(std::addressof(storage_)); }
		impl_type const & impl() const { return *pimpl(); }

		template <typename ... Args>
		in_place(Args && ... args)
		{
			static_assert(sizeof(impl_type) <= Size, "impl storage size too small");
			new (pimpl()) impl_type (std::forward<Args>(args)...);
		}

		~in_place()
		{
			impl().~impl_type();
		}

	private:

		typename std::aligned_storage<Size>::type storage_;
	};

	template <typename Parent>
	struct dynamic
	{
	protected:

		using impl_type = pimpl::impl<Parent>;

		impl_type * pimpl() { return pointer_.get(); }
		impl_type & impl() { return *pimpl(); }

		impl_type const * pimpl() const { return pointer_.get(); }
		impl_type const & impl() const { return *pimpl(); }

		template <typename ... Args>
		dynamic(Args && ... args)
		{
			pointer_.reset(new impl_type(std::forward<Args>(args)...));
		}

		~dynamic() = default;

	private:

		std::unique_ptr<impl_type> pointer_;
	};

}
