#ifndef SCALAR_HPP
#define SCALAR_HPP

#include "symbol.hpp"
#include "context.hpp"

template<typename>
class scalar_t;
template<typename>
class complex_t;

typedef scalar_t<real_type>   scalar;
typedef scalar_t<large_real>  scalar_hp;
typedef complex_t<real_type>  complex;
typedef complex_t<large_real> complex_hp;

template<typename T>
struct type_traits {
    static std::string serialize(const T& t, const interpreter_context& context) {
        return numeric_traits<T>::to_string(t, context);
    }

    static const std::string type_name;

    using symbol_t = scalar_t<T>;
    static const bool is_numeric = true;
};

template<typename T>
const std::string type_traits<T>::type_name = symbol_t::static_type_name();
template<typename T>
const bool type_traits<T>::is_numeric;

template<typename T>
struct type_traits<std::complex<T>> {
    static std::string serialize(const std::complex<T>& t, const interpreter_context& context) {
        return numeric_traits<std::complex<T>>::to_string(t, context);
    }

    static const std::string type_name;

    using symbol_t = complex_t<T>;
    static const bool is_numeric = true;
};

template<typename T>
const std::string type_traits<std::complex<T>>::type_name = symbol_t::static_type_name();
template<typename T>
const bool type_traits<std::complex<T>>::is_numeric;

template<typename ntype>
ntype factorial(ntype d) {
    if (d > numeric_traits<ntype>::one)
        return d*factorial(d - numeric_traits<ntype>::one);
    else
        return numeric_traits<ntype>::one;
}

template<typename T>
class scalar_t : public symbol_impl_proxy<scalar_t<T>> {
public :
    typedef T ntype;

    ntype value;

    explicit scalar_t(ntype d) : value(d) {}

    scalar_t* clone() const override {
        return new scalar_t(value);
    }

    scalar_t* evaluate(const interpreter_context&) const override {
        return clone();
    }

    std::string serialize(const interpreter_context& context) const override {
        return numeric_traits<ntype>::to_string(value, context);
    }

    static const std::string& static_type_name() {
        static const std::string name = "scalar";
        return name;
    }

    bool is_litteral() const override {
        return true;
    }

    std::pair<std::size_t,std::size_t> priority(const interpreter_context&) const override {
        return std::pair<std::size_t,std::size_t>(0, 0);
    }

    static void reg(interpreter_context& context);
};

template<typename T>
class complex_t : public symbol_impl_proxy<complex_t<T>> {
public :
    typedef T ntype;
    typedef std::complex<ntype> ctype;
    typedef ::scalar_t<ntype> scalar_t;

    ctype value;

    explicit complex_t(ctype c) : value(c) {}
    explicit complex_t(ntype r, ntype i) : value(r, i) {}

    complex_t* clone() const override {
        return new complex_t(value);
    }

    symbol_impl* evaluate(const interpreter_context&) const override {
        return clone();
    }

    std::string serialize(const interpreter_context& context) const override {
        return numeric_traits<ctype>::to_string(value, context);
    }

    static const std::string& static_type_name() {
        static const std::string name = "complex";
        return name;
    }

    bool is_litteral() const override {
        return true;
    }

    std::pair<std::size_t,std::size_t> priority(const interpreter_context&) const override {
        return std::pair<std::size_t,std::size_t>(0, 0);
    }

    static void reg(interpreter_context& context);
};

template<typename T, typename enable = typename std::enable_if<!numeric_traits<T>::is_complex>::type>
T real(const T& t) {
    return t;
}

template<typename T, typename enable = typename std::enable_if<!numeric_traits<T>::is_complex>::type>
T imag(const T& t) {
    return numeric_traits<T>::zero;
}

template<typename T, typename enable = typename std::enable_if<numeric_traits<T>::is_complex>::type>
T round(const T& t) {
    return T(round(real(t)), round(imag(t)));
}

template<typename T, typename enable = typename std::enable_if<numeric_traits<T>::is_complex>::type>
T ceil(const T& t) {
    return T(ceil(real(t)), ceil(imag(t)));
}

template<typename T, typename enable = typename std::enable_if<numeric_traits<T>::is_complex>::type>
T floor(const T& t) {
    return T(floor(real(t)), floor(imag(t)));
}

