// Copyright (C) 2026 Kiyotsugu Arai
// SPDX-License-Identifier: LGPL-3.0-or-later

// special_common.hpp
// Common foundation for special-function templates
// Provides type-classification concepts and shared utilities.

#ifndef SANGI_SPECIAL_COMMON_HPP
#define SANGI_SPECIAL_COMMON_HPP

#include <math/core/mp/Float.hpp>
#include <math/core/Complex.hpp>
#include <concepts>
#include <cmath>
#include <numbers>
#include <limits>
#include <type_traits>

namespace sangi {

// ================================================================
// Type-classification concepts
// ================================================================

/// Whether the type is sangi::Float
template<typename T>
concept IsSangiFloat = std::same_as<std::remove_cvref_t<T>, Float>;

/// Native floating-point type (float, double, long double)
template<typename T>
concept IsNativeFloat = std::floating_point<T>;

/// Whether the type is sangi::Complex<T>
template<typename T>
struct is_sangi_complex_type : std::false_type {};

template<ComplexScalar U>
struct is_sangi_complex_type<Complex<U>> : std::true_type {};

template<typename T>
inline constexpr bool is_sangi_complex_type_v = is_sangi_complex_type<T>::value;

template<typename T>
concept IsSangiComplex = is_sangi_complex_type_v<std::remove_cvref_t<T>>;

/// Get the inner scalar type of Complex<T>
template<typename T>
struct complex_value_type { using type = T; };

template<ComplexScalar U>
struct complex_value_type<Complex<U>> { using type = U; };

template<typename T>
using complex_value_type_t = typename complex_value_type<T>::type;

/// Scalar types usable with special functions (float, double, long double, Float)
template<typename T>
concept SpecialFunctionScalar = IsNativeFloat<T> || IsSangiFloat<T>;

// ================================================================
// Special-function utilities (with Complex<Float> support)
// ================================================================

namespace special {
namespace detail {

    /// Return the machine epsilon appropriate for the type
    template<typename R>
    [[nodiscard]] R getEpsilon() {
        if constexpr (IsSangiFloat<R>)
            return Float::epsilon(Float::defaultPrecision());
        else
            return std::numeric_limits<R>::epsilon();
    }

    template<typename R>
    [[nodiscard]] R getEpsilon(int precision) {
        if constexpr (IsSangiFloat<R>)
            return Float::epsilon(precision);
        else
            return std::numeric_limits<R>::epsilon();
    }

    /// π
    template<typename R>
    [[nodiscard]] R getPi() {
        if constexpr (IsSangiFloat<R>)
            return Float::pi(Float::defaultPrecision());
        else
            return std::numbers::template pi_v<R>;
    }

    template<typename R>
    [[nodiscard]] R getPi(int precision) {
        if constexpr (IsSangiFloat<R>)
            return Float::pi(precision);
        else
            return std::numbers::template pi_v<R>;
    }

    /// Euler-Mascheroni constant γ
    template<typename R>
    [[nodiscard]] R getEulerGamma() {
        if constexpr (IsSangiFloat<R>)
            return Float::euler(Float::defaultPrecision());
        else
            return static_cast<R>(0.5772156649015328606065120900824024310421593359L);
    }

    /// ln(2)
    template<typename R>
    [[nodiscard]] R getLn2() {
        if constexpr (IsSangiFloat<R>)
            return log(Float(2), Float::defaultPrecision());
        else
            return std::numbers::template ln2_v<R>;
    }

    /// NaN
    template<typename R>
    [[nodiscard]] R getNaN() {
        if constexpr (IsSangiFloat<R>)
            return Float::nan();
        else
            return std::numeric_limits<R>::quiet_NaN();
    }

    /// +Infinity
    template<typename R>
    [[nodiscard]] R getInfinity() {
        if constexpr (IsSangiFloat<R>)
            return Float::positiveInfinity();
        else
            return std::numeric_limits<R>::infinity();
    }

} // namespace detail
} // namespace special

} // namespace sangi

#endif // SANGI_SPECIAL_COMMON_HPP
