// SPDX-License-Identifier: BSD-3-Clause
#ifndef SUBSTRATE_UTILITIES
#define SUBSTRATE_UTILITIES

#include <cstdint>
#include <type_traits>
#include <typeinfo>
#include <array>
#include <memory>
#include <string>
#include <vector>
#include <chrono>
#include <utility>
#include <substrate/internal/defs>
#include <substrate/promotion_helpers>

namespace substrate
{
/* C++ 14 helpers */
#if __cplusplus >= 201402L
	template<bool B, class T = void>
	using enable_if_t = typename std::enable_if_t<B, T>;

	template<typename T>
	using underlying_type_t = typename std::underlying_type_t<T>;

	template<bool B, class T, class F>
	using conditional_t = typename std::conditional_t<B, T, F>;

	template<class T>
	using remove_reference_t = typename std::remove_reference_t<T>;

	template<class T>
	using remove_cv_t = typename std::remove_cv_t<T>;

	template<class T>
	using remove_const_t = typename std::remove_const_t<T>;

	template<class T>
	using remove_volatile_t = typename std::remove_volatile_t<T>;

	template<class T>
	using remove_extent_t = typename std::remove_extent_t<T>;

	template<class T>
	using add_cv_t = typename std::add_cv_t<T>;

	template<class T>
	using add_const_t = typename std::add_const_t<T>;

	template<class T>
	using add_volatile_t = typename std::add_volatile_t<T>;

	template<class T>
	using add_lvalue_reference_t = typename std::add_lvalue_reference_t<T>;

	template<class T>
	using add_rvalue_reference_t = typename std::add_rvalue_reference_t<T>;

	template<class T>
	using remove_pointer_t = typename std::remove_pointer_t<T>;

	template<class T>
	using add_pointer_t = typename std::add_pointer_t<T>;

	template<class T>
	using make_signed_t = typename std::make_signed_t<T>;

	template<class T>
	using make_unsigned_t = typename std::make_unsigned_t<T>;
#else
	template<bool B, class T = void>
	using enable_if_t = typename std::enable_if<B,T>::type;

	template<typename T>
	using underlying_type_t = typename std::underlying_type<T>::type;

	template<bool B, class T, class F>
	using conditional_t = typename std::conditional<B, T, F>::type;

	template<class T>
	using remove_reference_t = typename std::remove_reference<T>::type;

	template<class T>
	using remove_cv_t = typename std::remove_cv<T>::type;

	template<class T>
	using remove_const_t = typename std::remove_const<T>::type;

	template<class T>
	using remove_volatile_t = typename std::remove_volatile<T>::type;

	template<class T>
	using remove_extent_t = typename std::remove_extent<T>::type;

	template<class T>
	using add_cv_t = typename std::add_cv<T>::type;

	template<class T>
	using add_const_t = typename std::add_const<T>::type;

	template<class T>
	using add_volatile_t = typename std::add_volatile<T>::type;

	template<class T>
	using add_lvalue_reference_t = typename std::add_lvalue_reference<T>::type;

	template<class T>
	using add_rvalue_reference_t = typename std::add_rvalue_reference<T>::type;

	template<class T>
	using remove_pointer_t = typename std::remove_pointer<T>::type;

	template<class T>
	using add_pointer_t = typename std::add_pointer<T>::type;

	template<class T>
	using make_signed_t = typename std::make_signed<T>::type;

	template<class T>
	using make_unsigned_t = typename std::make_unsigned<T>::type;
#endif

/* C++ 17 helpers */
#if __cplusplus >= 201703L
	template<class T, class U>
	constexpr inline bool is_same_v = std::is_same_v<T, U>;

	template<class T>
	constexpr inline bool is_null_pointer_v = std::is_null_pointer_v<T>;

	template<class T>
	constexpr inline bool is_void_v = std::is_void_v<T>;

	template<class T>
	constexpr inline bool is_integral_v = std::is_integral_v<T>;

	template<class T>
	constexpr inline bool is_floating_point_v = std::is_floating_point_v<T>;

	template<class T>
	constexpr inline bool is_array_v = std::is_array_v<T>;

	template<class T>
	constexpr inline bool is_enum_v = std::is_enum_v<T>;

