#ifndef NUMERIC_TRAITS_HPP
#define NUMERIC_TRAITS_HPP

#define MPFR_REAL_DATA_PUBLIC
#include "mpfr/real.hpp"

#include <sstream>
#include <iomanip>
#include <complex>
#include <cstdlib>

typedef double                           real_type;
const std::size_t                        large_real_precision = 128;
typedef mpfr::real<large_real_precision> large_real;

template<typename T>
struct numeric_traits {
    template<typename context_t>
    static std::string to_string(const T& v, const context_t& context) {
        return to_string(v, context.digits);
    }

    static std::string to_string(const T& v, std::size_t digits) {
        std::stringstream ss;
        ss << std::setprecision(digits) << v;
        return ss.str();
    }

    static T to_num(const std::string& s) {
        std::stringstream ss(s);
        T v; ss >> v;
        return v;
    }

    static std::size_t to_uint(const T& v) {
        return (std::size_t)v;
    }

    static std::intptr_t to_int(const T& v) {
        return (std::intptr_t)v;
    }

    static constexpr T zero = 0.0;
    static constexpr T one  = 1.0;
    static constexpr T pi   = acos(-1.0);
    static constexpr bool is_complex  = false;
};

template<typename T>
constexpr T numeric_traits<T>::zero;
template<typename T>
constexpr T numeric_traits<T>::one;
template<typename T>
constexpr T numeric_traits<T>::pi;
template<typename T>
constexpr bool numeric_traits<T>::is_complex;

template<>
struct numeric_traits<large_real> {
    static std::string   to_string(const large_real& v, const interpreter_context& context);
    static std::string   to_string(const large_real& v, std::size_t digits);
    static large_real    to_num(const std::string& s);
    static std::size_t   to_uint(const large_real& v);
    static std::intptr_t to_int(const large_real& v);

    static const large_real zero;
    static const large_real one;
    static const large_real pi;
    static const bool is_complex = false;
};

template<typename T>
struct numeric_traits<std::complex<T>> {
    template<typename context_t>
    static std::string to_string(const std::complex<T>& v, const context_t& context) {
        std::string s;
        if (v.imag() == numeric_traits<T>::zero) {
            s = numeric_traits<T>::to_string(v.real(), context);
        } else if (v.imag() == numeric_traits<T>::one || v.imag() == -numeric_traits<T>::one) {
            if (v.real() != numeric_traits<T>::zero) {
                if (v.imag() > numeric_traits<T>::zero) {
                    s = numeric_traits<T>::to_string(v.real(), context) + "+i";
                } else {
                    s = numeric_traits<T>::to_string(v.real(), context) + "-i";
                }
            } else {
                if (v.imag() > numeric_traits<T>::zero) {
                    s = "i";
                } else {
                    s = "-i";
                }
            }
        } else {
            if (v.real() != numeric_traits<T>::zero) {
                if (v.imag() > numeric_traits<T>::zero) {
                    s = numeric_traits<T>::to_string(v.real(), context) + "+"
                        + numeric_traits<T>::to_string(v.imag(), context) + "i";
                } else {
                    s = numeric_traits<T>::to_string(v.real(), context) +
                        numeric_traits<T>::to_string(v.imag(), context) + "i";
                }
            } else {
                s = numeric_traits<T>::to_string(v.imag(), context) + "i";
            }
        }
        return s;
    }

    static const std::complex<T> zero;
    static const std::complex<T> one;
    static const std::complex<T> pi;
    static const bool is_complex = true;
};

template<typename T>
const std::complex<T> numeric_traits<std::complex<T>>::zero = {numeric_traits<T>::zero, numeric_traits<T>::zero};
template<typename T>
const std::complex<T> numeric_traits<std::complex<T>>::one  = {numeric_traits<T>::one, numeric_traits<T>::zero};
template<typename T>
const std::complex<T> numeric_traits<std::complex<T>>::pi   = {numeric_traits<T>::pi, numeric_traits<T>::zero};
template<typename T>
const bool numeric_traits<std::complex<T>>::is_complex;

#endif
