/*
 * arithmetic.cpp: basic arithmetic operations. Implemented by means
 * of a special-purpose spigot core, which takes two sources and can
 * fetch a matrix from either one.
 */

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

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

class Core2 : public Core {
    unique_ptr<Source> sx, sy;
    bool same;                         // means sy is just the same as sx
    bool started { false };
    Tensor orig_tensor, tensor;
    bigint xbot, ybot;
    bigint_or_inf xtop, ytop;
    void eval_endpoint(vector<endpoint> &out, const Tensor &T,
                       const bigint_or_inf &x, const bigint_or_inf &y);

    virtual void premultiply(const Matrix &matrix) override;
    virtual Spigot clone() override;
    virtual void refine() override;
    virtual void endpoints(vector<endpoint> &) override;

  public:
    Core2(unique_ptr<Source> ax, unique_ptr<Source> ay,
          const Tensor &t, bool same = false);
};

/*
 * Core2 works by having an eight-element array (representing a small
 * tensor, i.e. a 2x2x2 array giving the coefficients of a bilinear
 * map) in which (a,b,c,d,e,f,g,h) represents a sort of 'dyadic Mobius
 * transformation'
 *
 *           a x y + b x + c y + d
 *  T(x,y) = ---------------------
 *           e x y + f x + g y + h
 *
 * We need to be able to do three things to this tensor: premultiply
 * in an output transform provided by our owning Generator,
 * postmultiply in an input transform from the x source, and ditto the
 * y source. All of these operations involve an ordinary 4-element
 * matrix as the other parameter. So let's suppose we have a matrix
 * (p,q,r,s) representing the ordinary Mobius transformation
 *
 *         p x + q
 *  M(x) = -------
 *         r x + s
 *
 * Then premultiplication means finding a replacement tensor
 * representing T'(x,y) = M(T(x,y)), and the two postmultiplications
 * similarly find T(M(x),y) and T(x,M(y)) respectively.
 *
 * Getting Maxima to do the tedious algebra for us, the following
 * commands will produce the rational functions we need, from which
 * the numerator and denominator terms are easily collected back up
 * into coefficients of xy, x, y and 1:
 *
 *    T : lambda([x,y], (a*x*y + b*x + c*y + d) / (e*x*y + f*x + g*y + h));
 *    M : lambda([x], (p*x + q) / (r*x + s));
 *    factor(M(T(x,y)));
 *    factor(T(M(x),y));
 *    factor(T(x,M(y)));
 */

static Tensor tensor_pre(const Matrix &M, const Tensor &T)
{
    const bigint &p = M[0], &q = M[1], &r = M[2], &s = M[3];
    const bigint &a = T[0], &b = T[1], &c = T[2], &d = T[3];
    const bigint &e = T[4], &f = T[5], &g = T[6], &h = T[7];
    /*
     * Maxima generates M(T(x,y)) as:
     *
     *   e q x y + a p x y + g q y + c p y + f q x + b p x + h q + d p
     *   -------------------------------------------------------------
     *   e s x y + a r x y + g s y + c r y + f s x + b r x + h s + d r
     */
    return { a*p + e*q, b*p + f*q, c*p + g*q, d*p + h*q,
             a*r + e*s, b*r + f*s, c*r + g*s, d*r + h*s };
}

static Tensor tensor_postx(const Tensor &T, const Matrix &M)
{
    const bigint &p = M[0], &q = M[1], &r = M[2], &s = M[3];
    const bigint &a = T[0], &b = T[1], &c = T[2], &d = T[3];
    const bigint &e = T[4], &f = T[5], &g = T[6], &h = T[7];
    /*
     * Maxima generates T(M(x),y) as:
     *
     *   c r x y + a p x y + c s y + a q y + d r x + b p x + d s + b q
     *   -------------------------------------------------------------
     *   g r x y + e p x y + g s y + e q y + h r x + f p x + h s + f q
     */
    return { a*p + c*r, b*p + d*r, a*q + c*s, b*q + d*s,
             e*p + g*r, f*p + h*r, e*q + g*s, f*q + h*s };
}

