// =====================================================================================================================
//  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/format.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_FORMAT_H
#define FENNEC_FORMAT_FORMAT_H

#include <fennec/string/string.h>

#include <fennec/format/detail/_format.h>
#include <fennec/format/formatter.h>

namespace fennec
{

///
/// \brief C++ 20 format specification
/// \tparam ArgsT The argument types
/// \param str The format string
/// \param args The argument values
/// \returns A formatted string using the C++20 format specification
template<typename...ArgsT>
string format(const cstring& str, ArgsT&&...args) {
	static constexpr size_t argc = sizeof...(ArgsT);
	static constexpr format_arg default_fmt = {
		.fill  = ' ',
		.align = '\0',  // default to locale
		.sign  = '\0',  // default to sign only for negative numbers, gets handled later in code
		.alt   = false, // default no prefix
		.upper = false,
		.width = 0,
		.precision = 6, // default to 6 sigfigs
		.base = 10,
		.type  = '\0',
	};

	static_assert(argc > 0, "fennec::format may not accept 0 arguments");

	// empty case
	if constexpr(argc == 0) {
		return str;
	}

	detail::_format_argarray<argc> argarray = { fennec::forward<ArgsT>(args)... };
	string res;
	size_t i = 0;
	size_t arg_c = -1;

	while (i <= str.length()) {
		size_t brace = str.find('{', i);
		size_t end = str.find('}', i);
		format_arg fmt = default_fmt;

		// check for '}}'
		if (end < brace) {
			if (str[end + 1] == '}') {
				res += string(str.data() + i, end - i);
				i = end + 2;
				continue;
			}
			assertf(false, "fennec::format syntax error, encountered unexpected '{'")
		}

		// append string
		if (brace >= str.length()) { // handle end case
			res += string(str.data() + i, str.length() - i);
			break;
		}
		res += string(str.data() + i, brace - i);

		// next brace, validate escape
		size_t next_brace = str.find('{', brace + 1);
		if (brace + 1 == next_brace) {
			res += '{';
			i = next_brace + 1;
			continue;
		}

		// find contained colon
		size_t colon = str.find(':', brace);

		// validate colon and brace location
		assertf(colon < next_brace or end < next_brace, "fennec::format syntax error, mismatched '{}'");

		// parse index if present
		size_t id = min(colon, end) - 1;
		if (id > brace) {
			arg_c = 0;
		} else {
			++arg_c;
		}
		for (size_t j = id, k = 1; j > brace; --j, k *= 10) {
			size_t u = (str[j] - '0');
			assertf(u < 10, "fennec::format syntax error, invalid argument index");
			arg_c += k * u;
		}

		// store argument to allow nested replacement fields
		size_t arg = arg_c;

		// validate index
		assertf(arg < argc, "fennec::format syntax error, invalid argument index");

		// early return case for no colon
		if (colon > end) {
			fmt.sign = fmt.sign == '\0' ? '-' : fmt.sign;
			res += argarray.format(arg, fmt);
			i = end + 1;
			continue;
		}

		// parse format specifiers

		// we're going to parse right-to-left since the valid combinations
		// of specifiers change based on the type of the argument

		// to compensate for this, the nested replacement fields need to be computed in this loop
		// (nested replacement deduced)
		size_t nrfd = 0;
		size_t nnrf = 0;

		// first find the matching '}' brace, e is not necessarily the matching brace
		// since some specifiers allow nested replacement fields
		size_t parse = colon;
		while (str[parse + 1] != '}') {
			if (next_brace < end) { // if the next brace is before the next closing brace
				++nnrf;
				nrfd += str[end - 1] == '{';
				parse = end + 1;
				end = str.find('}', parse);
				next_brace = str.find('{', parse);
			} else {
				parse = end - 1;
				break;
			}
		}

		assertf(nrfd <= 2 and parse < str.length() - 1 and str[parse + 1] == '}',
			    "fennec::format syntax error, mismatched '{}'");

		// check type
		switch (str[parse]) {
			default: break;

			case 's': case '?': // strings
			case 'c':           // char
				fmt.type = str[parse--];
				break;


			case 'd':           // decimal
				fmt.base = 10;
				fmt.type = str[parse--];
				break;

			case 'B':           // binary
				fmt.upper = true; [[fallthrough]];
			case 'b':
				fmt.base = 2;
				fmt.type = str[parse--];
				break;

			case 'o':           // octal
				fmt.base = 8;
				fmt.type = str[parse--];
				break;

			case 'X':           // hex
				fmt.upper = true; [[fallthrough]];
			case 'x':
				fmt.base = 16;
				fmt.type = str[parse--];
				break;


			case 'A':
				fmt.upper = true; [[fallthrough]];
			case 'a':           // float hex
				fmt.base = 16;
				fmt.type = str[parse--];
				break;

			case 'E':           // scientific notation
				fmt.upper = true; [[fallthrough]];
			case 'e':
				fmt.base = 16;
				fmt.type = str[parse--];
				break;

			case 'F':           // fixed precision
				fmt.upper = true; [[fallthrough]];
			case 'f':
				fmt.base = 10;
				fmt.type = str[parse--];
				break;

			case 'G':           // general precision
				fmt.upper = true; [[fallthrough]];
			case 'g':
				fmt.base = 10;
				fmt.type = str[parse--];
				break;
		}

		// early return
		if (parse == colon) {
			fmt.sign = fmt.sign == '\0' ? '-' : fmt.sign;
			res += argarray.format(arg, fmt);
			i = end + 1;
			continue;
		}

		// search for width and precision
		size_t x = 0, j = 1;
		bool   found_decimal = false;
		size_t num_decimals = 0;
		bool   is_float_t   = detail::_isfmt_f(fmt.type);
		bool   is_str_t     = fmt.type == 's';
		bool   is_integer_t = detail::_isfmt_i(fmt.type);
		bool   ded_width_f  = false;
		bool   ded_width    = false;
		size_t ded_temp_i   = 0;

		// default "precision" for strings should be 0 for no limit
		if (is_str_t) {
			fmt.precision = 0;
		}

		// parse width and precision
		while (isdigit(str[parse]) or (found_decimal = (str[parse] == '.')) or str[parse] == '{' or str[parse] == '}') {
			// handle decimal point for precision
			if (found_decimal) {
				assertf(is_float_t or is_str_t, "fennec::format syntax error, encountered precision argument on non-floating point format");
				assertf(num_decimals == 0, "fennec::format syntax error, multiple decimals detected in floating point format");
				++num_decimals;
				found_decimal = false;

				fmt.precision = x;
				x = 0, j = 1;
				--parse;
				continue;
			}

			// check for nested replacement field
			if (str[parse] == '{') {
				assertf(str[parse - 1] == '0' or str[parse - 1] == '.' or not isdigit(str[parse - 1]),
						"fennec::format syntax error, unexpected digit preceding nested replacement field");

				bool prec = str[parse - 1] == '.';
				bool ded  = str[parse + 1] == '}';

				size_t sub;
				if (nrfd == 2) { // if both are deduced, parse normally. Hack with prefix and postfix.
					sub = prec ? ++arg_c + 1 : arg_c++;
				} else if (nrfd == 1 and nnrf == 2 and prec and ded) { // if only precision is nrf, deduce width first
					ded_width_f = true;
					ded_temp_i  = parse;
					continue;
				} else { // otherwise deduce normally
					sub = ded ? ++arg_c : x;
				}

				assertf(sub < argc, "fennec::format syntax error, argument index out of range in nested replacement field");
				assertf(argarray.is_integer(sub), "fennec::format argument error, nested replacement field argument is not convertible to integral type");

				(prec ? fmt.precision : fmt.width) = argarray.int_value(sub);
				x = 0;

				if (ded_width_f) {
					ded_width_f = false;
					ded_width   = true;
					swap(ded_temp_i, parse);
					arg_c = sub;
					continue;
				}

				if (ded_width) {
					parse     = ded_temp_i;
					ded_width = false;
				}

				parse -= 1 + prec;
				continue;
			}

			// ignore closing brace for nested replacement fields
			if (str[parse] == '}') {
				--parse;
				continue;
			}

			// crude way to only handle 0 case if 0 is the last digit
			fmt.fill = str[parse] == '0' ? '0' : ' ';

			// parse the number
			x += j * (str[parse] - '0');
			j *= 10;
			--parse;
		}
		if (x != 0) {
			fmt.width = x;
		}

		// early return
		if (parse == colon) {
			fmt.sign = fmt.sign == '\0' ? '-' : fmt.sign;
			res += argarray.format(arg, fmt);
			i = end + 1;
			continue;
		}

		// check for alt form
		if (str[parse] == '#') {
			assertf(is_float_t or is_integer_t, "fennec::format syntax error, encountered alt spec ('#') with non-decimal type");
			fmt.alt = true;
			--parse;
		}

		// check for sign
		if (str[parse] == '-' or str[parse] == '+' or str[parse] == ' ') {
			fmt.sign = str[parse];
			if (str[parse] == ' ') { // handle fill if only space, gets overwritten if encounters fill character
				fmt.fill = ' ';
			}
			--parse;
		}

		// check for alignment
		if (str[parse] == '<' or str[parse] == '>' or str[parse] == '^') {
			fmt.align = str[parse];
			--parse;
		}

		// fill character
		if (str[parse] != ':') {
			fmt.fill = str[parse];
			if (str[parse] == ' ') {
				fmt.sign = fmt.sign == '\0' ? ' ' : fmt.sign;
			}
			--parse;
		}

		// default sign
		fmt.sign = fmt.sign == '\0' ? '-' : fmt.sign;

		// validate that we handled the entire format arg
		assertf(parse == colon, "fennec::format syntax error, malformed format string detected, possible double colon");


		// add formatted argument
		res += argarray.format(arg, fmt);
		i = end + 1;
	}

	return res;
}

}

#endif // FENNEC_FORMAT_FORMAT_H