#include <assert.h>

#include <vector>
#include <memory>
#include <algorithm>
#include <utility>
#include <initializer_list>

using std::vector;
using std::shared_ptr;
using std::make_shared;
using std::vector;
using std::min;
using std::max;
using std::swap;
using std::initializer_list;

#include "spigot.h"
#include "funcs.h"
#include "error.h"
#include "holefiller.h"
#include "cr.h"

struct HgFactor {
    bigint start, step;
};

static void debug_print_item(FILE *fp, const vector<HgFactor> &fs)
{
    const char *sep = "";
    for (const auto &f: fs) {
        debug_print_item(fp, sep);
        sep = ",";

        debug_print_item(fp, f.start);
        debug_print_item(fp, "+");
        debug_print_item(fp, f.step);
        debug_print_item(fp, "n");
    }
}

class HgRational : public Source {
    bigint n, d;
    shared_ptr<vector<HgFactor>> nfactors, dfactors;
    bigint mn, md, max_pole;
    bool stopped_forcing { false };
    bigint i { 0 };
    int fixed_sign;
    int current_sign { +1 };

    virtual Spigot clone() override;
    virtual bool gen_interval(bigint *low, bigint_or_inf *high) override;
    virtual bool gen_matrix(Matrix &matrix) override;

  public:
    HgRational(const bigint &an, const bigint &ad,
               shared_ptr<vector<HgFactor>> anfactors,
               shared_ptr<vector<HgFactor>> adfactors,
               int a_fixed_sign = 0);
};

MAKE_CLASS_GREETER(HgRational);

HgRational::HgRational(const bigint &an, const bigint &ad,
                       shared_ptr<vector<HgFactor>> anfactors,
                       shared_ptr<vector<HgFactor>> adfactors,
                       int a_fixed_sign)
    : n{an}, d{ad}, nfactors{anfactors}, dfactors{adfactors},
      fixed_sign{a_fixed_sign}
{
    dgreet("(", *nfactors, "; ", *dfactors, "; ", n, "/", d, ")",
           (a_fixed_sign > 0 ? ", positive terms only" :
            a_fixed_sign < 0 ? ", negative terms only" : ""));
}

Spigot HgRational::clone()
{
    return spigot_clone(this, n, d, nfactors, dfactors, fixed_sign);
}