	template<class T>
	constexpr inline bool is_union_v = std::is_union_v<T>;

	template<class T>
	constexpr inline bool is_class_v = std::is_class_v<T>;

	template<class T>
	constexpr inline bool is_function_v = std::is_function_v<T>;

	template<class T>
	constexpr inline bool is_pointer_v = std::is_pointer_v<T>;

	template<class T>
	constexpr inline bool is_lvalue_reference_v = std::is_lvalue_reference_v<T>;

	template<class T>
	constexpr inline bool is_rvalue_reference_v = std::is_rvalue_reference_v<T>;

	template<class T>
	constexpr inline bool is_member_object_pointer_v = std::is_member_object_pointer_v<T>;

	template<class T>
	constexpr inline bool is_member_function_pointer_v = std::is_member_function_pointer_v<T>;

	template<class T>
	constexpr inline bool is_fundamental_v = std::is_fundamental_v<T>;

	template<class T>
	constexpr inline bool is_arithmetic_v = std::is_arithmetic_v<T>;

	template<class T>
	constexpr inline bool is_object_v = std::is_object_v<T>;

	template<class T>
	constexpr inline bool is_compound_v = std::is_compound_v<T>;

	template<class T>
	constexpr inline bool is_reference_v = std::is_reference_v<T>;

	template<class T>
	constexpr inline bool is_member_pointer_v = std::is_member_pointer_v<T>;

	template<class T>
	constexpr inline bool is_scalar_v = std::is_scalar_v<T>;

	template<class T, class U>
	constexpr inline bool is_base_of_v = std::is_base_of_v<T, U>;

	template<class T, class U>
	constexpr inline bool is_convertible_v = std::is_convertible_v<T, U>;

	template<class T>
	constexpr inline bool has_virtual_destructor_v = std::has_virtual_destructor_v<T>;

	template<class T>
	constexpr inline bool is_destructible_v = std::is_destructible_v<T>;

	template<class T>
	constexpr inline bool is_trivially_destructible_v = std::is_trivially_destructible_v<T>;

	template<class T>
	constexpr inline bool is_nothrow_destructible_v = std::is_nothrow_destructible_v<T>;

	template<class T>
	constexpr inline bool is_move_assignable_v = std::is_move_assignable_v<T>;

	template<class T>
	constexpr inline bool is_trivially_move_assignable_v = std::is_trivially_move_assignable_v<T>;

	template<class T>
	constexpr inline bool is_nothrow_move_assignable_v = std::is_nothrow_move_assignable_v<T>;

	template<class T>
	constexpr inline bool is_copy_assignable_v = std::is_copy_assignable_v<T>;

	template<class T>
	constexpr inline bool is_trivially_copy_assignable_v = std::is_trivially_copy_assignable_v<T>;

	template<class T>
	constexpr inline bool is_nothrow_copy_assignable_v = std::is_nothrow_copy_assignable_v<T>;

	template<class T, class U>
	constexpr inline bool is_assignable_v = std::is_assignable_v<T, U>;

	template<class T, class U>
	constexpr inline bool is_trivially_assignable_v = std::is_trivially_assignable_v<T, U>;

	template<class T, class U>
	constexpr inline bool is_nothrow_assignable_v = std::is_nothrow_assignable_v<T, U>;

	template<class T>
	constexpr inline bool is_move_constructible_v = std::is_move_constructible_v<T>;

	template<class T>
	constexpr inline bool is_trivially_move_constructible_v = std::is_trivially_move_constructible_v<T>;

	template<class T>
	constexpr inline bool is_nothrow_move_constructible_v = std::is_nothrow_move_constructible_v<T>;

	template<class T>
	constexpr inline bool is_copy_constructible_v = std::is_copy_constructible_v<T>;

	template<class T>
	constexpr inline bool is_trivially_copy_constructible_v = std::is_trivially_copy_constructible_v<T>;

	template<class T>
	constexpr inline bool is_nothrow_copy_constructible_v = std::is_nothrow_copy_constructible_v<T>;

	template<class T>
	constexpr inline bool is_default_constructible_v = std::is_default_constructible_v<T>;

	template<class T>
	constexpr inline bool is_trivially_default_constructible_v = std::is_trivially_default_constructible_v<T>;

