/// @file UniquePtr.h
/// @author Braxton Salyer <braxtonsalyer@gmail.com>
/// @brief This includes Hyperion's `constexpr` equivalents and extensions to the C++ standard
/// library's `std::unique_ptr<T, Deleter>`
/// @version 0.1
/// @date 2021-11-05
///
/// MIT License
/// @copyright Copyright (c) 2021 Braxton Salyer <braxtonsalyer@gmail.com>
///
/// Permission is hereby granted, free of charge, to any person obtaining a copy
/// of this software and associated documentation files (the "Software"), to deal
/// in the Software without restriction, including without limitation the rights
/// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
/// copies of the Software, and to permit persons to whom the Software is
/// furnished to do so, subject to the following conditions:
///
/// The above copyright notice and this permission notice shall be included in all
/// copies or substantial portions of the Software.
///
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
/// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
/// SOFTWARE.
#pragma once

#include <Hyperion/Concepts.h>
#include <Hyperion/HyperionDef.h>
#include <Hyperion/memory/CompressedPair.h>
#include <gsl/gsl>

/// @ingroup memory
/// @{
///	@defgroup UniquePtr UniquePtr
/// Hyperion provides a `constexpr` equivalent to the C++ standard library's
/// `std::unique_ptr<T, Deleter>` in our `UniquePtr`, with additional functionality like allocator
/// aware factory functions (`allocate_unique`).
///
/// Example:
///
/// auto ptr = hyperion::make_unique<i32>(42);
/// ptr.reset(24);
///
/// auto ptr2 = hyperion::allocate_unique<i32>(std::allocator<i32>(), 42);
/// ptr2.reset(24);
/// @headerfile "Hyperion/memory/UniquePtr.h"
/// @}

namespace hyperion {

	/// @brief Default Deleter type for Hyperion smart pointers
	/// This is the default deleter type used by Hyperion smart pointers
	/// such as `hyperion::UniquePtr<T, Deleter>`
	///
	/// @tparam T The type to handle deletion for (eg, `i32`, `std::string`)
	///
	/// # Requirements
	/// - `concepts::Deletable<T>`: T must be a deletable type
	/// - `concepts::NotFunction<T>`: T must NOT be a function type
	/// @ingroup memory
	/// @headerfile "Hyperion/memory/UniquePtr.h"
	template<typename T>
	requires concepts::Deletable<T> && concepts::NotFunction<T>
	struct DefaultDeleter {

		/// @brief Default constructs a `DefaultDeleter`
		/// @ingroup memory
		constexpr DefaultDeleter() noexcept = default;
		/// @brief Copy-constructs a `DefaultDeleter` from the given one
		/// @ingroup memory
		constexpr DefaultDeleter(const DefaultDeleter&) noexcept = default;
		/// @brief Move-constructs a `DefaultDeleter` from the given one
		/// @ingroup memory
		constexpr DefaultDeleter(DefaultDeleter&&) noexcept = default;
		/// @brief Destructs a `DefaultDeleter`
		/// @ingroup memory
		constexpr ~DefaultDeleter() noexcept = default;
		/// @brief Constructs a `DefaultDeleter<T>` from a `DefaultDeleter<U>`
		///
		/// @tparam U - The type managed by the given deleter
		///
		/// @param deleter - The deleter to construct this from
		///
		/// # Requirements
		/// - `concepts::Convertible<U*, T*>`: The pointer type of `deleter` must be convertible two
		/// the pointer type of `DefaultDeleter<T>` in order to construct a `DefaultDeleter` from it
		/// @ingroup memory
		template<typename U>
		requires concepts::Convertible<U*, T*>
		explicit DefaultDeleter([[maybe_unused]] const DefaultDeleter<U>& deleter) noexcept {
		}

		/// @brief Deletes the data at the given pointer
		///
		/// @param ptr - The pointer to the data to delete
		/// @ingroup memory
		constexpr auto
		operator()(gsl::owner<T*> ptr) const noexcept(concepts::NoexceptDeletable<T>) {
			delete ptr;
		}

		/// @brief Copy-assigns this `DefaultDeleter` from the given one
		/// @ingroup memory
		constexpr auto operator=(const DefaultDeleter&) noexcept -> DefaultDeleter& = default;
		/// @brief Move-assigns this `DefaultDeleter` from the given one
		/// @ingroup memory
		constexpr auto operator=(DefaultDeleter&&) noexcept -> DefaultDeleter& = default;
	};

	template<typename T>
	requires concepts::Deletable<T[]> && concepts::NotFunction<T>
	struct DefaultDeleter<T[]> { // NOLINT
		constexpr DefaultDeleter() noexcept = default;
		constexpr DefaultDeleter(const DefaultDeleter&) noexcept = default;
		constexpr DefaultDeleter(DefaultDeleter&&) noexcept = default;
		constexpr ~DefaultDeleter() noexcept = default;
		template<typename U>
		requires concepts::Convertible<U (*)[], T (*)[]>							 // NOLINT
		explicit DefaultDeleter([[maybe_unused]] const DefaultDeleter<U[]>& deleter) // NOLINT
			noexcept {
		}

		constexpr auto
		operator()(gsl::owner<T*> ptr) const noexcept(concepts::NoexceptDeletable<T[]>) // NOLINT
		{
			delete[] ptr;
		}

		constexpr auto operator=(const DefaultDeleter&) noexcept -> DefaultDeleter& = default;
		constexpr auto operator=(DefaultDeleter&&) noexcept -> DefaultDeleter& = default;
	};

	/// @brief `UniquePtr<T, Deleter>` is Hyperion's `constexpr` equivalent to the standard
	/// library's `std::unique_ptr<T, Deleter>`
	///
	/// `UniquePtr<T, Deleter>` is a `constexpr` implementation of a unique owning pointer, with
	/// almost complete semantic equivalence with `std::unique_ptr<T, Deleter>`.
	/// It is a drop-in replacement in almost all situations(1).
	/// Hyperion also provides extended functionality for `UniquePtr<T, Deleter>`
	/// over `std::unique_ptr<T, Deleter>`, eg: an allocator-aware factory function set in
	/// `allocate_unique`
	///
	/// @tparam T - The type to store in the `UniquePtr`
	/// @tparam Deleter - The deleter type to handle deletion of the stored `T`, by default, this is
	/// `hyperion::DefaultDeleter<T>`. This type only needs to be explicitly provided when custom
	/// deletion or resource-freeing strategies are required.
	///
	/// # Requirements
	/// - `!std::is_rvalue_reference_v<Deleter>`: `Deleter` cannot be an r-value reference type
	///
	/// @note(1): The exception is in situations where destructor ordering is required and code was
	/// compiled with Clang. `UniquePtr<T, Deleter>` makes use of Clang's `trivial_abi` attribute,
	/// which allows `UniquePtr<T, Deleter>` to be passed via register in function calls
	/// (and thus removes its overhead compared to a raw pointer), but moves the call to its
	/// destructor from the calling code to the callee. The result is that the destructor for
	/// `UniquePtr<T, Deleter>` will not nest in the same way as `std::unique_ptr<T, Deleter>`, and
	/// code relying on that behavior may experience issues. This is identical behavior to
	/// `std::unique_ptr<T, Deleter>` when `_LIBCPP_ABI_ENABLE_UNIQUE_PTR_TRIVIAL_ABI` is defined
	/// @ingroup UniquePtr
	/// @headerfile "Hyperion/memory/UniquePtr.h"
	template<typename T, typename Deleter = DefaultDeleter<T>>
	requires concepts::NotRValueReference<Deleter>
	class HYPERION_TRIVIAL_ABI UniquePtr {
	  public:
		/// @brief The element type allocated in the `UniquePtr`
		/// @ingroup UniquePtr
		using element_type = T;
		/// @brief The deleter type used by the `UniquePtr`
		/// @ingroup UniquePtr
		using deleter_type = Deleter;
		/// @brief The pointer type stored in the `UniquePtr`
		/// @ingroup UniquePtr
		using pointer = T*;
		/// @brief The pointer type stored in the `UniquePtr`, as a pointer to const
		/// @ingroup UniquePtr
		using pointer_to_const = const T*;