bool HgRational::gen_interval(bigint *low, bigint_or_inf *high)
{
    /*
     * Strangely, deciding on a starting interval is the hardest part
     * of converting this particular series into a spigot description.
     * To ensure that the matrix corresponding to each term of the
     * series refines the starting interval to a subinterval of
     * itself, we must find the maximum _ratio_ between successive
     * terms.
     *
     * The inter-term ratio of the series we're summing has the form
     *
     *    z (a + A i) (b + B i) ...
     *    -------------------------
     *    i (c + C i) (d + D i) ...
     *
     * where a,A,b,B,... are the start and step values out of
     * nfactors, and c,C,d,D,... the ones out of dfactors. (The plain
     * factor of i on the bottom comes from the factorial divisor
     * which the user isn't required to specify every time.)
     *
     * We need to analyse this fraction to find an upper bound on the
     * values it can take - or rather, the values it can take for
     * positive integer i. (Clearly a general fraction of the above
     * form can take unbounded values if you let its domain include
     * arbitrary _real_ i - just let i get as close as you like to a
     * zero of one of the linear factors in the denominator.)
     *
     * We start by factoring out the A,B,... and C,D,... coefficients
     * of i, so as to rewrite it as
     *
     *    z A B ...    (a/A + i) (b/B + i) ...
     *    --------- x -------------------------
     *     C D ...    i (c/C + i) (d/D + i) ...
     *
     * and now the ratio is clearly equal to the constant (zAB/CD)
     * (where AB and CD each stands in for some product of an
     * arbitrary number of terms), times a quotient of monic linear
     * terms in i. Let's say that there are n of those terms on top
     * (so n = nfactors.size()), and m on the bottom (so m =
     * dfactors.size() + 1, again not forgetting that factorial term).
     *
     * So the limiting value of the ratio is (zAB/CD) in the case
     * where n=m, and zero if n<m. If n>m then the inter-term ratio
     * will increase without bound and the series cannot converge at
     * all.
     */

    bigint rn, rd;

    size_t max = dfactors->size() + 1;
    if (nfactors->size() > max)
        internal_fault("Top-heavy hypergeometric series");

    /*
     * Find out whether the inter-term ratio can sometimes be negative.
     */
    bool ratio_can_be_negative = false;
    if (n < 0)
        ratio_can_be_negative = true;
    for (const auto &f: *nfactors) {
        if (f.step <= 0)
            internal_fault("Numerator factor not in canonical form");
        if (f.start < 0)
            ratio_can_be_negative = true;
    }
    for (const auto &f: *dfactors) {
        if (f.step <= 0)
            internal_fault("Denominator factor not in canonical form");
        if (f.start < 0)
            ratio_can_be_negative = true;
    }

    /*
     * During evaluation, we'll also need to know when all the
     * denominator terms have passed their zero
     */

    /*
     * Calculate the limiting inter-term ratio.
     */
    if (nfactors->size() < max) {
        // More linear terms on the bottom than on the top, so the
        // ratio tends to zero in the long run.
        rn = 0;
        rd = 1;
    } else {
        // Same number of linear terms on top and bottom, so the
        // limiting ratio is a non-zero constant.
        rn = bigint_abs(n);
        rd = d;
        for (const auto &f: *nfactors)
            rn *= bigint_abs(f.step);
        for (const auto &f: *dfactors)
            rd *= f.step;
    }

    dprint("limiting interterm ratio = ", rn, "/", rd);
    if (rd <= 0)
        internal_fault("limit ratio not in canonical form");
    if (rn >= rd)
        throw domain_error("hypergeometric function does not converge");

    /*
     * Now we decide on a _maximum_ inter-term ratio that our interval
     * bounds will cope with. Arbitrarily, I set this to the
     * arithmetic mean of 1 and the limiting ratio rn/rd.
     */
    mn = rn + rd;
    md = 2*rd;
    dprint("max interterm ratio for interval = ", mn, "/", md);

    /*
     * Once we stop setting the force flag on return from
     * gen_matrix(), each matrix we return will represent a Mobius
     * transformation of the form x |-> 1 + kx, where |k| <= mn/md.
     *
     * When does this kind of transformation guarantee not to increase
     * the upper bound t of an interval? When 1 + (mn/md)t <= t, i.e.
     * t >= md/(md-mn). So that's where we need to set the upper limit
     * of our starting interval to ensure that all the matrices we
     * return (without the force flag) are refining ones.
     */
    bigint tn = md, td = md - mn;
    assert(td > 0);
    bigint int_hi = -fdiv(-tn, td);

    /*
     * If the inter-term ratio is always positive, then the series
     * we're summing consists of positive terms; we can simply set the
     * lower bound of our interval to be zero, and it will increase
     * with the partial sums. But if the ratio can change sign, then
     * we must make our interval symmetric about 0.
     */
    bigint int_lo;
    if (ratio_can_be_negative)
        int_lo = -int_hi;
    else
        int_lo = 0;

    dprint("interval bounds [", int_lo, ",", int_hi, "]");

    if (int_hi <= int_lo)
        internal_fault("Starting interval somehow ended up backwards");

    *low = int_lo;
    *high = int_hi;

    return true;
}

bool HgRational::gen_matrix(Matrix &matrix)
{
    /*
     * We're summing, for i = 0, 1, 2, ..., the values
     *
     *    (product over nfactors and i) of (start + i*step) * z^i
     *    -------------------------------------------------------
     *    (product over dfactors and i) of (start + i*step) * i!
     *
     * Factorising this in the usual spigot form of
     *
     *    a_0 + m_0 a_1 + (m_0 m_1) a_2 + (m_0 m_1 m_2) a_3 + ...
     *  = a_0 + m_0 (a_1 + m_1 (a_2 + m_2 (...)))
     *
     * is most easily done by setting a_i = 1.
     */
    bigint i_new = i + 1;
    bigint m_num = n, m_den = d * i_new;
    bigint all_den_positive = true;

    for (const auto &f: *nfactors)
        m_num *= f.start + i * f.step;
    for (const auto &f: *dfactors) {
        m_den *= f.start + i * f.step;
        if (m_den < 0)
            all_den_positive = false;
    }

    // Mobius function is 1 + mn/md x = (mn x + md) / (0 x + md).
    matrix = { m_num, m_den, 0, m_den };

    if (current_sign * fixed_sign < 0) {
        // ... unless this is a term of the wrong sign, which we're
        // omitting, in which case the constant term of the Mobius
        // function becomes zero instead of 1.
        matrix[1] = 0;
    }

    // Update current_sign based on the multiplicative factor we're
    // applying to all subsequent series terms.
    current_sign *= bigint_sign(m_num) * bigint_sign(m_den);

    dprint("m_", i, " = ", m_num, "/", m_den, " -> ", matrix);

    i = i_new;

    if (!stopped_forcing) {
        /*
         * Check whether we've finished outputting terms larger than
         * the ratio mn/md we computed above. If not, keep setting the
         * force flag.
         *
         * For this to be true, we need _this_ inter-term ratio to
         * have an absolute value of at most mn/md, and also we need
         * to guarantee that it'll keep getting closer to its limiting
         * value, which is true as long as none of the linear factors
         * on the bottom of the fraction has yet to go past its zero.
         */
        if (!all_den_positive) {
            dprint("still forcing (negative denominator factor)");
            return true;
        }

        if (bigint_abs(m_num) * md > mn * bigint_abs(m_den)) {
            dprint("still forcing (inter-term ratio ",
                   m_num, "/", m_den, " still too big)");
            return true;
        }

        dprint("safe to stop forcing");
        stopped_forcing = true;
    }

    return false;
}

