/*
 * Copyright 2015 - 2022 gary@drinkingtea.net
 *
 * 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

#if __has_include(<string>)
#include <string>
#endif

#include "algorithm.hpp"
#include "memops.hpp"
#include "serialize.hpp"
#include "stringview.hpp"
#include "strops.hpp"
#include "vector.hpp"

namespace ox {

template<std::size_t SmallStringSize_v>
class BasicString {
	private:
		Vector<char, SmallStringSize_v> m_buff;

	public:
		static constexpr auto SmallStringSize = SmallStringSize_v;
		constexpr BasicString() noexcept;

		constexpr explicit BasicString(std::size_t cap) noexcept;

		constexpr explicit BasicString(const char *str) noexcept;

		constexpr explicit BasicString(const char8_t *str) noexcept;

		constexpr BasicString(const char *str, std::size_t size) noexcept;

		constexpr explicit BasicString(CRStringView str) noexcept;

		constexpr explicit BasicString(BasicString const&) noexcept;

		constexpr BasicString(BasicString&&) noexcept;

		[[nodiscard]]
		constexpr auto begin() noexcept {
			return m_buff.begin();
		}

		[[nodiscard]]
		constexpr auto end() noexcept {
			return m_buff.end();
		}

		[[nodiscard]]
		constexpr auto begin() const noexcept {
			return m_buff.begin();
		}

		[[nodiscard]]
		constexpr auto end() const noexcept {
			return m_buff.end();
		}

		[[nodiscard]]
		constexpr auto cbegin() const noexcept {
			return m_buff.begin();
		}

		[[nodiscard]]
		constexpr auto cend() const noexcept {
			return m_buff.end();
		}

		[[nodiscard]]
		constexpr auto rbegin() noexcept {
			return m_buff.rbegin();
		}

		[[nodiscard]]
		constexpr auto rend() noexcept {
			return m_buff.rend();
		}

		[[nodiscard]]
		constexpr auto rbegin() const noexcept {
			return m_buff.rbegin();
		}

		[[nodiscard]]
		constexpr auto rend() const noexcept {
			return m_buff.rend();
		}

		[[nodiscard]]
		constexpr auto crbegin() const noexcept {
			return m_buff.rbegin();
		}

		[[nodiscard]]
		constexpr auto crend() const noexcept {
			return m_buff.rend();
		}

		constexpr BasicString &operator=(const char *str) noexcept;

		constexpr BasicString &operator=(char c) noexcept;

		constexpr BasicString &operator=(int i) noexcept;

		constexpr BasicString &operator=(int64_t i) noexcept;

		constexpr BasicString &operator=(uint64_t i) noexcept;

		constexpr BasicString &operator=(const BasicString &src) noexcept;

		constexpr BasicString &operator=(BasicString &&src) noexcept;

		constexpr BasicString &operator=(CRStringView src) noexcept;

		constexpr BasicString &operator+=(const char *str) noexcept;

		constexpr BasicString &operator+=(char *str) noexcept;

		constexpr BasicString &operator+=(char c) noexcept;

		constexpr BasicString &operator+=(Integer_c auto i) noexcept;

		constexpr BasicString &operator+=(StringView src) noexcept;

		constexpr BasicString &operator+=(BasicString const&src) noexcept;

		constexpr BasicString operator+(const char *str) const noexcept;

		constexpr BasicString operator+(char *str) const noexcept;

		constexpr BasicString operator+(char c) const noexcept;

		constexpr BasicString operator+(Integer_c auto i) const noexcept;

		constexpr BasicString operator+(CRStringView src) const noexcept;

		constexpr BasicString operator+(BasicString const&src) const noexcept;

		constexpr bool operator==(const char *other) const noexcept;

		constexpr bool operator==(OxString_c auto const&other) const noexcept;

		constexpr bool operator!=(const char *other) const noexcept;

		constexpr bool operator!=(OxString_c auto const&other) const noexcept;

		constexpr bool operator<(BasicString const&other) const noexcept;

		constexpr bool operator>(BasicString const&other) const noexcept;

		constexpr bool operator<=(BasicString const&other) const noexcept;

		constexpr bool operator>=(BasicString const&other) const noexcept;

		constexpr char operator[](std::size_t i) const noexcept;

		constexpr char &operator[](std::size_t i) noexcept;

		constexpr Error append(const char *str, std::size_t strLen) noexcept {
			auto currentLen = len();
			m_buff.resize(m_buff.size() + strLen);
			ox_memcpy(&m_buff[currentLen], str, strLen);
			// make sure last element is a null terminator
			m_buff[currentLen + strLen] = 0;
			// this can't fail, but it returns an Error to match BString::append
			return OxError(0);
		}

		[[nodiscard]]
		constexpr BasicString substr(std::size_t pos) const noexcept;

		[[nodiscard]]
		constexpr BasicString substr(std::size_t begin, std::size_t end) const noexcept;

		[[nodiscard]]
		constexpr const char *data() const noexcept {
			return m_buff.data();
		}

		[[nodiscard]]
		constexpr char *data() noexcept {
			return m_buff.data();
		}

		[[nodiscard]]
		constexpr const char *c_str() const noexcept {
			return static_cast<const char*>(m_buff.data());
		}

		[[nodiscard]]
		inline explicit operator const char*() const {
			return c_str();
		}

#if __has_include(<string>)
		[[nodiscard]]
		inline std::string toStdString() const {
			return c_str();
		}

		[[nodiscard]]
		inline explicit operator std::string() const {
			return c_str();
		}
#endif

		/**
		 * Returns the number of characters in this string.
		 */
		[[nodiscard]]
		constexpr std::size_t len() const noexcept;

		/**
		 * Returns the number of bytes used for this string.
		 */
		[[nodiscard]]
		constexpr std::size_t bytes() const noexcept;

	private:
		constexpr void set(CRStringView str) noexcept;

		constexpr void set(const char8_t *str) noexcept;
};

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v>::BasicString() noexcept {
	if (!m_buff.empty()) {
		m_buff[0] = 0;
	} else {
		m_buff.push_back(0);
	}
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v>::BasicString(std::size_t cap) noexcept: m_buff(cap + 1) {
	// GCC complains if you don't do this pretty unnecessary size check
	if (!m_buff.empty()) {
		m_buff[0] = 0;
	}
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v>::BasicString(const char *str) noexcept {
	if (!m_buff.empty()) {
		m_buff[0] = 0;
	} else {
		m_buff.push_back(0);
	}
	set(str);
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v>::BasicString(const char8_t *str) noexcept {
	if (!m_buff.empty()) {
		m_buff[0] = 0;
	} else {
		m_buff.push_back(0);
	}
	set(str);
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v>::BasicString(const char *str, std::size_t size) noexcept {
	m_buff.resize(size + 1);
	memcpy(m_buff.data(), str, size);
	m_buff[size] = 0;
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v>::BasicString(CRStringView str) noexcept {
	if (m_buff.empty()) {
		m_buff.push_back(0);
	} else {
		m_buff[0] = 0;
	}
	set(str);
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v>::BasicString(const BasicString &other) noexcept {
	m_buff = other.m_buff;
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v>::BasicString(BasicString &&other) noexcept: m_buff(std::move(other.m_buff)) {
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> &BasicString<SmallStringSize_v>::operator=(const char *str) noexcept {
	set(str);
	return *this;
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> &BasicString<SmallStringSize_v>::operator=(char c) noexcept {
	ox::Array<char, 2> str{c, 0};
	set(str.data());
	return *this;
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> &BasicString<SmallStringSize_v>::operator=(int i) noexcept {
	this->operator=(static_cast<int64_t>(i));
	return *this;
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> &BasicString<SmallStringSize_v>::operator=(int64_t i) noexcept {
	ox::Array<char, 65> str{};
	ox_itoa(i, str.data());
	set(str.data());
	return *this;
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> &BasicString<SmallStringSize_v>::operator=(uint64_t i) noexcept {
	ox::Array<char, 65> str{};
	ox_itoa(i, str.data());
	set(str.data());
	return *this;
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> &BasicString<SmallStringSize_v>::operator=(const BasicString &src) noexcept {
	if (this != &src) {
		set(src);
	}
	return *this;
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> &BasicString<SmallStringSize_v>::operator=(BasicString &&src) noexcept {
	if (this != &src) {
		m_buff = std::move(src.m_buff);
	}
	return *this;
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> &BasicString<SmallStringSize_v>::operator=(CRStringView src) noexcept {
	set(src);
	return *this;
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> &BasicString<SmallStringSize_v>::operator+=(const char *str) noexcept {
	std::size_t strLen = ox_strlen(str);
	oxIgnoreError(append(str, strLen));
	return *this;
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> &BasicString<SmallStringSize_v>::operator+=(char *str) noexcept {
	return *this += static_cast<const char*>(str);
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> &BasicString<SmallStringSize_v>::operator+=(char c) noexcept {
	const char str[] = {c, 0};
	return this->operator+=(str);
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> &BasicString<SmallStringSize_v>::operator+=(Integer_c auto i) noexcept {
	char str[65] = {};
	ox_itoa(i, str);
	return this->operator+=(str);
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> &BasicString<SmallStringSize_v>::operator+=(StringView s) noexcept {
	std::size_t strLen = s.bytes();
	oxIgnoreError(append(s.data(), strLen));
	return *this;
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> &BasicString<SmallStringSize_v>::operator+=(BasicString const&src) noexcept {
	oxIgnoreError(append(src.c_str(), src.len()));
	return *this;
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> BasicString<SmallStringSize_v>::operator+(const char *str) const noexcept {
	const std::size_t strLen = ox_strlen(str);
	const auto currentLen = len();
	BasicString<SmallStringSize_v> cpy(currentLen + strLen);
	cpy.m_buff.resize(m_buff.size() + strLen);
	memcpy(&cpy.m_buff[0], m_buff.data(), currentLen);
	memcpy(&cpy.m_buff[currentLen], str, strLen);
	// make sure last element is a null terminator
	cpy.m_buff[currentLen + strLen] = 0;
	return cpy;
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> BasicString<SmallStringSize_v>::operator+(char *str) const noexcept {
	return *this + static_cast<const char*>(str);
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> BasicString<SmallStringSize_v>::operator+(char c) const noexcept {
	const char str[] = {c, 0};
	return *this + str;
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> BasicString<SmallStringSize_v>::operator+(Integer_c auto i) const noexcept {
	char str[65] = {};
	ox_itoa(i, str);
	return *this + str;
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> BasicString<SmallStringSize_v>::operator+(CRStringView src) const noexcept {
	const std::size_t strLen = src.len();
	const auto currentLen = len();
	BasicString<SmallStringSize_v> cpy(currentLen + strLen);
	cpy.m_buff.resize(m_buff.size() + strLen);
	memcpy(&cpy.m_buff[0], m_buff.data(), currentLen);
	memcpy(&cpy.m_buff[currentLen], src.data(), strLen + 1);
	return cpy;
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> BasicString<SmallStringSize_v>::operator+(BasicString const&src) const noexcept {
	const std::size_t strLen = src.len();
	const auto currentLen = len();
	BasicString<SmallStringSize_v> cpy(currentLen + strLen);
	cpy.m_buff.resize(m_buff.size() + strLen);
	memcpy(&cpy.m_buff[0], m_buff.data(), currentLen);
	memcpy(&cpy.m_buff[currentLen], src.data(), strLen + 1);
	return cpy;
}

template<std::size_t SmallStringSize_v>
constexpr bool BasicString<SmallStringSize_v>::operator==(const char *other) const noexcept {
	bool retval = true;
	for (auto i = 0u; i < m_buff.size() && (m_buff[i] || other[i]); ++i) {
		if (m_buff[i] != other[i]) {
			retval = false;
			break;
		}
	}
	return retval;
}

template<std::size_t SmallStringSize_v>
constexpr bool BasicString<SmallStringSize_v>::operator==(OxString_c auto const&other) const noexcept {
	return ox::StringView(*this) == ox::StringView(other);
}

template<std::size_t SmallStringSize_v>
constexpr bool BasicString<SmallStringSize_v>::operator!=(const char *other) const noexcept {
	return !operator==(other);
}

template<std::size_t SmallStringSize_v>
constexpr bool BasicString<SmallStringSize_v>::operator!=(OxString_c auto const&other) const noexcept {
	return !operator==(other);
}

template<std::size_t SmallStringSize_v>
constexpr bool BasicString<SmallStringSize_v>::operator<(BasicString const&other) const noexcept {
	return ox_strcmp(c_str(), other.c_str()) < 0;
}

template<std::size_t SmallStringSize_v>
constexpr bool BasicString<SmallStringSize_v>::operator>(BasicString const&other) const noexcept {
	return ox_strcmp(c_str(), other.c_str()) > 0;
}

template<std::size_t SmallStringSize_v>
constexpr bool BasicString<SmallStringSize_v>::operator<=(BasicString const&other) const noexcept {
	return ox_strcmp(c_str(), other.c_str()) < 1;
}

template<std::size_t SmallStringSize_v>
constexpr bool BasicString<SmallStringSize_v>::operator>=(BasicString const&other) const noexcept {
	return ox_strcmp(c_str(), other.c_str()) > -1;
}

template<std::size_t SmallStringSize_v>
constexpr char BasicString<SmallStringSize_v>::operator[](std::size_t i) const noexcept {
	return m_buff[i];
}

template<std::size_t SmallStringSize_v>
constexpr char &BasicString<SmallStringSize_v>::operator[](std::size_t i) noexcept {
	return m_buff[i];
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> BasicString<SmallStringSize_v>::substr(std::size_t pos) const noexcept {
	return BasicString(m_buff.data() + pos, m_buff.size() - pos);
}

template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> BasicString<SmallStringSize_v>::substr(std::size_t begin, std::size_t end) const noexcept {
	const auto src = m_buff.data() + begin;
	const auto size = end - begin;
	BasicString<SmallStringSize_v> out(size);
	const auto buff = out.data();
	memcpy(buff, src, size);
	buff[size] = 0;
	return out;
}

template<std::size_t SmallStringSize_v>
constexpr std::size_t BasicString<SmallStringSize_v>::bytes() const noexcept {
	std::size_t i;
	for (i = 0; i < m_buff.size() && m_buff[i]; ++i);
	return i + 1; // add one for null terminator
}

template<std::size_t SmallStringSize_v>
constexpr std::size_t BasicString<SmallStringSize_v>::len() const noexcept {
	std::size_t length = 0;
	for (const auto c : m_buff) {
		const auto b = static_cast<uint8_t>(c);
		if (b) {
			// normal ASCII character or start of UTF-8 character
			if ((b & 128) == 0 || (b & (256 << 6)) == (256 << 6)) {
				++length;
			}
		} else {
			break;
		}
	}
	return length;
}

template<std::size_t SmallStringSize_v>
constexpr void BasicString<SmallStringSize_v>::set(CRStringView str) noexcept {
	std::size_t const strBytes = str.bytes();
	m_buff.resize(strBytes + 1);
	copy_n(str.data(), strBytes, m_buff.data());
	*m_buff.back().value = 0;
}

template<std::size_t SmallStringSize_v>
constexpr void BasicString<SmallStringSize_v>::set(const char8_t *str) noexcept {
	std::size_t strBytes = ox_strlen(str) + 1;
	m_buff.resize(strBytes);
	memcpy(m_buff.data(), str, strBytes);
	*m_buff.back().value = 0;
}

extern template class BasicString<8>;

using String = BasicString<8>;
using CRString = String const&;


[[nodiscard]]
constexpr ox::String toString(ox::CRStringView sv) noexcept {
	return ox::String(sv);
}


template<typename PlatSpec, std::size_t SmallStringSize_v>
[[nodiscard]]
constexpr auto sizeOf(const ox::BasicString<SmallStringSize_v>*) noexcept {
	VectorMemMap<PlatSpec> v{.smallVecSize = SmallStringSize_v};
	return sizeOf<PlatSpec>(&v);
}

template<typename PlatSpec, std::size_t SmallStringSize_v>
[[nodiscard]]
constexpr auto alignOf(const ox::BasicString<SmallStringSize_v>&) noexcept {
	VectorMemMap<PlatSpec> v{.smallVecSize = SmallStringSize_v};
	return alignOf<PlatSpec>(&v);
}

}