	template<class T>
	constexpr inline bool is_nothrow_default_constructible_v = std::is_nothrow_default_constructible_v<T>;

	template<class T, class... Args>
	constexpr inline bool is_constructible_v = std::is_constructible_v<T, Args...>;

	template<class T, class... Args>
	constexpr inline bool is_trivially_constructible_v = std::is_trivially_constructible_v<T, Args...>;

	template<class T, class... Args>
	constexpr inline bool is_nothrow_constructible_v = std::is_nothrow_constructible_v<T, Args...>;

	template<class T>
	constexpr inline bool is_unsigned_v = std::is_unsigned_v<T>;

	template<class T>
	constexpr inline bool is_signed_v = std::is_signed_v<T>;

	template<class T>
	constexpr inline bool is_abstract_v = std::is_abstract_v<T>;

	template<class T>
	constexpr inline bool is_polymorphic_v = std::is_polymorphic_v<T>;

	template<class T>
	constexpr inline bool is_empty_v = std::is_empty_v<T>;

	template<class T>
	constexpr inline bool is_pod_v = std::is_trivial_v<T> && std::is_standard_layout_v<T>;

	template<class T>
	constexpr inline bool is_standard_layout_v = std::is_standard_layout_v<T>;

	template<class T>
	constexpr inline bool is_trivially_copyable_v = std::is_trivially_copyable_v<T>;

	template<class T>
	constexpr inline bool is_trivial_v = std::is_trivial_v<T>;

	template<class T>
	constexpr inline bool is_volatile_v = std::is_volatile_v<T>;

	template<class T>
	constexpr inline bool is_const_v = std::is_const_v<T>;

#if __cplusplus < 202002L
	template<class T>
	constexpr inline bool is_literal_type_v = std::is_literal_type_v<T>;
#endif

	template<class T>
	constexpr inline bool is_final_v = std::is_final_v<T>;
#else
	/* Due to the fact these use variable templates, they are only supported in C++ 14 */
#if __cplusplus >= 201402L
	template<class T, class U>
	constexpr bool is_same_v = std::is_same<T, U>::value;

	template<class T>
	constexpr bool is_null_pointer_v = std::is_null_pointer<T>::value;

	template<class T>
	constexpr bool is_void_v = std::is_void<T>::value;

	template<class T>
	constexpr bool is_integral_v = std::is_integral<T>::value;

	template<class T>
	constexpr bool is_floating_point_v = std::is_floating_point<T>::value;

	template<class T>
	constexpr bool is_array_v = std::is_array<T>::value;

	template<class T>
	constexpr bool is_enum_v = std::is_enum<T>::value;

	template<class T>
	constexpr bool is_union_v = std::is_union<T>::value;

	template<class T>
	constexpr bool is_class_v = std::is_class<T>::value;

	template<class T>
	constexpr bool is_function_v = std::is_function<T>::value;

	template<class T>
	constexpr bool is_pointer_v = std::is_pointer<T>::value;

	template<class T>
	constexpr bool is_lvalue_reference_v = std::is_lvalue_reference<T>::value;

	template<class T>
	constexpr bool is_rvalue_reference_v = std::is_rvalue_reference<T>::value;

	template<class T>
	constexpr bool is_member_object_pointer_v = std::is_member_object_pointer<T>::value;

	template<class T>
	constexpr bool is_member_function_pointer_v = std::is_member_function_pointer<T>::value;

	template<class T>
	constexpr bool is_fundamental_v = std::is_fundamental<T>::value;

	template<class T>
	constexpr bool is_arithmetic_v = std::is_arithmetic<T>::value;

	template<class T>
	constexpr bool is_object_v = std::is_object<T>::value;

	template<class T>
	constexpr bool is_compound_v = std::is_compound<T>::value;

	template<class T>
	constexpr bool is_reference_v = std::is_reference<T>::value;

	template<class T>
	constexpr bool is_member_pointer_v = std::is_member_pointer<T>::value;

	template<class T>
	constexpr bool is_scalar_v = std::is_scalar<T>::value;

	template<class T, class U>
	constexpr bool is_base_of_v = std::is_base_of<T, U>::value;

	template<class T, class U>
	constexpr bool is_convertible_v = std::is_convertible<T, U>::value;