class HgConstructor : public MonotoneConstructor, public Debuggable {
    shared_ptr<vector<HgFactor>> nfactors, dfactors;

  protected:
    virtual Spigot construct(const bigint &n, const bigint &d) override;
  public:
    HgConstructor(shared_ptr<vector<HgFactor>> anfactors,
                  shared_ptr<vector<HgFactor>> adfactors);
    virtual bool extra_slope_for_interval(
        const bigint &nlo, const bigint &nhi, bigint d,
        bigint &sn, bigint &sd) override;
};

MAKE_CLASS_GREETER(HgConstructor);

HgConstructor::HgConstructor(shared_ptr<vector<HgFactor>> anfactors,
                             shared_ptr<vector<HgFactor>> adfactors)
    : nfactors{anfactors}, dfactors{adfactors}
{
    dgreet("(", *nfactors, "; ", *dfactors, "; x)");
}

Spigot HgConstructor::construct(const bigint &n, const bigint &d)
{
    return make_unique<HgRational>(n, d, nfactors, dfactors);
}

bool HgConstructor::extra_slope_for_interval(
    const bigint &nlo, const bigint &nhi, bigint d,
    bigint &sn, bigint &sd)
{
    dprint("extra_slope_for_interval: [", nlo, ", ", nhi, "] / ", d);

    /*
     * Make another set of factors that will give a bound on the
     * derivative of this function.
     *
     * Differentiating the power series of a generalised
     * hypergeometric function term by term, you find that the x^n/n!
     * in each term turns into x^{n-1}/(n-1)!. In other words, the
     * whole series shifts along by one term, except that each
     * incrementing factor in the numerator and denominator starts one
     * step further along its arithmetic progression.
     *
     * But you also have to multiply the output of that hypergeometric
     * function by the product of all the factors you'd have got on
     * the _first_ term, of course.
     */
    auto nfdiff = make_shared<vector<HgFactor>>(*nfactors);
    auto dfdiff = make_shared<vector<HgFactor>>(*dfactors);
    bigint nd = 1, dd = 1;
    for (auto &f: *nfdiff) {
        nd *= f.start;
        f.start += f.step;
    }
    for (auto &f: *dfdiff) {
        dd *= f.start;
        f.start += f.step;
    }
    dprint("if y = Hg(", *nfactors, "; ", *dfactors, "; x)");
    dprint("then dy/dx = ", nd, "/", dd,
           " Hg(", *nfdiff, "; ", *dfdiff, "; x)");

    /*
     * Now we know how to compute f'. But we don't actually need to
     * compute its _value_; we only want a lower bound s such that
     * f'(x) >= s whenever nlo/d <= x <= nhi/d. (And in fact if s >= 0
     * then we're already fine and don't need any adjustment.)
     *
     * So the question is: how _negative_ can this value get? To
     * answer that, we instantiate HgRational with a special flag that
     * tells it to include only the terms with negative sign. That
     * winnowed version of the power series is monotonic in the
     * absolute value of the argument, so we evaluate it once for the
     * most negative value in the input interval, and once for the
     * most positive (omitting either of those if nothing in the
     * interval is negative or positive respectively), and take the
     * min of the two resulting lower bounds on f'.
     */

    bigint dydx_lower_bound = 0;       // we only care if it can be negative

    try {
        bigint lo, hi;

        if (nlo < 0) {
            dprint("need bound on dy/dx for lo = ", nlo, "/", d);
            StaticGenerator sg(make_unique<HgRational>(
                                   nlo, d, nfdiff, dfdiff, -1));
            sg.iterate_to_bounds(&lo, &hi, nullptr, nullptr, 2, nullptr, true);
            dprint("lower bound = ", lo);
            dydx_lower_bound = min(dydx_lower_bound, lo);
        }

        if (nhi > 0) {
            dprint("need bound on dy/dx for hi = ", nhi, "/", d);
            StaticGenerator sg(make_unique<HgRational>(
                                   nhi, d, nfdiff, dfdiff, -1));
            sg.iterate_to_bounds(&lo, &hi, nullptr, nullptr, 2, nullptr, true);
            dprint("lower bound = ", lo);
            dydx_lower_bound = min(dydx_lower_bound, lo);
        }

        dprint("overall lower bound = ", dydx_lower_bound);

        sn = -dydx_lower_bound;
        sd = 1;

        return true;
    } catch (domain_error &) {
        /*
         * If we can't get a bound on f' at all, that means the input
         * interval was set too large. Tell MonotoneHelper to try
         * again with a smaller one.
         */
        dprint("derivative failed to converge at interval endpoint");
        return false;
    }
}

