// =====================================================================================================================
//  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/>.
// =====================================================================================================================

#ifndef FENNEC_STRING_STRING_H
#define FENNEC_STRING_STRING_H

#include <fennec/string/cstring.h>
#include <fennec/string/string_view.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
{
// Definitions =========================================================================================================
public:

	/// \name Constants
	/// @{

	static constexpr size_t npos = -1; //!< Constant representing a null position in the string.

	/// @}


	/// \name Definitions
	/// @{

	using char_t = char;                      //!< the character type
	using alloc_t = allocation<char, AllocT>; //!< the allocator type

	/// @}


// Constructors & Destructor ===========================================================================================

	/// \name Constructors & Destructor
	/// @{

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

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

	///
	/// \brief Alloc Constructor
	/// \param alloc The allocator to use
	constexpr _string(const alloc_t& alloc)
		: _str(alloc) {
	}

	///
	/// \brief Sized Alloc Constructor, initializes a null-terminated string of size \emph{n} with `'c'...`
	/// \param n the number of characters
	/// \param c the character to fill with
	/// \param alloc The allocator to use
	///
	/// \details Adds additional character for null termination.
	constexpr _string(size_t n, char c, const alloc_t& alloc)
		: _str(n + 1, alloc) {
		fennec::memset(_str, c, n);
	}

	///
	/// \brief C-String Constructor
	/// \param cstr The C-Style string to construct from.
	constexpr _string(const cstring& cstr)
		: _str(cstr, cstr.size() + 1) {
	}

	///
	/// \brief Buffer Constructor, wraps the provided C-Style string, appending a null-terminator if not present
	/// \param buf the buffer to wrap
	/// \param n the number of characters in the buffer including the null-terminator, if present
	constexpr _string(const char* buf, size_t n)
		: _str(buf[n - 1] != '\0' ? n + 1 : n) {
		fennec::memcpy(_str, buf, n);
		if (buf[n - 1] != '\0') {
			_str[n] = '\0';
		}
	}

	///
	/// \brief String View Constructor
	/// \param view The C-Style string view to construct from.
	constexpr _string(const string_view& view)
		: _string(view.str, view.len) {
	}

	///
	/// \brief String Copy Constructor
	/// \param str the string to copy
	constexpr _string(const _string& str) = default;

	///
	/// \brief String Move Constructor
	/// \param str the string to take ownership of
	constexpr _string(_string&& str) noexcept = default;

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

	/// @}


// Assignment ==========================================================================================================
public:

	/// \name Assignment
	/// @{

	///
	/// \brief C-String Assignment
	/// \param cstr The C-Style string to assign.
	/// \returns A reference to \emph{this}
	constexpr _string& operator=(const cstring& cstr) {
		_str.allocate(cstr.capacity());
		fennec::memcpy(_str, cstr, cstr.capacity());
		return *this;
	}

	///
	/// \brief String Copy Assignment
	/// \param str the string to copy
	/// \returns A reference to \emph{this}
	constexpr _string& operator=(const _string& str) {
		_str = str._str;
		return *this;
	}

	///
	/// \brief String Move Assignment
	/// \param str the string to take ownership of
	/// \returns A reference to \emph{this}
	constexpr _string& operator=(_string&& str) noexcept {
		_str = fennec::move(str._str);
		return *this;
	}

	/// @}


// Properties ==========================================================================================================
public:

	/// \name Properties
	/// @{

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

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

	///
	/// \returns \emph{true} if the string contains no characters, \emph{false} otherwise.
	constexpr bool is_empty() const {
		return size() == 0;
	}

	/// @}


// Access ==============================================================================================================
public:

	/// \name Access
	/// @{

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

	///
	/// \brief Const-Array Access Operator
	/// \param i the index to access
	/// \returns a copy of the character
	constexpr const char& operator[](size_t i) const {
		return _str[i];
	}

	///
	/// \returns The underlying pointer to the held string
	constexpr char* data() {
		return _str;
	}

	///
	/// \returns The underlying pointer to the held string
	constexpr const char* data() const {
		return _str;
	}

	///
	/// \returns The underlying pointer to the held string
	constexpr const char* cstr() const {
		return _str;
	}

	/// @}


// Examination =========================================================================================================
public:

	/// \name Examination
	/// @{

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

	///
	/// \param str the string to compare against
	/// \param i An offset to start with in \emph{this}
	/// \param n The number of characters to compare
	/// \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(min(n, size() - i), str.size());

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

	///
	/// \brief String Comparison
	/// \param str the string to compare against
	/// \param i An offset to start at in \emph{this}
	/// \param n The number of characters to compare
	/// \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 _string& str, size_t i = 0, size_t n = npos) const {
		if (i >= size()) { // bounds check
			return -1;
		}
		n = min(min(n, size() - i), str.size());

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

	///
	/// \param str The string to compare against
	/// \returns Equivalent to `compare(str) == 0`
	constexpr bool operator==(const cstring& str) const {
		return compare(str) == 0;
	}

	///
	/// \brief String Equality
	/// \param str The string to compare against
	/// \returns Equivalent to `compare(str) == 0`
	constexpr bool operator==(const _string& str) const {
		return compare(str) == 0;
	}

	///
	/// \brief Check if the string contains a character
	/// \param c The character to find
	/// \param i An offset to start at in \emph{this}
	/// \returns \emph{true} if \emph{c} is contained in \emph{this}
	constexpr bool contains(char c, size_t i = 0) const {
		return find(c, i) != size();
	}

	///
	/// \brief Finds the index of the first occurrence of \emph{c} in the string
	/// \param c the character to find
	/// \param i An offset to start at in \emph{this}
	/// \returns The index of \emph{c} 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 \emph{str} in the string.
	/// \param str the string to find
	/// \param i An offset to start at in \emph{this}
	/// \returns The index of \emph{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._str);
		return loc ? loc - _str : size();
	}

	///
	/// \brief Finds the index of the first occurrence of \emph{str} in the string.
	/// \param str the string to find
	/// \param i An offset to start at in \emph{this}
	/// \returns The index of \emph{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 \emph{c} in the string.
	/// \param c the string to find
	/// \param i An offset to start at in \emph{this}
	/// \returns The index of \emph{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 \emph{str} in the string.
	/// \param str the string to find
	/// \param i An offset to start at in \emph{this}
	/// \returns The index of \emph{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 \emph{str} in the string.
	/// \param str the string to find
	/// \param i An offset to start at in \emph{this}
	/// \returns The index of \emph{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
	}

	///
	/// \brief Retrieve a substring of a string
	/// \param i An offset to start at in \emph{this}
	/// \param n the number of characters
	/// \returns A new string containing the range of characters specified by \emph{i} and \emph{n}
	constexpr _string substring(size_t i, size_t n = npos) const {
		if (i >= size() || n == 0) {
			return _string("");
		}
		n = fennec::min(n, size() - i);
		_string res;
		res._str.allocate(n + 1);
		fennec::memcpy(res.data(), _str + i, n);
		return res;
	}

	/// @}