		/// @brief Constructs a default `UniquePtr<T, Deleter>`, managing no pointer
		///
		/// # Requirements
		/// - `concepts::NoexceptDefaultConstructible<deleter_type>`: `deleter_type` must be
		/// noexcept default constructible in order to default construct a `UniquePtr`
		/// - `concepts::NotPointer<deleter_type>`: `deleter_type` must not be a pointer
		/// in order to default construct a `UniquePtr`
		/// @ingroup UniquePtr
		constexpr UniquePtr() noexcept requires concepts::NoexceptDefaultConstructible<
			deleter_type> && concepts::NotPointer<deleter_type>
			: m_ptr(pointer(), DefaultInitTag<deleter_type>()) {
		}
		/// @brief Constructs a `UniquePtr<T, Deleter>` managing no pointer
		///
		/// @param ptr - The `nullptr` signaling this `UniquePtr` should manage no pointer
		///
		/// # Requirements
		/// - `concepts::NoexceptDefaultConstructible<deleter_type>`: `deleter_type` must be
		/// noexcept default constructible in order to construct a `UniquePtr` from only a `nullptr`
		/// - `concepts::NotPointer<deleter_type>`: `deleter_type` must not be a pointer
		/// in order to construct a `UniquePtr` from only a `nullptr`
		/// @ingroup UniquePtr
		constexpr UniquePtr(std::nullptr_t ptr) noexcept // NOLINT
			requires concepts::NoexceptDefaultConstructible<deleter_type> && concepts::NotPointer<
				deleter_type> : m_ptr(ptr, DefaultInitTag<deleter_type>()) {
		}
		/// @brief Constructs a `UniquePtr<T, Deleter>` managing the given pointer
		///
		/// @param ptr - The pointer to manage with this `UniquePtr`
		///
		/// # Requirements
		/// - `concepts::NoexceptDefaultConstructible<deleter_type>`: `deleter_type` must be
		/// noexcept default constructible in order to construct a `UniquePtr` from only a `pointer`
		/// - `concepts::NotPointer<deleter_type>`: `deleter_type` must not be a pointer
		/// in order to construct a `UniquePtr` from only a `pointer`
		/// @ingroup UniquePtr
		explicit constexpr UniquePtr(pointer ptr) noexcept requires concepts::
			NoexceptDefaultConstructible<deleter_type> && concepts::NotPointer<deleter_type>
			: m_ptr(ptr, DefaultInitTag<deleter_type>()) {
		}
		/// @brief Constructs a `UniquePtr<T, Deleter>` managing the given pointer
		///
		/// @tparam U - The type of the pointer to manage in this `UniquePtr`
		///
		/// @param ptr - The pointer to manage with this `UniquePtr`
		///
		/// # Requirements
		/// - `concepts::Convertible<U, pointer>`: `U` must be convertible to `pointer` to construct
		/// a `UniquePtr` from it
		/// - `concepts::NoexceptDefaultConstructible<deleter_type>`: `deleter_type` must be
		/// noexcept default constructible in order to construct a `UniquePtr` from only a `pointer`
		/// - `concepts::NotPointer<deleter_type>`: `deleter_type` must not be a pointer
		/// in order to construct a `UniquePtr` from only a `pointer`
		/// @ingroup UniquePtr
		template<typename U>
		requires concepts::Convertible<U, pointer> && concepts::NoexceptDefaultConstructible<
			deleter_type> && concepts::NotPointer<deleter_type>
		explicit constexpr UniquePtr(U ptr) noexcept : m_ptr(ptr, DefaultInitTag<deleter_type>()) {
		}
		/// @brief Constructs a `UniquePtr<T, Deleter>` managing the given pointer
		///
		/// @param ptr - The pointer to manage with this `UniquePtr`
		/// @param deleter - The deleter to delete `ptr` with when the destructor of this
		/// `UniquePtr` is called
		///
		/// @ingroup UniquePtr
		constexpr UniquePtr(pointer ptr, const Deleter& deleter) noexcept requires
			concepts::NoexceptCopyConstructible<Deleter> : m_ptr(ptr, deleter) {
		}
		/// @brief Constructs a `UniquePtr<T, Deleter>` managing the given pointer
		///
		/// @param ptr - The pointer to manage with this `UniquePtr`
		/// @param deleter - The deleter to delete `ptr` with when the destructor of this
		/// `UniquePtr` is called
		///
		/// @ingroup UniquePtr
		constexpr UniquePtr(pointer ptr, Deleter&& deleter) noexcept requires
			concepts::NoexceptMoveConstructible<Deleter> && concepts::NotRValueReference<Deleter>
			: m_ptr(ptr, std::move(deleter)) {
		}
		/// @brief Constructs a `UniquePtr<T, Deleter>` managing the given pointer
		///
		/// @tparam U - The type of the pointer to manage in this `UniquePtr`
		///
		/// @param ptr - The pointer to manage with this `UniquePtr`
		/// @param deleter - The deleter to delete `ptr` with when the destructor of this
		/// `UniquePtr` is called
		///
		/// # Requirements
		/// - `concepts::Convertible<U, pointer>`: `U` must be convertible to `pointer` to construct
		/// a `UniquePtr` from it
		/// @ingroup UniquePtr
		template<typename U>
		requires concepts::Convertible<U, pointer> && concepts::NotSame<U, pointer> && concepts::
			NoexceptCopyConstructible<Deleter>
		constexpr UniquePtr(U ptr, const Deleter& deleter) noexcept : m_ptr(ptr, deleter) {
		}
		/// @brief Constructs a `UniquePtr<T, Deleter>` managing the given pointer
		///
		/// @tparam U - The type of the pointer to manage in this `UniquePtr`
		///
		/// @param ptr - The pointer to manage with this `UniquePtr`
		/// @param deleter - The deleter to delete `ptr` with when the destructor of this
		/// `UniquePtr` is called
		///
		/// # Requirements
		/// - `concepts::Convertible<U, pointer>`: `U` must be convertible to `pointer` to construct
		/// a `UniquePtr` from it
		/// @ingroup UniquePtr
		template<typename U>
		requires concepts::Convertible<U, pointer> && concepts::NotSame<U, pointer> && concepts::
			NoexceptMoveConstructible<Deleter> && concepts::NotRValueReference<Deleter>
		constexpr UniquePtr(U ptr, Deleter&& deleter) noexcept : m_ptr(ptr, std::move(deleter)) {
		}
		/// @brief Constructs a `UniquePtr<T, Deleter>` managing no pointer
		///
		/// @param ptr - The `nullptr` signaling this `UniquePtr` should manage no pointer
		/// @param deleter - The deleter to delete a managed pointer with when the destructor of
		/// this `UniquePtr` is called
		///
		/// @ingroup UniquePtr
		constexpr UniquePtr(std::nullptr_t ptr, const Deleter& deleter) noexcept requires
			concepts::NoexceptCopyConstructible<Deleter> : m_ptr(ptr, deleter) {
		}
		/// @brief Constructs a `UniquePtr<T, Deleter>` managing no pointer
		///
		/// @param ptr - The `nullptr` signaling this `UniquePtr` should manage no pointer
		/// @param deleter - The deleter to delete a managed pointer with when the destructor of
		/// this `UniquePtr` is called
		///
		/// @ingroup UniquePtr
		constexpr UniquePtr(std::nullptr_t ptr, Deleter&& deleter) noexcept requires
			concepts::NoexceptMoveConstructible<Deleter> && concepts::NotRValueReference<Deleter>
			: m_ptr(ptr, std::move(deleter)) {
		}
		/// @brief Constructs a `UniquePtr<T, Deleter>` from the given moved `UniquePtr<U, D>`
		///
		/// @param ptr - The `UniquePtr` to construct this one from
		///
		/// # Requirements
		/// - `concepts::NoexceptConstructibleFrom<deleter_type,
		/// decltype((std::forward<D>(ptr.get_deleter())))`: `deleter_type` must be noexcept
		/// constructible from the deleter type returned by `ptr.get_deleter()`
		/// - `concepts::Convertible<typename UniquePtr<U, D>::pointer, pointer>`: The pointer type
		/// of `UniquePtr<U, D>` must be convertible to `pointer`
		/// - `concepts::Convertible<typename UniquePtr<U, D>::deleter_type, deleter_type>`: The
		/// deleter type of `UniquePtr<U, D>` must be convertible to `deleter_type`
		/// @ingroup UniquePtr
		template<typename U, typename D>
		explicit constexpr UniquePtr(UniquePtr<U, D>&& ptr) noexcept requires concepts::
			NoexceptConstructibleFrom<deleter_type,
									  decltype((std::forward<D>(ptr.get_deleter())))> && concepts::
				Convertible<typename UniquePtr<U, D>::pointer, pointer> && concepts::
					Convertible<typename UniquePtr<U, D>::deleter_type, deleter_type>
			: m_ptr(ptr.release(), std::forward<D>(ptr.get_deleter())) {
		}
		/// @brief `UniquePtr` cannot be copied
		/// @ingroup UniquePtr
		UniquePtr(const UniquePtr&) = delete;
		/// @brief Move Constructs a `UniquePtr` from the given one
		///
		/// @param ptr - The `UniquePtr` to move
		///
		/// # Requirements
		/// - `concepts::NoexceptMoveConstructible<deleter_type>`: `deleter_type` must be noexcept
		/// move constructible in order to move construct a `UniquePtr`
		/// @ingroup UniquePtr
		constexpr UniquePtr(
			UniquePtr&& ptr) noexcept requires concepts::NoexceptMoveConstructible<deleter_type>
			: m_ptr(ptr.release(), std::forward<deleter_type>(ptr.get_deleter())) {
		}
		/// @brief `UniquePtr` destructor
		///
		/// # Requirements
		/// - `noexcept(std::declval<deleter_type>()(std::declval<pointer>()))`: Deleting the
		/// managed pointer via the associated `deleter_type`'s call operator must be noexcept
		/// @ingroup UniquePtr
		constexpr ~UniquePtr() noexcept
			requires(noexcept(std::declval<deleter_type>()(std::declval<pointer>()))) // NOLINT
		{
			reset();
		}

		/// @brief Releases ownership of the managed pointer and returns it
		///
		/// @return The managed pointer
		/// @ingroup UniquePtr
		[[nodiscard]] inline constexpr auto release() noexcept -> pointer {
			auto* ptr = m_ptr.first();
			m_ptr.first() = pointer();
			return ptr;
		}

		/// @brief Deletes the currently managed pointer, if any, and begins managing the given one
		///
		/// @param ptr - The new pointer to manage with this `UniquePtr`
		///
		/// # Requirements
		/// - `noexcept(std::declval<deleter_type>()(std::declval<pointer>()))`: Deleting the
		/// managed pointer via the associated `deleter_type`'s call operator must be noexcept
		inline constexpr auto reset(pointer ptr = pointer()) noexcept -> void requires(
			noexcept(std::declval<deleter_type>()(std::declval<pointer>()))) // NOLINT
		{
			gsl::owner<pointer> tmp = m_ptr.first(); // NOLINT
			m_ptr.first() = ptr;
			m_ptr.second()(tmp);
		}

		/// @brief Deletes the currently managed pointer, if any, and begins managing the given one
		///
		/// @tparam U - The type of the new pointer to manage
		///
		/// @param ptr - The new pointer to manage with this `UniquePtr`
		///
		/// # Requirements
		/// - `concepts::Convertible<U, pointer>`: `U` must be convertible to `pointer` in order for
		/// this `UniquePtr` to manage a `U`
		/// - `noexcept(std::declval<deleter_type>()(std::declval<pointer>()))`: Deleting the
		/// managed pointer via the associated `deleter_type`'s call operator must be noexcept
		/// @ingroup UniquePtr
		template<typename U>
		requires concepts::Convertible<U, pointer>
		inline constexpr auto reset(U ptr) noexcept -> void requires(
			noexcept(std::declval<deleter_type>()(std::declval<pointer>()))) // NOLINT
		{
			gsl::owner<pointer> tmp = m_ptr.first(); // NOLINT
			m_ptr.first() = ptr;
			m_ptr.second()(tmp);
		}

		/// @brief Swaps the managed pointer and deleter of this `UniquePtr` with the given one
		///
		/// @param ptr - The `UniquePtr` to swap with
		///
		/// # Requirements
		/// - `std::is_nothrow_swappable_v<CompressedPair<pointer, deleter_type>>`:
		/// `CompressedPair<pointer, deleter_type> must be noexcept swappable in order to
		/// swap two `UniquePtr`s. (`CompressedPair` is used internally to store the managed pointer
		/// and deleter, to allow for
		/// [Empty Base Class Optimization](https://en.cppreference.com/w/cpp/language/ebo) )
		/// @ingroup UniquePtr
		inline constexpr auto swap(UniquePtr& ptr) noexcept
			-> void requires concepts::NoexceptSwappable<CompressedPair<pointer, deleter_type>> {
			m_ptr.swap(ptr.m_ptr);
		}

		/// @brief Returns the managed pointer
		///
		/// Returns the managed pointer. This does not release ownership of the pointer, it only
		/// provides unmanaged access to it. Use of the returned pointer after the lifetime of this
		/// `UniquePtr` has ended results in undefined behavior.
		///
		/// @return The managed pointer
		/// @ingroup UniquePtr
		[[nodiscard]] inline constexpr auto get() noexcept -> pointer {
			return m_ptr.first();
		}

		/// @brief Returns the managed pointer
		///
		/// Returns the managed pointer. This does not release ownership of the pointer, it only
		/// provides unmanaged access to it. Use of the returned pointer after the lifetime of this
		/// `UniquePtr` has ended results in undefined behavior.
		///
		/// @return The managed pointer
		/// @ingroup UniquePtr
		[[nodiscard]] inline constexpr auto get() const noexcept -> pointer_to_const {
			return m_ptr.first();
		}

		/// @brief Returns the associated deleter
		///
		/// @return The associated deleter
		/// @ingroup UniquePtr
		[[nodiscard]] inline constexpr auto get_deleter() const noexcept -> const deleter_type& {
			return m_ptr.second();
		}

		/// @brief Returns the associated deleter
		///
		/// @return The associated deleter
		/// @ingroup UniquePtr
		[[nodiscard]] inline constexpr auto get_deleter() noexcept -> deleter_type& {
			return m_ptr.second();
		}

		/// @brief Converts this `UniquePtr` to a `bool`
		///
		/// Returns `true` if the managed pointer is not null, `false` otherwise.
		/// @return this, as a `bool`
		/// @ingroup UniquePtr
		explicit constexpr operator bool() const noexcept {
			return m_ptr.first() != nullptr;
		}

		constexpr auto operator*() const -> typename std::add_lvalue_reference_t<T> {
			return *(m_ptr.first());
		}

		constexpr auto operator->() const noexcept -> pointer {
			return m_ptr.first();
		}

		inline constexpr auto operator==(const UniquePtr& ptr) const noexcept -> bool {
			return m_ptr.first() == ptr.first();
		}

		inline constexpr auto operator==(std::nullptr_t) const noexcept -> bool {
			return m_ptr.first() == nullptr;
		}

		auto operator=(const UniquePtr&) -> UniquePtr& = delete;
		/// @brief Move-assigns this `UniquePtr` from the given one
		///
		/// @param ptr - The `UniquePtr` to move into this one
		///
		/// # Requirements
		/// - `concepts::NoexceptMoveAssignable<deleter_type>`: `deleter_type` must be noexcept move
		/// assignable in order to move assign a `UniquePtr`
		/// - `noexcept(std::declval<deleter_type>()(std::declval<pointer>()))`: Deleting the
		/// managed pointer via the associated `deleter_type`'s call operator must be noexcept
		///
		/// @return this
		/// @ingroup UniquePtr
		constexpr auto operator=(UniquePtr&& ptr) noexcept
			-> UniquePtr& requires concepts::NoexceptMoveAssignable<deleter_type> &&(
				noexcept(std::declval<deleter_type>()(std::declval<pointer>()))) // NOLINT
		{
			if(this == &ptr) {
				return *this;
			}

			reset(ptr.release());
			m_ptr.second() = std::forward<deleter_type>(ptr.get_deleter());

			return *this;
		}