template<template<typename> class symbol_tt, typename ntype>
void register_math_fun(interpreter_context& context) {
    using symbol_t = symbol_tt<ntype>;
    using scalar = scalar_t<ntype>;

    context.register_function<symbol_t>({"real", "re"}, [](const std::vector<symbol>& args) {
        return new scalar(real(args[0].as<symbol_t>().value));
    });
    context.register_function<symbol_t>({"imaginary", "im"}, [](const std::vector<symbol>& args) {
        return new scalar(imag(args[0].as<symbol_t>().value));
    });
    context.register_function<symbol_t>("round", [](const std::vector<symbol>& args) {
        return new symbol_t(round(args[0].as<symbol_t>().value));
    });
    context.register_function<symbol_t>({"round_up", "ceil"}, [](const std::vector<symbol>& args) {
        return new symbol_t(ceil(args[0].as<symbol_t>().value));
    });
    context.register_function<symbol_t>({"round_down", "floor"}, [](const std::vector<symbol>& args) {
        return new symbol_t(floor(args[0].as<symbol_t>().value));
    });

    context.register_function<symbol_t>({"square_root", "sqrt"}, [](const std::vector<symbol>& args) {
        return new symbol_t(sqrt(args[0].as<symbol_t>().value));
    });
    context.register_function<symbol_t>("exp", [](const std::vector<symbol>& args) {
        return new symbol_t(exp(args[0].as<symbol_t>().value));
    });
    context.register_function<symbol_t>("log", [](const std::vector<symbol>& args) {
        return new symbol_t(log(args[0].as<symbol_t>().value));
    });
    context.register_function<symbol_t>("log2", [](const std::vector<symbol>& args) {
        return new symbol_t(log(args[0].as<symbol_t>().value)/log(ntype(2)));
    });
    context.register_function<symbol_t>("log10", [](const std::vector<symbol>& args) {
        return new symbol_t(log10(args[0].as<symbol_t>().value));
    });
    context.register_function<symbol_t>("cos", [](const std::vector<symbol>& args) {
        return new symbol_t(cos(args[0].as<symbol_t>().value));
    });
    context.register_function<symbol_t>({"cosh", "ch"}, [](const std::vector<symbol>& args) {
        return new symbol_t(cosh(args[0].as<symbol_t>().value));
    });
    context.register_function<symbol_t>("acos", [](const std::vector<symbol>& args) {
        return new symbol_t(acos(args[0].as<symbol_t>().value));
    });
    context.register_function<symbol_t>("sin", [](const std::vector<symbol>& args) {
        return new symbol_t(sin(args[0].as<symbol_t>().value));
    });
    context.register_function<symbol_t>({"sinh", "sh"}, [](const std::vector<symbol>& args) {
        return new symbol_t(sinh(args[0].as<symbol_t>().value));
    });
    context.register_function<symbol_t>("asin", [](const std::vector<symbol>& args) {
        return new symbol_t(asin(args[0].as<symbol_t>().value));
    });
    context.register_function<symbol_t>("tan", [](const std::vector<symbol>& args) {
        return new symbol_t(tan(args[0].as<symbol_t>().value));
    });
    context.register_function<symbol_t>({"tanh", "th"}, [](const std::vector<symbol>& args) {
        return new symbol_t(tanh(args[0].as<symbol_t>().value));
    });
    context.register_function<symbol_t>("atan", [](const std::vector<symbol>& args) {
        return new symbol_t(atan(args[0].as<symbol_t>().value));
    });
    context.register_function<symbol_t>({"abs", "norm"}, [](const std::vector<symbol>& args) {
        return new scalar(abs(args[0].as<symbol_t>().value));
    });
}

