/*
 * 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>
using std::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
    string output, tentative_output;
    int tentative_digits;
    bool done;

    // things for the derived class to override
    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;

  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;

    void progress();

    // implementation of the parent OutputGenerator interface
    virtual bool get_definite_output(string &out) override;
    virtual bool get_tentative_output(string &out, int *digits) override;

  public:
    CfracOutputBaseClass(Spigot acore);
};

MAKE_CLASS_GREETER(CfracOutputBaseClass);

CfracOutputBaseClass::CfracOutputBaseClass(Spigot acore)
    : OutputGenerator(move(acore))
    , done(false)
    , crState(-1)
{
    dgreet(core->dbg_id());
}

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

    progress();

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

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

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

    return false;
}

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, nullptr, refine);
            refine = true;

            min_int_in_interval = (lo_open ? lo+1 : lo);
            max_int_in_interval = (hi_open ? hi-1 : hi);
            dprint("stage 1, ",
                   lo_open ? "(" : "[", lo, ",", hi, hi_open ? ")" : "]",
                   " contains integers [", 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;

            {
                Matrix outmatrix = { 0, 1, 1, -term };
                dprint("extract definite term ", 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.
         */
        {
            Matrix outmatrix = { 1, -term, 0, 1 };
            dprint("subtract off tentative term ", 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, ",
                   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 ", 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.
                 */
                {
                    static const Matrix outmatrix = { 0, 1, 1, 1 };
                    dprint("correcting for wrong tentative 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 ", term, " was correct");
                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, ",
                           lo_open ? "(" : "[", lo, ",",
                           hi, hi_open ? ")" : "]");

                    if (hi <= 1 && lo >= -1) {
                        dprint("stage 3, tentative digits = ", 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.
                 */
                {
                    static const Matrix outmatrix = { 0, 1, 1, 0 };
                    dprint("taking reciprocal: ", 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 = ", 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();

    virtual void got_definite_term(bigint term) override;
    virtual void got_definite_term_inf() override;
    virtual void clear_tentative_term() override;
    virtual void set_tentative_term(bigint term, int digits) override;
    virtual void set_tentative_term_inf(int digits) override;

  public:
    CfracOutputGenerator(Spigot acore, bool aoneline,
                         bool ahas_digitlimit, int adigitlimit);
};

const char *CfracOutputGenerator::next_sep()
{
    if (nterms == 1)
        return ";";
    else if (nterms > 1)
        return ",";
    else
        return "";
}

CfracOutputGenerator::CfracOutputGenerator(
    Spigot acore, bool aoneline, bool ahas_digitlimit, int adigitlimit)
    : CfracOutputBaseClass(move(acore))
    , oneline(aoneline)
    , nterms(0)
    , has_digitlimit(ahas_digitlimit)
    , digitlimit(adigitlimit)
{
    if (has_digitlimit && digitlimit < 0)
        throw config_error("cannot handle negative digit limit in "
                           "continued fraction output mode");
}

void CfracOutputGenerator::got_definite_term(bigint term)
{
    if (oneline)
        output += next_sep();

    output += bigint_decstring(term);

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

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

void CfracOutputGenerator::got_definite_term_inf()
{
    done = true;
}

void CfracOutputGenerator::clear_tentative_term()
{
    tentative_output = "";
}

void CfracOutputGenerator::set_tentative_term(bigint term, int digits) {
    tentative_output = "";
    if (oneline)
        tentative_output += next_sep();

    tentative_output += bigint_decstring(term);;

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

void CfracOutputGenerator::set_tentative_term_inf(int digits)
{
    if (oneline)
        tentative_output = " ";
    else
        tentative_output = "";

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

unique_ptr<OutputGenerator> cfrac_output(
    Spigot spig, bool oneline, bool has_digitlimit, int digitlimit)
{
    return make_unique<CfracOutputGenerator>(
        move(spig), oneline, has_digitlimit, digitlimit);
}

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

    static string format_rational(const bigint &n, const bigint &d);

    virtual void got_definite_term(bigint term) override;
    virtual void got_definite_term_inf() override;
    virtual void clear_tentative_term() override;
    virtual void set_tentative_term(bigint term, int digits) override;
    virtual void set_tentative_term_inf(int digits) override;

  public:
    ConvergentsOutputGenerator(Spigot acore,
                               bool ahas_digitlimit, int adigitlimit);
};

string ConvergentsOutputGenerator::format_rational(
    const bigint &n, const bigint &d)
{
    string ret = bigint_decstring(n);
    ret.push_back('/');
    ret += bigint_decstring(d);
    return ret;
}

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

void ConvergentsOutputGenerator::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;
}
        
void ConvergentsOutputGenerator::got_definite_term_inf()
{
    done = true;
}

void ConvergentsOutputGenerator::clear_tentative_term()
{
    tentative_output = "";
}

void ConvergentsOutputGenerator::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;
}

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

unique_ptr<OutputGenerator> convergents_output(
    Spigot spig, bool has_digitlimit, int digitlimit)
{
    return make_unique<ConvergentsOutputGenerator>(
        move(spig), has_digitlimit, digitlimit);
}

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

    static string format_rational(const bigint &n, const bigint &d);

    virtual void got_definite_term(bigint term) override;
    virtual void got_definite_term_inf() override;
    virtual void clear_tentative_term() override;
    virtual void set_tentative_term(bigint term, int digits) override;
    virtual void set_tentative_term_inf(int digits) override;

  public:
    RationalOutputGenerator(Spigot acore, bool has_digitlimit);
};

string RationalOutputGenerator::format_rational(
    const bigint &n, const bigint &d)
{
    string ret;

    ret = bigint_decstring(n);

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

    return ret;
}

RationalOutputGenerator::RationalOutputGenerator(
    Spigot acore, bool has_digitlimit)
    : CfracOutputBaseClass(move(acore))
    , cvn(1)
    , pcvn(0)
    , cvd(0)
    , pcvd(1)
{
    if (has_digitlimit) {
        throw config_error("cannot handle digit limit in "
                           "rational output mode");
    }

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

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

void RationalOutputGenerator::clear_tentative_term()
{
    tentative_output = "";
}

void RationalOutputGenerator::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;
}

void RationalOutputGenerator::set_tentative_term_inf(int digits)
{
    tentative_output = format_rational(cvn, cvd);

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

unique_ptr<OutputGenerator> rational_output(
    Spigot spig, bool has_digitlimit)
{
    return make_unique<RationalOutputGenerator>(move(spig), has_digitlimit);
}