static Tensor tensor_posty(const Tensor &T, const Matrix &M)
{
    const bigint &p = M[0], &q = M[1], &r = M[2], &s = M[3];
    const bigint &a = T[0], &b = T[1], &c = T[2], &d = T[3];
    const bigint &e = T[4], &f = T[5], &g = T[6], &h = T[7];
    /*
     * Maxima generates T(x,M(y)) as:
     *
     *   b r x y + a p x y + d r y + c p y + b s x + a q x + d s + c q
     *   -------------------------------------------------------------
     *   f r x y + e p x y + h r y + g p y + f s x + e q x + h s + g q
     */
    return { a*p + b*r, a*q + b*s, c*p + d*r, c*q + d*s,
             e*p + f*r, e*q + f*s, g*p + h*r, g*q + h*s };
}

MAKE_CLASS_GREETER(Core2);

Core2::Core2(unique_ptr<Source> ax, unique_ptr<Source> ay,
             const Tensor &t, bool asame)
    : sx(move(ax))
    , sy(move(ay))
    , same(asame)
    , orig_tensor(t)
    , tensor(t)
{
    if (same) {
        dgreet("[same] ", sx->dbg_id(), " ", tensor);
    } else {
        dgreet(sx->dbg_id(), " ", sy->dbg_id(), " ", tensor);
    }
}

void Core2::premultiply(const Matrix &inmatrix)
{
    dprint("premultiply: ", inmatrix, " ", tensor);
    tensor = tensor_pre(inmatrix, tensor);
    dprint("tensor after premult ", tensor);
}

Spigot Core2::clone()
{
    return spigot_clone(this,
                        sx->clone().release()->toSource(),
                        sy ? sy->clone().release()->toSource() : nullptr,
                        orig_tensor, same);
}

void Core2::refine()
{
    bool force_absorb_x = false, force_absorb_y = false;

    dprint("refine started");

    if (!started) {
        /*
         * Fetch the interval bounds.
         */
        force_absorb_x = sx->get_interval(&xbot, &xtop);
        if (same) {
            ybot = xbot;
            ytop = xtop;
        } else {
            force_absorb_y = sy->get_interval(&ybot, &ytop);
        }
        started = true;
        dprint("Core2 init: intervals "
               "[", xbot, ",", xtop, "] "
               "[", ybot, ",", ytop, "]");
    }

    do {
        Matrix inmatrix;
        if (same) {
            /*
             * If our two sources are the same source, then we have no
             * choice anyway about which one to fetch a matrix from,
             * because there is no possible answer except 'both at
             * once'.
             */
            force_absorb_x = true;
            force_absorb_y = false;
        } else if (!force_absorb_x && !force_absorb_y) {
            /*
             * We need to fetch something from _one_ of our two
             * sources, but which? To answer that, we'll evaluate our
             * current interval endpoints and see which ones are the
             * furthest apart.
             */
            vector<endpoint> ends;
            endpoints(ends);
            if (ends.size() == 5) {
                /*
                 * This is the special case in which both starting
                 * intervals are infinite and something exceptionally
                 * annoying has happened to the tensor. Grab another
                 * matrix from both sources in the hope that things
                 * settle down.
                 */
                force_absorb_x = force_absorb_y = true;
            } else {
                /*
                 * OK, only four endpoints, which are respectively
                 * from (xbot,ybot), (xbot,ytop), (xtop,ybot) and
                 * (xtop,ytop).
                 *
                 * If there's a pole between either pair of endpoints
                 * differing in the x value, then we should fetch from
                 * x to try to get rid of it. Similarly y.
                 *
                 * (A pole _at_ any endpoint is treated as between
                 * that endpoint and everything else, and causes a
                 * fetch from both sources.)
                 */
                if (bigint_sign(ends[0].d) * bigint_sign(ends[2].d) != 1 ||
                    bigint_sign(ends[1].d) * bigint_sign(ends[3].d) != 1)
                    force_absorb_x = true;
                if (bigint_sign(ends[0].d) * bigint_sign(ends[1].d) != 1 ||
                    bigint_sign(ends[2].d) * bigint_sign(ends[3].d) != 1)
                    force_absorb_y = true;
                if (!force_absorb_x && !force_absorb_y) {
                    /*
                     * If that still hasn't settled the matter, we'll
                     * have to look at the actual numeric differences.
                     */
                    bigint exy = fdiv(ends[0].n, ends[0].d);
                    bigint exY = fdiv(ends[1].n, ends[1].d);
                    bigint eXy = fdiv(ends[2].n, ends[2].d);
                    bigint eXY = fdiv(ends[3].n, ends[3].d);
                    bigint xdiff = bigint_abs(eXy-exy) + bigint_abs(eXY-exY);
                    bigint ydiff = bigint_abs(exY-exy) + bigint_abs(eXY-eXy);
                    dprint("decide: xdiff=", xdiff, " ydiff=", ydiff);
                    if (xdiff >= ydiff)
                        force_absorb_x = true;
                    if (ydiff >= xdiff)
                        force_absorb_y = true;
                }
            }
        }
        if (force_absorb_x) {
            force_absorb_x = sx->get_matrix(inmatrix);
            dprint("postmultiply x: ", tensor, " ", inmatrix);
            tensor = tensor_postx(tensor, inmatrix);
            dprint("tensor after postmult ", tensor);
            if (same) {
                dprint("postmultiply y: ", tensor, " ", inmatrix);
                tensor = tensor_posty(tensor, inmatrix);
                dprint("tensor after postmult ", tensor);
            }
        }
        if (force_absorb_y) {
            force_absorb_y = sy->get_matrix(inmatrix);
            dprint("postmultiply y: ", tensor, " ", inmatrix);
            tensor = tensor_posty(tensor, inmatrix);
            dprint("tensor after postmult ", tensor);
        }
    } while (force_absorb_x || force_absorb_y);
}

