/* -*- c++ -*-
 * funcs.h: list of functions which return Spigot objects describing
 * mathematical constants, operators and functions.
 */

#include <vector>
#include <array>
#include <memory>
#include <functional>
using std::vector;
using std::array;
using std::shared_ptr;
using std::make_shared;
using std::function;

/*
 * A subclass of 'vector', for holding a list of arguments to a
 * function call in the spigot expression language. Making it a
 * subclass means that as well as storing the arguments themselves, it
 * also has room to store syntactic details of the function call, such
 * as unusual punctuation separating subsections of the argument list.
 */

struct FnCallSyntax {
    // List of positions where a semicolon appears in the argument
    // list, used by some variadics (like the hypergeometric function)
    // to separate multiple variable-sized sublists of the arguments.
    //
    // Numbers in this list indicate positions between arguments, so 0
    // means there was a semicolon right at the start, 1 means there
    // was one after the first argument, and so on.
    //
    // The list is in increasing order. Multiple copies of the same
    // index can appear (e.g. f(1,2;;3,4)).
    vector<size_t> semicolons;

    void enforce_trivial(const string &name) const;
};

template<typename Element>
class argvector: public vector<Element> {
    using vector<Element>::vector;
  public:
    argvector() : vector<Element>() {}
    argvector(const vector<Element> &rhs) : vector<Element>(rhs) {}
    FnCallSyntax syntax;
};

/* In consts.cpp: */
Spigot spigot_pi();
Spigot spigot_pi_squared_over_6();
Spigot spigot_e();

/* In rational.cpp: */
Spigot spigot_integer(bigint const &n);
Spigot spigot_rational(bigint const &n, bigint const &d);

/* In arithmetic.cpp: */
Spigot spigot_add(Spigot a, Spigot b);
Spigot spigot_sub(Spigot a, Spigot b);
Spigot spigot_mul(Spigot a, Spigot b);
Spigot spigot_square(Spigot a);
Spigot spigot_invsquare(Spigot a);
Spigot spigot_quadratic(Spigot a, int a2, int a1, int a0);
Spigot spigot_quadratic_ratfn(Spigot a,
                              int n2, int n1, int n0,
                              int d2, int d1, int d0);
Spigot spigot_div(Spigot a, Spigot b);
Spigot spigot_combine(Spigot a, Spigot b, const Tensor &tensor);

/* In sqrt.cpp: */
Spigot spigot_sqrt(Spigot a);

/* In unary.cpp: */
Spigot spigot_identity(Spigot a);
Spigot spigot_reciprocal(Spigot a);
Spigot spigot_neg(Spigot a);
Spigot spigot_abs(Spigot a);
Spigot spigot_mod(Spigot a, Spigot b);
Spigot spigot_rem(Spigot a, Spigot b);
Spigot spigot_rational_mul(Spigot a, const bigint &n, const bigint &d);
Spigot spigot_mobius(Spigot x,
                     const bigint &a, const bigint &b,
                     const bigint &c, const bigint &d);
bigint spigot_to_integer(Spigot x, RoundingMode rmode,
                         bool *known_exact = nullptr);

/* In trig.cpp: */
Spigot spigot_atan(Spigot a);

/* In exp.cpp: */
Spigot spigot_exp(Spigot a);
Spigot spigot_log(Spigot a);
Spigot spigot_exp2(Spigot a);
Spigot spigot_log2(Spigot a);
Spigot spigot_pow(Spigot a, Spigot b);
Spigot spigot_log1p(Spigot a);

/* In gamma.cpp: */
Spigot spigot_factorial(Spigot a);

/* In algebraic.cpp: */
Spigot spigot_algebraic(const vector<bigint> &aP,
                        bigint nlo, bigint nhi, bigint d);

/* In expint.cpp: */
Spigot spigot_eulergamma();

/* In monotone.cpp (a utility class used by other function implementations,
 * not suitable for direct use by clients): */
struct MonotoneConstructor {
    virtual ~MonotoneConstructor() = default;
    virtual Spigot construct(const bigint &n, const bigint &d) = 0;

