// =====================================================================================================================
//  fennec, a free and open source game engine
//  Copyright © 2025  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/>.
// =====================================================================================================================

#ifndef FENNEC_FPROC_STRINGS_STRING_H
#define FENNEC_FPROC_STRINGS_STRING_H

#include <fennec/fproc/strings/detail/__ctype.h>
#include <fennec/fproc/strings/cstring.h>

#include <fennec/lang/assert.h>

#include <fennec/memory/allocator.h>
#include <fennec/memory/common.h>

#include <fennec/math/common.h>

namespace fennec
{

// Forward def
template<typename AllocT = allocator<char>> struct _string;

// Alias for default allocator
using string = _string<>;

///
/// \brief Struct for wrapping c-style strings
///
/// \details behaviour guarantees that the underlying string is null-terminated
template<typename AllocT>
struct _string
{
public:
	static constexpr size_t npos = -1;
	using alloc_t = allocation<char, AllocT>;


// Constructors ========================================================================================================

	///
	/// \brief Default Constructor, initializes empty string
	constexpr _string()
	: _str() {
	}

	///
	/// \brief Sized Constructor, initializes a null-terminated string of size `n` with null characters
	/// \param n the number of characters
	///
	/// \details adds additional character for null termination.
	constexpr _string(size_t n)
	: _string('\0', n) {
	}

	///
	/// \brief Sized Constructor, initializes a null-terminated string of size `n` filled with the character `c`
	/// \param c the character to fill with
	/// \param n the number of characters
	///
	/// \details adds additional character for null termination.
	constexpr _string(char c, size_t n)
	: _str(n + 1) {
		fennec::memset(_str, c, n); _str[n] = '\0';
	}

	///
	/// \brief Buffer Copy Constructor
	/// \param str the buffer to copy
	/// \param len number of characters in the buffer
	///
	/// \details adds additional character for null termination. Ignores whether str is null-terminated.
	///          This constructor makes the assumption that `len` is the intended number of characters.
	constexpr _string(const cstring& str)
	: _str(str, str.size() + 1) {
		_str[str.size()] = '\0';
	}

	///
	/// \brief String Copy Constructor
	/// \param str the string to copy
	constexpr _string(const string& str)
	: _string(str, str.size() - 1) {
	}

	constexpr _string(_string&& str) noexcept
		: _str(fennec::move(str._str)) { }

	///
	/// \brief String Destructor, cleans up the underlying allocation
	constexpr ~_string() = default; // allocation cleans up itself

// Properties ==========================================================================================================

	///
	/// \returns The size of the string excluding null terminator
	constexpr size_t size() const {
		return _str.capacity() - 1;
	}


// Access ==============================================================================================================

	///
	/// \brief Array Access Operator
	/// \param i the index to access
	/// \returns a reference to the character
	constexpr char& operator[](int i) {
		return _str[i];
	}

	///
	/// \brief Const-Array Access Operator
	/// \param i the index to access
	/// \returns a copy of the character
	constexpr char operator[](int i) const {
		assertd(i >= 0 && i < size(), "Array Out of Bounds");
		return _str[i];
	}

	///
	/// \brief Dereference Operator
	/// \returns A const qualified pointer to the underlying allocation
	constexpr const char* operator*() const {
		return _str;
	}

	///
	/// \brief Implicit Dereference Cast
	constexpr operator const char*() const {
		return _str;
	}


// Examination =========================================================================================================

	///
	/// \returns The length of the string to the first null-terminator
	constexpr size_t length() const {
		return find('\0');
	}

	///
	/// \brief String Comparison
	/// \param ostr the string to compare against
	/// \returns Zero if both strings are equal, otherwise a negative value if lhs appears before rhs according to the
	///          current locale, otherwise a positive value.
	constexpr int compare(const cstring& str, size_t i = 0, size_t n = npos) const {
		if (i >= size()) { // bounds check
			return -1;
		}
		n = min(n, max(_str, str.size()) + 1);

		return ::strncmp(_str + i, str, n);
	}

	///
	/// \brief Finds the index of the first occurrence of `x` in the string
	/// \param x the character to find
	/// \returns The index of `x` if it occurs in the string, otherwise returns `size()`
	constexpr size_t find(char c, size_t i = 0) const {
		if (i >= size()) { // bounds check
			return size();
		}

		const char* loc = ::strchr(_str + i, c); // get location using strchr
		return loc ? loc - _str : size(); // return size if not found
	}

	///
	/// \brief Finds the index of the first occurrence of `str` in the string.
	/// \param str the string to find
	/// \returns The index of `str` if it occurs in the string, otherwise returns `size()`
	constexpr size_t find(const string& str, size_t i = 0) const { // bounds check
		if (i >= size()) { // bounds check
			return size();
		}

		const char* loc = ::strstr(_str, str);
		return loc ? loc - _str : size();
	}

