#pragma once

#include <pl/helpers/types.hpp>

#include <type_traits>
#include <memory>

namespace pl {

    template<typename T>
    class Cloneable {
    public:
        [[nodiscard]] virtual std::unique_ptr<T> clone() const = 0;
    };

    template<typename>
    struct is_integral_helper : public std::false_type { };

    template<>
    struct is_integral_helper<u8> : public std::true_type { };

    template<>
    struct is_integral_helper<i8> : public std::true_type { };

    template<>
    struct is_integral_helper<u16> : public std::true_type { };

    template<>
    struct is_integral_helper<i16> : public std::true_type { };

    template<>
    struct is_integral_helper<u32> : public std::true_type { };

    template<>
    struct is_integral_helper<i32> : public std::true_type { };

    template<>
    struct is_integral_helper<u64> : public std::true_type { };

    template<>
    struct is_integral_helper<i64> : public std::true_type { };

    template<>
    struct is_integral_helper<u128> : public std::true_type { };

    template<>
    struct is_integral_helper<i128> : public std::true_type { };

    template<>
    struct is_integral_helper<bool> : public std::true_type { };

    template<>
    struct is_integral_helper<char> : public std::true_type { };

    template<>
    struct is_integral_helper<char8_t> : public std::true_type { };

    template<>
    struct is_integral_helper<char16_t> : public std::true_type { };

    template<>
    struct is_integral_helper<char32_t> : public std::true_type { };

    template<>
    struct is_integral_helper<wchar_t> : public std::true_type { };

    template<typename T>
    struct is_integral : public is_integral_helper<std::remove_cvref_t<T>>::type { };


    template<typename>
    struct is_signed_helper : public std::false_type { };

    template<>
    struct is_signed_helper<i8> : public std::true_type { };

    template<>
    struct is_signed_helper<i16> : public std::true_type { };

    template<>
    struct is_signed_helper<i32> : public std::true_type { };

    template<>
    struct is_signed_helper<i64> : public std::true_type { };

    template<>
    struct is_signed_helper<i128> : public std::true_type { };

    template<>
    struct is_signed_helper<char> : public std::true_type { };

    template<>
    struct is_signed_helper<float> : public std::true_type { };

    template<>
    struct is_signed_helper<double> : public std::true_type { };

    template<>
    struct is_signed_helper<long double> : public std::true_type { };

    template<typename T>
    struct is_signed : public is_signed_helper<std::remove_cvref_t<T>>::type { };


    template<typename>
    struct is_floating_point_helper : public std::false_type { };

    template<>
    struct is_floating_point_helper<float> : public std::true_type { };

    template<>
    struct is_floating_point_helper<double> : public std::true_type { };

    template<>
    struct is_floating_point_helper<long double> : public std::true_type { };

    template<typename T>
    struct is_floating_point : public is_floating_point_helper<std::remove_cvref_t<T>>::type { };

}

#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION <= 12000
#if __has_include(<concepts>)
        // Make sure we break when derived_from is implemented in libc++. Then we can fix a compatibility version above
        #include <concepts>
    #endif
// libcxx 12 still doesn't have many default concepts implemented, as a result we need to define it ourself using clang built-ins.
// [concept.derived] (patch from https://reviews.llvm.org/D74292)
namespace pl {
    template<class _Dp, class _Bp>
    concept derived_from =
        __is_base_of(_Bp, _Dp) && __is_convertible_to(const volatile _Dp *, const volatile _Bp *);
}

#else
// Assume supported
#include <concepts>
namespace pl {
    using std::derived_from;
}
#endif

// [concepts.arithmetic]
namespace pl {

    template<class T>
    concept integral = is_integral<T>::value;

    template<class T>
    concept signed_integral = integral<T> && is_signed<T>::value;

    template<class T>
    concept unsigned_integral = integral<T> && !signed_integral<T>;

    template<class T>
    concept floating_point = std::is_floating_point<T>::value;

}

namespace pl {

    template<typename T>
    struct always_false : std::false_type { };

    template<typename T, size_t Size>
    concept has_size = sizeof(T) == Size;

}
