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

// example_float.cpp — Float (multi-precision floating point) API demo (matches float.html)
//
// The Float class provides arbitrary-precision floating-point arithmetic. Internally it uses
// a limb-based representation of mantissa/exponent, and setDefaultPrecision(n) sets the number
// of significant digits.
//
// Demo contents:
//   demo_pi()           — compute π to 1000 digits (Chudnovsky Binary Splitting)
//   demo_exp_log()      — multi-precision exp/log and round-trip accuracy check
//   demo_nan_infinity() — safe propagation of NaN / infinity
//   demo_comparison()   — ordering comparison and equality test
//   demo_performance()  — computation time by digit count
//
// Build:
//   cl /std:c++latest /EHsc /O2 /MD /utf-8 /I<sangi>/include examples\example_float.cpp
//      /link Float.lib /MACHINE:X64

#include <math/core/mp/Float.hpp>
#include <iostream>
#include <chrono>

using namespace sangi;

// Pi computation
void demo_pi() {
    std::cout << "=== Pi Computation ===\n";
    Float::setDefaultPrecision(1000);
    Float pi = Float::pi(1000);
    std::cout << pi.toDecimalString(1000) << std::endl;
}

// Multi-precision exp / log
void demo_exp_log() {
    std::cout << "\n=== Multi-Precision exp / log ===\n";
    int prec = 30;
    Float::setDefaultPrecision(prec);
    Float x("1.5");

    Float ex = exp(x, prec);          // e^1.5
    Float lx = log(ex, prec);         // ln(e^1.5) == 1.5
    std::cout << "exp(1.5) = " << ex.toDecimalString(prec) << "\n";
    std::cout << "log(exp(1.5)) = " << lx.toDecimalString(prec) << "\n";
}

// Safe propagation of NaN / Infinity
void demo_nan_infinity() {
    std::cout << "\n=== Safe Propagation of NaN / Infinity ===\n";
    Float inf = Float::positiveInfinity();
    Float nan = Float::nan();
    Float z   = Float::zero();

    Float r1 = inf + Float(1);     // +Inf
    Float r2 = inf - inf;          // NaN
    Float r3 = nan + Float(42);    // NaN
    Float r4 = Float(1) / z;      // +Inf

    std::cout << "Inf + 1   = " << r1 << "\n";
    std::cout << "Inf - Inf = " << r2 << "\n";
    std::cout << "NaN + 42  = " << r3 << "\n";
    std::cout << "1 / 0     = " << r4 << "\n";
}

// Precision control and constant caching
void demo_precision_cache() {
    std::cout << "\n=== Precision Control & Constant Cache ===\n";
    using Clock = std::chrono::high_resolution_clock;

    auto t0 = Clock::now();
    Float pi1 = Float::pi(10000);   // first call: computed
    auto t1 = Clock::now();
    Float pi2 = Float::pi(10000);   // second call: returned from cache
    auto t2 = Clock::now();
    Float pi3 = Float::pi(50000);   // different precision: recomputed
    auto t3 = Clock::now();
    Float pi4 = Float::pi(50000);   // returned from cache
    auto t4 = Clock::now();

    auto us = [](auto d) { return std::chrono::duration_cast<std::chrono::microseconds>(d).count(); };

    std::cout << "pi(10000)  1st call (computed):    " << us(t1-t0) << " us\n";
    std::cout << "pi(10000)  2nd call (from cache):  " << us(t2-t1) << " us\n";
    std::cout << "pi(50000)  1st call (recomputed):  " << us(t3-t2) << " us\n";
    std::cout << "pi(50000)  2nd call (from cache):  " << us(t4-t3) << " us\n";

    // Rounding mode: display 1/3 in binary
    // 1/3 (binary) = 0.01010101... (infinite repeating)
    // setPrecision(1) -> internal 12-bit precision, difference appears in trailing bits
    std::cout << "\n--- Rounding Mode (1/3 in binary) ---\n";
    std::cout << "  1/3 (binary) = 0.010101... (infinite)\n\n";
    auto showRound = [](const char* name, RoundingMode mode) {
        Float::setRoundingMode(mode);
        Float pos = Float(1, 30) / Float(3, 30);
        Float neg = Float(-1, 30) / Float(3, 30);
        pos.setPrecision(1);
        neg.setPrecision(1);
        int bits = static_cast<int>(pos.mantissa().bitLength()) + 1;
        std::cout << "  " << name << ":   1/3 -> " << pos.toString(2, bits)
                  << "   -1/3 -> " << neg.toString(2, bits) << "\n";
    };
    showRound("ToNearest (default)", RoundingMode::ToNearest);
    showRound("TowardZero         ", RoundingMode::TowardZero);
    showRound("TowardPositive     ", RoundingMode::TowardPositive);
    showRound("TowardNegative     ", RoundingMode::TowardNegative);

    Float::setRoundingMode(RoundingMode::ToNearest);
}

// Mathematical functions at multi-precision
void demo_math_functions() {
    std::cout << "\n=== Mathematical Functions ===\n";
    Float::setDefaultPrecision(50);

    // Trigonometric
    Float x("0.5");
    std::cout << "sin(0.5)  = " << sin(x).toDecimalString(30) << std::endl;
    std::cout << "cos(0.5)  = " << cos(x).toDecimalString(30) << std::endl;
    std::cout << "tan(0.5)  = " << tan(x).toDecimalString(30) << std::endl;

    // Inverse trigonometric
    std::cout << "asin(0.5) = " << asin(x).toDecimalString(30) << std::endl;
    std::cout << "acos(0.5) = " << acos(x).toDecimalString(30) << std::endl;
    std::cout << "atan(0.5) = " << atan(x).toDecimalString(30) << std::endl;

    // Hyperbolic
    Float one(1);
    std::cout << "sinh(1)   = " << sinh(one).toDecimalString(30) << std::endl;
    std::cout << "cosh(1)   = " << cosh(one).toDecimalString(30) << std::endl;
    std::cout << "tanh(1)   = " << tanh(one).toDecimalString(30) << std::endl;

    // Powers and roots
    Float two(2);
    std::cout << "sqrt(2)   = " << sqrt(two).toDecimalString(30) << std::endl;
    std::cout << "cbrt(2)   = " << cbrt(two).toDecimalString(30) << std::endl;

    // Error function and gamma (require explicit precision)
    int prec = 50;
    std::cout << "erf(1)    = " << erf(one, prec).toDecimalString(30) << std::endl;
    std::cout << "gamma(5)  = " << gamma(Float(5), prec).toDecimalString(10)
              << "  (= 4! = 24)" << std::endl;

    // Constants (50 digits)
    std::cout << "\n--- Constants (50 digits) ---\n";
    std::cout << "pi    = " << Float::pi().toDecimalString(50) << std::endl;
    std::cout << "e     = " << Float::e().toDecimalString(50) << std::endl;
    std::cout << "euler = " << Float::euler().toDecimalString(50) << std::endl;
    std::cout << "sqrt2 = " << Float::sqrt2().toDecimalString(50) << std::endl;
}

int main() {
    demo_pi();
    demo_exp_log();
    demo_nan_infinity();
    demo_precision_cache();
    demo_math_functions();
    return 0;
}