		/// @brief Move-assigns this `UniquePtr` from the given one
		///
		/// @tparam U - The managed type of the given `UniquePtr`
		/// @tparam D - The deleter type of the given `UniquePtr`
		///
		/// @param ptr - The `UniquePtr<U, D>` to move into this one
		///
		/// # Requirements
		/// - `concepts::NoexceptAssignable<deleter_type,
		/// decltype(std::forward<D>(ptr.get_deleter()))>`: `deleter_type` must be assignable with
		/// the deleter type returned by `ptr`'s `get_deleter()` member function
		/// - `concepts::Convertible<typename UniquePtr<U, D>::pointer, pointer>`: The pointer type
		/// of `ptr` must be convertible to the pointer type of `this` in order to assign from
		/// it
		/// - `concepts::Convertible<typename UniquePtr<U, D>::deleter_type, deleter_type>`:
		/// The deleter type of `ptr` must be convertible to the pointer type of `this` in order to
		/// assign from it
		/// - `noexcept(std::declval<deleter_type>()(std::declval<pointer>()))`: Deleting the
		/// managed pointer via the associated `deleter_type`'s call operator must be noexcept
		///
		/// @return this
		/// @ingroup UniquePtr
		template<typename U, typename D>
		constexpr auto operator=(UniquePtr<U, D>&& ptr) noexcept -> UniquePtr& requires
			concepts::NoexceptAssignable<deleter_type,
										 decltype(std::forward<D>(ptr.get_deleter()))> && concepts::
				Convertible<typename UniquePtr<U, D>::pointer, pointer> && concepts::
					Convertible<typename UniquePtr<U, D>::deleter_type, deleter_type> &&(
						noexcept(std::declval<deleter_type>()(std::declval<pointer>()))) // NOLINT
		{
			reset(ptr.release());
			m_ptr.second() = std::forward<D>(ptr.get_deleter());
			return *this;
		}

		/// @brief Assigns this `UniquePtr` with `nullptr`
		///
		/// Calls the deleter on the managed pointer and replaces it with `nullptr`
		///
		/// # Requirements
		/// - `noexcept(std::declval<deleter_type>()(std::declval<pointer>()))`: Deleting the
		/// managed pointer via the associated `deleter_type`'s call operator must be noexcept
		///
		/// @return this
		/// @ingroup UniquePtr
		constexpr auto operator=(std::nullptr_t) noexcept -> UniquePtr& requires(
			noexcept(std::declval<deleter_type>()(std::declval<pointer>()))) // NOLINT
		{
			reset();
			return *this;
		}

		/// @brief Assigns this `UniquePtr` with `ptr`
		///
		/// Calls the deleter on the managed pointer and replaces it with `ptr`
		///
		/// # Requirements
		/// - `noexcept(std::declval<deleter_type>()(std::declval<pointer>()))`: Deleting the
		/// managed pointer via the associated `deleter_type`'s call operator must be noexcept
		///
		/// @return this
		/// @ingroup UniquePtr
		constexpr auto operator=(pointer ptr) noexcept -> UniquePtr& requires(
			noexcept(std::declval<deleter_type>()(std::declval<pointer>()))) // NOLINT
		{
			reset(ptr);
			return *this;
		}

		// clang-format off
	  private:
		template<typename T_, typename Deleter_>
		using CompressedPair = CompressedPair<T_, Deleter_>;
		template<typename T_>
		using DefaultInitTag = DefaultInitTag<T_>;

		CompressedPair<pointer, deleter_type> m_ptr;
		// clang-format on
	};

	template<typename T, typename Deleter>
	requires concepts::NotRValueReference<Deleter>
	class HYPERION_TRIVIAL_ABI UniquePtr<T[], Deleter> { // NOLINT
	  public:
		using element_type = T;
		using deleter_type = Deleter;
		using pointer = std::add_pointer_t<element_type>;
		using pointer_to_const = std::add_pointer_t<std::add_const_t<element_type>>;

		constexpr UniquePtr() noexcept requires concepts::NoexceptDefaultConstructible<
			deleter_type> && concepts::NotPointer<deleter_type>
			: m_ptr(pointer(), DefaultInitTag<deleter_type>()) {
		}

		constexpr UniquePtr(std::nullptr_t ptr) noexcept // NOLINT
			requires concepts::NoexceptDefaultConstructible<deleter_type> && concepts::NotPointer<
				deleter_type> : m_ptr(ptr, DefaultInitTag<deleter_type>()) {
		}
		explicit constexpr UniquePtr(pointer ptr) noexcept requires concepts::
			NoexceptDefaultConstructible<deleter_type> && concepts::NotPointer<deleter_type>
			: m_ptr(ptr, DefaultInitTag<deleter_type>()) {
		}
		template<typename U>
		requires concepts::Convertible<U, pointer> && concepts::NoexceptDefaultConstructible<
			deleter_type> && concepts::NotPointer<deleter_type> && concepts::NotSame<U, pointer>
		explicit constexpr UniquePtr(U ptr) noexcept : m_ptr(ptr, DefaultInitTag<deleter_type>()) {
		}
		constexpr UniquePtr(pointer ptr, const Deleter& deleter) noexcept requires
			concepts::NoexceptCopyConstructible<Deleter> : m_ptr(ptr, deleter) {
		}
		constexpr UniquePtr(pointer ptr, Deleter&& deleter) noexcept requires
			concepts::NoexceptMoveConstructible<Deleter> && concepts::NotRValueReference<Deleter>
			: m_ptr(ptr, std::move(deleter)) {
		}
		template<typename U>
		requires concepts::Convertible<U, pointer> && concepts::NotSame<U, pointer> && concepts::
			NoexceptCopyConstructible<Deleter>
		constexpr UniquePtr(U ptr, const Deleter& deleter) noexcept : m_ptr(ptr, deleter) {
		}
		template<typename U>
		requires concepts::Convertible<U, pointer> && concepts::NotSame<U, pointer> && concepts::
			NoexceptMoveConstructible<Deleter> && concepts::NotRValueReference<Deleter>
		constexpr UniquePtr(U ptr, Deleter&& deleter) noexcept : m_ptr(ptr, std::move(deleter)) {
		}
		constexpr UniquePtr(std::nullptr_t ptr, const Deleter& deleter) noexcept requires
			concepts::NoexceptCopyConstructible<Deleter> : m_ptr(ptr, deleter) {
		}
		constexpr UniquePtr(std::nullptr_t ptr, Deleter&& deleter) noexcept requires
			concepts::NoexceptMoveConstructible<Deleter> && concepts::NotRValueReference<Deleter>
			: m_ptr(ptr, std::move(deleter)) {
		}
		// clang-format off
		template<typename U, typename D>
		requires std::is_array_v<U>
		explicit constexpr UniquePtr(UniquePtr<U, D>&& ptr)
			noexcept(concepts::NoexceptConstructibleFrom<deleter_type,
														 decltype((std::forward<D>(ptr.get_deleter())))>)
			requires concepts::Convertible<typename UniquePtr<U, D>::element_type(*)[], // NOLINT
										   element_type (*)[] > 						// NOLINT
					 && concepts::Convertible<typename UniquePtr<U, D>::deleter_type, deleter_type>
			: m_ptr(ptr.release(), std::forward<D>(ptr.get_deleter())) {
		}
		// clang-format on
		UniquePtr(const UniquePtr&) = delete;
		constexpr UniquePtr(
			UniquePtr&& ptr) noexcept requires concepts::NoexceptMoveConstructible<deleter_type>
			: m_ptr(ptr.release(), std::forward<deleter_type>(ptr.get_deleter())) {
		}
		constexpr ~UniquePtr() noexcept
			requires(noexcept(std::declval<deleter_type>()(std::declval<pointer>()))) // NOLINT
		{
			reset();
		}

		[[nodiscard]] inline constexpr auto release() noexcept -> pointer {
			auto* ptr = m_ptr.first();
			m_ptr.first() = pointer();
			return ptr;
		}

		inline constexpr auto reset(pointer ptr = pointer()) noexcept -> void requires(
			noexcept(std::declval<deleter_type>()(std::declval<pointer>()))) // NOLINT
		{
			gsl::owner<pointer> tmp = m_ptr.first(); // NOLINT
			m_ptr.first() = ptr;
			m_ptr.second()(tmp);
		}

		template<typename U>
		requires concepts::Convertible<U, pointer>
		inline constexpr auto reset(U ptr) noexcept -> void requires(
			noexcept(std::declval<deleter_type>()(std::declval<pointer>()))) // NOLINT
		{
			gsl::owner<pointer> tmp = m_ptr.first(); // NOLINT
			m_ptr.first() = ptr;
			m_ptr.second()(tmp);
		}

		inline constexpr auto swap(UniquePtr& ptr) noexcept
			-> void requires concepts::NoexceptSwappable<CompressedPair<pointer, deleter_type>> {
			m_ptr.swap(ptr.m_ptr);
		}

		[[nodiscard]] inline constexpr auto get() noexcept -> pointer {
			return m_ptr.first();
		}

		[[nodiscard]] inline constexpr auto get() const noexcept -> pointer_to_const {
			return m_ptr.first();
		}

		[[nodiscard]] inline constexpr auto get_deleter() const noexcept -> const deleter_type& {
			return m_ptr.second();
		}

		[[nodiscard]] inline constexpr auto get_deleter() noexcept -> deleter_type& {
			return m_ptr.second();
		}

		explicit constexpr operator bool() const noexcept {
			return m_ptr.first() != nullptr;
		}

		inline constexpr auto operator==(const UniquePtr& ptr) const noexcept -> bool {
			return m_ptr.first() == ptr.first();
		}

		inline constexpr auto operator==(std::nullptr_t) const noexcept -> bool {
			return m_ptr.first() == nullptr;
		}

		// constexpr inline auto operator[](concepts::Integral auto i) const
		//	noexcept -> std::add_const_t<std::add_lvalue_reference_t<element_type>> {
		//	return m_ptr.first()[i];
		// }

		inline constexpr auto operator[](concepts::Integral auto index) const noexcept
			-> std::add_lvalue_reference_t<element_type> {
			return m_ptr.first()[index];
		}

		auto operator=(const UniquePtr&) -> UniquePtr& = delete;
		constexpr auto operator=(UniquePtr&& ptr) noexcept
			-> UniquePtr& requires concepts::NoexceptMoveAssignable<deleter_type> &&(
				noexcept(std::declval<deleter_type>()(std::declval<pointer>()))) // NOLINT
		{
			if(this == &ptr) {
				return *this;
			}

			reset(ptr.release());
			m_ptr.second() = std::forward<deleter_type>(ptr.get_deleter());

			return *this;
		}
		// clang-format off

		template<typename U, typename D>
		requires std::is_array_v<U>
		constexpr auto operator=(UniquePtr<U, D>&& ptr) noexcept -> UniquePtr&
			requires concepts::NoexceptAssignable<deleter_type,
												  decltype(std::forward<D>(ptr.get_deleter()))>
					 && concepts::Convertible<typename UniquePtr<U, D>::element_type(*)	[], // NOLINT
										   	  element_type(*)[]> 							// NOLINT
					 && concepts::Convertible<typename UniquePtr<U, D>::deleter_type, deleter_type>
					 && (noexcept(std::declval<deleter_type>()(std::declval<pointer>()))) // NOLINT
		{
			reset(ptr.release());
			m_ptr.second() = std::forward<D>(ptr.get_deleter());
			return *this;
		}
		// clang-format on

		constexpr auto operator=(std::nullptr_t) noexcept -> UniquePtr& requires(
			noexcept(std::declval<deleter_type>()(std::declval<pointer>()))) // NOLINT
		{
			reset();
			return *this;
		}

		constexpr auto operator=(pointer ptr) noexcept -> UniquePtr& requires(
			noexcept(std::declval<deleter_type>()(std::declval<pointer>()))) // NOLINT
		{
			reset(ptr);
			return *this;
		}

		// clang-format off
	  private:
		template<typename T_, typename Deleter_>
		using CompressedPair = CompressedPair<T_, Deleter_>;
		template<typename T_>
		using DefaultInitTag = DefaultInitTag<T_>;

		CompressedPair<pointer, deleter_type> m_ptr;
		// clang-format on
	};
	template<typename T, typename Deleter>
	UniquePtr(T*, Deleter) -> UniquePtr<T, Deleter>;

	/// @brief Swaps the managed pointer and deleter of the two `UniquePtr`s
	///
	/// @tparam T - The type held by the `UniquePtr`s
	/// @tparam Deleter - The deleter type used by the `UniquePtr`s
	///
	/// @param first - The `UniquePtr` to swap to
	/// @param second - The `UniquePtr` to swap from
	/// @ingroup UniquePtr
	template<typename T, typename Deleter>
	requires concepts::Swappable<Deleter>
	inline constexpr auto
	swap(UniquePtr<T, Deleter>& first, UniquePtr<T, Deleter>& second) noexcept -> void {
		first.swap(second);
	}
	// clang-format off

	IGNORE_UNUSED_TEMPLATES_START

