/*
     Description: 数学计算相关的工具函数

     Copyright : All right reserved by ICT

     Author : Zhang Zhonghai
     Date : 2025/12/25
*/
#pragma once

#include <cassert>
#include <climits>
#include <cmath>
#include <vector>

#include "binomial_dist.h"
#include "math_const.h"
#include "math_ulp.h"

using std::vector;

struct MathUtils {

    // 初始化math库里所有需要静态初始化的类静态变量
    static void StaticInit() {

    }

    /**
     * Rounds the double to the given number of decimal places.
     * For example, rounding 3.1415926 to 3 places would give 3.142.
     * The requirement is that it works exactly as writing a number down with string.format and reading back in.
     */
    static double RoundToNDecimalPlaces(const double in, const int n) {
        assert(n > 0);
        const double mult = std::pow(10, n);
        return std::round((in + Ulp64::ulp(in)) * mult) / mult;
    }

    /**
     * binomial Probability(int, int, double) with log applied to result
     */
    static double logBinomialProbability(const int n, const int k, const double p) {
        BinomialDistribution binomial(n, p);
        return binomial.logProbability(k);
    }

    template <typename ArrayType>
    static int maxElementIndex(const ArrayType& array, const int start, const int endIndex) {
        int maxI = start;
        for (int i = (start + 1); i < endIndex; i++) {
            if (array[i] > array[maxI])
                maxI = i;
        }
        return maxI;
    }

    static int fastRound(double d) { return (d > 0.0) ? (int)(d + 0.5) : (int)(d - 0.5); }

    /**
     * Compute in a numerically correct way the quantity log10(1-x)
     *
     * Uses the approximation log10(1-x) = log10(1/x - 1) + log10(x) to avoid very quick underflow
     * in 1-x when x is very small
     *
     * log10(1-x) = log10( x * (1-x) / x )
     *            = log10(x) + log10( (1-x) / x)
     *            = log10(x) + log10(1/x - 1)
     *
     * @param x a positive double value between 0.0 and 1.0
     * @return an estimate of log10(1-x)
     */
    static double log10OneMinusX(const double x) {
        if (x == 1.0)
            return MathConst::DOUBLE_NEGATIVE_INFINITY;
        else if (x == 0.0)
            return 0.0;
        else {
            const double d = std::log10(1 / x - 1) + std::log10(x);
            return std::isinf(d) || d > 0.0 ? 0.0 : d;
        }
    }

    static double log10SumLog10(const vector<double>& log10Values, const int start) {
        return log10SumLog10(log10Values, start, log10Values.size());
    }

    static double log10SumLog10(const vector<double>& log10Values) { return log10SumLog10(log10Values, 0); }

    static double log10SumLog10(const vector<double>& log10Values, const int start, const int finish) {
        // Utils.nonNull(log10Values);
        if (start >= finish) {
            return MathConst::DOUBLE_NEGATIVE_INFINITY;
        }
        const int maxElementIndex = MathUtils::maxElementIndex(log10Values, start, finish);
        
        
        const double maxValue = log10Values[maxElementIndex];
        if (maxValue == MathConst::DOUBLE_NEGATIVE_INFINITY) {
            return maxValue;
        }
        double sum = 1.0;
        for (int i = start; i < finish; i++) {
            const double curVal = log10Values[i];
            if (i == maxElementIndex || curVal == MathConst::DOUBLE_NEGATIVE_INFINITY) {
                continue;
            } else {
                const double scaled_val = curVal - maxValue;
                sum += std::pow(10.0, scaled_val);
            }
        }
        if (std::isinf(sum) || sum == MathConst::DOUBLE_POSITIVE_INFINITY) {
            // throw new IllegalArgumentException("log10 p: Values must be non-infinite and non-NAN");
        }
        return maxValue + (sum != 1.0 ? std::log10(sum) : 0.0);
    }

    static double log10SumLog10(const double a, const double b) {
        return a > b ? a + std::log10(1 + std::pow(10.0, b - a)) : b + std::log10(1 + std::pow(10.0, a - b));
    }

    /**
     * Do the log-sum trick for three double values.
     * @param a
     * @param b
     * @param c
     * @return the sum... perhaps NaN or infinity if it applies.
     */
    static double log10SumLog10(const double a, const double b, const double c) {
        if (a >= b && a >= c) {
            return a + std::log10(1 + std::pow(10.0, b - a) + std::pow(10.0, c - a));
        } else if (b >= c) {
            return b + std::log10(1 + std::pow(10.0, a - b) + std::pow(10.0, c - b));
        } else {
            return c + std::log10(1 + std::pow(10.0, a - c) + std::pow(10.0, b - c));
        }
    }
};
