/*
 * monotone.cpp: Reusable framework for constructing second-level
 * spigots which compute a continuous and locally monotonic function
 * of a real, given a means of computing that function for
 * rationals.
 *
 * The basic idea is pretty simple. We have a spigot-construction
 * function implementing some function f on rationals, which is
 * monotonic (one way or the other, it doesn't matter) on an
 * interval surrounding the target real x. We compute a pair of
 * successive continued fraction convergents x1,x2 to x; then we
 * construct spigots for f(x1) and f(x2), and extract continued
 * fraction terms from both until they stop agreeing with each
 * other. Since continued fraction convergents alternate above and
 * below the target real, we can therefore be sure that all the
 * output terms are valid for f(x) proper. Then we fetch two more
 * convergents and start again, throwing away the first few output
 * terms on the grounds that we've already output them.
 *
 * The vital criterion is that f should be monotonic in the region
 * around all the forthcoming convergents of x. (Otherwise f(x1) and
 * f(x2) might agree on a continued fraction term which isn't valid
 * for f(x).) If this isn't the case at the point you want to call
 * this function, extract a few more convergents until it is!
 */

#include <stdlib.h>

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

class MonotoneHelper : public BinaryIntervalSource {
    shared_ptr<MonotoneConstructor> fcons;
    BracketingGenerator x;
    Spigot xorig;
    unique_ptr<BracketingGenerator> fx1, fx2;
    int crState { -1 };
    unsigned inbits, outbits;
    bigint n1, n2, d, t1, t2;
    bool extra_slope_initialised { false };
    bool using_extra_slope;
    bigint sn, sd, an { 0 }, ad { 0 };
    int which_way { 0 };

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

  public:
    MonotoneHelper(shared_ptr<MonotoneConstructor> afcons, Spigot ax);
};

MAKE_CLASS_GREETER(MonotoneHelper);

MonotoneHelper::MonotoneHelper(shared_ptr<MonotoneConstructor> afcons,
                               Spigot ax)
    : fcons(afcons), x(ax->clone()), xorig(move(ax))
{
    dgreet();
}