void Core2::eval_endpoint(vector<endpoint> &out, const Tensor &T,
                          const bigint_or_inf &xbi, const bigint_or_inf &ybi)
{
    const bigint &a = T[0], &b = T[1], &c = T[2], &d = T[3];
    const bigint &e = T[4], &f = T[5], &g = T[6], &h = T[7];

    /*
     * Evaluating the image of an input point under the tensor T is
     * annoyingly fiddly because x or y or both or neither could be
     * infinite, and if either one is then we have multiple special
     * cases in turn (similarly to Core1 in spigot.cpp).
     */
    bigint x, y;
    if (xbi.finite(&x)) {
        if (ybi.finite(&y)) {
            /*
             * The easy finite case. A spot of factorisation reduces
             * the number of multiplications by x.
             */
            out.emplace_back((a*y + b) * x + (c*y + d),
                             (e*y + f) * x + (g*y + h));
            auto &ep = out[out.size() - 1];
            dprint("  finite endpoint ", ep.n, " / ", ep.d);
        } else {
            /*
             * x is finite, but y is infinite. So we either have
             * (ax+c)/(ex+g), or if that comes to 0/0, we fall back to
             * (bx+d)/(fx+h).
             */
            out.emplace_back(a*x + c, e*x + g);
            auto &ep = out[out.size() - 1];
            if (ep.n == 0 && ep.d == 0) {
                ep.n = b*x + d;
                ep.d = f*x + h;
            }
            dprint("  x-infinite endpoint ", ep.n, " / ", ep.d);
        }
    } else {
        if (ybi.finite(&y)) {
            /*
             * y is finite, but x is infinite. So we either have
             * (ay+b)/(ey+f), or if that comes to 0/0, we fall back to
             * (cy+d)/(gy+h).
             */
            out.emplace_back(a*y + b, e*y + f);
            auto &ep = out[out.size() - 1];
            if (ep.n == 0 && ep.d == 0) {
                ep.n = c*y + d;
                ep.d = g*y + h;
            }
            dprint("  y-infinite endpoint ", ep.n, " / ", ep.d);
        } else {
            /*
             * Both x and y are infinite. In this case, our opening
             * bid is just a/e, and our final fallback if all of
             * a,b,c,e,f,g are zero is d/h; but in between, there's a
             * more interesting case.
             */
            if (a != 0 || e != 0) {
                out.emplace_back(a, e);
                auto &ep = out[out.size() - 1];
                dprint("  xy-infinite endpoint a/e ", ep.n, " / ", ep.d);
            } else if (b == 0 && c == 0 && f == 0 && g == 0) {
                out.emplace_back(d, h);
                auto &ep = out[out.size() - 1];
                dprint("  xy-infinite endpoint d/h ", ep.n, " / ", ep.d);
            } else {
                /*
                 * a,e are zero, but at least one of b,c,f,g is not.
                 *
                 * If c,g are zero too, then we have no dependency on
                 * y at all, and simply return as if we were computing
                 * (bx+d)/(fx+h) - which we also know comes to just
                 * b/f since we only get to that case if at least one
                 * of b,f is nonzero.
                 *
                 * Similarly, if b,f are both zero, then we return
                 * c/g, by the same reasoning in mirror symmetry.
                 *
                 * But if one of c,g is nonzero _and_ one of b,f is
                 * nonzero, then what do we do? We're essentially
                 * asking for the limit of (bx+cy+d)/(fx+gy+h) as x
                 * and y tend to infinity, and that could come to
                 * either b/f or c/g depending on which of x,y tends
                 * to infinity 'fastest'. (In fact, it could also come
                 * to (thing between b,c) / (thing between f,g) if x
                 * and y tended to infinity in some particular
                 * relationship, but that's exactly the sort of thing
                 * the code in iterate_spigot_algorithm can handle -
                 * we only have to give it the two extreme endpoints
                 * of that range.)
                 *
                 * So in fact, what we do is to optionally return
                 * _both_ b/f and c/g, which gives Core2 the
                 * possibility of returning five endpoints rather than
                 * just four.
                 */
                if (b != 0 || f != 0) {
                    out.emplace_back(b, f);
                    auto &ep = out[out.size() - 1];
                    dprint("  xy-infinite endpoint b/f ", ep.n, " / ", ep.d);
                }
                if (c != 0 || g != 0) {
                    out.emplace_back(c, g);
                    auto &ep = out[out.size() - 1];
                    dprint("  xy-infinite endpoint c/g ", ep.n, " / ", ep.d);
                }
            }
        }
    }
}