	/// @brief Constructs a `UniquePtr<T, Deleter>` from the given arguments
	///
	/// Constructs a `UniquePtr<T, Deleter>` from the given arguments, passing them directly
	/// to `T`'s constructor
	///
	/// @tparam T - The type to hold in the `UniquePtr`
	/// @tparam Deleter - The deleter type to free the resources associated with `T`
	/// @tparam Args - The types of the arguments to pass to `T`'s constructor
	///
	/// @param args - The arguments to pass to `T`'s constructor
	///
	/// # Requirements
	/// - `concepts::ConstructibleFrom<T, Args...>`: `T` must be constructible from the given
	/// arguments in order to make a `UniquePtr` from them
	/// - `!std::is_array_v<T>`: Cannot make a `UniquePtr` holding an array type from a set of
	/// constructor arguments. Use the overload for arrays to make a `UniquePtr<T[]>`.
	///
	/// @return a `UniquePtr<T, Deleter>` with the `T` constructed from the given arguments
	/// @ingroup UniquePtr
	template<typename T, typename Deleter = DefaultDeleter<T>, typename... Args>
	requires concepts::ConstructibleFrom<T, Args...> && (!std::is_unbounded_array_v<T>)
	inline static constexpr auto make_unique(Args&&... args)
		noexcept(concepts::NoexceptConstructibleFrom<T, Args...>)
		-> UniquePtr<T, Deleter>
	{
		// NOLINTNEXTLINE(modernize-use-auto,hicpp-use-auto,bugprone-unhandled-exception-at-new)
		gsl::owner<T*> ptr = new T(std::forward<Args>(args)...);
		// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
		return UniquePtr<T, Deleter>(ptr);
	}
	// clang-format on

	/// @brief Constructs a `UniquePtr` holding array type `T` of size `N`
	///
	/// Constructs a `UniquePtr<T, Deleter>`, where `T` is an unbounded array type with initial
	/// size of `N`.
	///
	/// @tparam T - The array type to hold in the `UniquePtr`
	/// @tparam Deleter - The deleter type to free the resources associated with `T`
	/// @tparam ElementType - The element type of the array
	///
	/// @param N - The initial size of the array managed by the `UniquePtr`
	///
	/// # Requirements
	/// - `std::is_unbounded_array_v<T>`: `T` must be an unbounded array type to make a `UniquePtr`
	/// managing an array.
	///
	/// @return a `UniquePtr<T, Deleter>` managing an array of `N` `ElementType`s
	/// @note The array managed by the returned `UniquePtr` will be uninitialized, and thus each
	/// element must be appropriately initialized before use. Otherwise, the result is undefined
	/// behavior.
	/// @ingroup UniquePtr
	template<typename T,
			 typename Deleter = DefaultDeleter<T>,
			 typename ElementType = std::remove_extent_t<T>> // NOLINT
	requires std::is_unbounded_array_v<T>
	inline static constexpr auto make_unique(usize N) noexcept -> UniquePtr<T, Deleter> {
		// NOLINTNEXTLINE(modernize-use-auto,hicpp-use-auto,bugprone-unhandled-exception-at-new)
		gsl::owner<ElementType*> ptr = new ElementType[N];
		// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
		return UniquePtr<T, Deleter>(ptr);
	}
	IGNORE_UNUSED_TEMPLATES_STOP

	IGNORE_PADDING_START
	/// @brief An allocator-aware deleter type for Hyperion smart pointers
	///
	/// `AllocatorAwareDeleter` is useful for using smart pointers in situations that require custom
	/// allocation strategies. It allows use of smart pointers in an allocator-aware fashion.
	///
	/// @tparam T - The type to manage deletion for
	/// @tparam Allocator - The allocator type allocation was performed with
	/// @tparam ElementType - The element type of the managed deletion, in the case that `T` is an
	/// array
	///
	/// # Requirements
	/// - `concepts::Allocatable<ElementType, Allocator>`: `AllocatorAwareDeleter` can't be
	/// instantiated for an `ElementType`-`Allocator` pair where the element type `ElementType`
	/// isn't allocatable by the associated allocator type, `Allocator`
	/// @note In the case that `T` is an array type, `AllocatorAwareDeleter` requires that the
	/// managed array is fully-initialized and the same size as when the `AllocatorAwareDeleter`
	/// was constructed with it (by a call to one of the factory functions, `allocate_unique`, or
	/// `allocate_shared`) when its call operator is used to free the resources associated with the
	/// array.
	/// @note If `T` is an array type and the array is resized, the deleter associated with it in
	/// the owning smart pointer must be set to a new one to match the changed state.
	/// @note If `T` is an array type and the members of the array are not all initialized when the
	/// call operator of this deleter is used to free the resources associated with it, the result
	/// is undefined behavior.
	/// @ingroup memory
	template<typename T,
			 typename Allocator = std::allocator<T>,
			 typename ElementType = std::remove_cv_t<std::remove_all_extents_t<T>>>
	requires concepts::Allocatable<ElementType, Allocator>
	class AllocatorAwareDeleter {
	  public:
		/// @brief The rebound allocator type for this deleter
		/// @ingroup memory
		using Alloc = typename std::allocator_traits<Allocator>::template rebind_alloc<ElementType>;
		/// @brief The `std::allocator_traits` type for this deleter
		/// @ingroup memory
		using Traits = std::allocator_traits<Alloc>;
		/// @brief The pointer type for the associated allocator traits for this deleter
		/// @ingroup memory
		using pointer = typename Traits::pointer;

		/// @brief Default-Constructs an `AllocatorAwareDeleter`
		///
		/// # Requirements
		/// - `concepts::NoexceptDefaultConstructible<Alloc>`: The associated allocator type must be
		/// noexcept default constructible in order to construct an `AllocatorAwareDeleter` with one
		/// @ingroup memory
		constexpr AllocatorAwareDeleter() noexcept requires
			concepts::NoexceptDefaultConstructible<Alloc> : m_allocator() {
		}
		/// @brief Constructs an `AllocatorAwareDeleter` from the given allocator
		///
		/// @param alloc - The allocator to construct the allocator associated with this from
		///
		/// # Requirements
		/// - `concepts::NoexceptConstructibleFrom<Alloc, decltype(alloc)>`: The associated
		/// allocator type must be noexcept constructible from the given allocator in order to
		/// construct an `AllocatorAwareDeleter` from it
		/// @ingroup memory
		explicit constexpr AllocatorAwareDeleter(const Allocator& alloc) noexcept requires
			concepts::NoexceptConstructibleFrom<Alloc, decltype(alloc)> : m_allocator(alloc) {
		}
		/// @brief Copy-Constructs an `AllocatorAwareDeleter` from the given one
		///
		/// # Requirements
		/// - `concepts::NoexceptCopyConstructible<Alloc>`: The associated allocator type must be
		/// noexcept copy constructible in order to copy construct an `AllocatorAwareDeleter`
		/// @ingroup memory
		constexpr AllocatorAwareDeleter(const AllocatorAwareDeleter&) noexcept requires
			concepts::NoexceptCopyConstructible<Alloc>
		= default;
		/// @brief Move-Constructs an `AllocatorAwareDeleter` from the given one
		///
		/// # Requirements
		/// - `concepts::NoexceptMoveConstructible<Alloc>`: The associated allocator type must be
		/// noexcept move constructible in order to copy construct an `AllocatorAwareDeleter`
		/// @ingroup memory
		constexpr AllocatorAwareDeleter(
			AllocatorAwareDeleter&&) noexcept requires concepts::NoexceptMoveConstructible<Alloc>
		= default;

		/// @brief Destructs this `AllocatorAwareDeleter`
		/// @ingroup memory
		constexpr ~AllocatorAwareDeleter() noexcept = default;

		/// @brief Frees the resources associated with the pointed-to object
		///
		/// @param p - Pointer to the object to free associated resources of
		/// @ingroup memory
		inline constexpr auto operator()(pointer p) const noexcept {
			Alloc allocator = m_allocator;

			Traits::destroy(allocator, std::addressof(*p));

			Traits::deallocate(allocator, p, 1);
		}

		/// @brief Copy-Assigns this `AllocatorAwareDeleter` from the given one
		///
		/// # Requirements
		/// - `concepts::NoexceptCopyAssignable<Alloc>`: The associated allocator type must be
		/// noexcept copy assignable in order to copy assign this `AllocatorAwareDeleter`
		/// @ingroup memory
		constexpr auto operator=(const AllocatorAwareDeleter&) noexcept
			-> AllocatorAwareDeleter& requires concepts::NoexceptCopyAssignable<Alloc>
		= default;
		/// @brief Move-Assigns this `AllocatorAwareDeleter` from the given one
		///
		/// # Requirements
		/// - `concepts::NoexceptMoveAssignable<Alloc>`: The associated allocator type must be
		/// noexcept move assignable in order to move assign this `AllocatorAwareDeleter`
		/// @ingroup memory
		constexpr auto operator=(AllocatorAwareDeleter&&) noexcept
			-> AllocatorAwareDeleter& requires concepts::NoexceptMoveAssignable<Alloc>
		= default;

	  private:
		Alloc m_allocator;
	};

	template<typename T, typename Allocator, typename ElementType>
	requires concepts::Allocatable<ElementType, Allocator>
	class AllocatorAwareDeleter<T[], Allocator, ElementType> { // NOLINT
	  public:
		using Alloc = typename std::allocator_traits<Allocator>::template rebind_alloc<ElementType>;
		using Traits = std::allocator_traits<Alloc>;
		using pointer = typename Traits::pointer;

		constexpr AllocatorAwareDeleter() noexcept requires
			concepts::NoexceptDefaultConstructible<Alloc> : m_allocator() {
		}
		explicit constexpr AllocatorAwareDeleter(const Allocator& alloc,
												 usize num_elements) noexcept requires
			concepts::NoexceptConstructibleFrom<Alloc, decltype(alloc)>
			: m_allocator(alloc),
			  m_num_elements(num_elements) {
		}
		constexpr AllocatorAwareDeleter(const AllocatorAwareDeleter&) noexcept requires
			concepts::NoexceptCopyConstructible<Alloc>
		= default;
		constexpr AllocatorAwareDeleter(
			AllocatorAwareDeleter&&) noexcept requires concepts::NoexceptMoveConstructible<Alloc>
		= default;
		constexpr ~AllocatorAwareDeleter() noexcept = default;

		inline constexpr auto operator()(pointer p) const noexcept {
			Alloc allocator = m_allocator;

			auto i = m_num_elements >= 1_usize ? m_num_elements - 1_usize : 0_usize;
			// we need to use do-while to ensure we destroy the 0th element
			// in a 1 element array
			do {
				Traits::destroy(allocator, std::addressof(*p) + i); // NOLINT
				--i;
			} while(i != 0_usize);

			Traits::deallocate(allocator, p, m_num_elements);
		}

		constexpr auto operator=(const AllocatorAwareDeleter&) noexcept
			-> AllocatorAwareDeleter& requires concepts::NoexceptCopyAssignable<Alloc>
		= default;
		constexpr auto operator=(AllocatorAwareDeleter&&) noexcept
			-> AllocatorAwareDeleter& requires concepts::NoexceptMoveAssignable<Alloc>
		= default;

	  private:
		Alloc m_allocator;
		usize m_num_elements = 1_usize;
	};
	IGNORE_PADDING_STOP

