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

// IntCombinatorics.hpp
// 組合せ論的関数（階乗、二項係数など）for Int

#ifndef CALX_INT_COMBINATORICS_HPP
#define CALX_INT_COMBINATORICS_HPP

#include "IntBase.hpp"

namespace calx {

/**
 * @brief 組合せ論的関数
 *
 * 階乗、二項係数などの組合せ論で使用される関数を提供します。
 */
class IntCombinatorics {
public:
    /**
     * @brief 階乗 n!
     *
     * n! = n × (n-1) × ... × 2 × 1 を計算します。
     * ベンチマーク (2026-02-14) に基づき最適なアルゴリズムを選択:
     *   - N <= 100: テーブル参照 (O(1))
     *   - 100 < N <= 228: 単純ループ (テーブルをベースに順次乗算)
     *   - N > 228: 篩法 (素因数分解 + トーナメント乗算)
     * 注: トーナメント法は全範囲で篩法に劣るため自動選択では使用しない。
     * 詳細: TODO/NOTES_FACTORIAL_BENCHMARK.md
     *
     * @param n 非負整数
     * @return n! の値
     *
     * 特殊ケース:
     *   factorial(0) = 1（定義により）
     *   factorial(1) = 1
     *   factorial(n < 0) = NaN（負の数の階乗は未定義）
     *   factorial(NaN) = NaN
     *   factorial(∞) = ∞
     */
    static Int factorial(const Int& n);

    /**
     * @brief 二重階乗 n!!
     *
     * n!! = n × (n-2) × (n-4) × ... を計算します。
     * - n が偶数: n × (n-2) × ... × 4 × 2
     * - n が奇数: n × (n-2) × ... × 3 × 1
     *
     * @param n 非負整数
     * @return n!! の値
     *
     * 特殊ケース:
     *   doubleFactorial(0) = 1（定義により）
     *   doubleFactorial(1) = 1
     *   doubleFactorial(-1) = 1（定義により）
     *   doubleFactorial(n < -1) = NaN
     *   doubleFactorial(NaN) = NaN
     *   doubleFactorial(∞) = ∞
     *
     * 例:
     *   6!! = 6 × 4 × 2 = 48
     *   7!! = 7 × 5 × 3 × 1 = 105
     */
    static Int doubleFactorial(const Int& n);

    /**
     * @brief 二項係数 C(n, k) = n! / (k! × (n-k)!)
     *
     * 組合せの数 "n 個から k 個選ぶ場合の数" を計算します。
     *
     * @param n 非負整数
     * @param k 非負整数（0 <= k <= n）
     * @return C(n, k) の値
     *
     * 特殊ケース:
     *   binomial(n, 0) = 1（何も選ばない場合は1通り）
     *   binomial(n, n) = 1（全て選ぶ場合は1通り）
     *   binomial(n, k) = 0（k > n の場合）
     *   binomial(n, k < 0) = NaN
     *   binomial(n < 0, k) = NaN
     *   binomial(NaN, k) = NaN
     *
     * 最適化:
     *   - C(n, k) = C(n, n-k) を利用して計算量を削減
     *   - オーバーフロー回避のため、乗算と除算を交互に実行
     *
     * 例:
     *   binomial(5, 2) = 5!/(2!×3!) = 10
     *   binomial(10, 3) = 10!/(3!×7!) = 120
     */
    static Int binomial(const Int& n, const Int& k);

    /**
     * @brief フィボナッチ数 F(n)
     *
     * 高速倍加法 (fast doubling) で O(log n) 回の乗算で計算:
     *   F(2k)   = F(k) * (2*F(k+1) - F(k))
     *   F(2k+1) = F(k)^2 + F(k+1)^2
     *
     * @param n 非負整数
     * @return F(n)
     *
     * 特殊ケース:
     *   fibonacci(0) = 0, fibonacci(1) = 1
     *   fibonacci(n < 0) = NaN
     */
    static Int fibonacci(const Int& n);

    /**
     * @brief リュカ数 L(n)
     *
     * L(n) = F(n-1) + F(n+1) = 2*F(n+1) - F(n) の関係を利用。
     *
     * @param n 非負整数
     * @return L(n)
     *
     * 特殊ケース:
     *   lucas(0) = 2, lucas(1) = 1
     *   lucas(n < 0) = NaN
     */
    static Int lucas(const Int& n);

    /**
     * @brief プリモリアル primorial(n) = n 以下の素数の積
     *
     * GMP mpz_primorial_ui 相当。
     * エラトステネス篩で n 以下の素数を列挙し、バイナリ分割積で計算。
     *
     * @param n 非負整数 (int)
     * @return n 以下の全素数の積。n < 2 → 1。
     *
     * 例: primorial(10) = 2×3×5×7 = 210
     */
    static Int primorial(int n);

    /**
     * @brief m-階乗 mfac(n, m) = n · (n-m) · (n-2m) · ...
     *
     * GMP mpz_mfac_uiui 相当。
     * m=1 → n!, m=2 → n!! (doubleFactorial) に帰着。
     *
     * @param n 非負整数
     * @param m 正整数 (ステップ幅)
     * @return n · (n-m) · (n-2m) · ... (各因子が 1 以上の間)
     *
     * 例: mfac(10, 3) = 10×7×4×1 = 280
     */
    static Int mfac(int n, int m);

    // 個別アルゴリズム (テスト・ベンチマーク用に公開)

    /** テーブル参照: N <= 100 */
    static const Int& factorialTable(int n);

    /** 単純ループ: テーブル(100!)をベースに順次乗算 */
    static Int factorialSimple(int n);

    /** トーナメント法: 2の冪を分離 + 端×端バランス乗算 (ベンチマーク用、自動選択では未使用) */
    static Int factorialTournament(int n);

    /** 篩法: エラトステネス風素因数分解 + トーナメント乗算 */
    static Int factorialSieve(int n);

private:
    static constexpr int TABLE_MAX = 100;
    static constexpr int SIMPLE_MAX = 228;
};

} // namespace calx

#endif // CALX_INT_COMBINATORICS_HPP
