#include <stdio.h>
#include <stdlib.h>

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

/*
 * Family of integrals of trigonometric functions. Of course the
 * actual trig functions themselves - sin, cos, tan - are trivial to
 * integrate; but if you make small tweaks to turn them into things
 * like sin(x)/x and sin(x^2), you need a fresh set of special
 * functions to act as their indefinite integrals.
 *
 * All of these have just the one implementation strategy, which is to
 * take the power series of the integrand and integrate it term by
 * term. This does pretty badly for large input values, because unlike
 * the ordinary trig functions with their exact periodicity, there's
 * no useful property which you can use to range-reduce down to a nice
 * small interval on which the series converges readily. But I don't
 * know of any useful way to do better; if anyone reading this does
 * know one, please do help out!
 *
 * (Since for large x all of these functions end up spiralling round
 * and round some centre point in the (sin,cos) plane and gradually
 * tending inwards towards the centre point as x->inf, you'd like to
 * think you could do some sort of large-scale approximation based on
 * saying 'weeellll, for x in _this_ region, the current radius of the
 * twirling is about _that_, so we can approximate the function as a
 * circle of that radius about the limit point, and by finding the
 * nearest root of the integrand we can easily enough know how far
 * round that circle we are, hence we must be about _here_' and then
 * you 'just' have to find a nicely converging way to compute some
 * kind of correction term. Sadly, every treatment of that idea that
 * I've actually seen ends up talking about 'asymptotic series', i.e.
 * ones which for any given x only give you so much precision before
 * ceasing to converge; so I suspect there wouldn't in fact be a
 * correction formula which you can keep computing out to arbitrary
 * precision.)
 */

/*
 * The TrigIntSeries class computes four different power series, all
 * obtained by integrating a sin or cos power series term by term.
 *
 * The sine integral, i.e. integral of sin(x)/x. Take the series for
 * sin(x), divide by x (i.e. just shift every term down one place),
 * and integrate (i.e. shift every term back up and divide it by its
 * new index).
 *
 *        sin x     = x - x^3/3!     + x^5/5!     - x^7/7!     + ...
 *        sin x / x = 1 - x^2/3!     + x^4/5!     - x^6/7!     + ...
 *   \int sin x / x = x - x^3/(3*3!) + x^5/(5*5!) - x^7/(7*7!) + ...
 *
 * The cosine integral, i.e. integral of cos(x)/x. Do the same to the
 * series for cos(x). But the initial constant term causes a problem,
 * which we deal with by just pretending it isn't there - i.e. the
 * series we're going to compute here will be the integral of
 * (cos(x)-1)/x, and then we can add on the integral of 1/x (i.e. log
 * x) in post-production. So that series (helpfully already monotonic)
 * is
 *
 *        (cos x - 1)     = x^2/2!     + x^4/4!     - x^6/6!     + ...
 *        (cos x - 1) / x = x  /2!     + x^3/4!     - x^5/6!     + ...
 *   \int (cos x - 1) / x = x^2/(2*2!) + x^4/(4*4!) - x^6/(6*6!) + ...
 *
 * but for speed's sake, we pass the square of the true input value as
 * the argument to the class in this case (which is safe since odd
 * powers of it are never needed in this expression). So actually the
 * series we end up evaluating here is
 *
 *   \int (cos sqrt(x) - 1) / sqrt(x)
 *                        = x  /(2*2!) + x^2/(4*4!) - x^3/(6*6!) + ...
 *
 * The Fresnel sin integral, i.e. integral of sin(x^2). This time we
 * start with the series for sin(x) and space it out by inserting more
 * zero terms, then integrate _that_ term by term:
 *
 *        sin x   = x     - x^3/3!     + x^5/5!       - x^7/7!       + ...
 *        sin x^2 = x^2   - x^6/3!     + x^10/5!      - x^14/7!      + ...
 *   \int sin x^2 = x^3/3 - x^7/(7*3!) + x^11/(11*5!) - x^15/(15*7!) + ...
 *
 * The Fresnel cos integral, i.e. integral of cos(x^2). Do exactly the
 * same to cos, which introduces no further trouble:
 *
 *        cos x   = 1 - x^2/2!     + x^4/4!     - x^6/6!       + ...
 *        cos x^2 = 1 - x^4/2!     + x^8/4!     - x^12/6!      + ...
 *   \int cos x^2 = x - x^5/(5*2!) + x^9/(9*4!) - x^13/(13*6!) + ...
 *
 * All of these power series are variants on a general schema, in
 * which each term consists of a power of x in arithmetic progression
 * (starting at x^1, x^2 or x^3 and then going up by 2 or 4 every
 * time); a factorial on the denominator (starting at 0! or 1!, and
 * again going up by either 2 or 4 places each time); and another
 * denominator factor in arithmetic progression (starting at 1, 2 or
 * 3, and going up by 2 or 4 each time). And they all have alternating
 * signs. In other words, we can write them all in the form
 *
 *   X/(f0*k0) - XY/(f0*f1*k1) + XY^2/(f0*f1*f2*k2) - ...
 *
 * where k0,k1,k2,... are integers in arithmetic progression, f0,f1,f2
 * are the successive factors you multiply in to make up each
 * factorial, X is the starting power of x, and Y is the power of x we
 * multiply in each time.
 *
 * We spigotise a power series in this format by writing X=n/d, Y=N/D,
 * and then that power series is rewritten in factorised form as
 *
 *     n     1     N     1     N     1     N
 *   ---- ( -- - ---- ( -- - ---- ( -- - ---- ( ... ))))
 *   f0*d   k0   f1*D   k1   f2*D   k2   f3*D
 *
 * which gives a series of matrices looking like this:
 *
 *  ( n   0  ) ( -N*k0  f1*D    ) ( -N*k1  f2*D    ) ( -N*k2  f3*D    ) ...
 *  ( 0 f0*d ) (   0    f1*D*k0 ) (   0    f2*D*k1 ) (   0    f3*D*k2 )
 */