// Modifiers ===========================================================================================================
public:

	/// \name Modifiers
	/// @{

	///
	/// \brief String Resize
	/// \brief Resizes the underlying allocation to hold \emph{n} characters and a null terminator.
	/// \param n The new size of the string
	constexpr void resize(size_t n) {
		_str.reallocate(n + 1);
		_str[size()] = '\0';
	}

	///
	/// \brief Character Append
	/// \param c A character to append
	/// \returns A new string containing the previous contents and an additional character \emph{c}.
	constexpr _string operator+(char c) const {
		if (_str == nullptr) {
			return _string(1, c);
		}
		_string res;
		res._str.allocate(capacity() + 1);
		fennec::memcpy(res.data(), _str, size());
		res[size()] = c;
		return res;
	}

	///
	/// \brief Character Prepend
	/// \param c A character to append
	/// \returns A new string containing the character \emph{c} followed by the previous contents.
	friend constexpr _string operator+(char c, const _string& str) {
		_string res(str.size() + 1);
		res[0] = c;
		fennec::memcpy(res.data() + 1, str.data(), str.size());
		return res;
	}

	///
	/// \param cstr The string to append
	/// \returns A new string containing the previous contents followed by the contents of \emph{cstr}
	constexpr _string operator+(const cstring& cstr) const {
		if (_str == nullptr) {
			return _string(cstr);
		}
		_string res;
		res._str.allocate(size() + cstr.size() + 1);
		fennec::memcpy(res.data(), _str, size());
		fennec::memcpy(res.data() + size(), cstr, cstr.size());
		return res;
	}

	///
	/// \brief String Append
	/// \param str The string to append
	/// \returns A new string containing the previous contents followed by the contents of \emph{cstr}
	constexpr _string operator+(const _string& str) const {
		if (_str == nullptr) {
			return _string(str);
		}
		if (str.data() == nullptr) {
			return _string(*this);
		}
		_string res;
		res._str.allocate(size() + str.size() + 1);
		fennec::memcpy(res.data(), _str, size());
		fennec::memcpy(res.data() + size(), str.data(), str.size());
		return res;
	}

	///
	/// \brief Character Append Assignment
	/// \param c A character to append
	/// \returns \emph{this} string containing an additional character \emph{c}.
	constexpr _string& operator+=(char c) {
		if (_str == nullptr) {
			_str.allocate(2); // TODO: Performance Improvements with Capacity Expansion
			_str[0] = c;
			return *this;
		}
		_str.reallocate(capacity() + 1);
		_str[size() - 1] = c;
		return *this;
	}

	///
	/// \param cstr The string to append
	/// \returns \emph{this} string expanded to additionally contain \emph{cstr}
	constexpr _string& operator+=(const cstring& cstr) {
		if (_str == nullptr) {
			return *this = cstr;
		}
		size_t middle = size();
		_str.reallocate(middle + cstr.size() + 1);
		fennec::memcpy(_str + middle, cstr, cstr.size());
		return *this;
	}

	///
	/// \brief String Append Assignment
	/// \param str The string to append
	/// \returns \emph{this} string expanded to additionally contain \emph{str}
	constexpr _string& operator+=(const _string& str) {
		if (_str == nullptr) {
			return *this = str;
		}
		if (str.data() == nullptr) {
			return *this;
		}
		size_t middle = size();
		_str.reallocate(middle + str.size() + 1);
		fennec::memcpy(_str + middle, str.data(), str.size());
		return *this;
	}

	/// @}


// Iteration ===========================================================================================================
public:

	/// \name Iteration
	/// @{

	///
	/// \returns A pointer to the first character in the string.
	constexpr char* begin() {
		return _str.data();
	}

	///
	/// \brief Iterable begin()
	/// \returns A pointer to the first character in the string.
	constexpr const char* begin() const {
		return _str.data();
	}

	///
	/// \returns A pointer to the null terminator of the string.
	constexpr char* end() {
		return _str.data() + _str.capacity();
	}


	///
	/// \brief Iterable end()
	/// \returns A pointer to the null terminator of the string.
	constexpr const char* end() const {
		return _str.data() + _str.capacity();
	}

	/// @}


// Private Member Variables ============================================================================================
private:
	alloc_t _str;
};

/// \brief Hashing for `fennec::string`
template<typename AllocT>
struct hash<_string<AllocT>> : hash<byte_array> {
	constexpr size_t operator()(const _string<AllocT>& str) const {
		return hash<byte_array>::operator()(byte_array(str.data(), str.size()));
	}
};

}


#endif // FENNEC_STRING_STRING_H