static Spigot spigot_hg_wrapper(argvector<Spigot> &args)
{
    if (args.syntax.semicolons.size() != 2)
        throw expr_error("Hg expects two semicolons in its argument list");
    size_t semi0 = args.syntax.semicolons[0];
    size_t semi1 = args.syntax.semicolons[1];
    assert(0 <= semi0);
    assert(semi0 <= semi1);
    assert(semi1 <= args.size());
    if (semi1 != args.size() - 1)
        throw expr_error("Hg expects a single parameter after the "
                         "second semicolon");

    size_t nsize = semi0;
    size_t dsize = semi1 - semi0;
    assert(nsize + dsize == args.size() - 1);
    Spigot arg = move(args[nsize+dsize]);

    if (nsize == 0 && dsize == 0) {
        // In this degenerate case, the hypergeometric function turns
        // into exp. Use our real implementation of exp, so as to
        // benefit from its range reduction.
        return spigot_exp(move(arg));
    }

    if (nsize > dsize + 1)
        throw spigot_error("Hg requires at most one more numerator factor "
                           "than it has denominator factors");

    auto nfactors = make_shared<vector<HgFactor>>();
    auto dfactors = make_shared<vector<HgFactor>>();
    bigint n, d, n_overall = 1, d_overall = 1;
    for (size_t i = 0; i < nsize; i++) {
        if (!args[i]->is_rational(&n, &d))
            throw expr_error("numerator parameter #", i, " to Hg must be"
                             " rational");
        nfactors->push_back(HgFactor{n, d});
        n_overall *= d;
    }
    for (size_t i = 0; i < dsize; i++) {
        if (!args[i+nsize]->is_rational(&n, &d))
            throw expr_error("denominator parameter #", i, " to Hg must be"
                             " rational");
        if (d == 1 && n <= 0)
            throw expr_error("denominator parameter #", i, " to Hg may not be"
                             " a non-positive integer");
        dfactors->push_back(HgFactor{n, d});
        d_overall *= d;
    }

    auto constructor = make_shared<HgConstructor>(nfactors, dfactors);
    return spigot_monotone(constructor, spigot_rational_mul(
                               move(arg), d_overall, n_overall));
}

static const VariadicFnWrapper expr_Hg("Hg", spigot_hg_wrapper);