	template<class T>
	constexpr bool has_virtual_destructor_v = std::has_virtual_destructor<T>::value;

	template<class T>
	constexpr bool is_destructible_v = std::is_destructible<T>::value;

	template<class T>
	constexpr bool is_trivially_destructible_v = std::is_trivially_destructible<T>::value;

	template<class T>
	constexpr bool is_nothrow_destructible_v = std::is_nothrow_destructible<T>::value;

	template<class T>
	constexpr bool is_move_assignable_v = std::is_move_assignable<T>::value;

	template<class T>
	constexpr bool is_trivially_move_assignable_v = std::is_trivially_move_assignable<T>::value;

	template<class T>
	constexpr bool is_nothrow_move_assignable_v = std::is_nothrow_move_assignable<T>::value;

	template<class T>
	constexpr bool is_copy_assignable_v = std::is_copy_assignable<T>::value;

	template<class T>
	constexpr bool is_trivially_copy_assignable_v = std::is_trivially_copy_assignable<T>::value;

	template<class T>
	constexpr bool is_nothrow_copy_assignable_v = std::is_nothrow_copy_assignable<T>::value;

	template<class T, class U>
	constexpr bool is_assignable_v = std::is_assignable<T, U>::value;

	template<class T, class U>
	constexpr bool is_trivially_assignable_v = std::is_trivially_assignable<T, U>::value;

	template<class T, class U>
	constexpr bool is_nothrow_assignable_v = std::is_nothrow_assignable<T, U>::value;

	template<class T>
	constexpr bool is_move_constructible_v = std::is_move_constructible<T>::value;

	template<class T>
	constexpr bool is_trivially_move_constructible_v = std::is_trivially_move_constructible<T>::value;

	template<class T>
	constexpr bool is_nothrow_move_constructible_v = std::is_nothrow_move_constructible<T>::value;

	template<class T>
	constexpr bool is_copy_constructible_v = std::is_copy_constructible<T>::value;

	template<class T>
	constexpr bool is_trivially_copy_constructible_v = std::is_trivially_copy_constructible<T>::value;

	template<class T>
	constexpr bool is_nothrow_copy_constructible_v = std::is_nothrow_copy_constructible<T>::value;

	template<class T>
	constexpr bool is_default_constructible_v = std::is_default_constructible<T>::value;

	template<class T>
	constexpr bool is_trivially_default_constructible_v = std::is_trivially_default_constructible<T>::value;

	template<class T>
	constexpr bool is_nothrow_default_constructible_v = std::is_nothrow_default_constructible<T>::value;

	template<class T, class... Args>
	constexpr bool is_constructible_v = std::is_constructible<T, Args...>::value;

	template<class T, class... Args>
	constexpr bool is_trivially_constructible_v = std::is_trivially_constructible<T, Args...>::value;

	template<class T, class... Args>
	constexpr bool is_nothrow_constructible_v = std::is_nothrow_constructible<T, Args...>::value;

#if defined(__clang__) && (defined(__MINGW32__) || defined(__MINGW64__))
	// MSYS Clang (all standards) leaks the signedness of unscoped enums
	template <class T>
	constexpr bool is_unsigned_v = std::is_integral<T>::value && std::is_unsigned<T>::value;
#else
	template <class T>
	constexpr bool is_unsigned_v = std::is_unsigned<T>::value;
#endif

	template<class T>
	constexpr bool is_signed_v = std::is_signed<T>::value;

	template<class T>
	constexpr bool is_abstract_v = std::is_abstract<T>::value;

	template<class T>
	constexpr bool is_polymorphic_v = std::is_polymorphic<T>::value;

	template<class T>
	constexpr bool is_empty_v = std::is_empty<T>::value;

	template<class T>
	constexpr bool is_pod_v = std::is_trivial<T>::value && std::is_standard_layout<T>::value;

	template<class T>
	constexpr bool is_standard_layout_v = std::is_standard_layout<T>::value;

	template<class T>
	constexpr bool is_trivially_copyable_v = std::is_trivially_copyable<T>::value;

	template<class T>
	constexpr bool is_trivial_v = std::is_trivial<T>::value;

	template<class T>
	constexpr bool is_volatile_v = std::is_volatile<T>::value;