	/// @brief Constructs an allocator-aware `UniquePtr`
	///
	/// Constructs a `UniquePtr<T, AllocatorAwareDeleter<T, Alloc>>`. The managed `T` will be
	/// constructed from the given arguments, `args`, passing them directly to `T`'s constructor.
	/// Allocation of the managed `T` will be performed by the given allocator.
	///
	/// @tparam T - The type to manage in the `UniquePtr`
	/// @tparam Allocator - The type of the allocator to use for allocation and deletion of the `T`
	/// @tparam ElementType - The element type of the array, in the case that `T` is an unbounded
	/// array type
	/// @tparam Alloc - The allocator type `Allocator` rebound to the element type, `ElementType`
	///
	/// @param alloc - The allocator to perform allocation and deallocation with
	/// @param args - The arguments to pass to `T`'s constructor
	///
	/// # Requirements
	/// - `concepts::NoexceptConstructibleFrom<ElementType, Args...>`: `ElementType` must be
	/// noexcept constructible from `args` in order to make a `UniquePtr` from them
	/// - `concepts::Allocatable<ElementType, Alloc>`: `AllocatorAwareDeleter` can't be
	/// instantiated for an `ElementType`-`Alloc` pair where the element type `ElementType`
	/// isn't allocatable by the associated allocator type, `Alloc`
	/// - `!std::is_array_v<T>`: Cannot make a `UniquePtr` holding an array type from a set of
	/// constructor arguments. Use the overload for arrays to make a `UniquePtr<T[]>`.
	/// - `concepts::NoexceptConstructibleFrom<Alloc, decltype(alloc)>`: The associated allocator
	/// type, `Alloc`, must be noexcept constructible from the given allocator in order to create
	/// a `UniquePtr` using it
	/// @ingroup UniquePtr
	template<typename T,
			 typename Allocator = std::allocator<T>,
			 typename ElementType = std::remove_cv_t<std::remove_all_extents_t<T>>,
			 typename Alloc =
				 typename std::allocator_traits<Allocator>::template rebind_alloc<ElementType>,
			 typename... Args>
	requires concepts::NoexceptConstructibleFrom<ElementType, Args...> && concepts::
		Allocatable<ElementType, Alloc> &&(!std::is_unbounded_array_v<T>)
			[[nodiscard]] inline constexpr auto allocate_unique(const Allocator& alloc,
																Args&&... args) noexcept
		-> UniquePtr<T, AllocatorAwareDeleter<T, Alloc>>
	requires concepts::NoexceptConstructibleFrom<Alloc, decltype(alloc)> {
		using Traits = std::allocator_traits<Alloc>;
		using Deleter = AllocatorAwareDeleter<T, Alloc>;

		Alloc allocator(alloc);
		auto* p = Traits::allocate(allocator, 1);
		Traits::construct(allocator, std::addressof(*p), std::forward<Args>(args)...);

		// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
		return UniquePtr<T, Deleter>(p, Deleter(allocator));
	}

	/// @brief Constructs an allocator-aware `UniquePtr`
	///
	/// Constructs a `UniquePtr<T, AllocatorAwareDeleter<T, Alloc>>`, where `T` is an unbounded
	/// array type. The managed array will have its elements default constructed in place.
	/// Allocation of the managed array will be performed by the given allocator.
	///
	/// @tparam T - The type to manage in the `UniquePtr`
	/// @tparam Allocator - The type of the allocator to use for allocation and deletion of the `T`
	/// @tparam ElementType - The element type of the array, in the case that `T` is an unbounded
	/// array type
	/// @tparam Alloc - The allocator type `Allocator` rebound to the element type, `ElementType`
	///
	/// @param alloc - The allocator to perform allocation and deallocation with
	/// @param N - The number of elements to allocate in the array
	///
	/// # Requirements
	/// - `concepts::NoexceptDefaultConstructible<ElementType>`: `ElementType` must be
	/// noexcept default-constructible in order to make a `UniquePtr` managing an array of them
	/// - `concepts::Allocatable<ElementType, Alloc>`: `AllocatorAwareDeleter` can't be
	/// instantiated for an `ElementType`-`Alloc` pair where the element type `ElementType`
	/// isn't allocatable by the associated allocator type, `Alloc`
	/// - `std::is_unbounded_array_v<T>`: `T` must be an unbounded array type to make a `UniquePtr`
	/// managing an array.
	/// - `concepts::NoexceptConstructibleFrom<Alloc, decltype(alloc)>`: The associated allocator
	/// type, `Alloc`, must be noexcept constructible from the given allocator in order to create
	/// a `UniquePtr` using it
	/// @ingroup UniquePtr
	template<typename T,
			 typename Allocator = std::allocator<T>,
			 typename ElementType = std::remove_cv_t<std::remove_all_extents_t<T>>,
			 typename Alloc =
				 typename std::allocator_traits<Allocator>::template rebind_alloc<ElementType>>
	requires concepts::NoexceptDefaultConstructible<
		ElementType> && concepts::Allocatable<ElementType, Alloc> && std::is_unbounded_array_v<T>
	[[nodiscard]] inline constexpr auto allocate_unique(const Allocator& alloc, usize N) noexcept
		-> UniquePtr<T, AllocatorAwareDeleter<T, Alloc>>
	requires concepts::NoexceptConstructibleFrom<Alloc, decltype(alloc)> {
		using Traits = std::allocator_traits<Alloc>;
		using Deleter = AllocatorAwareDeleter<ElementType[], Alloc>; // NOLINT (c arrays)

		Alloc allocator(alloc);
		auto* p = Traits::allocate(allocator, N);
		// we need to use do-while to make sure we construct the 0th element in a one element array
		auto i = 0_usize;
		do {
			Traits::construct(allocator, std::addressof(*p) + i); // NOLINT
			++i;
		} while(i < N);

		// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
		return UniquePtr<ElementType[], Deleter>(p, Deleter(allocator, N)); // NOLINT ( c arrays)
	}

	/// @brief Constructs an allocator-aware `UniquePtr`
	///
	/// Constructs a `UniquePtr<T, AllocatorAwareDeleter<T, Alloc>>`, where `T` is an unbounded
	/// array type. The managed array will have its elements default constructed in place.
	/// Allocation of the managed array will be performed by the given allocator.
	///
	/// @tparam T - The type to manage in the `UniquePtr`
	/// @tparam Allocator - The type of the allocator to use for allocation and deletion of the `T`
	/// @tparam ElementType - The element type of the array, in the case that `T` is an unbounded
	/// array type
	/// @tparam Alloc - The allocator type `Allocator` rebound to the element type, `ElementType`
	/// @tparam Args - The types of the arguments to use to default-construct the elements of the
	/// array
	///
	/// @param alloc - The allocator to perform allocation and deallocation with
	/// @param N - The number of elements to allocate in the array
	///
	/// # Requirements
	/// - `concepts::NoexceptDefaultConstructible<ElementType>`: `ElementType` must be
	/// noexcept default-constructible in order to make a `UniquePtr` managing an array of them
	/// - `concepts::Allocatable<ElementType, Alloc>`: `AllocatorAwareDeleter` can't be
	/// instantiated for an `ElementType`-`Alloc` pair where the element type `ElementType`
	/// isn't allocatable by the associated allocator type, `Alloc`
	/// - `std::is_unbounded_array_v<T>`: `T` must be an unbounded array type to make a `UniquePtr`
	/// managing an array.
	/// - `concepts::NoexceptConstructibleFrom<Alloc, decltype(alloc)>`: The associated allocator
	/// type, `Alloc`, must be noexcept constructible from the given allocator in order to create
	/// a `UniquePtr` using it
	/// @ingroup UniquePtr
	template<typename T,
			 typename Allocator = std::allocator<T>,
			 typename ElementType = std::remove_cv_t<std::remove_all_extents_t<T>>,
			 typename Alloc =
				 typename std::allocator_traits<Allocator>::template rebind_alloc<ElementType>,
			 typename... Args>
	requires concepts::NoexceptConstructibleFrom<ElementType, Args...> && mpl::for_all_types_v<
		std::is_nothrow_copy_constructible,
		std::true_type,
		mpl::list<Args...>> && concepts::Allocatable<ElementType,
													 Alloc> && std::is_unbounded_array_v<T>
	[[nodiscard]] inline constexpr auto
	allocate_unique(const Allocator& alloc, usize N, Args&&... args) noexcept
		-> UniquePtr<T, AllocatorAwareDeleter<T, Alloc>>
	requires concepts::NoexceptConstructibleFrom<Alloc, decltype(alloc)> {
		using Traits = std::allocator_traits<Alloc>;
		using Deleter = AllocatorAwareDeleter<ElementType[], Alloc>; // NOLINT (c arrays)

		Alloc allocator(alloc);
		auto* p = Traits::allocate(allocator, N);
		auto tuple = std::make_tuple(std::forward<Args>(args)...);
		// we need to use do-while to make sure we construct the 0th element in a one element array
		auto i = 0_usize;
		do {
			auto t = std::tuple(tuple);
			Traits::construct(allocator,
							  std::addressof(*p) + i,
							  std::make_from_tuple<ElementType>(std::move(t))); // NOLINT
			++i;
		} while(i < N);

		return UniquePtr<ElementType[], Deleter>(p, Deleter(allocator, N)); // NOLINT (c arrays)
	}

#if HYPERION_DEFINE_TESTS

	// NOLINTNEXTLINE(modernize-use-trailing-return-type)
	TEST_SUITE("UniquePtr") {
		TEST_CASE("Constructor") {
			auto ptr1 = UniquePtr<i32>();
			// NOLINTNEXTLINE
			auto ptr2 = UniquePtr<i32>(new i32(3_i32));
			auto ptr3 = hyperion::make_unique<i32>(2_i32);

			CHECK_EQ(ptr1, nullptr);
			CHECK_NE(ptr2, nullptr);
			CHECK_NE(ptr3, nullptr);
			CHECK_EQ(*ptr2, 3_i32);
			CHECK_EQ(*ptr3, 2_i32);

			SUBCASE("move") {
				auto ptr4 = std::move(ptr3);
				// NOLINTNEXTLINE(bugprone-use-after-move,hicpp-invalid-access-moved)
				CHECK_EQ(ptr3, nullptr);
				CHECK_NE(ptr4, nullptr);
				CHECK_EQ(*ptr4, 2_i32);
			}

			SUBCASE("accessors_and_modifiers") {
				CHECK(ptr3);
				CHECK(static_cast<bool>(ptr3));
				CHECK_NE(ptr3.get(), nullptr);
				CHECK_EQ(*(ptr3.get()), 2_i32);

				auto* ptr4 = ptr3.release();
				CHECK_EQ(ptr3, nullptr);
				CHECK_NE(ptr4, nullptr);
				CHECK_EQ(*ptr4, 2_i32);

				*ptr4 = 4_i32;
				ptr3.reset(ptr4);
				CHECK_NE(ptr3, nullptr);
				CHECK(ptr3);
				CHECK_EQ(*ptr3, 4_i32);
				CHECK_EQ(*(ptr3.get()), 4_i32);
			}
		}
	}
#endif // HYPERION_DEFINE_TESTS
} // namespace hyperion


#ifndef LOGGER_INCLUDE
#define LOGGER_INCLUDE

#include <gsl/string_span>

#include "log/log.h"
BOOST_LOG_GLOBAL_LOGGER(exec_helper_test_logger, execHelper::log::LoggerType);

static const gsl::czstring<> LOG_CHANNEL = "test";
#define LOG(x)                                                                 \
    BOOST_LOG_STREAM_CHANNEL_SEV(exec_helper_test_logger::get(), LOG_CHANNEL,  \
                                 execHelper::log::x)                           \
        << boost::log::add_value(fileLog, __FILE__)                            \
        << boost::log::add_value(lineLog, __LINE__)

#endif /* LOGGER_INCLUDE */


//          Copyright Jean Pierre Cimalando 2019.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE.md or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#pragma once
#include <SimpleIni.h>
#include <gsl/gsl>
#include <string>
#include <memory>

const std::string &get_configuration_dir();
std::string get_configuration_file(gsl::cstring_span name);

std::unique_ptr<CSimpleIniA> create_configuration();
std::unique_ptr<CSimpleIniA> load_configuration(gsl::cstring_span name);
bool save_configuration(gsl::cstring_span name, const CSimpleIniA &ini);

std::unique_ptr<CSimpleIniA> load_global_configuration();
bool save_global_configuration(const CSimpleIniA &ini);


/** GSLDAPCom.h - <title>GSLDAP: Common</title>

   Copyright (C) 2002-2003 Free Software Foundation, Inc.

   Written by:	Manuel Guesdon <mguesdon@orange-concept.com>
   Date: 	Sep 2002

   $Revision$
   $Date$
   $Id$

   This file is part of the GNUstep LDAP Library.

   <license>
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   </license>
**/

#ifndef _GSLDAPCom_h__
#define _GSLDAPCom_h__

#include <stdio.h>
#include <string.h>
#include <ldap.h>
#include <lber.h>
#include <sys/time.h>
#include <Foundation/NSObject.h>
#include <Foundation/NSString.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSData.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSDebug.h>
#include <Foundation/NSScanner.h>
#include <Foundation/NSCalendarDate.h>
#include <Foundation/NSException.h>
#include <GNUstepBase/GSCategories.h>
#include <gsldap/GSLDAPUtils.h>
#include <gsldap/GSLDAPConnection.h>
#include <gsldap/GSLDAPEntry.h>
#include <gsldap/GSLDAPAttribute.h>
#include <gsldap/GSLDAPObjectClass.h>
#include <gsldap/GSLDAPSyntax.h>
#include <gsldap/GSLDAPMatchingRule.h>
#include <gsldap/GSLDAPSchema.h>

