/*
 * 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 defined(OX_USE_STDLIB)
#include <string>
#endif

#include "buffer.hpp"
#include "memops.hpp"
#include "strops.hpp"
#include "typetraits.hpp"
#include "vector.hpp"

namespace ox {

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

	public:
		constexpr BasicString() noexcept;

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

		constexpr BasicString(const char *str) noexcept;

		constexpr BasicString(const char8_t *str) noexcept;

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

		constexpr BasicString(const BasicString&) 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+=(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+=(const BasicString &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+(const BasicString &src) const noexcept;

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

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

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

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

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

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

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

		constexpr bool operator>=(const BasicString &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 bool beginsWith(const char *ending) const noexcept;

		[[nodiscard]]
		constexpr bool beginsWith(const BasicString &ending) const noexcept;

		[[nodiscard]]
		constexpr bool endsWith(const char *ending) const noexcept;

		[[nodiscard]]
		constexpr bool endsWith(const BasicString &ending) 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();
		}

#ifdef OX_USE_STDLIB
		[[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:
		template<std::size_t OtherSize>
		constexpr void set(const BasicString<OtherSize> &src) noexcept;

		constexpr void set(const char *str) noexcept;

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

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

template<std::size_t SmallStringSize>
constexpr BasicString<SmallStringSize>::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.size()) {
		m_buff[0] = 0;
	}
}

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

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

template<std::size_t SmallStringSize>
constexpr BasicString<SmallStringSize>::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>
constexpr BasicString<SmallStringSize>::BasicString(const BasicString &other) noexcept {
	m_buff = other.m_buff;
}

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

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

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

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

template<std::size_t SmallStringSize>
constexpr BasicString<SmallStringSize> &BasicString<SmallStringSize>::operator=(int64_t i) noexcept {
	char str[65] = {};
	ox_itoa(i, str);
	set(str);
	return *this;
}

template<std::size_t SmallStringSize>
constexpr BasicString<SmallStringSize> &BasicString<SmallStringSize>::operator=(uint64_t i) noexcept {
	char str[65] = {};
	ox_itoa(i, str);
	set(str);
	return *this;
}

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

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

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

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

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

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

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

template<std::size_t SmallStringSize>
constexpr BasicString<SmallStringSize> BasicString<SmallStringSize>::operator+(const char *str) const noexcept {
	const std::size_t strLen = ox_strlen(str);
	const auto currentLen = len();
	BasicString<SmallStringSize> 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>
constexpr BasicString<SmallStringSize> BasicString<SmallStringSize>::operator+(char *str) const noexcept {
	return *this + static_cast<const char*>(str);
}

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

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

template<std::size_t SmallStringSize>
constexpr BasicString<SmallStringSize> BasicString<SmallStringSize>::operator+(const BasicString &src) const noexcept {
	const std::size_t strLen = src.len();
	const auto currentLen = len();
	BasicString<SmallStringSize> 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>
constexpr bool BasicString<SmallStringSize>::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>
constexpr bool BasicString<SmallStringSize>::operator==(const BasicString &other) const noexcept {
	if (len() != other.len()) {
		return false;
	}
	bool retval = true;
	std::size_t i = 0;
	while (i < m_buff.size() && (m_buff[i] || other.m_buff[i])) {
		if (m_buff[i] != other.m_buff[i]) {
			retval = false;
			break;
		}
		i++;
	}
	return retval;
}

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

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

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

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

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

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

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

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

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

template<std::size_t SmallStringSize>
constexpr BasicString<SmallStringSize> BasicString<SmallStringSize>::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> out(size);
	const auto buff = out.data();
	memcpy(buff, src, size);
	buff[size] = 0;
	return out;
}

template<std::size_t SmallStringSize>
constexpr bool BasicString<SmallStringSize>::beginsWith(const char *beginning) const noexcept {
	const auto beginningLen = ox::min(ox_strlen(beginning), len());
	return ox_strncmp(data(), beginning, beginningLen) == 0;
}

template<std::size_t SmallStringSize>
constexpr bool BasicString<SmallStringSize>::beginsWith(const BasicString &beginning) const noexcept {
	const auto sz = ox::min(beginning.len(), len());;
	return ox_strncmp(data(), beginning.c_str(), sz) == 0;
}

template<std::size_t SmallStringSize>
constexpr bool BasicString<SmallStringSize>::endsWith(const char *ending) const noexcept {
	const auto endingLen = ox_strlen(ending);
	return len() >= endingLen && ox_strcmp(data() + (len() - endingLen), ending) == 0;
}

template<std::size_t SmallStringSize>
constexpr bool BasicString<SmallStringSize>::endsWith(const BasicString &ending) const noexcept {
	const auto endingLen = ending.len();
	return len() >= endingLen && ox_strcmp(data() + (len() - endingLen), ending.c_str()) == 0;
}

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

template<std::size_t SmallStringSize>
template<std::size_t OtherSize>
constexpr void BasicString<SmallStringSize>::set(const BasicString<OtherSize> &src) noexcept {
	std::size_t strBytes = src.bytes();
	if (strBytes > 1) {
		m_buff.resize(strBytes);
		memcpy(m_buff.data(), src.data(), strBytes);
		m_buff.back().value = 0;
	}
}

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

template<std::size_t SmallStringSize>
constexpr void BasicString<SmallStringSize>::set(const char8_t *str) noexcept {
	std::size_t strBytes = ox_strlen(str) + 1;
	if (strBytes > 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>;

}
