/*
 * cfracout.cpp: handle all forms of output which are based on
 * retrieving the continued fraction terms of the number, whether we
 * output them directly or turn them into rational convergents.
 */

#include <assert.h>

#include <string>

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

class CfracOutputBaseClass : public OutputGenerator {
  protected:
    // things for the derived class to write output into
    std::string output, tentative_output;
    int tentative_digits;
    bool done;
  private:
    int crState;                       // for coroutine-structured progress()

    // local variables of progress()
    bool first_digit, refine;
    bool lo_open, hi_open;
    bigint lo, hi;
    bigint min_int_in_interval, max_int_in_interval;
    bigint term, scale;
    int digits;
  public:
    CfracOutputBaseClass(Coreable *acore);
    ~CfracOutputBaseClass() {}
    void progress();

    bool get_definite_output(std::string &out) {
        if (done && output.size() == 0)
            return false;

        progress();

        out = output;
        output = "";
        return true;
    }

    bool get_tentative_output(std::string &out, int *digits) {
        if (done)
            return false;

        if (tentative_output.size() > 0) {
            out = tentative_output;
            *digits = tentative_digits;
            return true;
        }

        return false;
    }

    virtual void got_definite_term(bigint term) = 0;
    virtual void got_definite_term_inf() = 0;
    virtual void clear_tentative_term() = 0;
    virtual void set_tentative_term(bigint term, int digits) = 0;
    virtual void set_tentative_term_inf(int digits) = 0;
};

CfracOutputBaseClass::CfracOutputBaseClass(Coreable *acore)
    : OutputGenerator(acore)
    , done(false)
    , crState(-1)
{
    dprint("hello CfracOutputBaseClass %p", core);
}