    /*
     * Use this function to compensate if you're trying to use
     * spigot_monotone with a constructor that implements a function
     * that is _not_ monotonic. It's given the interval [nlo/d,nhi/d]
     * on which MonotoneHelper will focus its attention, and its job
     * is to return a value s = sn/sd such that the function that maps
     * x to f(x) + s*x _is_ monotonic.
     *
     * Typically this means that it's after a lower bound on f': for
     * example, if you know f'(x) > -1 throughout your interval, then
     * it follows that f(x) + x has derivative >= 0 everywhere, i.e.
     * is monotonic. So you could return s=+1 in that situation. But
     * you could also return a negative s if it's easier to turn f
     * into a decreasing function than an increasing one.
     *
     * It should return true if successful at doing this job (even if
     * it filled in sn==0, sd==1 to mean no adjustment was needed), or
     * false if it was unable to establish any bound on the derivative
     * at all for the given interval. In the latter case,
     * MonotoneHelper will try again with a smaller interval about the
     * target point.
     *
     * Note that, unlike construct(), this method is optional - if
     * your function is already monotonic, all you need to do is not
     * implement it at all.
     */
    virtual bool extra_slope_for_interval(
        const bigint &nlo, const bigint &nhi, bigint d,
        bigint &sn, bigint &sd);
};

Spigot spigot_monotone(shared_ptr<MonotoneConstructor> f, Spigot x);
Spigot spigot_monotone_invert(shared_ptr<MonotoneConstructor> f,
                              bool increasing,
                              bigint n1, bigint n2, bigint d, Spigot x);

/* In inverter.cpp (another similar utility class): */
struct InverseConstructor {
    virtual ~InverseConstructor() = default;

    /*
     * To compute the inverse of a function f, subclass this abstract
     * base class to represent your function, and call spigot_invert()
     * with the target value y, and a binary interval known to contain
     * some x such that f(x) = y. The returned number will be x.
     *
     * The function must be continuous, monotonic, differentiable, and
     * have bounded derivative on the whole interval passed to
     * spigot_invert. However, it doesn't matter _which way round_ it
     * is monotonic.
     */

    /* This function should compute f(n / 2^dbits). */
    virtual Spigot f(bigint n, unsigned dbits) = 0;

    /* This function should compute the derivative f', at whatever
     * point in the interval (nlo,nhi)/2^dbits its absolute value is
     * greatest. */
    virtual Spigot fprime_max(bigint nlo, bigint nhi, unsigned dbits) = 0;
};

Spigot spigot_invert(shared_ptr<InverseConstructor> f,
                     bigint n1, bigint n2, unsigned dbits, Spigot y);

class ExprNodeTypeBase {
  public:
    bool debug_only { false };
    virtual Spigot evaluatev(argvector<Spigot> &args) const = 0;
};

class ExprNodeType : public virtual ExprNodeTypeBase {
    string name_str;
  public:
    const string &name() const;
    const char *c_name() const;
  protected:
    ExprNodeType(const string &aname);
};

class ExprNodeTypeNullary : public virtual ExprNodeTypeBase {
    virtual Spigot evaluatev(argvector<Spigot> &args) const override;
  public:
    virtual Spigot evaluate() const = 0;
};

class ExprNodeTypeUnary : public virtual ExprNodeTypeBase {
    virtual Spigot evaluatev(argvector<Spigot> &args) const override;
  public:
    virtual Spigot evaluate(Spigot x) const = 0;
};

class ExprNodeTypeBinary : public virtual ExprNodeTypeBase {
    virtual Spigot evaluatev(argvector<Spigot> &args) const override;
  public:
    virtual Spigot evaluate(Spigot x, Spigot y) const = 0;
};

class ExprNodeTypeDebug : public virtual ExprNodeTypeBase {
  public:
    ExprNodeTypeDebug();
};

enum class Prec : int;

class ExprOperator : public ExprNodeType {
  private:
    Prec precedence;
  public:
    Prec prec() const;
  protected:
    ExprOperator(const string &aname, Prec aprec);
};

class ExprUnaryOperator : public ExprOperator, public ExprNodeTypeUnary {
    using ExprOperator::ExprOperator;
};

