// =====================================================================================================================
//  fennec, a free and open source game engine
//  Copyright © 2025 - 2026  Medusa Slockbower
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program 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 General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
// =====================================================================================================================

#ifndef FENNEC_LANG_DETAIL_TYPE_TRAITS_H
#define FENNEC_LANG_DETAIL_TYPE_TRAITS_H

#include <fennec/lang/constants.h>
#include <fennec/lang/declval.h>

namespace fennec::detail
{

	// Nothing interesting to note here
	template<typename> struct _is_void       : false_type {};
	template<>         struct _is_void<void> : true_type {};

	template<typename> struct _is_bool         : false_type {};
	template<>         struct _is_bool<bool_t> : true_type {};

	template<typename> struct _is_null_pointer            : false_type {};
	template<>         struct _is_null_pointer<nullptr_t> : true_type {};

	// Provides definitions for all builtin int types
	template<typename> struct _is_integral           : false_type {};
	template<>         struct _is_integral<bool_t>   : true_type {};
	template<>         struct _is_integral<char_t>   : true_type {};
	template<>         struct _is_integral<char8_t>  : true_type {};
	template<>         struct _is_integral<char16_t> : true_type {};
	template<>         struct _is_integral<char32_t> : true_type {};
	template<>         struct _is_integral<schar_t>  : true_type {};
	template<>         struct _is_integral<uchar_t>  : true_type {};
	template<>         struct _is_integral<wchar_t>  : true_type {};
	template<>         struct _is_integral<short_t>  : true_type {};
	template<>         struct _is_integral<ushort_t> : true_type {};
	template<>         struct _is_integral<int_t>    : true_type {};
	template<>         struct _is_integral<uint_t>   : true_type {};
	template<>         struct _is_integral<long_t>   : true_type {};
	template<>         struct _is_integral<ulong_t>  : true_type {};
	template<>         struct _is_integral<llong_t>  : true_type {};
	template<>         struct _is_integral<ullong_t> : true_type {};

	template<typename>   struct _is_const          : false_type {};
	template<typename T> struct _is_const<const T> : true_type {};

	template<typename>   struct _is_volatile             : false_type {};
	template<typename T> struct _is_volatile<volatile T> : true_type {};

	// Most unsigned types will underflow `-1` to the types maximum value
	template<typename TypeT> struct _is_signed   : bool_constant<TypeT(-1) < TypeT(0)> {};
	template<typename TypeT> struct _is_unsigned : bool_constant<TypeT(-1) >= TypeT(0)> {};

	template<typename> struct _is_floating_point           : false_type {};
	template<>         struct _is_floating_point<float_t>  : true_type {};
	template<>         struct _is_floating_point<double_t> : true_type {};

	template<typename>   struct _is_pointer     : false_type {};
	template<typename T> struct _is_pointer<T*> : true_type {};

	template<typename>   struct _is_reference      : false_type {};
	template<typename T> struct _is_reference<T&>  : true_type {};
	template<typename T> struct _is_reference<T&&> : true_type {};

	template<typename>   struct _is_lvalue_reference     : false_type {};
	template<typename T> struct _is_lvalue_reference<T&> : true_type  {};

	template<typename>   struct _is_rvalue_reference      : false_type {};
	template<typename T> struct _is_rvalue_reference<T&&> : true_type  {};

	template<typename>             struct _is_bounded_array       : false_type {};
	template<typename T, size_t N> struct _is_bounded_array<T[N]> : true_type  {};

	template<typename>   struct _is_unbounded_array      : false_type {};
	template<typename T> struct _is_unbounded_array<T[]> : true_type  {};

	template<typename T> struct _is_complete {
		template<typename U>
		static auto test(U*)  -> bool_constant<sizeof(U) == sizeof(U)>;
		static auto test(...) -> false_type;
		using type = decltype(_is_complete::test(static_cast<T*>(nullptr)));
	};

	template<typename T> struct _is_destructible {
		template<typename U, typename = decltype(declval<T&>().~T())>
		static auto test(int) -> true_type;
		template<typename>
		static auto test(...) -> false_type;
		using type = decltype(test<T>(0));
	};

	template<typename T> struct _is_nothrow_destructible {
		template<typename U, typename = decltype(noexcept(declval<T&>().~T()))>
		static auto test(int) -> true_type;
		template<typename>
		static auto test(...) -> false_type;
		using type = decltype(test<T>(0));
	};

	template<typename ContainerT>
	auto _begin(ContainerT& c) noexcept(noexcept(c.begin())) -> decltype(c.begin()) {
		return c.begin();
	}

	template<typename T, size_t N>
	auto _begin(T (&arr)[N]) -> T* {
		return arr[0];
	}

	void _begin(...);

	template<typename ContainerT>
	auto _end(ContainerT& c) noexcept(noexcept(c.end())) -> decltype(c.end()) {
		return c.end();
	}

	template<typename T, size_t N>
	auto _end(T (&arr)[N]) -> T* {
		return arr[N - 1];
	}

	void _end(...);

	// https://stackoverflow.com/questions/13830158/how-to-write-a-trait-which-checks-whether-a-type-is-iterable
	template<typename T>
	auto _is_iterable(int) -> decltype(
		detail::_begin(declval<T&>()) != detail::_end(declval<T&>()),
		void(),
		++declval<decltype(detail::_begin(declval<T&>()))&>(),
		void(*detail::_begin(declval<T&>())),
		true_type{}
	);

	template<typename T>
	auto _is_iterable(...) -> false_type;



	template<typename T>
	auto _is_indexable(int) -> decltype(
		declval<T&>()[0],
		true_type{}
		);

	template<typename T>
	auto _is_indexable(...) -> false_type;


	// https://stackoverflow.com/a/31409532
	template<typename T>
	auto _is_iterator(...) -> decltype(
		declval<T>() != declval<T>(),
		void(),
		++declval<T>(),
		*declval<T>(),
		true_type{}
	);

	template<typename T>
	auto _is_iterator(...) -> false_type;



	template<typename T>
	auto _is_mappable(int) -> decltype(
		declval<T&>()[declval<typename T::key_t&>()],
		true_type{}
	);

	template<typename T>
	auto _is_mappable(...) -> false_type;


	template<typename B> auto _ptr_conv(const volatile B*)    -> true_type;
	template<typename>   auto _ptr_conv(const volatile void*) -> false_type;

	template<typename B, typename D> auto _is_base_of(int) -> decltype(detail::_ptr_conv<B>(static_cast<D*>(nullptr)));
	template<typename,   typename>   auto _is_base_of(...) -> false_type;
}

#endif // FENNEC_LANG_DETAIL_TYPE_TRAITS_H