void CfracOutputBaseClass::progress()
{
    crBegin;

    first_digit = true;

    /*
     * The 'refine' flag is used to ensure that we squeeze as much
     * juice as we can out of any useful data returned from our Core.
     * We set it to true immediately after any call to
     * iterate_to_bounds, and back to false in any situation where the
     * results of that iterate_to_bounds causes us to have actually
     * made some progress.
     *
     * The rationale is that some Cores might return data in large
     * lumps which are expensive to generate, in which case whenever
     * iterate_to_bounds does something useful at all, it will have
     * provided enough information to get lots of extra terms or
     * digits out of the system, and it will be expensive to call it
     * again with refine=true, so we should delay as long as possible.
     * The basic rule is that we should always pass refine=false,
     * unless _nothing_ useful at all has happened since the last
     * attempt.
     */
    refine = false;

    while (true) {
        /*
         * If we didn't need to get tentative output right, we could
         * just call iterate_to_floor_or_inf as CfracGenerator does,
         * to iterate until we were sure of the integer part of the
         * number. But instead, we have to do the same job in multiple
         * stages.
         */

        /*
         * Stage 1: narrow the interval until there is at most one
         * integer contained in it.
         */
        do {
            iterate_to_bounds(&lo, &hi, &lo_open, &hi_open, 0, NULL, refine);
            refine = true;

            min_int_in_interval = (lo_open ? lo+1 : lo);
            max_int_in_interval = (hi_open ? hi-1 : hi);
            dprint("stage 1, %s%b,%b%s contains integers [%b..%b]",
                   lo_open ? "(" : "[", &lo, &hi, hi_open ? ")" : "]",
                   &min_int_in_interval, &max_int_in_interval);
        } while (min_int_in_interval < max_int_in_interval);
        refine = false;

        if (min_int_in_interval > max_int_in_interval) {
            /*
             * If we're really lucky, we've narrowed to the point
             * where there's _no_ integer in the interval, in which
             * case we already know the next continued fraction term
             * and can deal with it all in one go.
             *
             * Premultiply in a matrix which represents subtracting
             * that number and then taking the reciprocal, as in the
             * simple CfracGenerator.
             */

            term = max_int_in_interval;

            {
                bigint outmatrix[4];
                outmatrix[0] = 0;
                outmatrix[1] = outmatrix[2] = 1;
                outmatrix[3] = -term;
                dprint("extract definite term %b: %4m", &lo, outmatrix);
                core->premultiply(outmatrix);
            }

            got_definite_term(term);
            clear_tentative_term();
            first_digit = false;
            crReturnV;

            /*
             * And go straight back round the loop.
             */
            continue;
        }
        /*
         * If there _is_ still an integer in the interval,
         * then that's our tentative continued fraction term -
         * i.e. if the number turns out to _be_ an integer, it
         * will have to be this one.
         */
        term = min_int_in_interval;

        /*
         * Premultiply a matrix which subtracts off that term, but
         * don't take the reciprocal yet - we can't do that until
         * we know that the result of the subtraction is nonzero.
         */
        {
            bigint outmatrix[4];
            outmatrix[0] = 1;
            outmatrix[1] = -term;
            outmatrix[2] = 0;
            outmatrix[3] = 1;
            dprint("subtract off tentative term %b: %4m",
                   &term, outmatrix);
            core->premultiply(outmatrix);
        }

        /*
         * Stage 2: now our interval surrounds zero. Keep narrowing it
         * until one of the following things happens:
         *
         *  - the interval becomes entirely negative, i.e. not even
         *    containing zero at the top. Then our definite term is
         *    one less than the tentative term we output.
         *
         *  - the interval becomes entirely positive. Then our
         *    definite term is the same as the tentative term, but not
         *    exact.
         *
         *  - the interval is composed entirely of zero. Then our
         *    tentative term was exact and we know it, so terminate.
         *
         *  - the interval becomes entirely _non-negative_, i.e. it
         *    may include zero at the bottom. Then our definite term
         *    is the same as our tentative term, _but_ we can't take
         *    the reciprocal yet until we narrow further and prove the
         *    number isn't exact.
         *
         * Also, while we do this, we keep increasing the scale
         * parameter to iterate_to_bounds, which allows us to see how
         * close the tentative term is to being exact.
         */ 
        scale = 1;
        digits = 0;
        while (true) {
            iterate_to_bounds(&lo, &hi, &lo_open, &hi_open, 0, &scale, refine);
            refine = true;
            dprint("stage 2, %s%b,%b%s",
                   lo_open ? "(" : "[", &lo, &hi, hi_open ? ")" : "]");

            if (hi == 0 && lo == 0) {
                /*
                 * Exact zero. We're done.
                 */
                got_definite_term(term);
                got_definite_term_inf();
                clear_tentative_term();
                dprint("exact final term %b", &term);
                refine = false;
                crReturnV;
                assert(!"Should never come here");
            } else if (hi < 0 || (hi == 0 && hi_open)) {
                /*
                 * Interval is entirely negative, i.e. tentative term
                 * was one too high. Reduce it by 1.
                 *
                 * In most cases, we should still get a positive
                 * integer term. The exception is if we haven't output
                 * the number's integer part yet, in which case the
                 * output term could have any value at all.
                 */
                assert(first_digit || term > 1);
                term -= 1;

                /*
                 * Correct for having subtracted off the wrong term by
                 * adding 1 and then taking the reciprocal.
                 */
                {
                    bigint outmatrix[4];
                    outmatrix[0] = 0;
                    outmatrix[1] = outmatrix[2] = outmatrix[3] = 1;
                    dprint("correcting for wrong tentative term: %4m",
                           &term, outmatrix);
                    core->premultiply(outmatrix);
                }

                got_definite_term(term);
                clear_tentative_term();
                first_digit = false;
                refine = false;
                crReturnV;
                break;
            } else if (lo >= 0) {
                /*
                 * Interval is entirely non-negative, so we know what
                 * the definite term is.
                 */
                dprint("tentative term %b was correct", &term);
                got_definite_term(term);
                clear_tentative_term();
                first_digit = false;
                refine = false;
                crReturnV;

                /*
                 * Now if we produce any tentative output before
                 * taking the reciprocal, it will be the special kind
                 * of output that says the term we already generated
                 * was the final one.
                 */

                /*
                 * Now if zero is still at the bottom of the interval,
                 * keep iterating until it isn't.
                 */
                while (lo == 0 && !lo_open) {
                    iterate_to_bounds(&lo, &hi, &lo_open, &hi_open,
                                      0, &scale, refine);
                    refine = true;

                    dprint("stage 3, %s%b,%b%s",
                           lo_open ? "(" : "[", &lo, &hi, hi_open ? ")" : "]");

                    if (hi <= 1 && lo >= -1) {
                        dprint("stage 3, tentative digits = %d", (int)digits);
                        set_tentative_term_inf(digits);
                        tentative_digits = digits;
                        refine = false;
                        crReturnV;

                        digits++;
                        scale *= 10;
                    }
                }
                refine = false;

                /*
                 * And now we're all positive, so we can take the
                 * reciprocal.
                 */
                {
                    bigint outmatrix[4];
                    outmatrix[0] = outmatrix[3] = 0;
                    outmatrix[1] = outmatrix[2] = 1;
                    dprint("taking reciprocal: %4m", &term, outmatrix);
                    core->premultiply(outmatrix);
                }

                break;
            }

            /*
             * If we get here, we didn't manage to make any progress.
             * See if we've at least increased the number of tentative
             * digits we can print.
             */
            if (hi <= 1 && lo >= -1) {
                dprint("stage 2, tentative digits = %d", (int)digits);
                set_tentative_term(term, digits);
                tentative_digits = digits;
                crReturnV;

                digits++;
                scale *= 10;
                refine = false;
            }
        }
    }

    crEnd;
}