	///
	/// \brief Finds the index of the first occurrence of `str` in the string.
	/// \param str the string to find
	/// \returns The index of `str` if it occurs in the string, otherwise returns `size()`
	constexpr size_t find(const cstring& str, size_t i = 0) const {
		if (i + str.size() > size()) { // bounds check
			return size();
		}

		const char* loc = ::strstr(_str + i, str); // get location using strstr
		return loc ? loc - _str : size(); // return size if not found
	}

	///
	/// \brief Finds the index of the last occurrence of `c` in the string.
	/// \param c the string to find
	/// \param i the index to start at
	/// \returns The index of `c` if it occurs in the string, otherwise returns `size()`
	constexpr size_t rfind(char c, size_t i = npos) const {
		if (size() == 0) {
			return size();
		}
		i = min(i, size() - 1); // clamp i to bounds
		do {
			if (_str[i] == c) { // loop backwards looking for c
				return i;
			}
		} while (i--);
		return size(); // base case
	}

	///
	/// \brief Finds the index of the last occurrence of `str` in the string.
	/// \param str the string to find
	/// \param i the index to start at
	/// \returns The index of `str` if it occurs in the string, otherwise returns `size()`
	constexpr size_t rfind(const cstring& str, size_t i = npos) const {
		if (size() == 0) {
			return size();
		}
		const char first = str[0];
		i = min(i, size() - str.size());
		do {
			if(_str[i] == first) {
				if (compare(str, i) == 0) {
					return i;
				}
			}
		} while (i--);
		return size(); // base case
	}

	///
	/// \brief Finds the index of the last occurrence of `str` in the string.
	/// \param str the string to find
	/// \param i the index to start at
	/// \returns The index of `str` if it occurs in the string, otherwise returns `size()`
	constexpr size_t rfind(const string& str, size_t i = npos) const {
		if (size() == 0) {
			return size();
		}
		const char first = str[0];
		i = min(i, size() - str.size());
		do {
			if(_str[i] == first) {
				if (compare(str, i) == 0) { // loop backwards looking for str
					return i;
				}
			}
		} while (i--);
		return size(); // base case
	}

// Manipulation ========================================================================================================

	///
	/// \brief Resize the string, filling additional bytes with `'\0'`
	/// \param n the new size of the string
	constexpr void resize(size_t n) {
		size_t i = size();
		_str.reallocate(n + 1);
		if (n > i) fennec::memset(*_str + i, '\0', n - i);
	}

	///
	/// \brief Copy Assignment Operator
	/// \param str the string to copy
	/// \returns a reference to `this`
	constexpr string& operator=(const string& str) {
		if (str.size() > size()) resize(str.size());
		fennec::memcpy(_str, str, str.size());
		_str[str.size()] = '\0';
		return *this;
	}

	///
	/// \brief Move Assignment Operator
	/// \param str the string to move
	/// \returns a reference to `this`
	constexpr string& operator=(string&& str) noexcept {
		_str = fennec::move(str._str);
		return *this;
	}

	///
	/// \brief Retrieve a substring of a string
	/// \param i the start index
	/// \param n the number of characters
	/// \return
	constexpr string substring(size_t i, size_t n = size()) const {
		assert(i < size(), "Array Out of Bounds");
		n = min(n, size() - i);
		return string(&_str[i], n);
	}

	///
	/// \brief Returns a string with `c` appended to it
	/// \param c
	/// \returns
	constexpr string operator+(char c) const {
		// Copy contents with one additional byte.
		string res(_str, _str.size());
		res[size()] = c; // Set the last character to c
		return res;
	}

	constexpr string operator+(const cstring& str) const {
		string res(size() + str.size()); // Make a new string with the size of this + str
		fennec::memcpy(res, _str, size()); // Copy the contents of this
		fennec::memcpy(&res[size()], str, str.size()); // Append the contents of str
		return res;
	}

	constexpr string operator+(const string& str) const {
		string res(size() + str.size()); // Make a new string with the size of this + str
		fennec::memcpy(res, _str, size()); // Copy the contents of this
		fennec::memcpy(&res[size()], str, str.size()); // Append the contents of str
		return res;
	}

	constexpr string& operator+=(char c) {
		size_t x = size();
		resize(x + 1);
		_str[x] = c;
		return *this;
	}

	constexpr string& operator+=(const cstring& str) {
		size_t x = size();
		resize(x + str.size());
		fennec::memcpy(&_str[x], str, str.size());
		return *this;
	}

	constexpr string& operator+=(const string& str) {
		size_t x = size();
		resize(x + str.size());
		fennec::memcpy(&_str[x], str, str.size());
		return *this;
	}

private:
	alloc_t _str;
};

}


#endif // FENNEC_FPROC_STRINGS_STRING_H