Spigot MonotoneHelper::clone()
{
    return spigot_clone(this, fcons, xorig->clone());
}

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

    /*
     * No point starting with really small d. Get at least a few
     * bits to be going on with.
     */
    inbits = 16;

    /*
     * But on the output side, we take whatever we get.
     */
    outbits = 0;

    while (1) {
        x.set_denominator_lower_bound_shift(inbits);
        inbits = inbits * 5 / 4;

        /*
         * Get a pair of bounds on x.
         */
        x.get_bracket(&n1, &n2, &d);
        dprint("input bracket (", n1, ",", n2, ") / ", d);
        fx1 = make_unique<BracketingGenerator>(fcons->construct(n1, d));
        fx2 = make_unique<BracketingGenerator>(fcons->construct(n2, d));
        fx1->set_denominator_lower_bound_shift(outbits);
        fx2->set_denominator_lower_bound_shift(outbits);

        /*
         * The first time round this loop, see if we need to add a
         * slope to the function to make it monotonic on that
         * interval.
         */
        if (!extra_slope_initialised) {
            bool ok = fcons->extra_slope_for_interval(n1, n2, d, sn, sd);
            if (!ok) {
                /*
                 * If the constructor couldn't bound f' on this
                 * interval, that's because the interval is too big.
                 * Try again with a smaller one.
                 */
                dprint("extra_slope_for_interval returned failure; "
                       "going round again");
                continue;
            }

            dprint("slope adjustment ", sn, " / ", sd);
            assert(sd > 0);
            using_extra_slope = (sn != 0);

            if (using_extra_slope) {
                /*
                 * If the user has provided a nonzero slope
                 * adjustment, they are promising that the function is
                 * monotonic in the same direction as that. You should
                 * never give a positive slope adjustment that makes
                 * the function into a _decreasing_ one, or a negative
                 * adjustment that makes the function increasing.
                 *
                 * It would be pointless to do so, because in either
                 * of those cases, returning no adjustment at all
                 * would be a better strategy! And it simplifies the
                 * logic of how to work out each interval bound
                 * further down.
                 */
                which_way = bigint_sign(sn);
                dprint("which_way = ", which_way > 0 ? "+1" : "-1",
                       " (from slope)");
            }
        }

        /*
         * Every time round this function, work out the extra we have
         * to add to our bounds on f(x) to account for that.
         */
        if (using_extra_slope) {
            an = (n1 - n2) * sn;
            ad = d * sd;
            dprint("slope adjustment for this interval ", an, " / ", ad);
        }

        while (1) {
            {
                bigint nlo1, nhi1, nlo2, nhi2;
                unsigned dbits1, dbits2, dbits;

                fx1->get_bracket_shift(&nlo1, &nhi1, &dbits1);
                fx2->get_bracket_shift(&nlo2, &nhi2, &dbits2);
                dprint("output bracket 1 (", nlo1, ",", nhi1, ") / 2^",dbits1);
                dprint("output bracket 2 (", nlo2, ",", nhi2, ") / 2^",dbits2);

                dbits = match_dyadic_rationals(nlo1, dbits1, nhi1, dbits1,
                                               nlo2, dbits2, nhi2, dbits2);

                dprint("evened output brackets (", nlo1, ",", nhi1,
                       "),(", nlo2, ",", nhi2, ") / 2^", dbits);

                bigint sum_widths = nhi1 - nlo1 + nhi2 - nlo2;

                if (using_extra_slope) {
                    /*
                     * We have a nonzero slope adjustment, so bounds
                     * on f(a) and f(b) don't by themselves give a
                     * bound on f(anything in the interval [a,b]). We
                     * have to widen our bounding interval based on
                     * the slope adjustment s.
                     *
                     * Suppose s > 0, and g(x) = f(x) + sx is
                     * increasing. Then what we _do_ know is that
                     *
                     *   f(a) + sa <= f(x) + sx <= f(b) + sb
                     *
                     * Subtracting sx from all three sides gives
                     *
                     *   f(a) + s(a-x) <= f(x) <= f(b) + s(b-x)
                     *
                     * and since a<x<b and s>0, it follows that
                     * -sb<-sx<-sa, which we can substitute into the
                     * LHS and RHS of that triple inequality to get
                     *
                     *   f(a) + s(a-b) <= f(x) <= f(b) + s(b-a)
                     *
                     * In other words, we have to add s*(a-b) to the
                     * bounds of our interval for f(a), and subtract
                     * it from the bounds of our interval for f(b).
                     * s*(a-b) is exactly what we computed above as
                     * the rational an/ad, so this is easy.
                     *
                     * (In the opposite case, where s < 0 and f(x)+sx
                     * is decreasing rather than increasing, the sign
                     * changes cancel out and it all works out the
                     * same in the end. And the user is supposed to
                     * ensure that the sign of s matches the sense of
                     * the monotonic function you get, so the case
                     * where s>0 but f+sx is decreasing - or vice
                     * versa - is dealt with by failing the assertion
                     * about inconsistent which_way.)
                     */

                    /*
                     * One direction-dependent thing: which way do we
                     * round this approximation to an/ad? It's s times
                     * a negative number, so for an increasing
                     * function it's negative, and hence adding it to
                     * nlo _lowers_ the lower bound of the output
                     * interval, which we want to do conservatively,
                     * so we round the quotient down, which is
                     * conservative. But for a decreasing function,
                     * with s < 0, this quantity becomes positive and
                     * we're raising the upper bound of the interval,
                     * so we switch to rounding up.
                     */
                    bigint adj;
                    if (which_way > 0) {
                        adj = fdiv(an << dbits, ad);
                        dprint("adj = floor(", an << dbits, "/", ad,
                               ") = ", adj);
                    } else {
                        adj = -fdiv(an << dbits, -ad);
                        dprint("adj = ceil(", an << dbits, "/", ad,
                               ") = ", adj);
                    }
                    nlo1 += adj;
                    nhi1 += adj;
                    nlo2 -= adj;
                    nhi2 -= adj;
                    dprint("slope-adjusted output bracket 1 (",
                           nlo1, ",", nhi1, ") / 2^",dbits);
                    dprint("slope-adjusted output bracket 2 (",
                           nlo2, ",", nhi2, ") / 2^",dbits);
                }

                bigint lo = (nlo1 < nlo2 ? nlo1 : nlo2);
                bigint hi = (nhi1 > nhi2 ? nhi1 : nhi2);

                if (nhi1 < nlo2) {
                    if (which_way == -1)
                        internal_fault("monotonicity failure! +1 -> -1");
                    which_way = +1;
                    dprint("which_way = +1 (from outputs)");
                } else if (nhi2 < nlo1) {
                    if (which_way == +1)
                        internal_fault("monotonicity failure! -1 -> +1");
                    which_way = -1;
                    dprint("which_way = -1 (from outputs)");
                }

                if (sum_widths + 2 < hi - lo) {
                    /*
                     * The combined widths of the original output
                     * intervals are smaller than the overall interval
                     * we're returning (even after accounting for a
                     * rounding error of 1 in each one).
                     *
                     * In other words: the uncertainty in the input
                     * interval is bigger than the uncertainty in the
                     * values of f at the endpoints, and hence, is the
                     * larger factor affecting the size of the
                     * interval we're outputting.
                     *
                     * So this seems like a good moment to give up on
                     * this input interval, and go back and fetch a
                     * narrower one.
                     */
                    dprint("going round again");
                    break;
                }

                *ret_lo = (nlo1 < nlo2 ? nlo1 : nlo2);
                *ret_hi = (nhi1 > nhi2 ? nhi1 : nhi2);
                *ret_bits = outbits = dbits;

                dprint("returning (", *ret_lo, ",", *ret_hi,
                       ") / 2^", *ret_bits);
            }

            crReturnV;
        }

        fx1 = nullptr;
        fx2 = nullptr;
    }

    crEnd;
}

