// =====================================================================================================================
//  fennec, a free and open source game engine
//  Copyright © 2025 - 2026  Medusa Slockbower
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
// =====================================================================================================================

///
/// \file fennec/format/formatter.h
/// \brief
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 - 2026 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
///
///

#ifndef FENNEC_FORMAT_FORMATTER_H
#define FENNEC_FORMAT_FORMATTER_H

#include <fennec/format/charconv.h>
#include <fennec/string/string.h>
#include <fennec/format/format_arg.h>

namespace fennec
{

// base template =======================================================================================================

///
/// \brief Formatter struct, used to turn values into formatted strings
/// \tparam T The type to format
template<typename T>
struct formatter {
	///
	/// \brief default format function
	///
	/// \details Fails an assertion, the formatter is not implemented for the provided type
	/// \returns empty string
	string operator()(const format_arg&, const T&) const {
		static_assert(false, "Formatter not implemented for the provided type.");
		return string("");
	}
};


// strings =============================================================================================================

// TODO: String formatting

///
/// \brief formatter of a character array
/// \tparam N the number of characters
template<size_t N>
struct formatter<char[N]> {
	///
	/// \brief format function
	/// \param str the string argument
	/// \returns the formatted version of \emph{str}
	string operator()(const format_arg&, const char (&str)[N]) const {
		return string(str);
	}
};

///
/// \brief formatter of a character array
/// \tparam N the number of characters
template<size_t N>
struct formatter<const char[N]> {
	///
	/// \brief format function
	/// \param str the string argument
	/// \returns the formatted version of \emph{str}
	string operator()(const format_arg&, const char (&str)[N]) const {
		return string(str);
	}
};

///
/// \brief formatter of a C-Style string
template<>
struct formatter<cstring> {
	///
	/// \brief format function
	/// \param str the string argument
	/// \returns the formatted version of \emph{str}
	string operator()(const format_arg&, const cstring& str) const {
		return str;
	}
};

///
/// \brief formatter of a string
template<>
struct formatter<string> {
	///
	/// \brief format function
	/// \param str the string argument
	/// \returns the formatted version of \emph{str}
	string operator()(const format_arg&, const string& str) const {
		return str;
	}
};


// decimal types =======================================================================================================

///
/// \brief formatter of integral types
template<typename IntT> requires(is_integral_v<IntT> and not is_bool_v<IntT>)
struct formatter<IntT> {
	///
	/// \brief format function
	/// \param fmt the format specification
	/// \param x the integral argument
	/// \returns the formatted version of \emph{x}
	string operator()(const format_arg& fmt, IntT x) const {
		char digits[128] = {};
		auto chk = fennec::to_chars(digits, digits + sizeof(digits), fennec::abs(x), fmt.base);
		assertf(chk != nullptr, "fennec::format error, to_chars error");
		const size_t len = chk - digits;

		// handle uppercase
		if (fmt.upper) {
			for (auto& digit : digits) {
				if (digit == 0) {
					break;
				}
				digit = toupper(digit);
			}
		}

		const bool   has_sign = (x < 0 or fmt.sign != '-');
		const bool   zero     = fmt.fill == '0';
		const size_t prefix = fmt.alt ? (fmt.type == 'd' ? 0 : 2 - (fmt.type == 'o')) : 0;
		const size_t sgnlen = len + (zero ? has_sign + prefix : 0);
		const size_t explen = fennec::max(sgnlen, fmt.width) + (zero ? 0 : has_sign + prefix);
		const size_t fill   = fmt.width > sgnlen ? fmt.width - sgnlen : 0;
		size_t sign = 0;

		string res = string(explen);

		if (fill > 0) {
			switch (fmt.align) {
				case '<':
					memcpy(res.data() + has_sign + prefix, digits, len);
					memset(res.data() + has_sign + prefix + len, fmt.fill == '0' ? ' ' : fmt.fill, fill);
					break;
				case '>': case '\0':
					memcpy(res.data() + explen - len, digits, len);
					sign = fmt.fill == '0' ? 0 : explen - len - 1 - prefix;
					memset(res.data(), fmt.fill, explen - len);
					break;
				case '^':
					const size_t bef = fill / 2 + has_sign + prefix;
					const size_t aft = explen - bef;
					memcpy(res.data() + bef, digits, len);
					sign = fmt.fill == '0' ? 0 : bef - 1 - prefix;
					memset(res.data(), fmt.fill, bef);
					memset(res.data() + bef + len, fmt.fill == '0' ? ' ' : fmt.fill, aft);
					break;


			}
		} else {
			memcpy(res.data() + has_sign + prefix, digits, len);
		}

		if (has_sign) {
			res[sign] = (x < 0) ? '-' : fmt.sign;
		}

		if (prefix) {
			res[sign + has_sign] = '0';
			if (fmt.type != 'o') {
				res[sign + has_sign + 1] = fmt.type;
			}
		}

		return res;
	}
};

///
/// \brief formatter of boolean types
template<typename BoolT> requires(is_bool_v<BoolT>)
struct formatter<BoolT> {
	///
	/// \brief format function
	/// \param fmt the format specification
	/// \param x the boolean argument
	/// \returns the formatted version of \emph{x}
	string operator()(const format_arg& fmt, BoolT x) const {
		if (fmt.type == 's' or fmt.type == '\0') {
			return x ? string("true") : string("false");
		}

		return formatter<uint8_t>{}(fmt, static_cast<uint8_t>(x));
	}
};

///
/// \brief formatter of floating point types
template<typename FloatT> requires(is_floating_point_v<FloatT>)
struct formatter<FloatT> {
	///
	/// \brief format function
	/// \param fmt the format specification
	/// \param x the float argument
	/// \returns the formatted version of \emph{x}
	string operator()(const format_arg& fmt, FloatT x) const {

		// nan & inf cases
		if (fennec::isnan(x)) {
			return string("nan");
		}
		if (fennec::isinf(x)) {
			return string("inf");
		}


		char digits[128] = {};
		auto chk = fennec::to_chars(digits, digits + sizeof(digits), fennec::abs(x), fmt.type, fmt.precision);
		assertf(chk != nullptr, "fennec::format error, to_chars error");
		size_t len = chk - digits;

		// handle uppercase
		if (fmt.upper) {
			for (auto& digit : digits) {
				if (digit == 0) {
					break;
				}
				digit = toupper(digit);
			}
		}

		const bool   has_sign = (x < 0 or fmt.sign != '-');
		const bool   zero     = fmt.fill == '0';
		const size_t prefix = fmt.alt ? 2 : 0;
		const size_t sgnlen = len + (zero ? has_sign + prefix : 0);
		const size_t explen = fennec::max(sgnlen, fmt.width) + (zero ? 0 : has_sign + prefix);
		const size_t fill   = fmt.width > sgnlen ? fmt.width - sgnlen : 0;
		size_t sign = 0;

		string res = string(explen);

		if (fill > 0) {
			switch (fmt.align) {
				case '<':
					memcpy(res.data() + has_sign + prefix, digits, len);
					memset(res.data() + has_sign + prefix + len, fmt.fill == '0' ? ' ' : fmt.fill, fill);
					break;
				case '>': case '\0':
					memcpy(res.data() + explen - len, digits, len);
					sign = fmt.fill == '0' ? 0 : explen - len - 1 - prefix;
					memset(res.data(), fmt.fill, explen - len);
					break;
				case '^':
					const size_t bef = fill / 2 + has_sign + prefix;
					const size_t aft = explen - bef;
					memcpy(res.data() + bef, digits, len);
					sign = fmt.fill == '0' ? 0 : bef - 1 - prefix;
					memset(res.data(), fmt.fill, bef);
					memset(res.data() + bef + len, fmt.fill == '0' ? ' ' : fmt.fill, aft);
					break;


			}
		} else {
			memcpy(res.data() + has_sign + prefix, digits, len);
		}

		if (has_sign) {
			res[sign] = (x < 0) ? '-' : fmt.sign;
		}

		if (prefix) {
			res[sign + has_sign] = '0';
			res[sign + has_sign + 1] = fmt.type + ('x' - 'a');
		}

		return res;
	}


};

}

#endif // FENNEC_FORMAT_FORMATTER_H