void Core2::endpoints(vector<endpoint> &endpoints)
{
    dprint("endpoints for ", tensor);
    endpoints.clear();
    /*
     * These first three calls to eval_endpoint return exactly one
     * endpoint each, because eval_endpoint can only return 2 if both
     * inputs are infinite (and not necessarily even then).
     *
     * Note that the code in refine() which decides which source to
     * fetch from depends on the order of these calls, so don't switch
     * them around casually!
     */
    eval_endpoint(endpoints, tensor, xbot, ybot);
    eval_endpoint(endpoints, tensor, xbot, ytop);
    eval_endpoint(endpoints, tensor, xtop, ybot);
    /*
     * This final call to eval_endpoint can return 2 instead of 1.
     */
    eval_endpoint(endpoints, tensor, xtop, ytop);
}

Spigot spigot_add(Spigot a, Spigot b)
{
    bigint an, ad, bn, bd;
    bool arat = a->is_rational(&an, &ad);
    bool brat = b->is_rational(&bn, &bd);

    if (arat && brat)
        return spigot_rational(an * bd + bn * ad, ad * bd);
    else if (arat)
        return spigot_mobius(move(b), ad, an, 0, ad);
    else if (brat)
        return spigot_mobius(move(a), bd, bn, 0, bd);

    return make_unique<Core2>(
        a.release()->toSource(), b.release()->toSource(),
        /* (0xy+1x+1y+0)/(0xy+0x+0y+1) == x+y */
        Tensor {0,1,1,0,0,0,0,1});
}