#endif //_GSLDAPCom_h__


/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 *     Copyright 2018 Couchbase, Inc
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
#pragma once

#include "dcp/dcp-types.h"
#include "evp_engine_test.h"
#include "evp_store_single_threaded_test.h"
#include "vbucket_fwd.h"
#include <memcached/engine_error.h>
#include <gsl/gsl>

class Item;
class MockDcpProducer;
class MockActiveStream;
struct dcp_message_producers;

/**
 * Test fixture for unit tests related to DCP.
 */
class DCPTest : public EventuallyPersistentEngineTest {
protected:
    void SetUp() override;

    void TearDown() override;

    // Create a DCP producer; initially with no streams associated.
    void create_dcp_producer(
            int flags = 0,
            IncludeValue includeVal = IncludeValue::Yes,
            IncludeXattrs includeXattrs = IncludeXattrs::Yes,
            std::vector<std::pair<std::string, std::string>> controls = {});

    // Setup a DCP producer and attach a stream and cursor to it.
    void setup_dcp_stream(
            int flags = 0,
            IncludeValue includeVal = IncludeValue::Yes,
            IncludeXattrs includeXattrs = IncludeXattrs::Yes,
            std::vector<std::pair<std::string, std::string>> controls = {});

    void destroy_dcp_stream();

    struct StreamRequestResult {
        ENGINE_ERROR_CODE status;
        uint64_t rollbackSeqno;
    };

    /**
     * Helper function to simplify calling producer->streamRequest() - provides
     * sensible default values for common invocations.
     */
    static StreamRequestResult doStreamRequest(DcpProducer& producer,
                                               uint64_t startSeqno = 0,
                                               uint64_t endSeqno = ~0,
                                               uint64_t snapStart = 0,
                                               uint64_t snapEnd = ~0,
                                               uint64_t vbUUID = 0);

    /**
     * Helper function to simplify the process of preparing DCP items to be
     * fetched from the stream via step().
     * Should be called once all expected items are present in the vbuckets'
     * checkpoint, it will then run the correct background tasks to
     * copy CheckpointManager items to the producers' readyQ.
     */
    static void prepareCheckpointItemsForStep(
            dcp_message_producers& msgProducers,
            MockDcpProducer& producer,
            VBucket& vb);

    /*
     * Creates an item with the key \"key\", containing json data and xattrs.
     * @return a unique_ptr to a newly created item.
     */
    std::unique_ptr<Item> makeItemWithXattrs();

    /*
     * Creates an item with the key \"key\", containing json data and no xattrs.
     * @return a unique_ptr to a newly created item.
     */
    std::unique_ptr<Item> makeItemWithoutXattrs();

    /* Add items onto the vbucket and wait for the checkpoint to be removed */
    void addItemsAndRemoveCheckpoint(int numItems);

    void removeCheckpoint(int numItems);

    void runCheckpointProcessor(dcp_message_producers& producers);

    std::shared_ptr<MockDcpProducer> producer;
    std::shared_ptr<MockActiveStream> stream;
    VBucketPtr vb0;

    /*
     * Fake callback emulating dcp_add_failover_log
     */
    static ENGINE_ERROR_CODE fakeDcpAddFailoverLog(
            vbucket_failover_t* entry,
            size_t nentries,
            gsl::not_null<const void*> cookie) {
        callbackCount++;
        return ENGINE_SUCCESS;
    }

    // callbackCount needs to be static as its used inside of the static
    // function fakeDcpAddFailoverLog.
    static int callbackCount;
};


#pragma once

#include "poll_event.h"
#include "poll_response.h"
#include "poll_target.h"

#include <gsl/span>

#include <optional>
#include <vector>

namespace wrappers::zmq {

[[nodiscard]] std::optional<std::vector<poll_response>>
blocking_poll(gsl::span<poll_target> targets) noexcept;

} // namespace wrappers::zmq


/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
// Portions Copyright (c) Microsoft Corporation

#pragma once

#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <gsl/gsl>

#include "core/common/common.h"
#include "core/common/path_string.h"
#include "core/framework/callback.h"
#include "core/platform/env_time.h"
#include "core/platform/telemetry.h"

#ifndef _WIN32
#include <sys/types.h>
#include <unistd.h>
#endif
namespace Eigen {
class ThreadPoolInterface;
}
namespace onnxruntime {

#ifdef _WIN32
using PIDType = unsigned long;
using FileOffsetType = int64_t;
#else
using PIDType = pid_t;
using FileOffsetType = off_t;
#endif

class EnvThread {
 public:
  virtual void OnCancel() = 0;
  virtual ~EnvThread() = default;
};

// Parameters that are required to create a set of threads for a thread pool
struct ThreadOptions {
  // Stack size for a new thread. If it is 0, the operating system uses the same value as the stack that's specified for
  // the main thread, which is usually set in the main executable(not controlled by onnxruntime.dll).
  unsigned int stack_size = 0;

  // Thread affinity means a thread can only run on the logical processors that the thread is allowed to run on.
  // If the vector is not empty, set the affinity of each thread to just one CPU.
  // Index is thread index, value is CPU ID, starting from zero. For example, the first thread in the pool will be bound
  // to the logical processor with id of affinity[0]. If the vector is empty, the thread can run on all the processors
  // its process can run on. NOTE: When hyperthreading is enabled, for example, on a 4 cores 8 physical threads CPU,
  // processor group [0,1,2,3] may only contain half of the physical cores.
  std::vector<size_t> affinity;

  // Set or unset denormal as zero.
  bool set_denormal_as_zero = false;
};
/// \brief An interface used by the onnxruntime implementation to
/// access operating system functionality like the filesystem etc.
///
/// Callers may wish to provide a custom Env object to get fine grain
/// control.
///
/// All Env implementations are safe for concurrent access from
/// multiple threads without any external synchronization.
class Env {
 public:
  using EnvThread = onnxruntime::EnvThread;
  virtual ~Env() = default;
  // clang-format off
  /**
   * Start a new thread for a thread pool
   * \param name_prefix A human-readable string for debugging purpose, can be NULL
   * \param index The index value of the thread, for each thread pool instance, the index should start from 0 and be continuous.
   * \param start_address The entry point of thread
   * \param threadpool The thread pool that the new thread belongs to
   * \param thread_options options to create the thread
   *
   * Caller is responsible for deleting the returned value
   */
  // clang-format on
  virtual EnvThread* CreateThread(_In_opt_z_ const ORTCHAR_T* name_prefix, int index,
                                  _In_ unsigned (*start_address)(int id, Eigen::ThreadPoolInterface* param),
                                  Eigen::ThreadPoolInterface* threadpool, const ThreadOptions& thread_options) = 0;

  /// \brief Returns a default environment suitable for the current operating
  /// system.
  ///
  /// Sophisticated users may wish to provide their own Env
  /// implementation instead of relying on this default environment.
  ///
  /// The result of Default() belongs to this library and must never be deleted.
  static Env& Default();

  virtual int GetNumCpuCores() const = 0;

  // This function doesn't support systems with more than 64 logical processors
  virtual std::vector<size_t> GetThreadAffinityMasks() const = 0;

  /// \brief Returns the number of micro-seconds since the Unix epoch.
  virtual uint64_t NowMicros() const {
    return env_time_->NowMicros();
  }

  /// \brief Returns the number of seconds since the Unix epoch.
  virtual uint64_t NowSeconds() const {
    return env_time_->NowSeconds();
  }

  /// Sleeps/delays the thread for the prescribed number of micro-seconds.
  /// On Windows, it's the min time to sleep, not the actual one.
  virtual void SleepForMicroseconds(int64_t micros) const = 0;

  /**
   * Gets the length of the specified file.
   */
  virtual common::Status GetFileLength(_In_z_ const ORTCHAR_T* file_path, size_t& length) const = 0;
  virtual common::Status GetFileLength(int fd, /*out*/ size_t& file_size) const = 0;

  /**
   * Copies the content of the file into the provided buffer.
   * @param file_path The path to the file.
   * @param offset The file offset from which to start reading.
   * @param length The length in bytes to read.
   * @param buffer The buffer in which to write.
   */
  virtual common::Status ReadFileIntoBuffer(_In_z_ const ORTCHAR_T* file_path, FileOffsetType offset, size_t length,
                                            gsl::span<char> buffer) const = 0;

  using MappedMemoryPtr = std::unique_ptr<char[], OrtCallbackInvoker>;

  /**
   * Maps the content of the file into memory.
   * This is a copy-on-write mapping, so any changes are not written to the
   * actual file.
   * @param file_path The path to the file.
   * @param offset The file offset from which to start the mapping.
   * @param length The length in bytes of the mapping.
   * @param[out] mapped_memory A smart pointer to the mapped memory which
   *             unmaps the memory (unless release()'d) when destroyed.
   */
  virtual common::Status MapFileIntoMemory(_In_z_ const ORTCHAR_T* file_path, FileOffsetType offset, size_t length,
                                           MappedMemoryPtr& mapped_memory) const = 0;

#ifdef _WIN32
  /// \brief Returns true if the directory exists.
  virtual bool FolderExists(const std::wstring& path) const = 0;
  /// \brief Recursively creates the directory, if it doesn't exist.
  virtual common::Status CreateFolder(const std::wstring& path) const = 0;
  // Mainly for use with protobuf library
  virtual common::Status FileOpenRd(const std::wstring& path, /*out*/ int& fd) const = 0;
  // Mainly for use with protobuf library
  virtual common::Status FileOpenWr(const std::wstring& path, /*out*/ int& fd) const = 0;
#endif
  /// \brief Returns true if the directory exists.
  virtual bool FolderExists(const std::string& path) const = 0;
  /// \brief Recursively creates the directory, if it doesn't exist.
  virtual common::Status CreateFolder(const std::string& path) const = 0;
  // Recursively deletes the directory and its contents.
  // Note: This function is not thread safe!
  virtual common::Status DeleteFolder(const PathString& path) const = 0;
  // Mainly for use with protobuf library
  virtual common::Status FileOpenRd(const std::string& path, /*out*/ int& fd) const = 0;
  // Mainly for use with protobuf library
  virtual common::Status FileOpenWr(const std::string& path, /*out*/ int& fd) const = 0;
  // Mainly for use with protobuf library
  virtual common::Status FileClose(int fd) const = 0;

  /** Gets the canonical form of a file path (symlinks resolved). */
  virtual common::Status GetCanonicalPath(
      const PathString& path,
      PathString& canonical_path) const = 0;

  // This functions is always successful. It can't fail.
  virtual PIDType GetSelfPid() const = 0;

  // \brief Load a dynamic library.
  //
  // Pass "library_filename" to a platform-specific mechanism for dynamically
  // loading a library.  The rules for determining the exact location of the
  // library are platform-specific and are not documented here.
  //
  // On success, returns a handle to the library in "*handle" and returns
  // OK from the function.
  // Otherwise returns nullptr in "*handle" and an error status from the
  // function.
  virtual common::Status LoadDynamicLibrary(const std::string& library_filename, void** handle) const = 0;

  virtual common::Status UnloadDynamicLibrary(void* handle) const = 0;

  // \brief Gets the file path of the onnx runtime code
  //
  // Used to help load other shared libraries that live in the same folder as the core code, for example
  // The DNNL provider shared library. Without this path, the module won't be found on windows in all cases.
  virtual std::string GetRuntimePath() const { return ""; }

  // \brief Get a pointer to a symbol from a dynamic library.
  //
  // "handle" should be a pointer returned from a previous call to LoadDynamicLibrary.
  // On success, store a pointer to the located symbol in "*symbol" and return
  // OK from the function. Otherwise, returns nullptr in "*symbol" and an error
  // status from the function.
  virtual common::Status GetSymbolFromLibrary(void* handle, const std::string& symbol_name, void** symbol) const = 0;

  // \brief build the name of dynamic library.
  //
  // "name" should be name of the library.
  // "version" should be the version of the library or NULL
  // returns the name that LoadDynamicLibrary() can use
  virtual std::string FormatLibraryFileName(const std::string& name, const std::string& version) const = 0;

  // \brief returns a provider that will handle telemetry on the current platform
  virtual const Telemetry& GetTelemetryProvider() const = 0;

  // \brief returns a value for the queried variable name (var_name)
  //
  // Returns the corresponding value stored in the environment variable if available
  // Returns empty string if there is no such environment variable available
  virtual std::string GetEnvironmentVar(const std::string& var_name) const = 0;

 protected:
  Env();

