#pragma once

#include "rs-format/numeric.hpp"
#include "rs-format/spec.hpp"
#include "rs-format/string.hpp"
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <type_traits>

namespace RS::Format {

    namespace Detail {

        template <typename T, typename U>
        std::string native_format(const char* pattern, U u, int prec) {
            std::string result(15, '\0');
            int rc = 0;
            for (;;) {
                rc = std::snprintf(result.data(), result.size(), pattern, prec, T(u));
                if (rc < int(result.size()))
                    break;
                result.resize(rc + 1);
            }
            result.resize(rc);
            return result;
        }

        inline std::string trim_exponent(const std::string& str, bool xsign) {
            size_t begin_cut = str.find_first_of("Ee");
            if (begin_cut == npos)
                return str;
            ++begin_cut;
            bool neg = str[begin_cut] == '-';
            if (neg)
                ++begin_cut;
            size_t end_cut = begin_cut;
            if (str[end_cut] == '+')
                ++end_cut;
            while (end_cut < str.size() && str[end_cut] == '0')
                ++end_cut;
            if (end_cut == str.size())
                --end_cut;
            auto result = str.substr(0, begin_cut);
            if (xsign && ! neg)
                result += '+';
            result += str.substr(end_cut);
            return result;
        }

        inline std::string trim_zeros(const std::string& str) {
            size_t dec_point = str.find('.');
            if (dec_point == npos)
                return str;
            size_t end_sig = str.find_first_not_of("0123456789", dec_point + 1);
            if (end_sig == npos)
                end_sig = str.size();
            size_t last_digit = end_sig - 1;
            while (str[last_digit] == '0')
                --last_digit;
            if (str[last_digit] != '.')
                ++last_digit;
            if (last_digit < end_sig)
                return str.substr(0, last_digit) + str.substr(end_sig);
            else
                return str;
        }

        template <typename T>
        std::string format_float_d(T t, const FormatSpec& spec) {
            int prec = std::max(spec.prec() - 1, 0);
            std::string native;
            if constexpr (sizeof(T) <= sizeof(double))
                native = native_format<double>("%.*e", t, prec);
            else
                native = native_format<long double>("%.*Le", t, prec);
            size_t sig_pos = size_t(native[0] == '-');
            size_t exp_pos = native.find_first_of("Ee", sig_pos);
            auto result = native.substr(sig_pos, exp_pos - sig_pos);
            if (result[1] == '.')
                result.erase(1, 1);
            int exp = to_int(native.substr(exp_pos + 1));
            if (exp < 0) {
                result.insert(0, "0.");
                result.insert(2, - exp - 1, '0');
            } else if (exp < prec) {
                result.insert(exp + 1, 1, '.');
            } else if (exp > prec - 1) {
                result.append(exp - prec, '0');
            }
            if (sig_pos > 0)
                result.insert(0, 1, '-');
            return result;
        }

        template <typename T>
        std::string format_float_e(T t, const FormatSpec& spec) {
            bool cap = ascii_isupper(spec.mode());
            int prec = std::max(spec.prec() - 1, 0);
            std::string native;
            if constexpr (sizeof(T) <= sizeof(double))
                native = native_format<double>(cap ? "%.*E" : "%.*e", t, prec);
            else
                native = native_format<long double>(cap ? "%.*LE" : "%.*Le", t, prec);
            return trim_exponent(native, spec.option('S'));
        }

        template <typename T>
        std::string format_float_f(T t, const FormatSpec& spec) {
            std::string result;
            if constexpr (sizeof(T) <= sizeof(double))
                result = native_format<double>("%.*f", t, spec.prec());
            else
                result = native_format<long double>("%.*Lf", t, spec.prec());
            if (result[0] == '-' && result.find_first_not_of("0.-") == npos)
                result.erase(0, 1);
            return result;
        }

        template <typename T>
        std::string format_float_g(T t, const FormatSpec& spec) {
            auto y = std::abs(t);
            if (y == 0 || (y >= 1e-3 && y < 1e6))
                return format_float_d(t, spec);
            else
                return format_float_e(t, spec);
        }

    }

    template <typename T>
    std::string format_floating_point(T t, FormatSpec spec = {}) {

        if constexpr(std::is_integral_v<T>) {

            using floating_type = std::conditional_t<(sizeof(T) >= sizeof(double)), long double, double>;

            return format_floating_point(floating_type(t), spec);

        } else {

            static_assert(std::is_floating_point_v<T>);

            if (t == 0 && spec.option('Z'))
                return "--";
            else if (spec.empty())
                spec = {'g', "z", 6};
            spec.default_prec(6);

            std::string result;

            switch (spec.lcmode()) {
                case 'd':  result = Detail::format_float_d(t, spec); break;
                case 'e':  result = Detail::format_float_e(t, spec); break;
                case 'f':  result = Detail::format_float_f(t, spec); break;
                default:   result = Detail::format_float_g(t, spec); break;
            }

            if (spec.option('z'))
                result = Detail::trim_zeros(result);
            Detail::expand_formatted_number(result, spec);

            return result;

        }

    }

}
#pragma once

