#include "Binomial.hxx"

namespace sd::disc
{

struct ElisaChernoff
{
    template <typename T>
    static auto log_pvalue(const T& p, const T& q, const size_t n) -> T
    {
        return binomial_log_cdf_chernoff(static_cast<size_t>(q * n), p, n);
    }
    template <typename T>
    static auto pvalue(const T& p, const T& q, const size_t n) -> T
    {
        return binomial_cdf_chernoff(static_cast<size_t>(q * n), p, n);
    }
};

struct ElisaChernoffTT
{
    template <typename T>
    static auto pvalue(const T& p, const T& q, const size_t n) -> T
    {
        using std::abs;
        auto   d  = abs(p - q);
        size_t ll = n * std::max(p - d, T(0));
        size_t ul = n * std::min(p + d, T(1));

        return binomial_cdf_chernoff(ll, p, n) + binomial_cdf_chernoff(ul, p, n);
    }

    template <typename T>
    static auto log_pvalue(const T& p, const T& q, const size_t n) -> T
    {
        using std::log;
        return log(pvalue(p, q, n));
    }
};

struct ElisaChernoffUpper
{
    template <typename T>
    static auto log_pvalue(const T& p, const T& q, const size_t n) -> T
    {
        return q <= p ? 0 : binomial_log_cdf_chernoff(static_cast<size_t>(q * n), p, n);
    }
    template <typename T>
    static auto pvalue(const T& p, const T& q, const size_t n) -> T
    {
        return q <= p ? 1 : binomial_cdf_chernoff(static_cast<size_t>(q * n), p, n);
    }
};

struct ElisaBinomialUpper
{
    template <typename T>
    static auto log_pvalue(const T& p, const T& q, const size_t n) -> T
    {
        using std::log;
        auto s = static_cast<size_t>(q * n);
        return n < 100 ? log(binomial_cdf_exact_upper(s, p, n))
                       : (q <= p ? 0 : binomial_log_cdf_chernoff_impl(s, p, n));
    }
    template <typename T>
    static auto pvalue(const T& p, const T& q, const size_t n) -> T
    {
        auto s = static_cast<size_t>(q * n);
        return n < 100 ? binomial_cdf_exact_upper(s, p, n)
                       : (q <= p ? 1 : exp(binomial_log_cdf_chernoff_impl(s, p, n)));
    }
};

struct ElisaBinomial
{
    template <typename T>
    static auto log_pvalue(const T& p, const T& q, const size_t n) -> T
    {
        using std::log;
        auto s = static_cast<size_t>(q * n);
        return n < 100 ? log(binomial_cdf_exact(s, p, n)) : binomial_log_cdf_chernoff(s, p, n);
    }
    template <typename T>
    static auto pvalue(const T& p, const T& q, const size_t n) -> T
    {
        auto s = static_cast<size_t>(q * n);
        return n < 100 ? binomial_cdf_exact(s, p, n) : binomial_cdf_chernoff(s, p, n);
    }
};
struct ElisaBinomialExact
{
    template <typename T>
    static auto log_pvalue(const T& p, const T& q, const size_t n) -> T
    {
        using std::log;
        auto s = static_cast<size_t>(q * n);
        return log(binomial_cdf_exact(s, p, n));
    }
    template <typename T>
    static auto pvalue(const T& p, const T& q, const size_t n) -> T
    {
        auto s = static_cast<size_t>(q * n);
        return binomial_cdf_exact(s, p, n);
    }
};

struct ElisaBinomialTT
{
    template <typename T>
    static auto pvalue(const T& p, const T& q, const size_t n) -> T
    {
        using std::abs;
        auto   d  = abs(p - q);
        size_t ll = n * std::max(p - d, T(0));
        size_t ul = n * std::min(p + d, T(1));

        if (n < 100)
            return binomial_cdf_exact(ll, p, n) + binomial_cdf_exact(ul, p, n);
        else
            return binomial_cdf_chernoff(ll, p, n) + binomial_cdf_chernoff(ul, p, n);
    }

    template <typename T>
    static auto log_pvalue(const T& p, const T& q, const size_t n) -> T
    {
        using std::log;
        return log(pvalue(p, q, n));
    }
};

struct ElisaGaussian
{
    template <typename T>
    static auto pvalue(const T& p, const T& q, const size_t n) -> T
    {
        return n < 100 ? binomial_cdf_exact(static_cast<size_t>(q * n), p, n)
                       : binomial_cdf_z(static_cast<size_t>(q * n), p, n);
    }

    template <typename T>
    static auto log_pvalue(const T& p, const T& q, const size_t n) -> T
    {
        using std::log;
        return log(pvalue(p, q, n));
    }
};

struct ElisaGaussianUpper
{
    template <typename T>
    static auto pvalue(const T& p, const T& q, const size_t n) -> T
    {
        auto s = static_cast<size_t>(q * n);
        return n < 100 ? binomial_cdf_exact_upper(s, p, n)
                       : (q <= p ? 1 : binomial_cdf_z(s, p, n));
    }

    template <typename T>
    static auto log_pvalue(const T& p, const T& q, const size_t n) -> T
    {
        using std::log;
        return log(pvalue(p, q, n));
    }
};

struct ElisaGaussianTT
{
    template <typename T>
    static auto pvalue(const T& p, const T& q, const size_t n) -> T
    {
        if (n < 100) {
            using std::abs;
            auto   d  = abs(p - q);
            size_t ll = n * std::max(p - d, T(0));
            size_t ul = n * std::min(p + d, T(1));
            return binomial_cdf_exact(ll, p, n) + binomial_cdf_exact(ul, p, n);
        }
        else
        {
            return cdf_z_tt(static_cast<size_t>(q * n), p, n);
        }
    }

    template <typename T>
    static auto log_pvalue(const T& p, const T& q, const size_t n) -> T
    {
        using std::log;
        return log(pvalue(p, q, n));
    }
};

struct ElisaGaussianOnly
{
    template <typename T>
    static auto pvalue(const T& p, const T& q, const size_t n) -> T
    {
        return binomial_cdf_z(static_cast<size_t>(q * n), p, n);
    }

    template <typename T>
    static auto log_pvalue(const T& p, const T& q, const size_t n) -> T
    {
        using std::log;
        return log(pvalue(p, q, n));
    }
};

struct ElisaGaussianOnlyTT
{
    template <typename T>
    static auto pvalue(const T& p, const T& q, const size_t n) -> T
    {
        return cdf_z_tt(static_cast<size_t>(q * n), p, n);
    }

    template <typename T>
    static auto log_pvalue(const T& p, const T& q, const size_t n) -> T
    {
        using std::log;
        return log(pvalue(p, q, n));
    }
};

struct ElisaGaussianOnlyUpper
{
    template <typename T>
    static auto pvalue(const T& p, const T& q, const size_t n) -> T
    {
        auto s = static_cast<size_t>(q * n);
        return (q <= p ? 1 : binomial_cdf_z(s, p, n));
    }

    template <typename T>
    static auto log_pvalue(const T& p, const T& q, const size_t n) -> T
    {
        using std::log;
        return log(pvalue(p, q, n));
    }
};

} // namespace sd::disc