// Copyright (C) 2026 Kiyotsugu Arai
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// static_dims.hpp
//
// Compile-time dimension check helpers for LinAlg algorithms.
//
// Background
// ----------
// BaseMatrix<T> and BaseVector<T> expose dimension traits:
//
//   static constexpr std::ptrdiff_t static_rows = -1;   // BaseMatrix
//   static constexpr std::ptrdiff_t static_cols = -1;   // BaseMatrix
//   static constexpr std::ptrdiff_t static_size = -1;   // BaseVector
//
// where -1 means "dynamic (only known at runtime)". Derived classes that carry
// statically known dimensions (StaticMatrix<T, R, C>, StaticVector<T, N>) shadow
// these with their template parameters, e.g.:
//
//   StaticMatrix<double, 3, 4>::static_rows == 3
//   StaticMatrix<double, 3, 4>::static_cols == 4
//   StaticVector<double, 5>::static_size    == 5
//
// LinAlg algorithms templated on the derived matrix/vector type can use the
// helpers in this header to assert dimension agreement at build time when both
// operands carry static dimensions, while remaining a no-op for dynamic Matrix/
// Vector arguments.
//
// Usage
// -----
//   template<typename MA, typename VB>
//       requires BaseMatrixLike<MA> && BaseVectorLike<VB>
//   Vector<element_t<MA>> lu_solve(const MA& A, const VB& b, ...) {
//       SANGI_STATIC_ASSERT_SQUARE(MA);
//       SANGI_STATIC_ASSERT_MATRIX_ROWS_EQ_VECTOR_SIZE(MA, VB);
//       // ... existing runtime body unchanged ...
//   }
//
// The macros expand to `if constexpr (...) static_assert(...);` blocks that
// fire only when both operands have known static dimensions.

#ifndef SANGI_LINALG_STATIC_DIMS_HPP
#define SANGI_LINALG_STATIC_DIMS_HPP

#include <cstddef>
#include <concepts>
#include <type_traits>

#include "../core/BaseMatrix.hpp"
#include "../core/BaseVector.hpp"

namespace sangi {

// ============================================================================
// Concepts: identify types that derive from BaseMatrix / BaseVector
// ============================================================================

namespace detail {

template<typename M, typename T>
inline constexpr bool is_base_matrix_of_v =
    std::is_base_of_v<BaseMatrix<T>, std::remove_cvref_t<M>>;

template<typename V, typename T>
inline constexpr bool is_base_vector_of_v =
    std::is_base_of_v<BaseVector<T>, std::remove_cvref_t<V>>;

} // namespace detail

// True if M derives from BaseMatrix<T> for some T.
template<typename M>
concept BaseMatrixLike = requires {
    typename std::remove_cvref_t<M>::value_type;
    requires detail::is_base_matrix_of_v<M, typename std::remove_cvref_t<M>::value_type>;
};

// True if V derives from BaseVector<T> for some T.
template<typename V>
concept BaseVectorLike = requires {
    typename std::remove_cvref_t<V>::value_type;
    requires detail::is_base_vector_of_v<V, typename std::remove_cvref_t<V>::value_type>;
};

// Element type extractor.
template<typename X>
using element_t = typename std::remove_cvref_t<X>::value_type;

// ============================================================================
// Compile-time dimension predicates (all -1-aware: -1 = dynamic, never fails)
// ============================================================================

// True iff both dimensions are statically known and equal, or at least one is dynamic.
constexpr bool dims_compatible(std::ptrdiff_t l, std::ptrdiff_t r) {
    return l < 0 || r < 0 || l == r;
}

// True iff both dimensions are statically known.
constexpr bool dims_both_static(std::ptrdiff_t l, std::ptrdiff_t r) {
    return l >= 0 && r >= 0;
}

} // namespace sangi

// ============================================================================
// Convenience macros for use inside templated LinAlg function bodies
// ============================================================================

// Static-assert that matrix type M is square (M::static_rows == M::static_cols).
// No-op if either dimension is dynamic.
#define SANGI_STATIC_ASSERT_SQUARE(MatType)                                                       \
    do {                                                                                          \
        using M_ = std::remove_cvref_t<MatType>;                                                  \
        if constexpr (::sangi::dims_both_static(M_::static_rows, M_::static_cols)) {              \
            static_assert(M_::static_rows == M_::static_cols,                                     \
                          "sangi LinAlg: matrix argument must be square (static check)");         \
        }                                                                                         \
    } while (0)

// Static-assert that matrix MA and vector VB satisfy MA.rows == VB.size.
// No-op if either dimension is dynamic.
#define SANGI_STATIC_ASSERT_MATRIX_ROWS_EQ_VECTOR_SIZE(MatType, VecType)                          \
    do {                                                                                          \
        using M_ = std::remove_cvref_t<MatType>;                                                  \
        using V_ = std::remove_cvref_t<VecType>;                                                  \
        if constexpr (::sangi::dims_both_static(M_::static_rows, V_::static_size)) {              \
            static_assert(M_::static_rows == V_::static_size,                                     \
                          "sangi LinAlg: matrix rows must equal rhs vector size (static check)"); \
        }                                                                                         \
    } while (0)

// Static-assert that matrix MA and matrix MB share the inner dimension (MA.cols == MB.rows).
// No-op if either dimension is dynamic.
#define SANGI_STATIC_ASSERT_INNER_DIM_EQ(MatTypeA, MatTypeB)                                      \
    do {                                                                                          \
        using MA_ = std::remove_cvref_t<MatTypeA>;                                                \
        using MB_ = std::remove_cvref_t<MatTypeB>;                                                \
        if constexpr (::sangi::dims_both_static(MA_::static_cols, MB_::static_rows)) {            \
            static_assert(MA_::static_cols == MB_::static_rows,                                   \
                          "sangi LinAlg: inner dimension mismatch A.cols != B.rows (static check)"); \
        }                                                                                         \
    } while (0)

// Static-assert that two vector types VA, VB satisfy VA::static_size == VB::static_size.
// No-op if either size is dynamic.
#define SANGI_STATIC_ASSERT_VECTOR_SIZES_EQ(VecTypeA, VecTypeB)                                   \
    do {                                                                                          \
        using VA_ = std::remove_cvref_t<VecTypeA>;                                                \
        using VB_ = std::remove_cvref_t<VecTypeB>;                                                \
        if constexpr (::sangi::dims_both_static(VA_::static_size, VB_::static_size)) {            \
            static_assert(VA_::static_size == VB_::static_size,                                   \
                          "sangi LinAlg: vector sizes must match (static check)");                \
        }                                                                                         \
    } while (0)

// Static-assert that VA::static_size == VB::static_size - 1 (off-diagonal vs main diagonal).
// Used by tridiagonal / pentadiagonal solvers. No-op if either size is dynamic.
#define SANGI_STATIC_ASSERT_VECTOR_SIZE_EQ_OTHER_MINUS_ONE(VecTypeA, VecTypeOtherDiag)            \
    do {                                                                                          \
        using VA_ = std::remove_cvref_t<VecTypeA>;                                                \
        using VB_ = std::remove_cvref_t<VecTypeOtherDiag>;                                        \
        if constexpr (VA_::static_size >= 0 && VB_::static_size > 0) {                            \
            static_assert(VA_::static_size == VB_::static_size - 1,                               \
                          "sangi LinAlg: off-diagonal vector size must equal main_diag - 1 (static check)"); \
        }                                                                                         \
    } while (0)

#endif // SANGI_LINALG_STATIC_DIMS_HPP