class TrigIntSeries : public Source {
  public:
    enum Type { SI, CI, SF, CF };

  private:
    bigint norig, dorig;
    Type type;

    bigint n, d, N, D, i, f0, fbase, fstep, kbase, kstep;
    bool still_forcing { true };
    int crState { -1 };

    bigint kval(const bigint &i);
    bigint fval(const bigint &i);

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

  public:
    TrigIntSeries(const bigint &an, const bigint &ad, Type atype);
};

MAKE_CLASS_GREETER(TrigIntSeries);

TrigIntSeries::TrigIntSeries(const bigint &an, const bigint &ad, Type atype)
    : norig(an), dorig(ad), type(atype)
{
    switch (type) {
      case SI:
        n = an; d = ad;
        N = an*an; D = ad*ad;
        kbase = 1; kstep = 2;
        f0 = 1; fbase = 1; fstep = 2;
        break;
      case CI:
        // These would be an*an and ad*ad if we hadn't pre-squared the input
        n = an; d = ad;
        N = an; D = ad;
        kbase = 2; kstep = 2;
        f0 = 2; fbase = 2; fstep = 2;
        break;
      case SF:
        n = an*an*an; d = ad*ad*ad;
        N = an*an*an*an; D = ad*ad*ad*ad;
        kbase = 3; kstep = 4;
        f0 = 1; fbase = 1; fstep = 2;
        break;
      case CF:
        n = an; d = ad;
        N = an*an*an*an; D = ad*ad*ad*ad;
        kbase = 1; kstep = 4;
        f0 = 1; fbase = 0; fstep = 2;
        break;
    }

    dgreet(n, "/", d, " ", N, "/", D, " k=", kbase, "[", kstep, "] f0=", f0,
           " fbase=", fbase, "[", fstep, "]");
}

bigint TrigIntSeries::kval(const bigint &i)
{
    return kbase + kstep * i;
}

bigint TrigIntSeries::fval(const bigint &i)
{
    bigint ret = 1;
    bigint base = fbase + fstep * (i-1);
    for (bigint j = 1; j <= fstep; ++j)
        ret *= ++base;
    return ret;
}

Spigot TrigIntSeries::clone()
{
    return spigot_clone(this, norig, dorig, type);
}

