//#####################################################################
// Copyright 2020, Craig Schroeder.
// This file is part of PhysBAM whose distribution is governed by the license contained in the accompanying file PHYSBAM_COPYRIGHT.txt.
//#####################################################################
#ifndef __SYMBOLIC__
#define __SYMBOLIC__

#include <Core/Data_Structures/HASH_REDUCE.h>
#include <Core/Math_Tools/cube.h>
#include <Core/Math_Tools/sqr.h>
#include <Core/Read_Write/STREAM_TYPE.h>
#include <Core/Symbolics/SYMBOLIC_BASE.h>
#include <Core/Vectors/SCALAR_POLICY.h>
#include <algorithm>
#include <cmath>
#include <complex>
#include <iosfwd>
#include <string>
using ::std::sin;
using ::std::cos;
using ::std::tan;
using ::std::exp;
using ::std::log;
using ::std::abs;
using ::std::sqrt;
using ::PhysBAM::sqr;
using ::PhysBAM::cube;
using ::std::hypot;
using ::std::cbrt;
using ::std::expm1;
using ::std::log1p;
namespace PhysBAM{

using ::std::sin;
using ::std::cos;
using ::std::tan;
using ::std::asin;
using ::std::acos;
using ::std::atan;
using ::std::sinh;
using ::std::cosh;
using ::std::tanh;
using ::std::asinh;
using ::std::acosh;
using ::std::atanh;
using ::std::atan2;
using ::std::exp;
using ::std::log;
using ::std::abs;
using ::std::pow;
using ::std::sqrt;
using ::PhysBAM::sqr;
using ::PhysBAM::cube;
using ::std::hypot;
using ::std::floor;
using ::std::ceil;
using ::std::rint;
using ::std::cbrt;
using ::std::expm1;
using ::std::log1p;
using ::std::max;
using ::std::min;
using ::std::fmod;
using ::std::modf;
using ::std::round;
using ::std::cyl_neumann;
using ::std::cyl_bessel_j;    

extern SYMBOLIC_BASE* sym_prototype;

template<class T>
void Register_Symbolic()
{
    sym_prototype=new T;
}

void Unregister_Symbolic();

class SYMBOLIC
{
public:
    double x;
    SYMBOLIC_BASE* z;

    SYMBOLIC(double a=0)
        :x(a),z(nullptr)
    {
        if(sym_prototype)
        {
            z=sym_prototype->Clone();
            z->Set(x);
        }
    }

    SYMBOLIC(double a,const SYMBOLIC& y,SYMBOLIC_BASE::type_1 op)
        :x(a),z(nullptr)
    {
        if(sym_prototype)
        {
            z=sym_prototype->Clone();
            if(y.z) z->Unary(x,y,op);
            else z->Set(x);
        }
    }

    SYMBOLIC(double a,const SYMBOLIC& w,const SYMBOLIC& y,SYMBOLIC_BASE::type_2 op)
        :x(a),z(nullptr)
    {
        if(sym_prototype)
        {
            z=sym_prototype->Clone();
            if(w.z || y.z) z->Binary(x,w,y,op);
            else z->Set(x);
        }
    }

    SYMBOLIC(const SYMBOLIC& s)
        :x(s.x),z(nullptr)
    {
        if(sym_prototype)
        {
            z=sym_prototype->Clone();
            if(s.z) z->Unary(x,s,SYMBOLIC_BASE::type_1::copy);
            else z->Set(x);
        }
    }

    SYMBOLIC(SYMBOLIC&& s)
        :x(s.x),z(s.z)
    {
        s.z=nullptr;
    }

    ~SYMBOLIC(){delete z;}

    SYMBOLIC& operator=(const SYMBOLIC& s);

    SYMBOLIC& operator=(SYMBOLIC&& s)
    {
        x=s.x;
        delete z;
        z=s.z;
        s.z=nullptr;
        return *this;
    }

    explicit operator bool() const {return x;}
    explicit operator int() const {return x;}
    explicit operator float() const {return x;}
    explicit operator double() const {return x;}
    double Value() const {return x;}