Spigot spigot_monotone(shared_ptr<MonotoneConstructor> f, Spigot x)
{
    bigint n, d;
    if (x->is_rational(&n, &d)) {
        return f->construct(n, d);
    } else
        return make_unique<MonotoneHelper>(move(f), move(x));
}

bool MonotoneConstructor::extra_slope_for_interval(
        const bigint &nlo, const bigint &nhi, bigint d,
        bigint &sn, bigint &sd)
{
    // Default implementation, for MonotoneConstructor subclasses that
    // don't need any adjustment.
    sn = 0;
    sd = 1;
    return true;
}

class MonotoneInverter : public Source {
    shared_ptr<MonotoneConstructor> fcons;
    bool increasing;
    bigint nlo, nhi, d;
    Spigot target;
    int crState { -1 };
    int slo, shi;

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

  public:
    MonotoneInverter(shared_ptr<MonotoneConstructor> afcons, bool aincreasing,
                     bigint n1, bigint n2, bigint ad,
                     Spigot atarget);
};

MAKE_CLASS_GREETER(MonotoneInverter);

MonotoneInverter::MonotoneInverter(
    shared_ptr<MonotoneConstructor> afcons, bool aincreasing,
    bigint n1, bigint n2, bigint ad, Spigot atarget)
    : fcons{ afcons },
      increasing{ aincreasing },
      target{ move(atarget) }
{
    if (ad < 0) {
        n1 = -n1;
        n2 = -n2;
        ad = -ad;
    }

    nlo = n1;
    nhi = n2;
    d = ad;

    dgreet("[", nlo, ",", nhi, "] / ", d);
    if (nlo >= nhi)
        internal_fault("interval bounds reversed");
}

Spigot MonotoneInverter::clone()
{
    return spigot_clone(this, fcons, increasing, nlo, nhi, d, target->clone());
}

bool MonotoneInverter::gen_interval(bigint *low, bigint_or_inf *high)
{
    *low = 0;
    *high = 1;
    return true; // the first matrix will probably expand this interval
}

bool MonotoneInverter::gen_matrix(Matrix &matrix)
{
    crBegin;

    /*
     * Start by converting our starting interval [0,1] into
     * [nlo/d, nhi/d].
     */
    matrix = { nhi - nlo, nlo, 0, d };
    crReturn(false);

    /*
     * Work out what signs we expect to see at the two interval
     * ends.
     */
    if (increasing) {
        slo = -1;
        shi = +1;
    } else {
        slo = +1;
        shi = -1;
    }

    /*
     * Now repeatedly narrow the interval. To avoid exactness
     * hazards, we trisect rather than bisecting: pick _two_ trial
     * points inside the existing interval, begin evaluating
     * f(x)-target with x equal to each of those points, and
     * whichever one we find out the sign of first, narrow the
     * interval to exclude either the first or last third.
     */
    while (1) {
        {
            bigint ndiff = nhi - nlo;

            if (ndiff % 3U != 0) {
                d *= 3;
                nlo *= 3;
                nhi *= 3;
            } else {
                ndiff /= 3;
            }

            bigint n1 = nlo + ndiff;
            bigint n2 = n1 + ndiff;

            int s = parallel_sign_test
                (spigot_sub(fcons->construct(n1, d), target->clone()),
                 spigot_sub(fcons->construct(n2, d), target->clone()));

            if (s == slo) {
                /* New interval is [n1, nhi]. */
                nlo = n1;
                /* Narrow to the top 2/3 of prior interval */
                matrix = { 2, 1, 0, 3 };
            } else if (s == -slo) {
                /* New interval is [nlo, n1]. */
                nhi = n1;
                /* Narrow to the bottom 1/3 of prior interval */
                matrix = { 1, 0, 0, 3 };
            } else if (s == 2*slo) {
                /* New interval is [n2, nhi]. */
                nlo = n2;
                /* Narrow to the top 1/3 of prior interval */
                matrix = { 1, 2, 0, 3 };
            } else {
                /* New interval is [nlo, n2]. */
                nhi = n2;
                /* Narrow to the bottom 2/3 of prior interval */
                matrix = { 2, 0, 0, 3 };
            }
        }

        crReturn(false);
    }

    crEnd;
}

Spigot spigot_monotone_invert(shared_ptr<MonotoneConstructor> f,
                              bool increasing,
                              bigint n1, bigint n2, bigint d, Spigot x)
{
    return make_unique<MonotoneInverter>(f, increasing, n1, n2, d, move(x));
}