	template<class T>
	constexpr bool is_const_v = std::is_const<T>::value;

	template<class T>
	constexpr bool is_literal_type_v = std::is_literal_type<T>::value;

#elif defined(SUBSTRATE_CXX11_COMPAT)
	#include <substrate/internal/cxx11_compat>
#endif
#endif

	template<typename T>
	using pure_type_t = typename std::remove_cv< remove_pointer_t<remove_reference_t<T>>>::type;

	template <typename Argument, typename Value>
	using requires_reference = std::integral_constant<bool, std::is_pointer<Argument>::value && !std::is_pointer<remove_reference_t<Value>>::value>;

	template <typename Argument, typename Value>
	using requires_dereference = std::integral_constant<bool, !std::is_pointer<Argument>::value && std::is_pointer<remove_reference_t<Value>>::value>;

	namespace internal
	{
		template<typename> struct is_char : std::false_type { };
		template<> struct is_char<char> : std::true_type { };

		template<typename> struct is_boolean : public std::false_type { };
		template<> struct is_boolean<bool> : public std::true_type { };
	}

	template<typename T> struct is_char : public std::integral_constant<bool,
		internal::is_char<remove_cv_t<T>>::value> { };

	template<typename T> struct is_boolean : public std::integral_constant<bool,
		internal::is_boolean<remove_cv_t<T>>::value> { };

	template<typename T> struct is_numeric : public std::integral_constant<bool,
		std::is_integral<T>::value && !is_boolean<T>::value && !is_char<T>::value> { };

	template<typename T> struct is_duration : public std::false_type { };
	template<typename R, typename P> struct is_duration<std::chrono::duration<R, P>> : public std::true_type { };

	template<typename T> struct is_pod : public std::integral_constant<bool,
		std::is_trivial<T>::value && std::is_standard_layout<T>::value> { };

#if defined(_MSC_VER) || !defined(__GNUC__) || defined(__GXX_RTTI)
	/* typename decoding */
	SUBSTRATE_NOWARN_UNUSED(SUBSTRATE_CLS_API std::string decode_typename(const char *mangledName));

	template<typename T> static std::string decode_typename()
	{
		using _type_ref = typename substrate::remove_reference_t<T>;
		auto _type_name{decode_typename(typeid(_type_ref).name())};

		if (std::is_const<_type_ref>::value)
			_type_name += " const";
		if (std::is_volatile<_type_ref>::value)
			_type_name += " volatile";
		if (std::is_lvalue_reference<T>::value)
			_type_name += "&";
		if (std::is_rvalue_reference<T>::value)
			_type_name += "&&";

		return _type_name;
	}
#endif

	/* C++11 and newer, version independent std::unique_ptr<> helpers */
	template<typename T> struct makeUnique_t { using uniqueType = std::unique_ptr<T>; };
	template<typename T> struct makeUnique_t<T []> { using arrayType = std::unique_ptr<T []>; };
	template<typename T, size_t N> struct makeUnique_t<T [N]> { struct invalidType { }; };

	template<typename T, typename... args_t> inline typename makeUnique_t<T>::uniqueType
		make_unique(args_t &&...args)
	{
		using type_t = remove_const_t<T>;
		return std::unique_ptr<T>{new type_t{std::forward<args_t>(args)...}};
	}

	template<typename T> inline typename makeUnique_t<T>::arrayType
		make_unique(const size_t num)
	{
		using type_t = remove_const_t<remove_extent_t<T>>;
		return std::unique_ptr<T>{new type_t[num]{{}}};
	}

	template<typename T, typename... args_t> inline typename makeUnique_t<T>::invalidType
		make_unique(args_t &&...) noexcept = delete;

	/* C++11 and newer, version independent nothrow std::unique_ptr<> helpers */
	template<typename T, typename... Args> inline typename makeUnique_t<T>::uniqueType
		make_unique_nothrow(Args &&...args) noexcept(noexcept(T{std::forward<Args>(args)...}))
	{
		using type_t = remove_const_t<T>;
		return std::unique_ptr<T>{new (std::nothrow) type_t{std::forward<Args>(args)...}};
	}