static Spigot spigot_agm_main(Spigot x, Spigot y)
{
    /*
     * agm(x, y) = agm(sqrt(xy), m)         where m = (x+y)/2
     *           = m agm(sqrt(xy)/m, 1)
     *
     * https://en.wikipedia.org/wiki/Elliptic_integral claims that the
     * complete elliptic integral of the first kind is given by two
     * formulae, one of which involves a hypergeometric function and
     * the other the arithmetic-geometric mean:
     *
     * K(k) = pi/2 2F1(1/2, 1/2; 1; k^2) = pi/2 / agm(1, sqrt(1-k^2))
     *
     * Conveniently, the factor of pi/2 cancels to give us
     *
     * 2F1(1/2, 1/2; 1; k^2) = pi/2 / agm(1, sqrt(1-k^2))
     *
     * Writing w = sqrt(1-k^2) so that k^2 = 1-w^2, we get
     *
     * agm(w, 1) = 1 / 2F1(1/2, 1/2; 1; 1-w^2)
     *
     * And plugging in w = sqrt(xy)/m from the above, we get
     * agm(x, y) = m / 2F1(1/2, 1/2; 1; 1-xy/m^2)
     */

    /*
     * The above expression will converge more and more slowly, the
     * closer to 1 is the primary argument (1-xy/m^2) to the
     * hypergeometric function.
     *
     * To avoid this, we first iterate the defining transformation of
     * the AGM - that is, replacing (x,y) with (AM(x,y),GM(x,y)) -
     * until |xy/m^2| is at least 1/2 (or, since we're using
     * get_approximate_approximant, close enough). Then we switch over
     * to the hypergeometric strategy from there onwards.
     */
    Spigot m;
    while (true) {
        m = spigot_rational_mul(
            spigot_add(x->clone(), y->clone()), 1, 2);

        StaticGenerator sg(spigot_div(spigot_mul(x->clone(), y->clone()),
                                      spigot_square(m->clone())));
        bigint n = sg.get_approximate_approximant(32);
        if (n >= 16)
            break;

        Spigot g = spigot_sqrt(spigot_mul(move(x), move(y)));
        x = move(m);
        y = move(g);
    }

    auto arg = spigot_sub(spigot_integer(1),
                          spigot_div(spigot_mul(move(x), move(y)),
                                     spigot_square(m->clone())));

    vector<HgFactor> nfactors { {1_bi, 2_bi}, {1_bi, 2_bi} };
    vector<HgFactor> dfactors { {4_bi, 4_bi} };
    auto constructor = make_shared<HgConstructor>(
        make_shared<vector<HgFactor>>(move(nfactors)),
        make_shared<vector<HgFactor>>(move(dfactors)));

    return spigot_div(move(m), spigot_monotone(constructor, move(arg)));
}

class AgmPrefixWrapper : public BinaryIntervalSource {
    Spigot x, y;
    unique_ptr<BracketingGenerator> bgx { nullptr };
    unique_ptr<BracketingGenerator> bgy { nullptr };
    unique_ptr<BracketingGenerator> bg { nullptr };
    int crState { -1 };

    virtual Spigot clone() override;
    virtual void gen_bin_interval(bigint *ret_lo, bigint *ret_hi,
                                  unsigned *ret_bits) override;

  public:
    AgmPrefixWrapper(Spigot ax, Spigot ay);
};

MAKE_CLASS_GREETER(AgmPrefixWrapper);

AgmPrefixWrapper::AgmPrefixWrapper(Spigot ax, Spigot ay)
    : x(move(ax)), y(move(ay))
{
    dgreet(x->dbg_id(), " ", x->dbg_id());
}

Spigot AgmPrefixWrapper::clone()
{
    return spigot_clone(this, x->clone(), y->clone());
}

void AgmPrefixWrapper::gen_bin_interval(bigint *ret_lo, bigint *ret_hi,
                                        unsigned *ret_bits)
{
    crBegin;

    /*
     * Initially, we set up two BracketingGenerators for x and y. As
     * long as they are not both proven to be greater than zero, we
     * compute an upper bound on their AGM, and return a bracket
     * between zero and that.
     */

    bgx = make_unique<BracketingGenerator>(x->clone());
    bgy = make_unique<BracketingGenerator>(y->clone());

    while (1) {
        {
            bigint xlo, xhi, ylo, yhi;
            unsigned xdbits, ydbits, dbits;
            bgx->get_bracket_shift(&xlo, &xhi, &xdbits);
            bgy->get_bracket_shift(&ylo, &yhi, &ydbits);
            dprint("input brackets:"
                   " x = (", xlo, ",", xhi, ") / 2^", xdbits,
                   " y = (", ylo, ",", yhi, ") / 2^", ydbits);

            if (xlo > 0 && ylo > 0) {
                dprint("bounded away from zero");
                break;
            }

            /*
             * Put the maximum values of x,y over a common
             * denominator.
             */
            dbits = match_dyadic_rationals(xhi, xdbits, yhi, ydbits);
            if (xhi > yhi)
                swap(xhi, yhi);
            dprint("upper bounds:"
                   " x <= ", xhi, " / 2^", dbits,
                   " y <= ", yhi, " / 2^", dbits);

            /*
             * Compute their AGM in integers, rounded up.
             */
            while (yhi - xhi >= 2) {
                bigint product = xhi * yhi;
                bigint gm = bigint_sqrt(product);
                if (gm * gm < product) {
                    ++gm;
                    assert(gm * gm >= product);
                }
                yhi = (xhi + yhi + 1) >> 1u;
                xhi = gm;
            }

            *ret_lo = 0;
            *ret_hi = yhi;
            *ret_bits = dbits;
        }
        dprint("output bracket for AGM: (",
               *ret_lo, ",", *ret_hi, ") / 2^", *ret_bits);
        crReturnV;
    }

    /*
     * If we've broken out of this loop, then we now know the answer
     * isn't going to just be zero, so we can switch to returning
     * intervals from the real AGM.
     *
     * The implementation of BinaryIntervalSource will take care
     * of us potentially not passing a _nested_ sequence of
     * intervals, so we've got no need to deal with any fiddly
     * special cases in that area.
     */
    bgx = nullptr;
    bgy = nullptr;
    bg = make_unique<BracketingGenerator>(spigot_agm_main(move(x), move(y)));
    while (1) {
        bg->get_bracket_shift(ret_lo, ret_hi, ret_bits);
        dprint("passthrough bracket for AGM: (",
               *ret_lo, ",", *ret_hi, ") / 2^", *ret_bits);
        crReturnV;
    }

    crEnd;
}

