// =====================================================================================================================
//  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_wstringS_WSTRING_H
#define FENNEC_LANGPROC_wstringS_WSTRING_H

#include <fennec/langproc/strings/detail/_ctype.h>
#include <fennec/langproc/strings/wcstring.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<wchar_t>> struct _wstring;

// Alias for default allocator
using wstring = _wstring<>;

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


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

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

	///
	/// \brief Sized Constructor, initializes a null-terminated string of size `n` with `'c'...`
	/// \param n the number of wchar_tacters
	/// \param c the wchar_tacter to fill with
	///
	/// \details adds additional wchar_tacter for null termination.
	constexpr _wstring(size_t n, wchar_t c = '\0')
		: _str(n + 1) {
		fennec::wmemset(_str, c, n);
	}

	constexpr _wstring(const alloc_t& alloc)
		: _str(alloc) {
	}

	constexpr _wstring(size_t n, wchar_t c, const alloc_t& alloc)
		: _str(n + 1, alloc) {
		fennec::wmemset(_str, c, n);
	}

	constexpr _wstring(const wcstring& cstr)
		: _str(cstr, cstr.size() + 1) {
	}

	///
	/// \brief Buffer Constructor, wraps the provided C-Style string, appending a null-terminator if not present
	/// \param str the buffer to wrap
	/// \tparam n the number of wchar_tacters in the buffer including the null-terminator, if present
	template<size_t n>
	explicit constexpr _wstring(const wchar_t (&str)[n])
		: _str(str[n - 1] != '\0' ? n + 1 : n) {
		fennec::wmemcpy(_str, str, n);
		if (str[n - 1] != '\0') {
			_str[n] = '\0';
		}
	}

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

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

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

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

// Assignment ==========================================================================================================

	constexpr _wstring& operator=(const wcstring& cstr) {
		_str.callocate(cstr.capacity());
		fennec::wmemcpy(_str, cstr, cstr.capacity());
		return *this;
	}

	constexpr _wstring& operator=(const _wstring& str) = default;
	constexpr _wstring& operator=(_wstring&& str) noexcept = default;

// 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();
	}

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

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

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

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

	constexpr wchar_t* data() {
		return _str;
	}

	constexpr const wchar_t* data() const {
		return _str;
	}

	constexpr const wchar_t* cstr() 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 wcstring& 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 ::wcsncmp(_str + 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 _wstring& 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 ::wcsncmp(_str + i, str.data(), n);
	}

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

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

		const wchar_t* loc = ::wcschr(_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 _wstring& str, size_t i = 0) const { // bounds check
		if (i >= size()) { // bounds check
			return size();
		}

		const wchar_t* loc = ::wcsstr(_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 wcstring& str, size_t i = 0) const {
		if (i + str.size() > size()) { // bounds check
			return size();
		}

		const wchar_t* loc = ::wcsstr(_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(wchar_t 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 wcstring& str, size_t i = npos) const {
		if (size() == 0) {
			return size();
		}
		const wchar_t 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 wchar_t 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 the start index
	/// \param n the number of wchar_tacters
	/// \return
	constexpr _wstring substring(size_t i, size_t n = npos) const {
		if (i >= size()) {
			return _wstring("");
		}
		n = fennec::min(n, size() - i);
		_wstring res;
		res._str.callocate(n + 1);
		fennec::wmemcpy(res.data(), _str + i, n);
		return res;
	}



// Modifiers ===========================================================================================================

	constexpr void resize(size_t n) {
		_str.creallocate(n + 1);
		_str[size()] = '\0';
	}

	constexpr _wstring operator+(wchar_t c) const {
		if (_str == nullptr) {
			return _wstring(1, c);
		}
		_wstring res;
		res._str.callocate(capacity() + 1);
		fennec::wmemcpy(res.data(), _str, size());
		res[size()] = c;
		return res;
	}

	friend constexpr _wstring operator+(wchar_t c, const _wstring& str) {
		_wstring res(1, c);
		return res += str;
	}

	constexpr _wstring operator+(const wcstring& cstr) const {
		if (_str == nullptr) {
			return _wstring(cstr);
		}
		_wstring res;
		res._str.callocate(size() + cstr.size() + 1);
		fennec::wmemcpy(res.data(), _str, size());
		fennec::wmemcpy(res.data() + size(), cstr, cstr.size());
		return res;
	}

	constexpr _wstring operator+(const _wstring& str) const {
		if (_str == nullptr) {
			return _wstring(str);
		}
		if (str.data() == nullptr) {
			return _wstring(*this);
		}
		_wstring res;
		res._str.callocate(size() + str.size() + 1);
		fennec::wmemcpy(res.data(), _str, size());
		fennec::wmemcpy(res.data() + size(), str.data(), str.size());
		return res;
	}

	constexpr _wstring& operator+=(wchar_t c) {
		if (_str == nullptr) {
			_str.callocate(2);
			_str[0] = c;
			return *this;
		}
		_str.creallocate(capacity() + 1);
		_str[size() - 1] = c;
		return *this;
	}

	constexpr _wstring& operator+=(const wcstring& cstr) {
		if (_str == nullptr) {
			return *this = cstr;
		}
		size_t middle = size();
		_str.creallocate(middle + cstr.size() + 1);
		fennec::wmemcpy(_str + middle, cstr, cstr.size());
		return *this;
	}

	constexpr _wstring& operator+=(const _wstring& str) {
		if (_str == nullptr) {
			return *this = str;
		}
		if (str.data() == nullptr) {
			return *this;
		}
		size_t middle = size();
		_str.creallocate(middle + str.size() + 1);
		fennec::wmemcpy(_str + middle, str.data(), str.size());
		return *this;
	}


private:
	alloc_t _str;
};

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

}


#endif // FENNEC_LANGPROC_wstringS_WSTRING_H