	template<typename T> inline typename makeUnique_t<T>::arrayType
		make_unique_nothrow(const size_t num) noexcept
	{
		using type_t = remove_const_t<remove_extent_t<T>>;
		return std::unique_ptr<T>{new (std::nothrow) type_t[num]{{}}};
	}

	template<typename T, typename... Args> inline typename makeUnique_t<T>::invalidType
		make_unique_nothrow(Args &&...) noexcept = delete;

	/* std::array<> helper */
	namespace internal
	{
#if __cplusplus < 201402L
		template<std::size_t... seq> struct indexSequence_t
		{
			static_assert(sizeof...(seq) > 0, "Cannot define a zero-length index sequence");
			using value_type = std::size_t;
			constexpr static std::size_t size() noexcept { return sizeof...(seq); }
		};

		template<std::size_t N> struct sequence_t
		{
			template<std::size_t... sequence> static auto expand(const indexSequence_t<sequence...> &) ->
				indexSequence_t<sequence..., N>;
			template<typename seq> static auto next() -> decltype(expand(typename seq::sequence{}));
			using sequence = decltype(next<sequence_t<N - 1>>());
		};

		template<> struct sequence_t<0> { using sequence = indexSequence_t<0>; };
		template<std::size_t N> using makeIndexSequence = typename sequence_t<N - 1>::sequence;
#else
		template<std::size_t... seq> using indexSequence_t = std::index_sequence<seq...>;
		template<std::size_t N> using makeIndexSequence = std::make_index_sequence<N>;
#endif
		template<typename T, size_t N, size_t... index> constexpr std::array<T, N>
			makeArray(T (&&elems)[N], indexSequence_t<index...>) // NOLINT(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays)
		{
			return {{elems[index]...}};
		}
	}

	template<typename T, size_t N> constexpr std::array<T, N>
		make_array(T (&&elems)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays)
	{
		return internal::makeArray(std::move(elems), internal::makeIndexSequence<N>{});
	}

	template<typename T> struct has_nullable_ctor final
	{
		template<typename U>
		static std::true_type _ctor(decltype(U(std::nullptr_t()))*);
		template<typename U>
		static std::false_type _ctor(...);

		static const bool value = std::is_same<decltype(_ctor<T>(nullptr)), std::true_type>::value;
	};

#if __cplusplus >= 201402L
	template<typename T>
	constexpr bool has_nullable_ctor_v = has_nullable_ctor<T>::value;
#endif

	/* LEB128 encoding and decoding */
	template<typename T> substrate::enable_if_t<std::is_integral<T>::value &&
		std::is_signed<T>::value, std::vector<uint8_t>> leb128_encode(T num)
	{
		using U = typename std::make_unsigned<T>::type;
		using V = promoted_type_t<U>;
		std::vector<uint8_t> enc{};
		bool more{true};
		while (more)
		{
			auto byte{uint8_t(static_cast<V>(num) & 0x7FU)};
			num >>= 7U;

			if ((!num && !(byte & 0x40U)) || (num == -1 && (byte & 0x40U)))
				more = false;
			else
				byte |= 0x80U;
			enc.emplace_back(byte);
		}
		return enc;
	}

	template<typename T> substrate::enable_if_t<std::is_integral<T>::value &&
		std::is_signed<T>::value, T> leb128_decode(const std::vector<uint8_t> &vec)
	{
		using U = typename std::make_unsigned<T>::type;
		using V = promoted_type_t<U>;
		V enc{};
		size_t shift{};
		for (const auto &byte : vec)
		{
			enc |= V{byte & 0x7FU} << shift;
			shift += 7U;
		}
		if (shift && shift < sizeof(T) * 8U)
		{
			--shift;
			for (size_t i{1U}; i < (sizeof(T) * 8U) - shift; ++i)
				enc |= (enc & (V{1U} << shift)) << i;
		}
		return static_cast<T>(enc);
	}

	template<typename T> substrate::enable_if_t<std::is_integral<T>::value &&
		std::is_unsigned<T>::value, std::vector<uint8_t>> leb128_encode(const T value)
	{
		using U = typename std::make_unsigned<T>::type;
		using V = promoted_type_t<U>;
		auto num{static_cast<V>(value)};
		std::vector<uint8_t> enc{};
		do
		{
			uint8_t byte = num & 0x7FU;
			num >>= 7U;
			if (num != 0U)
				byte |= 0x80U;
			enc.emplace_back(byte);
		}
		while (num != 0U);
		return enc;
	}

