// =====================================================================================================================
//  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_CSTRING_H
#define FENNEC_STRING_CSTRING_H

#include <fennec/string/detail/_ctype.h>
#include <fennec/memory/detail/_string.h>

#include <fennec/lang/assert.h>

#include <fennec/math/common.h>
#include <fennec/memory/bytes.h>

namespace fennec
{

// TODO: Document

using ::isalnum;
using ::isalpha;
using ::islower;
using ::isupper;
using ::isdigit;
using ::isxdigit;
using ::iscntrl;
using ::isgraph;
using ::isspace;
using ::isblank;
using ::isprint;
using ::ispunct;

using ::tolower;
using ::toupper;

///
/// \brief This struct wraps c-style strings
///
/// \details Requires that the string is null-terminated.
///          Prevents const qualified memory blocks from being manipulated.
///          This struct should be used when fennec::string would make unnecessary dynamic buffers.
struct cstring
{
// Definitions & Constants =============================================================================================
public:

	/// \name Definitions
	/// @{

	using char_t = char; //!< The character type.

	/// @}


	/// \name Constants
	/// @{

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

	/// @}

// Constructors & Destructor ===========================================================================================
public:

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

	///
	/// \brief Default Constructor, initializes with nullptr
	constexpr cstring()
		: _str(nullptr), _size(0), _const(true) {
	}

	///
	/// \brief Default Constructor, initializes with nullptr
	constexpr cstring(nullptr_t)
		: _str(nullptr), _size(0), _const(true) {
	}

	///
	/// \param str the buffer to wrap
	/// \param n the number of characters in the buffer plus the null terminator
	constexpr cstring(char* str, size_t n)
		: _str(str)
		, _size(n)
		, _const(false) {
		if constexpr(not is_constant_evaluated()) {
			assert(_str[n] == '\0', "Invalid NTBS.");
		}
	}

	///
	/// \param str the buffer to wrap
	/// \tparam n the number of characters in the buffer plus the null terminator
	template<size_t n>
	constexpr cstring(char(&str)[n])
		: _str(str)
		, _size(n - 1)
		, _const(false) {
		if constexpr(not is_constant_evaluated()) {
			assert(_str[n - 1] == '\0', "Invalid NTBS.");
		}
	}

	///
	/// \param str the buffer to wrap
	/// \param n the number of characters in the buffer plus the null terminator
	constexpr cstring(const char* str, size_t n)
		: _cstr(str)
		, _size(n)
		, _const(true) {
		if constexpr(not is_constant_evaluated()) {
			assert(_str[n] == '\0', "Invalid NTBS.");
		}
	}

	///
	/// \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>
	constexpr cstring(const char(&str)[n])
		: _cstr(str)
		, _size(n - 1)
		, _const(true) {
		if constexpr(not is_constant_evaluated()) {
			assert(_str[n - 1] == '\0', "Invalid NTBS.");
		}
	}

	///
	/// \brief Move Constructor
	/// \param str object to move
	constexpr cstring(cstring&& str) noexcept
		: _cstr(str._cstr)
		, _size(str._size)
		, _const(str._const) {
		str._cstr  = nullptr;
		str._size  = 0;
		str._const = true;
	}

	///
	/// \brief Copy Constructor
	/// \param str object to copy
	constexpr cstring(const cstring& str)
		: _cstr(str._cstr)
		, _size(str._size)
		, _const(str._const) {
	}

	///
	/// \brief Destructor
	constexpr ~cstring() = default;

	/// @}


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

	/// \name Assignment
	/// @{

	///
	/// \brief Null Assignment
	/// \returns A reference to \emph{this} after clearing the held reference.
	constexpr cstring& operator=(nullptr_t) {
		_str = nullptr, _size = 0, _const = true;
		return *this;
	}

	///
	/// \tparam N
	/// \param str
	/// \return
	template<size_t N>
	constexpr cstring& operator=(char(&str)[N]) {
		assert(_str[N - 1] == '\0', "Invalid NTBS.");
		_str = str, _size = N - 1, _const = false;
		return *this;
	}