#include "rs-format/numeric.hpp"
#include "rs-format/spec.hpp"
#include "rs-format/string.hpp"
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <type_traits>

namespace RS::Format {

    namespace Detail {

        template <typename T, typename U>
        std::string native_format(const char* pattern, U u, int prec) {
            std::string result(15, '\0');
            int rc = 0;
            for (;;) {
                rc = std::snprintf(result.data(), result.size(), pattern, prec, T(u));
                if (rc < int(result.size()))
                    break;
                result.resize(rc + 1);
            }
            result.resize(rc);
            return result;
        }

        inline std::string trim_exponent(const std::string& str, bool xsign) {
            size_t begin_cut = str.find_first_of("Ee");
            if (begin_cut == npos)
                return str;
            ++begin_cut;
            bool neg = str[begin_cut] == '-';
            if (neg)
                ++begin_cut;
            size_t end_cut = begin_cut;
            if (str[end_cut] == '+')
                ++end_cut;
            while (end_cut < str.size() && str[end_cut] == '0')
                ++end_cut;
            if (end_cut == str.size())
                --end_cut;
            auto result = str.substr(0, begin_cut);
            if (xsign && ! neg)
                result += '+';
            result += str.substr(end_cut);
            return result;
        }

        inline std::string trim_zeros(const std::string& str) {
            size_t dec_point = str.find('.');
            if (dec_point == npos)
                return str;
            size_t end_sig = str.find_first_not_of("0123456789", dec_point + 1);
            if (end_sig == npos)
                end_sig = str.size();
            size_t last_digit = end_sig - 1;
            while (str[last_digit] == '0')
                --last_digit;
            if (str[last_digit] != '.')
                ++last_digit;
            if (last_digit < end_sig)
                return str.substr(0, last_digit) + str.substr(end_sig);
            else
                return str;
        }

        template <typename T>
        std::string format_float_d(T t, const FormatSpec& spec) {
            int prec = std::max(spec.prec() - 1, 0);
            std::string native;
            if constexpr (sizeof(T) <= sizeof(double))
                native = native_format<double>("%.*e", t, prec);
            else
                native = native_format<long double>("%.*Le", t, prec);
            size_t sig_pos = size_t(native[0] == '-');
            size_t exp_pos = native.find_first_of("Ee", sig_pos);
            auto result = native.substr(sig_pos, exp_pos - sig_pos);
            if (result[1] == '.')
                result.erase(1, 1);
            int exp = to_int(native.substr(exp_pos + 1));
            if (exp < 0) {
                result.insert(0, "0.");
                result.insert(2, - exp - 1, '0');
            } else if (exp < prec) {
                result.insert(exp + 1, 1, '.');
            } else if (exp > prec - 1) {
                result.append(exp - prec, '0');
            }
            if (sig_pos > 0)
                result.insert(0, 1, '-');
            return result;
        }

        template <typename T>
        std::string format_float_e(T t, const FormatSpec& spec) {
            bool cap = ascii_isupper(spec.mode());
            int prec = std::max(spec.prec() - 1, 0);
            std::string native;
            if constexpr (sizeof(T) <= sizeof(double))
                native = native_format<double>(cap ? "%.*E" : "%.*e", t, prec);
            else
                native = native_format<long double>(cap ? "%.*LE" : "%.*Le", t, prec);
            return trim_exponent(native, spec.option('S'));
        }

        template <typename T>
        std::string format_float_f(T t, const FormatSpec& spec) {
            std::string result;
            if constexpr (sizeof(T) <= sizeof(double))
                result = native_format<double>("%.*f", t, spec.prec());
            else
                result = native_format<long double>("%.*Lf", t, spec.prec());
            if (result[0] == '-' && result.find_first_not_of("0.-") == npos)
                result.erase(0, 1);
            return result;
        }

        template <typename T>
        std::string format_float_g(T t, const FormatSpec& spec) {
            auto y = std::abs(t);
            if (y == 0 || (y >= 1e-3 && y < 1e6))
                return format_float_d(t, spec);
            else
                return format_float_e(t, spec);
        }

    }

    template <typename T>
    std::string format_floating_point(T t, FormatSpec spec = {}) {

        if constexpr(std::is_integral_v<T>) {

            using floating_type = std::conditional_t<(sizeof(T) >= sizeof(double)), long double, double>;

            return format_floating_point(floating_type(t), spec);

        } else {

            static_assert(std::is_floating_point_v<T>);

            if (t == 0 && spec.option('Z'))
                return "--";
            else if (spec.empty())
                spec = {'g', "z", 6};
            spec.default_prec(6);

            std::string result;

            switch (spec.lcmode()) {
                case 'd':  result = Detail::format_float_d(t, spec); break;
                case 'e':  result = Detail::format_float_e(t, spec); break;
                case 'f':  result = Detail::format_float_f(t, spec); break;
                default:   result = Detail::format_float_g(t, spec); break;
            }

            if (spec.option('z'))
                result = Detail::trim_zeros(result);
            Detail::expand_formatted_number(result, spec);

            return result;

        }

    }

}