bool TrigIntSeries::gen_interval(bigint *low, bigint_or_inf *high)
{
    *low = -1;
    *high = 1;
    return true;
}

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

    /* Anomalous initial matrix */
    matrix = { n, 0, 0, f0 * d };
    crReturn(still_forcing);

    i = 0;
    while (1) {
        {
            bigint k = kval(i);
            dprint("k", i, " = ", k);
            ++i;
            bigint f = fval(i);
            dprint("f", i, " = ", f);

            bigint t = f * D;
            matrix = { -N*k, t, 0, t*k };

            /*
             * Our returned matrices represent transformations of the
             * form x |-> 1/k - N/t x, which will refine the starting
             * interval [-1,+1] as long as |N/t| < 1-1/k.
             */
            if (k*N < (k-1) * bigint_abs(t))
                still_forcing = false;
        }

        crReturn(still_forcing);
    }

    crEnd;
}

class TrigIntSeriesConstructor : public MonotoneConstructor {
    TrigIntSeries::Type type;
    virtual Spigot construct(const bigint &n, const bigint &d) override;
  public:
    TrigIntSeriesConstructor(TrigIntSeries::Type atype);
    virtual bool extra_slope_for_interval(
        const bigint &nlo, const bigint &nhi, bigint d,
        bigint &sn, bigint &sd) override;
};

TrigIntSeriesConstructor::TrigIntSeriesConstructor(TrigIntSeries::Type atype)
    : type(atype)
{
}

Spigot TrigIntSeriesConstructor::construct(const bigint &n, const bigint &d)
{
    return make_unique<TrigIntSeries>(n, d, type);
}

bool TrigIntSeriesConstructor::extra_slope_for_interval(
    const bigint &nlo, const bigint &nhi, bigint d,
    bigint &sn, bigint &sd)
{
    /*
     * All of these functions are the integral of something bounded
     * within the interval [-1,+1], so their derivative is never less
     * than -1, and hence, adding x to them makes them all into
     * increasing functions.
     *
     * That's most obvious with the Fresnel integrals, whose
     * integrands are simply sin and cos of something. Si and Ci are
     * not quite so clear, because although the numerator of the
     * integrand is obviously bounded, you're dividing it by x, so
     * perhaps when |x| < 1 it might get too big? But if you check,
     * you find that even for -1 <= x <= +1, neither sin x / x nor
     * (cos x - 1) / x goes outside [-1,+1].
     */
    sn = sd = 1;
    return true;
}

static const MonotoneConstructorDebugWrapper<TrigIntSeriesConstructor>
expr_TrigIntSeriesSI("TrigIntSeriesSI", TrigIntSeries::SI);
static const MonotoneConstructorDebugWrapper<TrigIntSeriesConstructor>
expr_TrigIntSeriesCI("TrigIntSeriesCI", TrigIntSeries::CI);
static const MonotoneConstructorDebugWrapper<TrigIntSeriesConstructor>
expr_TrigIntSeriesSF("TrigIntSeriesSF", TrigIntSeries::SF);
static const MonotoneConstructorDebugWrapper<TrigIntSeriesConstructor>
expr_TrigIntSeriesCF("TrigIntSeriesCF", TrigIntSeries::CF);

static Spigot spigot_Si(Spigot x)
{
    /*
     * Si(x) is 'the' sine integral, according to Mathematica: the
     * integral of sin(x)/x, with Si(0)=0.
     */
    return spigot_monotone(
        make_shared<TrigIntSeriesConstructor>(TrigIntSeries::SI), move(x));
}

static const UnaryFnWrapper expr_Si("Si", spigot_Si);

static Spigot spigot_si(Spigot x)
{
    /*
     * si(x) is just Si(x) translated so that its limit at +inf is
     * zero, rather than Si(0)=0. Turns out the difference is pi/2.
     */
    return spigot_sub(spigot_Si(move(x)),
                      spigot_rational_mul(spigot_pi(), 1, 2));
}

static const UnaryFnWrapper expr_si("si", spigot_si);