class CfracOutputGenerator : public CfracOutputBaseClass {
    bool oneline;
    int nterms;   // for choosing separator; doesn't increment after 2
    bool has_digitlimit;
    int digitlimit;

    const char *next_sep() {
        if (nterms == 1)
            return ";";
        else if (nterms > 1)
            return ",";
        else
            return "";
    }
  public:
    CfracOutputGenerator(Coreable *acore, bool aoneline,
                         bool ahas_digitlimit, int adigitlimit)
        : CfracOutputBaseClass(acore)
        , oneline(aoneline)
        , nterms(0)
        , has_digitlimit(ahas_digitlimit)
        , digitlimit(adigitlimit) {
        if (has_digitlimit && digitlimit < 0)
            throw spigot_error("cannot handle negative digit limit in "
                               "continued fraction output mode");
    }

    virtual void got_definite_term(bigint term) {
        char *dec;

        if (oneline)
            output += next_sep();

        dec = bigint_decstring(term);
        output += dec;
        free(dec);

        if (!oneline)
            output.push_back('\n');

        if (nterms <= 1)
            nterms++;
        if (has_digitlimit && digitlimit-- <= 0)
            done = true;
    }

    virtual void got_definite_term_inf() {
        done = true;
    }

    virtual void clear_tentative_term() {
        tentative_output = "";
    }
    virtual void set_tentative_term(bigint term, int digits) {
        tentative_output = "";
        if (oneline)
            tentative_output += next_sep();

        char *dec = bigint_decstring(term);
        tentative_output += dec;
        free(dec);

        char buf[80];
        sprintf(buf, " (10^%d)", digits);
        tentative_output += buf;
    }
    virtual void set_tentative_term_inf(int digits) {
        if (oneline)
            tentative_output = " ";
        else
            tentative_output = "";

        char buf[80];
        sprintf(buf, "(10^%d)", digits);
        tentative_output += buf;
    }
};

OutputGenerator *cfrac_output(Spigot *spig, bool oneline,
                              bool has_digitlimit, int digitlimit)
{
    return new CfracOutputGenerator(spig, oneline, has_digitlimit, digitlimit);
}

class ConvergentsOutputGenerator : public CfracOutputBaseClass {
    bigint cvn, pcvn, cvd, pcvd;
    bool has_digitlimit;
    int digitlimit;

    static std::string format_rational(const bigint &n, const bigint &d) {
        std::string ret;
        char *dec;

        dec = bigint_decstring(n);
        ret = dec;
        free(dec);

        ret.push_back('/');

        dec = bigint_decstring(d);
        ret += dec;
        free(dec);

        return ret;
    }