static Spigot spigot_agm(Spigot x, Spigot y)
{
    bigint n, d;

    x = spigot_enforce(move(x), ENFORCE_GE, spigot_integer(0),
                       domain_error("agm of a non-positive number"));
    y = spigot_enforce(move(y), ENFORCE_GE, spigot_integer(0),
                       domain_error("agm of a non-positive number"));

    if ((x->is_rational(&n, &d) && !n) || (y->is_rational(&n, &d) && !n))
        return spigot_integer(0);

    return make_unique<AgmPrefixWrapper>(move(x), move(y));
}

static const BinaryFnWrapper expr_agm("agm", spigot_agm);

static Spigot spigot_gauss()
{
    /*
     * Gauss's constant is 1 / agm(1,sqrt(2))
     *                   = 1 / (sqrt(2) agm(sqrt(1/2),1))
     *                   = 2F1(1/2, 1/2; 1; 1/2) / sqrt(2)
     */
    vector<HgFactor> nfactors { {1_bi, 2_bi}, {1_bi, 2_bi} };
    vector<HgFactor> dfactors { {4_bi, 4_bi} };
    auto hg = make_unique<HgRational>(
        1, 2,
        make_shared<vector<HgFactor>>(move(nfactors)),
        make_shared<vector<HgFactor>>(move(dfactors)));
    return spigot_div(move(hg), spigot_sqrt(spigot_integer(2)));
}

static const ConstantFnWrapper expr_gauss{"gauss", spigot_gauss};

static Spigot spigot_dilogarithm_unreduced(Spigot x)
{
    /*
     * https://en.wikipedia.org/wiki/Spence%27s_function
     */
    vector<HgFactor> nfactors { {1_bi, 1_bi}, {1_bi, 1_bi}, {1_bi, 1_bi} };
    vector<HgFactor> dfactors { {2_bi, 1_bi}, {2_bi, 1_bi} };
    auto constructor = make_shared<HgConstructor>(
        make_shared<vector<HgFactor>>(move(nfactors)),
        make_shared<vector<HgFactor>>(move(dfactors)));
    auto hg = spigot_monotone(constructor, x->clone());
    return spigot_mul(move(x), move(hg));
}

class DilogarithmHoleFiller : public HoleFiller {
    virtual Spigot clone() override;
    virtual Spigot xspecial(int) override;
    virtual Spigot yspecial(int) override;
    virtual Spigot ygeneral(Spigot) override;
    virtual bool combine(bigint *, bigint *, unsigned *,
                         const bigint &, const bigint &,
                         const bigint &, const bigint &,
                         unsigned, int) override;

  public:
    DilogarithmHoleFiller(Spigot);
};

MAKE_CLASS_GREETER(DilogarithmHoleFiller);

DilogarithmHoleFiller::DilogarithmHoleFiller(Spigot x)
    : HoleFiller{ move(x) }
{
    dgreet();
}

Spigot DilogarithmHoleFiller::clone()
{
    return spigot_clone(this, x->clone());
}

Spigot DilogarithmHoleFiller::xspecial(int i)
{
    if (i == 0) return spigot_integer(1);
    return nullptr;
}

Spigot DilogarithmHoleFiller::yspecial(int i)
{
    if (i == 0) return spigot_pi_squared_over_6();
    return nullptr;
}