    template<class RW> void Read(UNTYPED_ISTREAM<RW>& input);
    template<class RW> void Write(UNTYPED_OSTREAM<RW>& output) const;
};

#define INST_OP1(op,name)                               \
    inline SYMBOLIC operator op(const SYMBOLIC& a)      \
    {return SYMBOLIC(op a.x,a,SYMBOLIC_BASE::type_1::name);}

#define INST_HELP1(func)                        \
    inline SYMBOLIC func(const SYMBOLIC& a)     \
    {return SYMBOLIC(func(a.x),a,SYMBOLIC_BASE::type_1::func);}

#define INST_HELP1n(func,name)                  \
    inline SYMBOLIC func(const SYMBOLIC& a)     \
    {return SYMBOLIC(func(a.x),a,SYMBOLIC_BASE::type_1::name);}

#define INST_OP2(op,name)                                               \
    inline SYMBOLIC operator op(const SYMBOLIC& a,const SYMBOLIC& b)    \
    {return SYMBOLIC(a.x op b.x,a,b,SYMBOLIC_BASE::type_2::name);}         \
    inline SYMBOLIC operator op(const SYMBOLIC& a,float b)              \
    {return a op SYMBOLIC(b);}                                          \
    inline SYMBOLIC operator op(const SYMBOLIC& a,double b)             \
    {return a op SYMBOLIC(b);}                                          \
    inline SYMBOLIC operator op(const SYMBOLIC& a,int b)                \
    {return a op SYMBOLIC(b);}                                          \
    inline SYMBOLIC operator op(float a,const SYMBOLIC& b)              \
    {return SYMBOLIC(a) op b;}                                          \
    inline SYMBOLIC operator op(double a,const SYMBOLIC& b)             \
    {return SYMBOLIC(a) op b;}                                          \
    inline SYMBOLIC operator op(int a,const SYMBOLIC& b)                \
    {return SYMBOLIC(a) op b;}

#define INST_HELP2(func)                                        \
    inline SYMBOLIC func(const SYMBOLIC& a,const SYMBOLIC& b)   \
    {return SYMBOLIC(func(a.x,b.x),a,b,SYMBOLIC_BASE::type_2::func);}      \
    inline SYMBOLIC func(const SYMBOLIC& a,float b)             \
    {return func(a,SYMBOLIC(b));}                               \
    inline SYMBOLIC func(const SYMBOLIC& a,double b)            \
    {return func(a,SYMBOLIC(b));}                               \
    inline SYMBOLIC func(const SYMBOLIC& a,int b)               \
    {return func(a,SYMBOLIC(b));}                               \
    inline SYMBOLIC func(float a,const SYMBOLIC& b)             \
    {return func(SYMBOLIC(a),b);}                               \
    inline SYMBOLIC func(double a,const SYMBOLIC& b)            \
    {return func(SYMBOLIC(a),b);}                               \
    inline SYMBOLIC func(int a,const SYMBOLIC& b)               \
    {return func(SYMBOLIC(a),b);}

#define INST_CMP2(op)                                                   \
    inline bool operator op(const SYMBOLIC& a,const SYMBOLIC& b)        \
    {return a.x op b.x;}                                                \
    inline bool operator op(const SYMBOLIC& a,float b)                  \
    {return a.x op b;}                                                  \
    inline bool operator op(const SYMBOLIC& a,double b)                 \
    {return a.x op b;}                                                  \
    inline bool operator op(const SYMBOLIC& a,int b)                    \
    {return a.x op b;}                                                  \
    inline bool operator op(float a,const SYMBOLIC& b)                  \
    {return a op b.x;}                                                  \
    inline bool operator op(double a,const SYMBOLIC& b)                 \
    {return a op b.x;}                                                  \
    inline bool operator op(int a,const SYMBOLIC& b)                    \
    {return a op b.x;}                                                  \

INST_OP1(+,pos);
INST_OP1(-,neg);
INST_OP2(+,add);
INST_OP2(-,sub);
INST_OP2(*,mul);
INST_OP2(/,div);
INST_CMP2(<);
INST_CMP2(>);
INST_CMP2(<=);
INST_CMP2(>=);
INST_CMP2(==);
INST_CMP2(!=);

inline SYMBOLIC& operator+= (SYMBOLIC& a,const SYMBOLIC& b) {return a=a+b;}
inline SYMBOLIC& operator-= (SYMBOLIC& a,const SYMBOLIC& b) {return a=a-b;}
inline SYMBOLIC& operator*= (SYMBOLIC& a,const SYMBOLIC& b) {return a=a*b;}
inline SYMBOLIC& operator/= (SYMBOLIC& a,const SYMBOLIC& b) {return a=a/b;}
inline SYMBOLIC& operator+= (SYMBOLIC& a,int b) {return a=a+b;}
inline SYMBOLIC& operator+= (SYMBOLIC& a,float b) {return a=a+b;}
inline SYMBOLIC& operator+= (SYMBOLIC& a,double b) {return a=a+b;}
inline SYMBOLIC& operator-= (SYMBOLIC& a,int b) {return a=a-b;}
inline SYMBOLIC& operator-= (SYMBOLIC& a,float b) {return a=a-b;}
inline SYMBOLIC& operator-= (SYMBOLIC& a,double b) {return a=a-b;}
inline SYMBOLIC& operator*= (SYMBOLIC& a,int b) {return a=a*b;}
inline SYMBOLIC& operator*= (SYMBOLIC& a,float b) {return a=a*b;}
inline SYMBOLIC& operator*= (SYMBOLIC& a,double b) {return a=a*b;}
inline SYMBOLIC& operator/= (SYMBOLIC& a,int b) {return a=a/b;}
inline SYMBOLIC& operator/= (SYMBOLIC& a,float b) {return a=a/b;}
inline SYMBOLIC& operator/= (SYMBOLIC& a,double b) {return a=a/b;}

INST_HELP1(abs);
INST_HELP1(sqrt);
INST_HELP1(exp);
INST_HELP1(log);
INST_HELP1(sin);
INST_HELP1(cos);
INST_HELP1(tan);
INST_HELP1(asin);
INST_HELP1(acos);
INST_HELP1(atan);
INST_HELP1(floor);
INST_HELP1(ceil);
INST_HELP1(rint);
INST_HELP1(round);
INST_HELP1(cbrt);
INST_HELP2(atan2);
INST_HELP2(hypot);
INST_HELP2(pow);
INST_HELP1(expm1);
INST_HELP1(log1p);
INST_HELP1(sinh);
INST_HELP1(cosh);
INST_HELP1(tanh);
INST_HELP1(asinh);
INST_HELP1(acosh);
INST_HELP1(atanh);
INST_HELP2(fmod);
INST_HELP2(cyl_neumann);
INST_HELP2(cyl_bessel_j);

#undef INST_HELP1
#undef INST_HELP2
#undef INST_OP1
#undef INST_OP2
#undef INST_CMP2

inline SYMBOLIC
clamp(const SYMBOLIC& v,const SYMBOLIC& a,const SYMBOLIC& b)
{return std::clamp(v,a,b);}

SYMBOLIC modf(const SYMBOLIC& a, SYMBOLIC *b);

template<> struct SCALAR_POLICY<SYMBOLIC> {typedef SYMBOLIC TYPE;};

template<class RW> inline void
Write_Binary(UNTYPED_OSTREAM<RW>& output,const SYMBOLIC& d)
{Write_Binary(output,d.x);}

template<class RW> inline void
Read_Binary(UNTYPED_ISTREAM<RW>& input,SYMBOLIC& d)
{Read_Binary(input,d.x);if(d.z) d.z->Set(d.x);}

inline std::ostream& operator<<(std::ostream& o, const SYMBOLIC& s)
{return o<<s.x;}

inline std::istream& operator>>(std::istream& i, SYMBOLIC& s)
{return i>>s.x;if(s.z) s.z->Set(s.x);}

inline SYMBOLIC
Magnitude_Squared(const SYMBOLIC& s)
{return s*s;}

inline SYMBOLIC
Dot_Product(const SYMBOLIC& a1,const SYMBOLIC& a2)
{return a1*a2;}

inline SYMBOLIC Componentwise_Max(const SYMBOLIC& v1,const SYMBOLIC& v2)
{return max(v1,v2);}

inline SYMBOLIC Componentwise_Min(const SYMBOLIC& v1,const SYMBOLIC& v2)
{return min(v1,v2);}

inline SYMBOLIC wrap(const SYMBOLIC& a,const SYMBOLIC& b,const SYMBOLIC& c)
{
    if(a<b) return b;
    if(a>c) return c;
    return a;
}

template<> struct HASH_REDUCE<SYMBOLIC>
{
    static int H(SYMBOLIC key)
    {
        return HASH_REDUCE<double>::H(key.x);
    }
};

template<> struct IS_FLOATING_POINT<SYMBOLIC> {static const bool value=true;};
inline SYMBOLIC Value(const SYMBOLIC& a){return a;}
inline STREAM_TYPE Stream_Type(const SYMBOLIC){return STREAM_TYPE::type_double;}
}

namespace std{template<> struct numeric_limits<PhysBAM::SYMBOLIC>:public numeric_limits<double>{};}
namespace std{inline auto to_string(const PhysBAM::SYMBOLIC& s){return to_string(s.x);}}

#endif
