// =====================================================================================================================
//  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_LANGPROC_STRINGS_STRING_H
#define FENNEC_LANGPROC_STRINGS_STRING_H

#include <fennec/langproc/strings/detail/_ctype.h>
#include <fennec/langproc/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.data(), c, n);
		_str[n] = '\0';
	}

	///
	/// \brief Buffer Copy Constructor
	/// \param str the buffer to copy
	/// \param n 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 `n` is the intended number of characters.
	constexpr _string(char* str, size_t n)
		: _str(str[n - 1] == '\0' ? n : n + 1) {
		fennec::memcpy(_str.data(), str, n);
		if (str[n - 1] != '\0') _str[n] = '\0';
	}

	///
	/// \brief Buffer Constructor, wraps the provided C-Style string
	/// \param str the buffer to wrap
	/// \tparam n the number of characters in the buffer plus the null terminator
	template<size_t n>
	explicit constexpr _string(char(&str)[n])
		: _str(str, n) {
		assert(_str[n - 1] == '\0', "Invalid NTBS.");
	}

	///
	/// \brief Buffer Copy Constructor
	/// \param str the buffer to copy
	/// \param n 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 `n` is the intended number of characters.
	constexpr _string(const char* str, size_t n)
		: _str(str[n - 1] == '\0' ? n : n + 1) {
		fennec::memcpy(_str.data(), str, n);
		if (str[n - 1] != '\0') _str[n] = '\0';
	}

	///
	/// \brief Buffer Constructor, wraps the provided C-Style string
	/// \param str the buffer to wrap
	/// \tparam n the number of characters in the buffer plus the null terminator
	template<size_t n>
	explicit constexpr _string(const char(&str)[n])
		: _str(str, n) {
		assert(_str[n - 1] == '\0', "Invalid NTBS.");
	}

	///
	/// \brief Buffer Copy Constructor
	/// \param str the buffer to copy
	constexpr _string(const cstring& str)
		: _str(str, str.size() + 1) {
	}

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

	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;
	}

	constexpr bool empty() const {
		return size() == 0;
	}


// 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 && (size_t)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.data();
	}

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


// 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 = fennec::min(n, fennec::max(_str, str.size()) + 1);

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

	///
	/// \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 string& str, size_t i = 0, size_t n = npos) const {
		if (i >= size()) { // bounds check
			return -1;
		}
		n = min(n, max(size(), str.size()) + 1);

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

	constexpr bool operator==(const _string& str) const {
		return compare(str) == 0;
	}

	///
	/// \brief Finds the index of the first occurrence of `c` in the string
	/// \param c the character to find
	/// \returns The index of `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.data() + i, c); // get location using strchr
		return loc ? loc - _str.data() : 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.data() + i, '\0', n + 1 - i);
	}

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

	///
	/// \brief Copy Assignment Operator
	/// \param str the string to copy
	/// \returns a reference to `this`
	constexpr _string& operator=(const string& str) {
		resize(str.size());
		fennec::memcpy(_str.data(), 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 = move(str._str);
		return *this;
	}

	///
	/// \brief Replace all instances of `x` with `y`
	/// \param x the character to search for
	/// \param y the character to replace with
	void replace(char x, char y) {
		size_t i = 0;
		while ((i = find(x, 0)) != size()) {
			_str[i] = y;
		}
	}

	///
	/// \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 = npos) const {
		if (i >= size()) {
			return _string("");
		}
		n = min(n, size() - i);
		return _string(_str.data() + 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.data(), _str.size() + 1);
		res[size()] = c; // Set the last character to c
		return res;
	}

	friend constexpr _string operator+(char c, _string& str) {
		return _string(c, 1) + str;
	}

	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[0], _str.data(), 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[0], _str.data(), 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;
};

template<>
struct hash<string> : hash<byte_array> {
	constexpr size_t operator()(const string& str) const {
		return hash<byte_array>::operator()(byte_array(*str, str.size()));
	}
};

}


#endif // FENNEC_LANGPROC_STRINGS_STRING_H