 private:
  ORT_DISALLOW_COPY_ASSIGNMENT_AND_MOVE(Env);
  EnvTime* env_time_ = EnvTime::Default();
};

}  // namespace onnxruntime


#pragma once

#include <memory>
#include <string>
#include <vector>

#include <gsl/span>

struct VfsSearchResult {
	std::string filename;
	bool dir;
	size_t sizeInBytes;
	uint32_t lastModified;
};

enum class SeekDir {
	Start = 0,
	Current = 1,
	End = 2
};

/*
	Abstractions for accessing files in the virtual file system.
	The virtual file system is backed by TIO in a normal game,
	which means that the files are scattered across the .dat files
	and data directories.
*/
class Vfs {
public:
	virtual ~Vfs() {
	};

	static Vfs* CreateStdIoVfs();

	/*
		Reads a file fully into a string.
	*/
	std::string ReadAsString(std::string_view filename);

	/*
	Reads a binary file fully into a vector of uint8_t.
	*/
	std::vector<uint8_t> ReadAsBinary(std::string_view filename);

	/**
	 * Does the file exist?
	 */
	virtual bool FileExists(std::string_view path) = 0;

	/**
	 * Does the directory exist?
	 */
	virtual bool DirExists(std::string_view path) = 0;

	/**
	 * Creates a directory.
	 */
	virtual bool MkDir(std::string_view path) = 0;

	/**
	 * Will return all files and directories that match the given glob pattern, which
	 * is not recursive.
	 * Example: mes\*.mes will return the names of all files in mes (not full paths) that
	            end with .mes.
	 */
	virtual std::vector<VfsSearchResult> Search(std::string_view globPattern) = 0;

	/**
	 * Removes an empty directory.
	 */
	virtual bool RemoveDir(std::string_view path) = 0;

	/**
	 * Removes a file (no directories).
	 */
	virtual bool RemoveFile(std::string_view path) = 0;

	/**
	* Deletes all files and dirctories within the given directory.
	*/
	bool CleanDir(std::string_view path);

	/**
	 * Returns true if the given directory does not contain anything.
	 */
	bool IsDirEmpty(std::string_view path);

	/**
	 * Writes binary data to a file.
	 */
	void WriteBinaryFile(std::string_view path, gsl::span<uint8_t> data);

	using FileHandle = void*;
	virtual FileHandle Open(std::string_view name, std::string_view mode) = 0;
	virtual size_t Read(void* buffer, size_t size, FileHandle handle) = 0;
	virtual size_t Write(const void* buffer, size_t size, FileHandle handle) = 0;
	virtual size_t Length(FileHandle handle) = 0;
	virtual size_t Tell(FileHandle handle) = 0;
	virtual void Seek(FileHandle handle, int position, SeekDir dir = SeekDir::Start) = 0;
	virtual void Close(FileHandle handle) = 0;

};

class VfsPath {
public:
	VfsPath() = delete;

	static bool IsFileSystem(std::string_view path);
	static std::string Concat(std::string_view a, std::string_view b);
};

extern std::unique_ptr<Vfs> vfs;


// Copyright 2021 atframework
// Created by owent

#pragma once

#include <config/atframe_utils_build_feature.h>
#include <config/compiler_features.h>

#include <design_pattern/nomovable.h>
#include <design_pattern/noncopyable.h>

#include <time/time_utility.h>

#include <gsl/select-gsl.h>

#include <list>
#include <vector>

#if defined(LIBATFRAME_UTILS_ENABLE_UNORDERED_MAP_SET) && LIBATFRAME_UTILS_ENABLE_UNORDERED_MAP_SET
#  include <unordered_map>
#  include <unordered_set>
#else
#  include <map>
#  include <set>
#endif

#include "atframe/atapp_conf.h"
#include "atframe/etcdcli/etcd_discovery.h"

namespace atapp {
class app;
class atapp_connection_handle;
class atapp_endpoint;
class atapp_connector_impl;

struct atapp_endpoint_bind_helper {
  // This API is used by inner system and will not be exported, do not call it directly
  static LIBATAPP_MACRO_API_SYMBOL_HIDDEN void unbind(atapp_connection_handle &handle, atapp_endpoint &connect);
  // This API is used by inner system and will not be exported, do not call it directly
  static LIBATAPP_MACRO_API_SYMBOL_HIDDEN void bind(atapp_connection_handle &handle, atapp_endpoint &connect);
};

class atapp_endpoint {
 public:
  using handle_set_t = std::unordered_set<atapp_connection_handle *>;
  using handle_set_iterator = handle_set_t::iterator;
  using handle_set_const_iterator = handle_set_t::const_iterator;
  using ptr_t = std::shared_ptr<atapp_endpoint>;
  using weak_ptr_t = std::weak_ptr<atapp_endpoint>;

  struct pending_message_t {
    util::time::time_utility::raw_time_t expired_timepoint;
    int32_t type;
    uint64_t message_sequence;
    std::vector<unsigned char> data;
    std::unique_ptr<atapp::protocol::atapp_metadata> metadata;
  };

  UTIL_DESIGN_PATTERN_NOCOPYABLE(atapp_endpoint)
  UTIL_DESIGN_PATTERN_NOMOVABLE(atapp_endpoint)

 private:
  struct construct_helper_t {};

 public:
  LIBATAPP_MACRO_API atapp_endpoint(app &owner, construct_helper_t &helper);
  static LIBATAPP_MACRO_API ptr_t create(app &owner);
  LIBATAPP_MACRO_API ~atapp_endpoint();

  LIBATAPP_MACRO_API uint64_t get_id() const noexcept;
  LIBATAPP_MACRO_API gsl::string_view get_name() const noexcept;

  UTIL_FORCEINLINE bool has_connection_handle() const noexcept { return !refer_connections_.empty(); }
  LIBATAPP_MACRO_API const etcd_discovery_node::ptr_t &get_discovery() const noexcept;
  LIBATAPP_MACRO_API void update_discovery(const etcd_discovery_node::ptr_t &discovery) noexcept;

  LIBATAPP_MACRO_API void add_connection_handle(atapp_connection_handle &handle);
  LIBATAPP_MACRO_API void remove_connection_handle(atapp_connection_handle &handle);
  LIBATAPP_MACRO_API atapp_connection_handle *get_ready_connection_handle() const noexcept;

  LIBATAPP_MACRO_API int32_t push_forward_message(int32_t type, uint64_t &msg_sequence, const void *data,
                                                  size_t data_size, const atapp::protocol::atapp_metadata *metadata);

  LIBATAPP_MACRO_API int32_t retry_pending_messages(const util::time::time_utility::raw_time_t &tick_time,
                                                    int32_t max_count = 0);
  LIBATAPP_MACRO_API void add_waker(util::time::time_utility::raw_time_t wakeup_time);

  UTIL_FORCEINLINE app *get_owner() const noexcept { return owner_; }

  LIBATAPP_MACRO_API size_t get_pending_message_count() const noexcept;
  LIBATAPP_MACRO_API size_t get_pending_message_size() const noexcept;

 private:
  void reset();
  void cancel_pending_messages();

  void trigger_on_receive_forward_response(atapp_connector_impl *connector, atapp_connection_handle *handle,
                                           int32_t type, uint64_t sequence, int32_t error_code, const void *data,
                                           size_t data_size, const atapp::protocol::atapp_metadata *metadata);

 private:
  bool closing_;
  app *owner_;
  util::time::time_utility::raw_time_t nearest_waker_;
  weak_ptr_t watcher_;
  handle_set_t refer_connections_;
  etcd_discovery_node::ptr_t discovery_;

  std::list<pending_message_t> pending_message_;
  size_t pending_message_size_;
#if defined(LIBATAPP_ENABLE_CUSTOM_COUNT_FOR_STD_LIST) && LIBATAPP_ENABLE_CUSTOM_COUNT_FOR_STD_LIST
  size_t pending_message_count_;
#endif

  friend struct atapp_endpoint_bind_helper;
};
}  // namespace atapp


#ifndef LIC_METHOD_DEFINITION
#define LIC_METHOD_DEFINITION

#include <gsl/gsl>

#include "Metadata.h"

namespace lic
{

class MethodDefinition : Metadata
{
public:
    MethodDefinition(Assembly& assembly, MetadataTable table, size_t rid, gsl::byte* row);
    ~MethodDefinition();

    const char* Name() const;
    const gsl::byte* Code() const;
};

}

#endif // !LIC_METHOD_DEFINITION


#ifndef TEST_COMMAND_INCLUDE
#define TEST_COMMAND_INCLUDE

#include <memory>
#include <string>
#include <vector>

#include <gsl/gsl>

#include "statement.h"
#include "yaml.h"

namespace execHelper {
namespace test {
namespace baseUtils {
using CommandKey = std::string;
using CommandKeys = std::vector<CommandKey>;
using Statements = std::vector<std::shared_ptr<Statement>>;

class TestCommand {
  public:
    TestCommand(CommandKey commandKey,
                Statements initialStatements = {}) noexcept;

    std::shared_ptr<Statement> operator[](size_t index) const noexcept;

    Statements::const_iterator begin() const noexcept;
    Statements::const_iterator end() const noexcept;

    size_t size() const noexcept;
    std::string get() const noexcept;
    unsigned int getNbOfStatements() const noexcept;
    unsigned int getNumberOfStatementExecutions()
        const noexcept; // Returns the sum of executions of all statements
    void add(std::shared_ptr<Statement> statement) noexcept;
    void resetExecutions() noexcept;

    void write(gsl::not_null<YamlWriter*> yaml) const noexcept;

  private:
    std::string m_command;
    Statements m_statements;
};
using Commands = std::vector<TestCommand>;

template <typename T, typename... Args>
inline std::shared_ptr<Statement> createStatement(Args... args) noexcept {
    return std::static_pointer_cast<Statement>(std::make_shared<T>(args...));
}
} // namespace baseUtils
} // namespace test
} // namespace execHelper

#endif /* TEST_COMMAND_INCLUDE */


#pragma once

#include <gsl/gsl>


#ifndef CONTROLLERS_IMAGEOPERATIONTOGGLECONTROLLER_H
#define CONTROLLERS_IMAGEOPERATIONTOGGLECONTROLLER_H

#include "imageoperationactioncontroller.h"

#include "s3d/cv/image_operation/image_operation.h"

#include <QAction>

#include <gsl/gsl>

class ImageOperationToggleController : public ImageOperationActionController {
  Q_OBJECT

public:
  ImageOperationToggleController(gsl::not_null<QAction*> action,
                                 gsl::not_null<s3d::image_operation::ImageOperation*> imageOperation);

  void onActionTriggered() override;
};

#endif //CONTROLLERS_IMAGEOPERATIONTOGGLECONTROLLER_H


/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 *     Copyright 2018 Couchbase, Inc.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

#pragma once

#include "engine_error.h"
#include "types.h"

#include <gsl/gsl>

struct ServerDocumentIface {
    virtual ~ServerDocumentIface() = default;

    /**
     * This callback is called from the underlying engine right before
     * it is linked into the list of available documents (it is currently
     * not visible to anyone). The engine should have validated all
     * properties set in the document by the client and the core, and
     * assigned a new CAS number for the document (and sequence number if
     * the underlying engine use those).
     *
     * The callback may at this time do post processing of the document
     * content (it is allowed to modify the content data, but not
     * reallocate or change the size of the data in any way).
     *
     * Given that the engine MAY HOLD LOCKS when calling this function
     * the core is *NOT* allowed to acquire *ANY* locks (except for doing
     * some sort of memory allocation for a temporary buffer).
     *
     * @param cookie The cookie provided to the engine for the storage
     *               command which may (which may hold more context)
     * @param info the items underlying data
     * @return ENGINE_SUCCESS means that the underlying engine should
     *                        proceed to link the item. All other
     *                        error codes means that the engine should
     *                        *NOT* link the item
     */
    virtual ENGINE_ERROR_CODE pre_link(gsl::not_null<const void*> cookie,
                                       item_info& info) = 0;

    /**
     * This callback is called from the underlying engine right before
     * a particular document expires. The callback is responsible for
     * modifying the contents of the itm_info passed in. The updated
     * total size is available in itm_info.nbytes.
     *
     * @param itm_info info pertaining to the item that is to be expired.
     * @return true indicating that the info has been modified in itm_info.
     *         false indicating that there is no data available in itm_info.
     * @throws std::bad_alloc in case of memory allocation failure
     * @throws std::logic_error if the data has grown
     */
    virtual bool pre_expiry(item_info& itm_info) = 0;
};