	template<typename T> substrate::enable_if_t<std::is_integral<T>::value &&
		std::is_unsigned<T>::value, T> leb128_decode(const std::vector<uint8_t> &vec)
	{
		using U = typename std::make_unsigned<T>::type;
		using V = promoted_type_t<U>;
		V enc{};
		size_t shift{};
		for (const auto &byte : vec)
		{
			enc |= V{byte & 0x7FU} << shift;
			shift += 7U;
		}
		return static_cast<T>(enc);
	}

#if __cplusplus >= 201902L
	template<typename C>
		[[nodiscard]] inline constexpr auto ssize(const C &c) noexcept -> decltype(std::ssize(c))
		{ return std::ssize(c); }
#elif __cplusplus >= 201402L
	template<typename C>
		SUBSTRATE_NO_DISCARD(inline constexpr auto ssize(const C &c) noexcept -> std::common_type_t<std::ptrdiff_t,
						  std::make_signed_t<decltype(c.size())>>)
		{ return c.size(); }
#else
	template<typename C>
		SUBSTRATE_NO_DISCARD(inline std::ptrdiff_t ssize(const C &c) noexcept)
		{ return static_cast<std::ptrdiff_t>(c.size()); }
#endif

// Otherwise covered by the clause above
#if __cplusplus < 201902L
	template<typename T, std::size_t N>
		SUBSTRATE_NO_DISCARD(inline constexpr std::ptrdiff_t ssize(const T (&)[N]) noexcept)
		{ return static_cast<std::ptrdiff_t>(N); }
#endif

#if __cplusplus >= 201411L
	template<typename C>
		SUBSTRATE_NO_DISCARD(inline constexpr auto size(const C &c) noexcept -> decltype(std::size(c)))
		{ return std::size(c); }
#elif __cplusplus >= 201402L
	template<typename C>
		SUBSTRATE_NO_DISCARD(inline constexpr auto size(const C &c) noexcept -> decltype(c.size()))
		{ return c.size(); }
#else
	template<typename C>
		SUBSTRATE_NO_DISCARD(inline std::size_t size(const C &c) noexcept)
		{ return c.size(); }
#endif

// Otherwise covered by the clause above
#if __cplusplus < 201411L
	template<typename T, std::size_t N>
		SUBSTRATE_NO_DISCARD(inline constexpr std::size_t size(const T (&)[N]) noexcept)
		{ return N; }
#endif

#if __cplusplus >= 201711L
	template <class C> constexpr auto data(C&& c) noexcept -> decltype(std::data(c))
		{ return std::data(std::forward<C>(c)); }
#else
	template<typename C> SUBSTRATE_NO_DISCARD(inline SUBSTRATE_CXX14_CONSTEXPR auto data(C &c)) -> decltype(c.data()) { return c.data(); }
	template<typename C> SUBSTRATE_NO_DISCARD(inline SUBSTRATE_CXX14_CONSTEXPR auto data(const C &c)) -> decltype(c.data()) { return c.data(); }
	template<typename T, std::size_t N> SUBSTRATE_NO_DISCARD(inline SUBSTRATE_CXX14_CONSTEXPR T *data(T (&array)[N]) noexcept) { return array; }
	template<typename E> SUBSTRATE_NO_DISCARD(inline SUBSTRATE_CXX14_CONSTEXPR const E *data(std::initializer_list<E> il) noexcept) { return il.begin(); }
#endif 

#if defined(_MSC_VER)
#	pragma warning(disable:4702) // abort is unreachable code
#endif
[[noreturn]] SUBSTRATE_ALWAYS_INLINE void unreachable()
{
#if defined(_MSC_VER) && !defined(__clang__)
	__assume(false);
#else
	__builtin_unreachable();
#endif
#ifdef _DEBUG
	abort();
#endif
}
#if defined(_MSC_VER)
#	pragma warning(default:4702)
#endif

} // namespace substrate

#endif /* SUBSTRATE_UTILITIES */
/* vim: set ft=cpp ts=4 sw=4 noexpandtab: */