Spigot spigot_sub(Spigot a, Spigot b)
{
    bigint an, ad, bn, bd;
    bool arat = a->is_rational(&an, &ad);
    bool brat = b->is_rational(&bn, &bd);

    if (arat && brat)
        return spigot_rational(an * bd - bn * ad, ad * bd);
    else if (arat)
        return spigot_mobius(move(b), -ad, an, 0, ad);
    else if (brat)
        return spigot_mobius(move(a), bd, -bn, 0, bd);

    return make_unique<Core2>(
        a.release()->toSource(), b.release()->toSource(),
        /* (0xy+1x-1y+0)/(0xy+0x+0y+1) == x-y */
        Tensor {0,1,-1,0,0,0,0,1});
}

Spigot spigot_mul(Spigot a, Spigot b)
{
    bigint an, ad, bn, bd;
    bool arat = a->is_rational(&an, &ad);
    bool brat = b->is_rational(&bn, &bd);

    if (arat && brat)
        return spigot_rational(an * bn, ad * bd);
    else if (arat)
        return spigot_mobius(move(b), an, 0, 0, ad);
    else if (brat)
        return spigot_mobius(move(a), bn, 0, 0, bd);

    return make_unique<Core2>(
        a.release()->toSource(), b.release()->toSource(),
        /* (1xy+0x+0y+0)/(0xy+0x+0y+1) == xy */
        Tensor {1,0,0,0,0,0,0,1});
}

Spigot spigot_quadratic_ratfn(Spigot a,
                              int n2, int n1, int n0,
                              int d2, int d1, int d0)
{
    bigint an, ad;
    bool arat = a->is_rational(&an, &ad);

    if (arat) {
        bigint denominator = d2 * an * an + d1 * an * ad + d0 * ad * ad;
        if (denominator == 0)
            throw domain_error("division by zero");
        return spigot_rational(n2 * an * an + n1 * an * ad + n0 * ad * ad,
                               denominator);
    }

    return make_unique<Core2>(
        a.release()->toSource(), nullptr,
        Tensor {n2,n1,0,n0,d2,d1,0,d0}, true);
}

Spigot spigot_quadratic(Spigot a, int a2, int a1, int a0)
{
    return spigot_quadratic_ratfn(move(a), a2, a1, a0, 0, 0, 1);
}

Spigot spigot_square(Spigot a)
{
    return spigot_quadratic_ratfn(move(a), 1, 0, 0, 0, 0, 1);
}

Spigot spigot_invsquare(Spigot a)
{
    return spigot_quadratic_ratfn(move(a), 0, 0, 1, 1, 0, 0);
}

Spigot spigot_div(Spigot a, Spigot b)
{
    bigint an, ad, bn, bd;
    bool arat = a->is_rational(&an, &ad);
    bool brat = b->is_rational(&bn, &bd);

    if (brat) {
        if (bn == 0)
            throw domain_error("division by zero");
        if (arat)
            return spigot_rational(an * bd, ad * bn);
        else
            return spigot_mobius(move(a), bd, 0, 0, bn);
    } else if (arat) {
        return spigot_mobius(move(b), 0, an, ad, 0);
    }

    return make_unique<Core2>(
        a.release()->toSource(), b.release()->toSource(),
        /* (0xy+1x+0y+0)/(0xy+0x+1y+0) = x/y */
        Tensor {0,1,0,0,0,0,1,0});
}

Spigot spigot_combine(Spigot a, Spigot b, const Tensor &t)
{
    /*
     * No special-case checking in this function - it's only called
     * from the internals of other spigot routines, and they're
     * assumed to take responsibility for interesting cases.
     */
    return make_unique<Core2>(a.release()->toSource(),
                              b.release()->toSource(), t);
}