Spigot DilogarithmHoleFiller::ygeneral(Spigot z)
{
    StaticGenerator sg(z->clone());
    bigint n = sg.get_approximate_approximant(32);

    /*
     * Range-reduce via the following relations:
     *
     * Li2(z) + Li2(1-z) = pi^2/6 - log(z)log(1-z)        (1)
     * Li2(z) + Li2(1/z) = -pi^2/6 - log(-z)^2/2          (2)
     * Li2(z) + Li2(1-1/(1-z)) = -log(1-z)^2/2              (3)
     */

    if (n > 24) {
        // reduce via (1)
        Spigot logz = spigot_log(z->clone());
        Spigot oneminusz = spigot_sub(spigot_integer(1), move(z));
        Spigot rhs = spigot_sub(
            spigot_pi_squared_over_6(), spigot_mul(
                move(logz), spigot_log(oneminusz->clone())));
        return spigot_sub(move(rhs),
                          spigot_dilogarithm_unreduced(move(oneminusz)));
    } else if (n < -48) {
        // reduce via (2)
        Spigot rhs = spigot_neg(
            spigot_add(
                spigot_pi_squared_over_6(),
                spigot_rational_mul(
                    spigot_square(spigot_log(spigot_neg(z->clone()))),
                    1, 2)));
        return spigot_sub(move(rhs), spigot_dilogarithm_unreduced(
                              spigot_reciprocal(move(z))));
    } else if (n < -16) {
        // reduce via (3)
        Spigot oneminusz = spigot_sub(spigot_integer(1), move(z));
        Spigot rhs = spigot_rational_mul(
            spigot_square(spigot_log(oneminusz->clone())), -1, 2);
        Spigot znew = spigot_sub(spigot_integer(1),
                                 spigot_reciprocal(move(oneminusz)));
        return spigot_sub(move(rhs), spigot_dilogarithm_unreduced(move(znew)));
    } else {
        return spigot_dilogarithm_unreduced(move(z));
    }
}

bool DilogarithmHoleFiller::combine(
    bigint *ret_lo, bigint *ret_hi, unsigned *ret_bits,
    const bigint &xnlo, const bigint &xnhi,
    const bigint &ynlo, const bigint &ynhi,
    unsigned dbits, int /*index*/)
{
    /*
     * This HoleFiller subclass has to override combine(), because
     * Li2's derivative behaves badly around the special point (x=1,
     * y=pi^2/6).
     *
     * pi^2/6 - Li2(1-eps), for small eps, goes roughly like
     * eps*(1-log(eps)), i.e. setting eps=exp(-k) gives you an error
     * of eps*(k+1). So this function is not Lipschitz at this point,
     * with any constant at all - pick any bound of m*eps for constant
     * m, and setting eps=exp(-m) will exceed it.
     */

    /*
     * Determine a power of 2 such that |x-1| < 2^-k. We only need to
     * look at xnlo, because the function isn't defined at all for x >
     * 1 (and spigot_enforce will ensure we find that out).
     */
    unsigned k = dbits + bigint_approxlog2(-xnlo) + 1;
    if (k < 3)
        return false; // don't even try to be useful until we get closer

    /*
     * If eps < 2^-k, then eps*(1-log(eps)) is about 2^-k*(1+k log 2),
     * which we crudely bound above by 2^-k*(1+k).
     */
    bigint deviation = (bigint(k) + 1_bi) << (k - dbits);
    *ret_bits = dbits;
    *ret_lo = ynlo - deviation;
    *ret_hi = ynhi + deviation;
    return true;
}

static Spigot spigot_dilogarithm(Spigot z)
{
    return make_unique<DilogarithmHoleFiller>(
        spigot_enforce(move(z), ENFORCE_LE, spigot_integer(1),
                       domain_error(
                           "dilogarithm of a number greater than 1")));
}

static const UnaryFnWrapper expr_Li2("Li2", spigot_dilogarithm);

class BesselIJConstructor : public HgConstructor {
    bigint alpha;
    int sign;
    virtual Spigot construct(const bigint &n, const bigint &d) override;
  public:
    BesselIJConstructor(bigint aalpha, int asign);
    virtual bool extra_slope_for_interval(
        const bigint &nlo, const bigint &nhi, bigint d,
        bigint &sn, bigint &sd) override;
};

BesselIJConstructor::BesselIJConstructor(bigint aalpha, int asign)
    : HgConstructor {
        make_shared<vector<HgFactor>>(),
        make_shared<vector<HgFactor>>(
            initializer_list<HgFactor>{ {aalpha + 1, 1_bi} })
      },
      alpha { aalpha }, sign { asign }
{
}

