//-----------------------------------------------------------------------------
// An expression in our symbolic algebra system, used to write, linearize,
// and solve our constraint equations.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#ifndef SOLVESPACE_EXPR_H
#define SOLVESPACE_EXPR_H

class Expr {
public:

    enum class Op : uint32_t {
        // A parameter, by the hParam handle
        PARAM          =  0,
        // A parameter, by a pointer straight in to the param table (faster,
        // if we know that the param table won't move around)
        PARAM_PTR      =  1,

        // Operands
        CONSTANT       = 20,
        VARIABLE       = 21,

        // Binary ops
        PLUS           = 100,
        MINUS          = 101,
        TIMES          = 102,
        DIV            = 103,
        // Unary ops
        NEGATE         = 104,
        SQRT           = 105,
        SQUARE         = 106,
        SIN            = 107,
        COS            = 108,
        ASIN           = 109,
        ACOS           = 110,
    };

    Op      op;
    Expr    *a;
    union {
        double  v;
        hParam  parh;
        Param  *parp;
        Expr    *b;
    };

    Expr() = default;
    Expr(double val) : op(Op::CONSTANT) { v = val; }

    static inline Expr *AllocExpr()
        { return (Expr *)AllocTemporary(sizeof(Expr)); }

    static Expr *From(hParam p);
    static Expr *From(double v);

    Expr *AnyOp(Op op, Expr *b);
    inline Expr *Plus (Expr *b_) { return AnyOp(Op::PLUS,  b_); }
    inline Expr *Minus(Expr *b_) { return AnyOp(Op::MINUS, b_); }
    inline Expr *Times(Expr *b_) { return AnyOp(Op::TIMES, b_); }
    inline Expr *Div  (Expr *b_) { return AnyOp(Op::DIV,   b_); }

    inline Expr *Negate() { return AnyOp(Op::NEGATE, NULL); }
    inline Expr *Sqrt  () { return AnyOp(Op::SQRT,   NULL); }
    inline Expr *Square() { return AnyOp(Op::SQUARE, NULL); }
    inline Expr *Sin   () { return AnyOp(Op::SIN,    NULL); }
    inline Expr *Cos   () { return AnyOp(Op::COS,    NULL); }
    inline Expr *ASin  () { return AnyOp(Op::ASIN,   NULL); }
    inline Expr *ACos  () { return AnyOp(Op::ACOS,   NULL); }

    Expr *PartialWrt(hParam p) const;
    double Eval() const;
    void ParamsUsedList(std::vector<hParam> *list) const;
    bool DependsOn(hParam p) const;
    static bool Tol(double a, double b);
    bool IsZeroConst() const;
    Expr *FoldConstants();
    void Substitute(hParam oldh, hParam newh);

    static const hParam NO_PARAMS, MULTIPLE_PARAMS;
    hParam ReferencedParams(ParamList *pl) const;

    void ParamsToPointers();

    std::string Print() const;

    // number of child nodes: 0 (e.g. constant), 1 (sqrt), or 2 (+)
    int Children() const;
    // total number of nodes in the tree
    int Nodes() const;

    // Make a simple copy
    Expr *DeepCopy() const;
    // Make a copy, with the parameters (usually referenced by hParam)
    // resolved to pointers to the actual value. This speeds things up
    // considerably.
    Expr *DeepCopyWithParamsAsPointers(IdList<Param,hParam> *firstTry,
                                       IdList<Param,hParam> *thenTry) const;

    static Expr *Parse(const std::string &input, std::string *error);
    static Expr *From(const std::string &input, bool popUpError);
};

class ExprVector {
public:
    Expr *x, *y, *z;

    static ExprVector From(Expr *x, Expr *y, Expr *z);
    static ExprVector From(Vector vn);
    static ExprVector From(hParam x, hParam y, hParam z);
    static ExprVector From(double x, double y, double z);

    ExprVector Plus(ExprVector b) const;
    ExprVector Minus(ExprVector b) const;
    Expr *Dot(ExprVector b) const;
    ExprVector Cross(ExprVector b) const;
    ExprVector ScaledBy(Expr *s) const;
    ExprVector WithMagnitude(Expr *s) const;
    Expr *Magnitude() const;

    Vector Eval() const;
};

class ExprQuaternion {
public:
    Expr *w, *vx, *vy, *vz;

    static ExprQuaternion From(Expr *w, Expr *vx, Expr *vy, Expr *vz);
    static ExprQuaternion From(Quaternion qn);
    static ExprQuaternion From(hParam w, hParam vx, hParam vy, hParam vz);

    ExprVector RotationU() const;
    ExprVector RotationV() const;
    ExprVector RotationN() const;

    ExprVector Rotate(ExprVector p) const;
    ExprQuaternion Times(ExprQuaternion b) const;

    Expr *Magnitude() const;
};
#endif