class ExprUnaryPrefixOperator : public ExprUnaryOperator {
    using ExprUnaryOperator::ExprUnaryOperator;
};

class ExprUnarySuffixOperator : public ExprUnaryOperator {
    using ExprUnaryOperator::ExprUnaryOperator;
};

class ExprBinaryOperator : public ExprOperator, public ExprNodeTypeBinary {
    using ExprOperator::ExprOperator;
  public:
    virtual bool right_associative() const = 0;
};

class ExprConstOrFunction : public ExprNodeType {
    using ExprNodeType::ExprNodeType;
  public:
    virtual int arity() const = 0;
};

class ExprConstant : public ExprConstOrFunction, public ExprNodeTypeNullary {
    using ExprConstOrFunction::ExprConstOrFunction;
    virtual int arity() const override;
};

class ExprUnaryFunction : public ExprConstOrFunction,
                          public ExprNodeTypeUnary {
    using ExprConstOrFunction::ExprConstOrFunction;
    virtual int arity() const override;
};

class ExprBinaryFunction : public ExprConstOrFunction,
                           public ExprNodeTypeBinary {
    using ExprConstOrFunction::ExprConstOrFunction;
    virtual int arity() const override;
};

class ExprVariadicFunction : public ExprConstOrFunction {
    using ExprConstOrFunction::ExprConstOrFunction;
    virtual int arity() const override;
};

class ConstantFnWrapper : public ExprConstant {
    using Fn = Spigot (*)();
    Fn fn;

    virtual Spigot evaluate() const override;
  public:
    ConstantFnWrapper(const string &aname, Fn afn);
};

class UnaryFnWrapper : public ExprUnaryFunction {
  public:
    using Fn = Spigot (*)(Spigot x);
  private:
    Fn fn;

    virtual Spigot evaluate(Spigot x) const override;
  public:
    UnaryFnWrapper(const string &aname, Fn afn);
};

class BinaryFnWrapper : public ExprBinaryFunction {
  public:
    using Fn = Spigot (*)(Spigot x, Spigot y);
  private:
    Fn fn;

    virtual Spigot evaluate(Spigot x, Spigot y) const override;
  public:
    BinaryFnWrapper(const string &aname, Fn afn);
};

class VariadicFnWrapper : public ExprVariadicFunction {
  public:
    using Fn = Spigot (*)(argvector<Spigot> &args);
  private:
    Fn fn;

    virtual Spigot evaluatev(argvector<Spigot> &args) const override;
  public:
    VariadicFnWrapper(const string &aname, Fn afn);
};

class ConstantDebugFnWrapper : public ConstantFnWrapper,
                               public ExprNodeTypeDebug {
    using ConstantFnWrapper::ConstantFnWrapper;
};

class UnaryDebugFnWrapper : public UnaryFnWrapper,
                            public ExprNodeTypeDebug {
    using UnaryFnWrapper::UnaryFnWrapper;
};

class BinaryDebugFnWrapper : public BinaryFnWrapper,
                             public ExprNodeTypeDebug {
    using BinaryFnWrapper::BinaryFnWrapper;
};

class VariadicDebugFnWrapper : public VariadicFnWrapper,
                               public ExprNodeTypeDebug {
    using VariadicFnWrapper::VariadicFnWrapper;
};

template<typename Constructor>
class MonotoneConstructorDebugWrapper : public ExprUnaryFunction,
                                        public ExprNodeTypeDebug {
    function<Spigot(Spigot)> evaluator;
    virtual Spigot evaluate(Spigot x) const override;
  public:
    template<typename... Args>
    MonotoneConstructorDebugWrapper(const string &aname, Args... args);
};

template<typename Constructor>
template<typename... Args>
MonotoneConstructorDebugWrapper<Constructor>::MonotoneConstructorDebugWrapper(
    const string &aname, Args... args)
    : ExprUnaryFunction(aname)
{
    evaluator = [=](Spigot x) {
        return spigot_monotone(make_shared<Constructor>(args...), move(x));
    };
}

template<typename Constructor>
Spigot MonotoneConstructorDebugWrapper<Constructor>::evaluate(Spigot x) const
{
    return evaluator(move(x));
}