	///
	/// \brief Array Constructor
	/// \tparam N The length of the array.
	/// \param str A reference to the array.
	/// \returns A reference to \emph{this} after having assigned and validated \emph{str}
	template<size_t N>
	constexpr cstring& operator=(const char(&str)[N]) {
		assert(str[N - 1] == '\0', "Invalid NTBS.");
		_cstr = str; _size = N - 1; _const = true;
		return *this;
	}

	///
	/// \brief Array Constructor
	/// \tparam N The length of the array.
	/// \param str A reference to the array.
	/// \returns A reference to \emph{this} after having taken ownership of \emph{str}
	constexpr cstring& operator=(cstring&& str) noexcept {
		_cstr  = str._cstr;  str._cstr  = nullptr;
		_size  = str._size;  str._size  = 0;
		_const = str._const; str._const = true;
		return *this;
	}

	///
	/// \brief Copy Assignment
	/// \param str The object to copy
	/// \returns A reference to \emph{this}
	constexpr cstring& operator=(const cstring& str) {
		_cstr = str._cstr;
		_size  = str._size;
		_const = str._const;
		return *this;
	}

	/// @}


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

	/// \name Properties
	/// @{

	///
	/// \returns the size of the string excluding its null terminator, i.e. `(*str)[size()] == '\0'`
	constexpr size_t size() const {
		return _size;
	}

	///
	/// \returns the size of the string including its null terminator, i.e. `(*str)[capacity() - 1] == '\0'`
	constexpr size_t capacity() const {
		return _size + 1;
	}

	///
	/// \returns \emph{true} if there is no held string, or the size is \math{0}, \emph{false} otherwise.
	constexpr bool is_empty() const {
		return _cstr == nullptr || _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) {
		assertd(not _const, "Attempted to Access Const-Qualified Memory as Non-Const");
		assertd(i < size(), "Array Out of Bounds");
		return _str[i];
	}

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

	///
	/// \returns A pointer to the underlying allocation
	constexpr char* data() {
		return _str;
	}

	///
	/// \brief Data Access
	/// \returns A const qualified pointer to the underlying allocation
	constexpr const char* data() const {
		return _cstr;
	}

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

	/// @}


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

	/// \name Examination
	/// @{

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

	///
	/// \brief String Comparison
	/// \param str the string to compare against
	/// \param i the index to start at
	/// \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) {
			return -1;
		}

		n = min(n, max(_size, str._size) + 1);
		return ::strncmp(_cstr + i, str, n);
	}

	///
	/// \brief String Equality
	/// \param str the string to compare against
	/// \returns \emph{true} if all characters are equal, \emph{false} otherwise
	template<size_t n>
	constexpr bool operator==(const char (&str)[n]) const {
		return compare(cstring(str)) == 0;
	}

	///
	/// \brief String Equality
	/// \param str the string to compare against
	/// \returns \emph{true} if all characters are equal, \emph{false} otherwise
	constexpr bool operator==(const cstring& str) const {
		return compare(str) == 0;
	}

	///
	/// \brief nullptr Equality
	/// \returns \emph{true} if there is no held string, \emph{false} otherwise.
	constexpr bool operator==(nullptr_t) const {
		return _cstr == nullptr;
	}

	///
	/// \brief Finds the index of the first occurrence of \emph{c} in the string
	/// \param c the character to find
	/// \param i the index to start at
	/// \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(_cstr + i, c); // get location using strchr
		return loc ? loc - _cstr : _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 the index to start at
	/// \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(_cstr + i, str); // get location using strstr
		return loc ? loc - _cstr : _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 the index to start at
	/// \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 (_cstr[i] == c) return i; // loop backwards looking for c
		} 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 the index to start at
	/// \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(_cstr[i] == first) {
				if (compare(str, i, str._size - 1) == 0) {
					return i;
				}
			} // loop backwards looking for str
		} while (i--);
		return _size; // base case
	}

	/// @}


// Private Member Variables ============================================================================================
private:
	union { // hack to allow both const qualified and non-const strings
		char*        _str;
		const char* _cstr;
	};
	size_t _size;
	bool  _const;
};

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

}

#endif // FENNEC_STRING_CSTRING_H