template<typename ntype>
void scalar_t<ntype>::reg(interpreter_context& context) {
    context.register_operator<scalar_t>("+", [](const symbol& s) {
        return s.as<scalar_t>().clone();
    });
    context.register_operator<scalar_t>("-", [](const symbol& s) {
        return new scalar_t(-s.as<scalar_t>().value);
    });
    context.register_operator<scalar_t, scalar_t>("+", [](const symbol& s1, const symbol& s2) {
        return new scalar_t(s1.as<scalar_t>().value + s2.as<scalar_t>().value);
    });
    context.register_operator<scalar_t, scalar_t>("-", [](const symbol& s1, const symbol& s2) {
        return new scalar_t(s1.as<scalar_t>().value - s2.as<scalar_t>().value);
    });
    context.register_operator<scalar_t, scalar_t>("*", [](const symbol& s1, const symbol& s2) {
        return new scalar_t(s1.as<scalar_t>().value * s2.as<scalar_t>().value);
    });
    context.register_operator<scalar_t, scalar_t>("/", [](const symbol& s1, const symbol& s2) {
        return new scalar_t(s1.as<scalar_t>().value / s2.as<scalar_t>().value);
    });
    context.register_operator<scalar_t, scalar_t>("^", [](const symbol& s1, const symbol& s2) {
        return new scalar_t(pow(s1.as<scalar_t>().value, s2.as<scalar_t>().value));
    });
    context.register_operator<scalar_t, scalar_t>("%", [](const symbol& s1, const symbol& s2) {
        return new scalar_t(fmod(s1.as<scalar_t>().value, s2.as<scalar_t>().value));
    });

    context.register_operator<scalar_t>("!", [](const symbol& s) {
        return new scalar_t(factorial(s.as<scalar_t>().value));
    });

    context.register_function("rand", [](const std::vector<symbol>& args) {
        return new scalar_t(2.0*rand()/RAND_MAX - 1.0);
    });
    context.register_function<scalar_t, scalar_t>("rand", [](const std::vector<symbol>& args) {
        typename scalar_t::ntype rmin = args[0].as<scalar_t>().value, rmax = args[1].as<scalar_t>().value;
        return new scalar_t((rmax - rmin)*rand()/RAND_MAX + rmin);
    });

    context.register_function<scalar_t, scalar_t>("min", [](const std::vector<symbol>& args) {
        return new scalar_t(std::min(args[0].as<scalar_t>().value, args[1].as<scalar_t>().value));
    });
    context.register_function<scalar_t, scalar_t>("max", [](const std::vector<symbol>& args) {
        return new scalar_t(std::max(args[0].as<scalar_t>().value, args[1].as<scalar_t>().value));
    });

    context.register_function<scalar_t,scalar_t>("atan", [](const std::vector<symbol>& args) {
        return new scalar_t(atan2(args[0].as<scalar_t>().value, args[1].as<scalar_t>().value));
    });

    context.register_function("pi", [](const std::vector<symbol>& args) {
        return new scalar_t(numeric_traits<ntype>::pi);
    });

    context.register_function<scalar_t>("digits", [&context](const std::vector<symbol>& args) {
        context.digits = numeric_traits<ntype>::to_uint(args[0].as<scalar_t>().value);
        return new no_type();
    });

    context.register_function<scalar_t>("max_cycles", [&context](const std::vector<symbol>& args) {
        context.max_cycles = numeric_traits<ntype>::to_uint(args[0].as<scalar_t>().value);
        return new no_type();
    });

    register_math_fun<scalar_t, ntype>(context);
}