  public:
    ConvergentsOutputGenerator(Coreable *acore,
                               bool ahas_digitlimit, int adigitlimit)
        : CfracOutputBaseClass(acore)
        , cvn(1)
        , pcvn(0)
        , cvd(0)
        , pcvd(1)
        , has_digitlimit(ahas_digitlimit)
        , digitlimit(adigitlimit) {
        if (has_digitlimit && digitlimit < 0)
            throw spigot_error("cannot handle negative digit limit in "
                               "convergents output mode");
    }

    virtual void got_definite_term(bigint term) {
        bigint newcvn = cvn * term + pcvn;
        bigint newcvd = cvd * term + pcvd;
        pcvn = cvn;
        pcvd = cvd;
        cvn = newcvn;
        cvd = newcvd;

        output += format_rational(cvn, cvd);
        output.push_back('\n');

        if (has_digitlimit && digitlimit-- <= 0)
            done = true;
    }
        
    virtual void got_definite_term_inf() {
        done = true;
    }

    virtual void clear_tentative_term() {
        tentative_output = "";
    }
    virtual void set_tentative_term(bigint term, int digits) {
        bigint newcvn = cvn * term + pcvn;
        bigint newcvd = cvd * term + pcvd;

        tentative_output = format_rational(newcvn, newcvd);

        char buf[80];
        sprintf(buf, " (10^%d)", digits);
        tentative_output += buf;
    }
    virtual void set_tentative_term_inf(int digits) {
        char buf[80];
        sprintf(buf, "(10^%d)", digits);
        tentative_output = buf;
    }
};

OutputGenerator *convergents_output(Spigot *spig,
                                    bool has_digitlimit, int digitlimit)
{
    return new ConvergentsOutputGenerator(spig, has_digitlimit, digitlimit);
}

class RationalOutputGenerator : public CfracOutputBaseClass {
    bigint cvn, pcvn, cvd, pcvd;

    static std::string format_rational(const bigint &n, const bigint &d) {
        std::string ret;
        char *dec;

        dec = bigint_decstring(n);
        ret = dec;
        free(dec);

        if (d != 1) {
            ret.push_back('/');

            dec = bigint_decstring(d);
            ret += dec;
            free(dec);
        }

        return ret;
    }
  public:
    RationalOutputGenerator(Coreable *acore, bool has_digitlimit)
        : CfracOutputBaseClass(acore)
        , cvn(1)
        , pcvn(0)
        , cvd(0)
        , pcvd(1) {
        if (has_digitlimit) {
            throw spigot_error("cannot handle digit limit in "
                               "rational output mode");
        }

        if (acore->is_rational(&cvn, &cvd)) {
            output = format_rational(cvn, cvd);
            done = true;
        }
    }

    virtual void got_definite_term(bigint term) {
        bigint newcvn = cvn * term + pcvn;
        bigint newcvd = cvd * term + pcvd;
        pcvn = cvn;
        pcvd = cvd;
        cvn = newcvn;
        cvd = newcvd;
    }
        
    virtual void got_definite_term_inf() {
        if (!done) {
            output = format_rational(cvn, cvd);
            done = true;
        }
    }

    virtual void clear_tentative_term() {
        tentative_output = "";
    }
    virtual void set_tentative_term(bigint term, int digits) {
        bigint newcvn = cvn * term + pcvn;
        bigint newcvd = cvd * term + pcvd;

        tentative_output = format_rational(newcvn, newcvd);

        char buf[80];
        sprintf(buf, " (10^%d)", digits);
        tentative_output += buf;
    }
    virtual void set_tentative_term_inf(int digits) {
        tentative_output = format_rational(cvn, cvd);

        char buf[80];
        sprintf(buf, " (10^%d)", digits);
        tentative_output += buf;
    }
};

OutputGenerator *rational_output(Spigot *spig, bool has_digitlimit)
{
    return new RationalOutputGenerator(spig, has_digitlimit);
}