static Spigot spigot_Cin(Spigot x)
{
    /*
     * Cin is defined by Wikipedia as the integral of (1-cos x)/x,
     * which is well defined everywhere, and hence a nice thing to
     * provide in addition to Ci. (Not to mention that it's simpler to
     * compute, being just a power series.)
     *
     * As discussed in the big comment above, we pre-square the input
     * to this power series, so that it gets good approximations
     * directly to x^2 rather than getting approximations to x itself
     * that it has to square.
     */
    return spigot_monotone(
        make_shared<TrigIntSeriesConstructor>(TrigIntSeries::CI),
        spigot_square(move(x)));
}

static const UnaryFnWrapper expr_Cin("Cin", spigot_Cin);

static Spigot spigot_Ci(Spigot x)
{
    /*
     * Ci is the actual integral of cos(x)/x. Since the integrand has
     * a pole at zero, we only permit positive arguments (and as far
     * as I can see, convention is _not_ to carry through to negative
     * x via the Cauchy principal value trick as Ei, li and Li do).
     * Instead we translate to make Ci(x) tend to 0 as x->inf, which
     * involves a constant term of gamma.
     */
    x = spigot_enforce(move(x), ENFORCE_GT, spigot_integer(0),
                       domain_error("Ci of a non-positive number"));
    Spigot extra = spigot_add(spigot_eulergamma(), spigot_log(x->clone()));
    return spigot_sub(move(extra), spigot_Cin(move(x)));
}

static const UnaryFnWrapper expr_Ci("Ci", spigot_Ci);

static Spigot spigot_UFresnelS(Spigot x)
{
    /*
     * UFresnel is my ugly name for the 'unnormalised' Fresnel
     * integrals, i.e. the straight-up integrals of sin(x^2) and
     * cos(x^2). Most software I've seen (Maple, Mathematica, various
     * special-function packages I found on the web for assorted
     * languages) seem to agree that the 'normalised' Fresnel
     * integrals below are the ones that deserve supporting (and
     * certainly the ones that deserve the sensible name). I expose
     * the unnormalised ones as spigot functions too, partly because
     * they're easier to compute (they're _just_ a nice power series
     * with rational coefficients) so good if you can get away with
     * using them, and mostly because I have to imagine that if you're
     * integrating something more complicated and get one of these as
     * an answer then this is the form it'll naturally drop out in.
     */
    return spigot_monotone(
        make_shared<TrigIntSeriesConstructor>(TrigIntSeries::SF), move(x));
}

static const UnaryFnWrapper expr_UFresnelS("UFresnelS", spigot_UFresnelS);

static Spigot spigot_UFresnelC(Spigot x)
{
    /*
     * UFresnelC: same discussion as for UFresnelS.
     */
    return spigot_monotone(
        make_shared<TrigIntSeriesConstructor>(TrigIntSeries::CF), move(x));
}

static const UnaryFnWrapper expr_UFresnelC("UFresnelC", spigot_UFresnelC);

static Spigot spigot_FresnelS(Spigot x)
{
    /*
     * The 'normalised' Fresnel integrals are the integrals of
     * sin(pi/2 x^2) and cos(pi/2 x^2), which are obtained from the
     * unnormalised version by scaling before and after, i.e.
     * FresnelS(x) = sqrt(2/pi) UFresnelS(sqrt(pi/2) x).
     *
     * All the hard work has been done in UFresnelS, so we just do
     * that scaling here in the simplest possible way.
     */
    Spigot xt, yt;
    xt = spigot_mul(move(x),
                    spigot_sqrt(spigot_rational_mul(spigot_pi(), 1, 2)));
    yt = spigot_UFresnelS(move(xt));
    return spigot_div(move(yt),
                      spigot_sqrt(spigot_rational_mul(spigot_pi(), 1, 2)));
}

static const UnaryFnWrapper expr_FresnelS("FresnelS", spigot_FresnelS);

static Spigot spigot_FresnelC(Spigot x)
{
    /*
     * FresnelC: same discussion as for FresnelS.
     */
    Spigot xt, yt;
    xt = spigot_mul(move(x),
                    spigot_sqrt(spigot_rational_mul(spigot_pi(), 1, 2)));
    yt = spigot_UFresnelC(move(xt));
    return spigot_div(move(yt),
                      spigot_sqrt(spigot_rational_mul(spigot_pi(), 1, 2)));
}

static const UnaryFnWrapper expr_FresnelC("FresnelC", spigot_FresnelC);
