//       Copyright Maarten L. Hekkelman, 2019-2026
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#if __cpp_lib_format

# include <format>

std::string FormatDecimal(double d, int integerDigits, int decimalDigits, std::locale loc)
{
	return std::vformat(loc, "{:{}.{}f}", std::make_format_args(integerDigits + decimalDigits, decimalDigits, d));
}

#else

# include "format.hpp"

# include "zeep/unicode-support.hpp"

# include <cmath>
# include <deque>
# include <iostream>
# include <limits>
# include <locale>
# include <tuple>

namespace zeep
{

std::string decimal_point(const std::locale &loc)
{
	std::string result;

	if (std::has_facet<std::numpunct<wchar_t>>(loc))
		zeep::append(result, std::use_facet<std::numpunct<wchar_t>>(loc).decimal_point());
	else if (std::has_facet<std::numpunct<char>>(loc))
		result = std::use_facet<std::numpunct<char>>(loc).decimal_point();
	else
		result = { '.' };

	return result;
}

std::string thousands_sep(const std::locale &loc)
{
	std::string result;

	if (std::has_facet<std::numpunct<wchar_t>>(loc))
		zeep::append(result, std::use_facet<std::numpunct<wchar_t>>(loc).thousands_sep());
	else if (std::has_facet<std::numpunct<char>>(loc))
		result = std::use_facet<std::numpunct<char>>(loc).thousands_sep();
	else
		result = { ',' };

	return result;
}

std::string grouping(const std::locale &loc)
{
	std::string result;

	if (std::has_facet<std::numpunct<wchar_t>>(loc))
		result = std::use_facet<std::numpunct<wchar_t>>(loc).grouping();
	else if (std::has_facet<std::numpunct<char>>(loc))
		result = std::use_facet<std::numpunct<char>>(loc).grouping();

	return result;
}

struct thousand_grouping
{
  public:
	thousand_grouping(const std::locale &loc)
		: m_sep(thousands_sep(loc))
		, m_grouping(grouping(loc))
	{
	}

	bool operator()(int exp10) const
	{
		std::deque<int> gs(m_grouping.begin(), m_grouping.end());

		bool result = false;

		if (not gs.empty())
		{
			int g = gs.front();

			for (;;)
			{
				if (exp10 < g)
					break;

				if (exp10 == g)
				{
					result = true;
					break;
				}

				if (gs.size() > 1)
					gs.pop_front();

				g += gs.front();
			}
		}

		return result;
	}

	[[nodiscard]] std::string separator() const
	{
		return m_sep;
	}

  private:
	std::string m_sep, m_grouping;
};

template <typename T>
class Decimal
{
  public:
	Decimal(T x);

	std::string formatFixed(int minIntDigits, int decimals, const std::locale &loc);

	friend std::ostream &operator<<(std::ostream &os, Decimal d)
	{
		os << "crunched: " << d.m_crunched << " exp10: " << d.m_exp10 << " dec: " << d.m_dec;
		return os;
	}

  private:
	std::tuple<std::string, int> roundDecimal(int n);

	T m_crunched;
	int m_exp10;
	std::string m_dec;
};

template <typename T>
Decimal<T>::Decimal(T x)
{
	// CrunchDouble

	int exp = (x == 0) ? 0 : static_cast<int>(1 + std::logb(x));
	int n = m_exp10 = (exp * 301029) / 1000000;

	auto p = 10.0;

	if (n < 0)
	{
		for (n = -n; n > 0; n >>= 1)
		{
			if (n & 1)
				x *= p;
			p *= p;
		}
	}
	else
	{
		T f = 1.0;
		for (; n > 0; n >>= 1)
		{
			if (n & 1)
				f *= p;
			p *= p;
		}
		x /= f;
	}

	if (x != 0)
	{
		while (std::fabs(x) >= 1.0)
		{
			x *= 0.1;
			++m_exp10;
		}

		while (std::fabs(x) < 0.1)
		{
			x *= 10.0;
			--m_exp10;
		}
	}

	m_crunched = x;

	// Num2Dec

	int digits = std::numeric_limits<T>::digits10 + 1;
	int ix = 0;
	if (x < 0)
		x = -x;

	const double kDigitValues[8] = {
		1.0E+01,
		1.0E+02,
		1.0E+03,
		1.0E+04,
		1.0E+05,
		1.0E+06,
		1.0E+07,
		1.0E+08
	};

	while (digits > 0)
	{
		n = digits;
		if (n > 8)
			n = 8;

		digits -= n;
		m_dec.insert(m_dec.end(), n, ' ');

		x *= kDigitValues[n - 1];

		auto lx = lrint(trunc(x));
		x -= lx;

		for (int i = n - 1; i >= 0; --i)
		{
			m_dec[ix + i] = lx % 10 + '0';
			lx /= 10;
		}
		ix += 8;
	}
}

template <typename T>
std::string Decimal<T>::formatFixed(int intDigits, int decimals, const std::locale &loc)
{
	int digits = decimals + intDigits;
	if (m_exp10 > intDigits)
		digits += m_exp10 - intDigits;

	int exp10;
	std::string dec;

	std::tie(dec, exp10) = roundDecimal(decimals + m_exp10);

	std::string s;
	thousand_grouping tg(loc);

	auto dp = dec.begin(), de = dec.end();
	int exp = intDigits;
	if (exp < exp10)
		exp = exp10;

	for (int i = 0; i < digits; ++i)
	{
		if (i > 0)
		{
			if (tg(exp))
				s += tg.separator();
			else if (exp == 0)
				s += decimal_point(loc);
		}

		if (exp <= exp10 and dp != de)
			s += *dp++;
		else
			s += '0';

		--exp;
	}

	return s;
}

template <typename T>
std::tuple<std::string, int> Decimal<T>::roundDecimal(int newLength)
{
	std::string dec = m_dec;
	int exp10 = m_exp10;

	int l = static_cast<int>(dec.length());

	if (newLength < 0)
		dec = "0";
	else if (newLength < l)
	{
		l = newLength + 1;

		int carry = dec[l - 1] >= '5';
		dec.resize(l - 1);

		while (newLength > 0)
		{
			--l;
			int c = dec[l - 1] - '0' + carry;
			carry = c > 9;
			if (carry == 1)
			{
				--newLength;
				dec.resize(newLength);
			}
			else
			{
				dec[l - 1] = static_cast<char>(c + '0');
				dec.resize(l);
				break;
			}
		}

		if (carry == 1)
		{
			++exp10;
			dec += '1';
		}
		else if (newLength == 0)
			dec = "0";
	}

	return std::make_tuple(dec, exp10);
}

std::string format_decimal(double d, int integerDigits, int decimalDigits, const std::locale &loc)
{
	Decimal<double> dec(d);

	std::string result;
	if (d < 0)
		result = "-";
	result += dec.formatFixed(integerDigits, decimalDigits, loc);
	return result;
}

// #endif
} // namespace zeep

#endif