#pragma once

#include "CesiumGltf/IExtensionJsonHandler.h"
#include "CesiumGltf/Model.h"
#include "CesiumGltf/ReaderLibrary.h"
#include <functional>
#include <gsl/span>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>

namespace CesiumGltf {

struct ReaderContext;

/**
 * @brief The result of reading a glTF model with
 * {@link GltfReader::readModel}.
 */
struct CESIUMGLTFREADER_API ModelReaderResult {
  /**
   * @brief The read model, or std::nullopt if the model could not be read.
   */
  std::optional<Model> model;

  /**
   * @brief Errors, if any, that occurred during the load process.
   */
  std::vector<std::string> errors;

  /**
   * @brief Warnings, if any, that occurred during the load process.
   */
  std::vector<std::string> warnings;
};

/**
 * @brief The result of reading an image with
 * {@link GltfReader::readImage}.
 */
struct CESIUMGLTFREADER_API ImageReaderResult {

  /**
   * @brief The {@link ImageCesium} that was read.
   *
   * This will be `std::nullopt` if the image could not be read.
   */
  std::optional<ImageCesium> image;

  /**
   * @brief Error messages that occurred while trying to read the image.
   */
  std::vector<std::string> errors;

  /**
   * @brief Warning messages that occurred while reading the image.
   */
  std::vector<std::string> warnings;
};

/**
 * @brief The state of a glTF extension.
 */
enum class ExtensionState {
  /**
   * @brief The extension is enabled.
   *
   * If a statically-typed class is available for the extension, it will be
   * used. Otherwise the extension will be represented as a
   * {@link CesiumUtility::JsonValue}.
   */
  Enabled,

  /**
   * @brief The extension is enabled but will always be deserialized as a
   * {@link CesiumUtility::JsonValue}.
   *
   * Even if a statically-typed class is available for the extension, it will
   * not be used.
   */
  JsonOnly,

  /**
   * @brief The extension is disabled.
   *
   * It will not be represented in the loaded model at all.
   */
  Disabled
};

/**
 * @brief Options for how to read a glTF.
 */
struct CESIUMGLTFREADER_API ReadModelOptions {
  /**
   * @brief Whether data URLs in buffers and images should be automatically
   * decoded as part of the load process.
   */
  bool decodeDataUrls = true;

  /**
   * @brief Whether data URLs should be cleared after they are successfully
   * decoded.
   *
   * This reduces the memory usage of the model.
   */
  bool clearDecodedDataUrls = true;

  /**
   * @brief Whether embedded images in {@link Model::buffers} should be
   * automatically decoded as part of the load process.
   *
   * The {@link ImageSpec::mimeType} property is ignored, and instead the
   * [stb_image](https://github.com/nothings/stb) library is used to decode
   * images in `JPG`, `PNG`, `TGA`, `BMP`, `PSD`, `GIF`, `HDR`, or `PIC` format.
   */
  bool decodeEmbeddedImages = true;

  /**
   * @brief Whether geometry compressed using the `KHR_draco_mesh_compression`
   * extension should be automatically decoded as part of the load process.
   */
  bool decodeDraco = true;
};

/**
 * @brief Reads glTF models and images.
 */
class CESIUMGLTFREADER_API GltfReader {
public:
  /**
   * @brief Constructs a new instance.
   */
  GltfReader();

  /**
   * @brief Registers an extension for a glTF object.
   *
   * @tparam TExtended The glTF object to extend.
   * @tparam TExtensionHandler The extension's
   * {@link CesiumJsonReader::JsonHandler}.
   * @param extensionName The name of the extension.
   */
  template <typename TExtended, typename TExtensionHandler>
  void registerExtension(const std::string& extensionName) {
    auto it =
        this->_extensions.emplace(extensionName, ObjectTypeToHandler()).first;
    it->second.insert_or_assign(
        TExtended::TypeName,
        ExtensionReaderFactory([](const ReaderContext& context) {
          return std::make_unique<TExtensionHandler>(context);
        }));
  }

  /**
   * @brief Registers an extension for a glTF object.
   *
   * The extension name is obtained from `TExtensionHandler::ExtensionName`.
   *
   * @tparam TExtended The glTF object to extend.
   * @tparam TExtensionHandler The extension's
   * {@link CesiumJsonReader::JsonHandler}.
   */
  template <typename TExtended, typename TExtensionHandler>
  void registerExtension() {
    auto it =
        this->_extensions
            .emplace(TExtensionHandler::ExtensionName, ObjectTypeToHandler())
            .first;
    it->second.insert_or_assign(
        TExtended::TypeName,
        ExtensionHandlerFactory([](const ReaderContext& context) {
          return std::make_unique<TExtensionHandler>(context);
        }));
  }

  /**
   * @brief Enables or disables a glTF extension.
   *
   * By default, all extensions are enabled. When an enabled extension is
   * encountered in the source glTF, it is read into a statically-typed
   * extension class, if one is registered, or into a
   * {@link CesiumUtility::JsonValue} if not.
   *
   * When a disabled extension is encountered in the source glTF, it is ignored
   * completely.
   *
   * An extension may also be set to `ExtensionState::JsonOnly`, in which case
   * it will be read into a {@link CesiumUtility::JsonValue} even if a
   * statically-typed extension class is registered.
   *
   * @param extensionName The name of the extension to be enabled or disabled.
   * @param newState The new state for the extension.
   */
  void
  setExtensionState(const std::string& extensionName, ExtensionState newState);

  /**
   * @brief Reads a glTF or binary glTF (GLB) from a buffer.
   *
   * @param data The buffer from which to read the glTF.
   * @param options Options for how to read the glTF.
   * @return The result of reading the glTF.
   */
  ModelReaderResult readModel(
      const gsl::span<const std::byte>& data,
      const ReadModelOptions& options = ReadModelOptions()) const;

  /**
   * @brief Reads an image from a buffer.
   *
   * The [stb_image](https://github.com/nothings/stb) library is used to decode
   * images in `JPG`, `PNG`, `TGA`, `BMP`, `PSD`, `GIF`, `HDR`, or `PIC` format.
   *
   * @param data The buffer from which to read the image.
   * @return The result of reading the image.
   */
  ImageReaderResult readImage(const gsl::span<const std::byte>& data) const;

  std::unique_ptr<IExtensionJsonHandler> createExtensionHandler(
      const ReaderContext& context,
      const std::string_view& extensionName,
      const std::string& extendedObjectType) const;

private:
  using ExtensionHandlerFactory =
      std::function<std::unique_ptr<IExtensionJsonHandler>(
          const ReaderContext&)>;
  using ObjectTypeToHandler = std::map<std::string, ExtensionHandlerFactory>;
  using ExtensionNameMap = std::map<std::string, ObjectTypeToHandler>;

  ExtensionNameMap _extensions;
  std::unordered_map<std::string, ExtensionState> _extensionStates;
};

} // namespace CesiumGltf


#ifndef CONSUMER_H
#define CONSUMER_H

#include <gsl/gsl>
#include <producer.h>

class consumer
{
public:

    explicit consumer(gsl::not_null<producer *> p)
    { p->print_msg(); }

    ~consumer() = default;

public:

    consumer(consumer &&) noexcept = default;
    consumer &operator=(consumer &&) noexcept = default;

    consumer(const consumer &) = delete;
    consumer &operator=(const consumer &) = delete;
};

#endif


#ifndef EXECUTE_INCLUDE
#define EXECUTE_INCLUDE

#include <filesystem>
#include <string>
#include <vector>

#include <gsl/string_span>

#include "path.h"

namespace execHelper {
namespace test {
namespace baseUtils {
namespace execution {
using CommandLine = std::vector<std::string>;

int execute(const CommandLine& commandLine,
            const Path& workingDir = std::filesystem::current_path()) noexcept;
} // namespace execution
} // namespace baseUtils
} // namespace test
} // namespace execHelper

#endif /* EXECUTE_INCLUDE */


#pragma once
//#include <gsl/string_span>
namespace Util {
	//static gsl::string_span<> toLower(gsl::string_span<> const&);
}; // namespace util


#pragma once

#include "rev/gl/Context.h"
#include "rev/gl/Resource.h"

#include <gsl/span>

namespace rev {

using Buffer = Resource<singleCreate<gl::genBuffers>, singleDestroy<gl::deleteBuffers>>;

} // namespace rev


#pragma once

#include "Cesium3DTilesReader/Library.h"

#include <Cesium3DTiles/Subtree.h>
#include <CesiumJsonReader/ExtensionReaderContext.h>

#include <gsl/span>

#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>

namespace Cesium3DTilesReader {

/**
 * @brief The result of reading a subtree with
 * {@link SubtreeReader::readSubtree}.
 */
struct CESIUM3DTILESREADER_API SubtreeReaderResult {
  /**
   * @brief The read subtree, or std::nullopt if the subtree could not be read.
   */
  std::optional<Cesium3DTiles::Subtree> subtree;

  /**
   * @brief Errors, if any, that occurred during the load process.
   */
  std::vector<std::string> errors;

  /**
   * @brief Warnings, if any, that occurred during the load process.
   */
  std::vector<std::string> warnings;
};

/**
 * @brief Reads subtrees.
 */
class CESIUM3DTILESREADER_API SubtreeReader {
public:
  /**
   * @brief Constructs a new instance.
   */
  SubtreeReader();

  /**
   * @brief Gets the context used to control how extensions are loaded from a
   * subtree.
   */
  CesiumJsonReader::ExtensionReaderContext& getExtensions();

  /**
   * @brief Gets the context used to control how extensions are loaded from a
   * subtree.
   */
  const CesiumJsonReader::ExtensionReaderContext& getExtensions() const;

  /**
   * @brief Reads a subtree.
   *
   * @param data The buffer from which to read the subtree.
   * @param options Options for how to read the subtree.
   * @return The result of reading the subtree.
   */
  SubtreeReaderResult readSubtree(const gsl::span<const std::byte>& data) const;

private:
  CesiumJsonReader::ExtensionReaderContext _context;
};

} // namespace Cesium3DTilesReader


#pragma once

#include "iobserver.h"
#include "irequest.h"
#include <gsl/pointers>
#include <atomic>
#include <vector>
#include <Windows.h>

namespace died
{
	namespace observer
	{
		namespace callback
		{
			unsigned WINAPI start_thread_proc(LPVOID);
			VOID CALLBACK terminate_proc(__in ULONG_PTR);
			VOID CALLBACK add_directory_proc(__in ULONG_PTR);
		}
	}

	class observer_impl final : public iobserver
	{
	public:
		observer_impl(idirectory_watcher*);

		// diable copy
		observer_impl(observer_impl const&) = delete;
		observer_impl& operator=(observer_impl const&) = delete;

		// interface
	private:
		unsigned int do_inc_request() final;
		unsigned int do_dec_request() final;
		idirectory_watcher* do_get_watcher() const final;

	private:
		void run();
		bool terminated() const;
		bool empty_request() const;
		bool add_directory(irequest* pBlock);
		void request_termination();

		friend unsigned WINAPI observer::callback::start_thread_proc(LPVOID);
		friend VOID CALLBACK observer::callback::terminate_proc(__in ULONG_PTR);
		friend VOID CALLBACK observer::callback::add_directory_proc(__in ULONG_PTR);

	private:
		gsl::not_null<idirectory_watcher*> mDirWatcher;
		std::atomic_bool mTerminated{};
		std::atomic_uint mOutstandingRequests{};
		std::vector<gsl::not_null<irequest*>> mBlocks;
	};
}


#ifndef _AVA_COMMON_SUPPORT_STD_SPAN_H_
#define _AVA_COMMON_SUPPORT_STD_SPAN_H_
#pragma once

#include <gsl/span>
#include <gsl/span_ext>

namespace std {
using gsl::span;
}  // namespace std

#define EMPTY_CHAR_SPAN std::span<const char>()

#define STRING_AS_SPAN(STR_VAR) std::span<const char>((STR_VAR).data(), (STR_VAR).length())

#define VECTOR_AS_SPAN(VEC_VAR) std::span<const decltype(VEC_VAR)::value_type>((VEC_VAR).data(), (VEC_VAR).size())

#define VECTOR_AS_CHAR_SPAN(VEC_VAR)                                      \
  std::span<const char>(reinterpret_cast<const char *>((VEC_VAR).data()), \
                        sizeof(decltype(VEC_VAR)::value_type) * (VEC_VAR).size())

#endif  // _AVA_COMMON_SUPPORT_STD_SPAN_H_