Spigot BesselIJConstructor::construct(const bigint &n, const bigint &d)
{
    /*
     * J_alpha(z) = H(-z^2/4) (z/2)^alpha / alpha!
     * I_alpha(z) = H(+z^2/4) (z/2)^alpha / alpha!
     *
     * where H is the hypergeometric function defined in the
     * constructor above, with no nfactors and the single dfactor
     * ((alpha+1) + i).
     */

    // This is H(+-z^2/4).
    Spigot hg = HgConstructor::construct(n*n*sign, 4*d*d);

    // This is (z/2)^alpha / alpha!, which is rational.
    bigint mn = 1, md = 1;
    for (bigint i = 1; i <= alpha; ++i) {
        mn *= n;
        md *= 2*d*i;
    }

    // This is their product.
    return spigot_rational_mul(move(hg), mn, md);
}

bool BesselIJConstructor::extra_slope_for_interval(
    const bigint &nlo, const bigint &nhi, bigint d,
    bigint &sn, bigint &sd)
{
    if (sign < 0) {
        /*
         * Bessel J functions have derivatives in [-1,+1] always, i.e.
         * they are Lipschitz with constant 1. This is because of two
         * properties:
         *
         *  (a) every J_alpha is bounded within [-1,+1]
         *  (b) the derivative of J_alpha is the arithmetic mean of
         *      J_{alpha+1} and J_{alpha-1}
         *
         * so the derivative of J_alpha is the mean of two functions
         * both bounded within [-1,+1], and so it must be bounded
         * within the same interval itself.
         *
         * So we can give a nice small value here, and avoid the
         * complicated and overcautious extra-slope value that
         * HgConstructor would have computed.
         */
        sn = sd = 1;
        return true;
    } else {
        /*
         * Bessel I functions, however, have no such convenient
         * property, so we have to fall back to HgConstructor's fully
         * general approach.
         */
        return HgConstructor::extra_slope_for_interval(nlo, nhi, d, sn, sd);
    }
}

class BesselIJHoleFiller : public HoleFiller {
    bigint alpha;
    int sign;
    shared_ptr<MonotoneConstructor> constructor;

    virtual Spigot clone() override;
    virtual Spigot xspecial(int i) override;
    virtual Spigot yspecial(int i) override;
    virtual Spigot ygeneral(Spigot x) override;

  public:
    BesselIJHoleFiller(bigint aalpha, int asign, Spigot ax);
};

MAKE_CLASS_GREETER(BesselIJHoleFiller);

BesselIJHoleFiller::BesselIJHoleFiller(bigint aalpha, int asign, Spigot ax)
    : HoleFiller { move(ax) }, alpha { aalpha }, sign { asign },
      constructor { make_shared<BesselIJConstructor>(alpha, sign) }
{
    dgreet();
}

Spigot BesselIJHoleFiller::clone()
{
    return spigot_clone(this, alpha, sign, x->clone());
}

Spigot BesselIJHoleFiller::xspecial(int i)
{
    if (i == 0) return spigot_integer(0);
    return nullptr;
}

Spigot BesselIJHoleFiller::yspecial(int i)
{
    if (i == 0) {
        /*
         * J_0(0) = 1, but J_{>0}(0) = 0.
         */
        return alpha == 0 ? spigot_integer(1) : spigot_integer(0);
    }
    return nullptr;
}

Spigot BesselIJHoleFiller::ygeneral(Spigot x)
{
    return spigot_monotone(constructor, move(x));
}

static Spigot spigot_bessel_IJ(bigint alpha, int sign, Spigot x)
{
    /*
     * Convert to positive alpha via J_{-a}(x) = (-1)^a J_a(x)
     */
    bool negate = false;
    if (alpha < 0) {
        alpha = -alpha;
        negate = (alpha % 2u) != 0;
    }
    Spigot toret = make_unique<BesselIJHoleFiller>(alpha, sign, move(x));
    if (negate)
        toret = spigot_neg(move(toret));
    return toret;
}

template<int sign>
static Spigot spigot_bessel_IJ(Spigot alpha, Spigot x)
{
    bigint n, d;
    if (!alpha->is_rational(&n, &d) || d != 1)
        throw expr_error("first parameter must be an integer");
    return spigot_bessel_IJ(n, sign, move(x));
}

static const BinaryFnWrapper expr_BesselJ("BesselJ", spigot_bessel_IJ<-1>);
static const BinaryFnWrapper expr_BesselI("BesselI", spigot_bessel_IJ<+1>);