template<typename ntype>
void complex_t<ntype>::reg(interpreter_context& context) {
    context.register_operator<complex_t>("+", [](const symbol& s) {
        return s.as<complex_t>().clone();
    });
    context.register_operator<complex_t>("-", [](const symbol& s) {
        const complex_t& c = s.as<complex_t>();
        return new complex_t(-c.value.real(), -c.value.imag());
    });

    context.register_operator<complex_t, complex_t>("+", [](const symbol& s1, const symbol& s2) {
        const complex_t& c1 = s1.as<complex_t>();
        const complex_t& c2 = s2.as<complex_t>();
        return new complex_t(c1.value + c2.value);
    });
    context.register_operator<complex_t, complex_t>("-", [](const symbol& s1, const symbol& s2) {
        const complex_t& c1 = s1.as<complex_t>();
        const complex_t& c2 = s2.as<complex_t>();
        return new complex_t(c1.value - c2.value);
    });
    context.register_operator<complex_t, complex_t>("*", [](const symbol& s1, const symbol& s2) {
        const complex_t& c1 = s1.as<complex_t>();
        const complex_t& c2 = s2.as<complex_t>();
        return new complex_t(c1.value*c2.value);
    });
    context.register_operator<complex_t, complex_t>("/", [](const symbol& s1, const symbol& s2) {
        const complex_t& c1 = s1.as<complex_t>();
        const complex_t& c2 = s2.as<complex_t>();
        return new complex_t(c1.value/c2.value.real());
    });

    context.register_commutative_operator<complex_t, scalar_t>("+", [](const symbol& s1, const symbol& s2) {
        const scalar_t& s = s2.as<scalar_t>();
        const complex_t& c = s1.as<complex_t>();
        return new complex_t(c.value + s.value);
    });
    context.register_operator<complex_t, scalar_t>("-", [](const symbol& s1, const symbol& s2) {
        const scalar_t& s = s2.as<scalar_t>();
        const complex_t& c = s1.as<complex_t>();
        return new complex_t(c.value - s.value);
    });
    context.register_operator<scalar_t, complex_t>("-", [](const symbol& s1, const symbol& s2) {
        const scalar_t& s = s1.as<scalar_t>();
        const complex_t& c = s2.as<complex_t>();
        return new complex_t(s.value - c.value);
    });
    context.register_commutative_operator<complex_t, scalar_t>("*", [](const symbol& s1, const symbol& s2) {
        const scalar_t& s = s2.as<scalar_t>();
        const complex_t& c = s1.as<complex_t>();
        return new complex_t(c.value*s.value);
    });
    context.register_operator<complex_t, scalar_t>("/", [](const symbol& s1, const symbol& s2) {
        const scalar_t& s = s2.as<scalar_t>();
        const complex_t& c = s1.as<complex_t>();
        return new complex_t(c.value/s.value);
    });
    context.register_operator<scalar_t, complex_t>("/", [](const symbol& s1, const symbol& s2) {
        const scalar_t& s = s1.as<scalar_t>();
        const complex_t& c = s2.as<complex_t>();
        return new complex_t(s.value/c.value);
    });

    context.register_function<complex_t, complex_t>("min", [](const std::vector<symbol>& args) {
        const complex_t& c1 = args[0].as<complex_t>();
        const complex_t& c2 = args[1].as<complex_t>();
        return new complex_t(
            std::min(c1.value.real(), c2.value.real()),
            std::min(c1.value.imag(), c2.value.imag())
        );
    });
    context.register_function<complex_t, scalar_t>("min", [](const std::vector<symbol>& args) {
        const complex_t& c1 = args[0].as<complex_t>();
        const scalar_t&  s2 = args[1].as<scalar_t>();
        return new complex_t(
            std::min(c1.value.real(), s2.value),
            std::min(c1.value.imag(), numeric_traits<ntype>::zero)
        );
    });
    context.register_function<scalar_t, complex_t>("min", [](const std::vector<symbol>& args) {
        const complex_t& c1 = args[1].as<complex_t>();
        const scalar_t&  s2 = args[0].as<scalar_t>();
        return new complex_t(
            std::min(c1.value.real(), s2.value),
            std::min(c1.value.imag(), numeric_traits<ntype>::zero)
        );
    });
    context.register_function<complex_t, complex_t>("max", [](const std::vector<symbol>& args) {
        const complex_t& c1 = args[0].as<complex_t>();
        const complex_t& c2 = args[1].as<complex_t>();
        return new complex_t(
            std::max(c1.value.real(), c2.value.real()),
            std::max(c1.value.imag(), c2.value.imag())
        );
    });
    context.register_function<complex_t, scalar_t>("max", [](const std::vector<symbol>& args) {
        const complex_t& c1 = args[0].as<complex_t>();
        const scalar_t&  s2 = args[1].as<scalar_t>();
        return new complex_t(
            std::max(c1.value.real(), s2.value),
            std::max(c1.value.imag(), numeric_traits<ntype>::zero)
        );
    });
    context.register_function<scalar_t, complex_t>("max", [](const std::vector<symbol>& args) {
        const complex_t& c1 = args[1].as<complex_t>();
        const scalar_t&  s2 = args[0].as<scalar_t>();
        return new complex_t(
            std::max(c1.value.real(), s2.value),
            std::max(c1.value.imag(), numeric_traits<ntype>::zero)
        );
    });

    context.register_function("i", [](const std::vector<symbol>& args) {
        static const std::complex<ntype> i(numeric_traits<ntype>::zero, numeric_traits<ntype>::one);
        return new complex_t(i);
    });

    register_math_fun<complex_t, ntype>(context);
}
#endif
