/*
 * Copyright (c) 2017-2023 zhllxt
 *
 * author   : zhllxt
 * qq       : 37792738
 * email    : 37792738@qq.com
 * 
 * https://github.com/Shot511/strutil
 * 
 */

#ifndef __ASIO2_STRING_HPP__
#define __ASIO2_STRING_HPP__

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
#pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)

#include <asio2/base/detail/push_options.hpp>

#if defined(__GNUC__) || defined(__GNUG__)
#  pragma GCC diagnostic ignored "-Warray-bounds"
#endif

#include <cstdint>
#include <cstdarg>
#include <cstdio>
#include <cwchar>
#include <climits>
#include <cctype>
#include <cstring>

#include <string>
#include <string_view>
#include <vector>
#include <type_traits>
#include <algorithm>
#include <sstream>
#include <regex>
#include <map>

#include <asio2/base/detail/type_traits.hpp>

namespace asio2
{
	/**
	 * @brief Converts any datatype into std::basic_string.
	 * @tparam T
	 * @param v - will be converted into std::basic_string.
	 * @return Converted value as std::basic_string.
	 */
	template<typename T>
	inline auto to_basic_string(T&& v)
	{
		using type  = typename detail::remove_cvref_t<T>;
		using CharT = typename detail::char_type<type>::type;

		if /**/ constexpr (detail::is_string_view_v<type>)
		{
			return std::basic_string<CharT>{ v.data(), v.size() };
		}
		else if constexpr (detail::is_string_v<type>)
		{
			return std::forward<T>(v);
		}
		else if constexpr (detail::is_char_v<type>)
		{
			return std::basic_string<CharT>{ 1, v };
		}
		else if constexpr (std::is_integral_v<type>)
		{
			std::basic_string<CharT> r;
			std::string s = std::to_string(v);
			for (auto c : s)
			{
				r += static_cast<CharT>(c);
			}
			return r;
		}
		else if constexpr (std::is_floating_point_v<type>)
		{
			std::basic_string<CharT> r;
			std::string s = std::to_string(v);
			for (auto c : s)
			{
				r += static_cast<CharT>(c);
			}
			return r;
		}
		else if constexpr (detail::is_char_pointer_v<type>)
		{
			if (v)
				return std::basic_string<CharT>{ v };
			else
				return std::basic_string<CharT>{ };
		}
		else if constexpr (detail::is_char_array_v<type>)
		{
			return std::basic_string<CharT>{ reinterpret_cast<const CharT*>(v) };
		}
		else
		{
			std::basic_stringstream<CharT> ss;
			ss << std::forward<T>(v);
			return ss.str();
		}
	}

	/**
	 * @brief Converts any datatype into std::basic_string_view.
	 * @tparam T
	 * @param v - will be converted into std::basic_string_view.
	 * @return Converted value as std::basic_string_view.
	 */
	template<typename T>
	inline auto to_basic_string_view(const T& v)
	{
		using type  = typename detail::remove_cvref_t<T>;
		using CharT = typename detail::char_type<type>::type;

		if /**/ constexpr (detail::is_string_view_v<type>)
		{
			return v;
		}
		else if constexpr (detail::is_string_v<type>)
		{
			return std::basic_string_view<CharT>{ v };
		}
		else if constexpr (detail::is_char_v<type>)
		{
			return std::basic_string_view<CharT>{ std::addressof(v), 1 };
		}
		else if constexpr (detail::is_char_pointer_v<type>)
		{
			return (v ? std::basic_string_view<CharT>{ v } : std::basic_string_view<CharT>{});
		}
		else if constexpr (detail::is_char_array_v<type>)
		{
			return std::basic_string_view<CharT>{ reinterpret_cast<const CharT*>(v) };
		}
		else
		{
			return std::basic_string_view<CharT>{ v };
		}
	}

	/**
	 * @brief Converts any datatype into std::string.
	 * @tparam T
	 * @param v - will be converted into std::string.
	 * @return Converted value as std::string.
	 */
	template<typename T>
	inline std::string to_string(T&& v)
	{
		using type = detail::remove_cvref_t<T>;

		std::string s;

		if /**/ constexpr (std::is_same_v<std::string_view, type>)
		{
			s = { v.data(), v.size() };
		}
		else if constexpr (std::is_same_v<std::string, type>)
		{
			s = std::forward<T>(v);
		}
		else if constexpr (std::is_integral_v<type>)
		{
			s = std::to_string(v);
		}
		else if constexpr (std::is_floating_point_v<type>)
		{
			s = std::to_string(v);
		}
		else if constexpr (detail::is_char_pointer_v<type>)
		{
			if (v) s = v;
		}
		else if constexpr (detail::is_char_array_v<type>)
		{
			s = std::forward<T>(v);
		}
		else
		{
			std::stringstream ss;
			ss << std::forward<T>(v);
			s = ss.str();
		}
		return s;
	}

	/**
	 * @brief Converts any datatype into std::string_view.
	 * @tparam T
	 * @param v - will be converted into std::string_view.
	 * @return Converted value as std::string_view.
	 */
	template<typename T>
	inline std::string_view to_string_view(const T& v)
	{
		using type = detail::remove_cvref_t<T>;

		if /**/ constexpr (std::is_same_v<std::string_view, type>)
		{
			return std::string_view{ v };
		}
		else if constexpr (std::is_same_v<std::string, type>)
		{
			return std::string_view{ v };
		}
		else if constexpr (detail::is_char_v<type>)
		{
			return std::string_view{ std::addressof(v), 1 };
		}
		else if constexpr (detail::is_char_pointer_v<type>)
		{
			return (v ? std::string_view{ v } : std::string_view{});
		}
		else if constexpr (detail::is_char_array_v<type>)
		{
			return std::string_view{ v };
		}
		else
		{
			return std::string_view{ v };
		}
	}

	/**
	 * @brief Converts iterator range into std::string_view.
	 * @tparam T
	 * @param v - will be converted into std::string_view.
	 * @return Converted value as std::string_view.
	 */
	template<typename Iterator>
	inline std::string_view to_string_view(const Iterator& first, const Iterator& last)
	{
		using iter_type = typename detail::remove_cvref_t<Iterator>;
		using diff_type = typename std::iterator_traits<iter_type>::difference_type;

		diff_type n = std::distance(first, last);

		if (n <= static_cast<diff_type>(0))
		{
			return std::string_view{};
		}

		if constexpr (std::is_pointer_v<iter_type>)
		{
			return { first, static_cast<std::string_view::size_type>(n) };
		}
		else
		{
			return { first.operator->(), static_cast<std::string_view::size_type>(n) };
		}
	}

	/**
	 * @brief Converts any datatype into a numeric.
	 * @tparam IntegerType - integer or floating
	 * @param v - will be converted into numeric.
	 * @return Converted value as numeric.
	 */
	template<typename IntegerType, typename T>
	inline IntegerType to_numeric(T&& v) noexcept
	{
		using type = detail::remove_cvref_t<T>;

		if /**/ constexpr (std::is_integral_v<type>)
		{
			return static_cast<IntegerType>(v);
		}
		else if constexpr (std::is_floating_point_v<type>)
		{
			return static_cast<IntegerType>(v);
		}
		else
		{
			std::string s = asio2::to_string(std::forward<T>(v));
			int rx = 10;
			if (s.size() >= std::size_t(2) && s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
				rx = 16;
			return static_cast<IntegerType>(std::strtoull(s.data(), nullptr, rx));
		}
	}

	/**
	 * @brief Converts std::string into any datatype.
	 *        Datatype must support << operator.
	 * @tparam T
	 * @param str - std::string that will be converted into datatype T.
	 * @return Variable of datatype T.
	 */
	template<typename T>
	inline T string_to(const std::string& str)
	{
		T result{};
		std::istringstream(str) >> result;

		return result;
	}

	/**
	 * @brief Returns `true` if two strings are equal, using a case-insensitive comparison.
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>
	>
	inline bool iequals(
		std::basic_string_view<CharT, Traits> str1,
		std::basic_string_view<CharT, Traits> str2) noexcept
	{
		auto n = str1.size();
		if (str2.size() != n)
			return false;
		auto p1 = str1.data();
		auto p2 = str2.data();
		CharT a, b;
		// fast loop
		while (n--)
		{
			a = *p1++;
			b = *p2++;
			if (a != b)
				goto slow;
		}
		return true;
	slow:
		do
		{
			if (std::tolower(a) != std::tolower(b))
				return false;
			a = *p1++;
			b = *p2++;
		} while (n--);
		return true;
	}

	/**
	 * @brief Returns `true` if two strings are equal, using a case-insensitive comparison.
	 */
	template<class String1, class String2>
	inline bool iequals(const String1& str1, const String2& str2) noexcept
	{
		return asio2::iequals(asio2::to_basic_string_view(str1), asio2::to_basic_string_view(str2));
	}

	/**
	 * @brief Compares two std::strings ignoring their case (lower/upper).
	 * @param str1 - string to compare
	 * @param str2 - string to compare
	 * @return True if str1 and str2 are equal, false otherwise.
	 */
	template<class String1, class String2>
	inline bool compare_ignore_case(const String1& str1, const String2& str2)
	{
		return asio2::iequals(str1, str2);
	}

	/**
	 * std::string format
	 */
	inline std::string formatv(const char * format, va_list args)
	{
		std::string str;

		if (format && *format)
		{
			// under windows and linux system,std::vsnprintf(nullptr, 0, format, args)
			// can get the need buffer len for the output,
			va_list args_copy;

			va_copy(args_copy, args);
			int len = std::vsnprintf(nullptr, 0, format, args_copy);

			if (len > 0)
			{
				str.resize(len);

				va_copy(args_copy, args);
				std::vsprintf((char*)str.data(), format, args_copy);
			}
		}

		return str;
	}

	/**
	 * std::wstring format
	 */
	inline std::wstring formatv(const wchar_t * format, va_list args)
	{
		std::wstring str;

		if (format && *format)
		{
			va_list args_copy;

			while (true)
			{
				str.resize(str.capacity());

				va_copy(args_copy, args);

				// if provided buffer size is less than required size,vswprintf will return -1
				// so if len equal -1,we increase the buffer size again, and has to use a loop 
				// to get the correct output buffer len,
				int len = std::vswprintf((wchar_t*)(&str[0]), str.size(), format, args_copy);
				if (len == -1)
					str.reserve(str.capacity() * 2);
				else
				{
					str.resize(len);
					break;
				}
			}
		}

		return str;
	}

	/**
	 * std::string format
	 */
	inline std::string format(const char * format, ...)
	{
		std::string str;

		if (format && *format)
		{
			// under windows and linux system,std::vsnprintf(nullptr, 0, format, args)
			// can get the need buffer len for the output,
			va_list args;
			va_start(args, format);

			str = formatv(format, args);

			va_end(args);
		}

		return str;
	}

	/**
	 * std::wstring format
	 */
	inline std::wstring format(const wchar_t * format, ...)
	{
		std::wstring str;

		if (format && *format)
		{
			va_list args;
			va_start(args, format);

			str = formatv(format, args);

			va_end(args);
		}

		return str;
	}

	/**
	 * @brief Converts string to lower case.
	 * @param str - string that needs to be converted.
	 * @return Lower case input string.
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator>& to_lower(std::basic_string<CharT, Traits, Allocator>& str)
	{
		std::transform(str.begin(), str.end(), str.begin(), [](CharT c) -> CharT
		{
			return static_cast<CharT>(std::tolower(c));
		});

		return str;
	}

	/**
	 * @brief Converts string to lower case.
	 * @param str - string that needs to be converted.
	 * @return Lower case input string.
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> to_lower(std::basic_string<CharT, Traits, Allocator>&& str)
	{
		std::transform(str.begin(), str.end(), str.begin(), [](CharT c) -> CharT
		{
			return static_cast<CharT>(std::tolower(c));
		});

		return std::move(str);
	}

	/**
	 * @brief Converts string to upper case.
	 * @param str - string that needs to be converted.
	 * @return Upper case input string.
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator>& to_upper(std::basic_string<CharT, Traits, Allocator>& str)
	{
		std::transform(str.begin(), str.end(), str.begin(), [](CharT c) -> CharT
		{
			return static_cast<CharT>(std::toupper(c));
		});

		return str;
	}

	/**
	 * @brief Converts string to upper case.
	 * @param str - string that needs to be converted.
	 * @return Upper case input string.
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> to_upper(std::basic_string<CharT, Traits, Allocator>&& str)
	{
		std::transform(str.begin(), str.end(), str.begin(), [](CharT c) -> CharT
		{
			return static_cast<CharT>(std::toupper(c));
		});

		return std::move(str);
	}

	/**
	 * @brief Converts the first character of a string to uppercase letter and lowercases all other characters, if any.
	 * @param str - input string to be capitalized.
	 * @return A string with the first letter capitalized and all other characters lowercased.
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator>& capitalize(std::basic_string<CharT, Traits, Allocator>& str)
	{
		asio2::to_lower(str);

		if (!str.empty())
		{
			str.front() = static_cast<CharT>(std::toupper(str.front()));
		}

		return str;
	}

	/**
	 * @brief Converts the first character of a string to uppercase letter and lowercases all other characters, if any.
	 * @param str - input string to be capitalized.
	 * @return A string with the first letter capitalized and all other characters lowercased.
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> capitalize(std::basic_string<CharT, Traits, Allocator>&& str)
	{
		asio2::to_lower(str);

		if (!str.empty())
		{
			str.front() = static_cast<CharT>(std::toupper(str.front()));
		}

		return std::move(str);
	}

	/**
	 * @brief Converts only the first character of a string to uppercase letter, all other characters stay unchanged.
	 * @param str - input string to be modified.
	 * @return A string with the first letter capitalized. All other characters stay unchanged.
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator>& capitalize_first_char(
		std::basic_string<CharT, Traits, Allocator>& str)
	{
		if (!str.empty())
		{
			str.front() = static_cast<CharT>(std::toupper(str.front()));
		}

		return str;
	}

	/**
	 * @brief Converts only the first character of a string to uppercase letter, all other characters stay unchanged.
	 * @param str - input string to be modified.
	 * @return A string with the first letter capitalized. All other characters stay unchanged.
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> capitalize_first_char(
		std::basic_string<CharT, Traits, Allocator>&& str)
	{
		if (!str.empty())
		{
			str.front() = static_cast<CharT>(std::toupper(str.front()));
		}

		return std::move(str);
	}

	/**
	 * @brief Checks if input string str contains specified substring.
	 * @param str - string to be checked.
	 * @param substring - searched substring or character.
	 * @return True if substring or character was found in str, false otherwise.
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>
	>
	inline bool contains(std::basic_string_view<CharT, Traits> str, std::basic_string_view<CharT, Traits> substring)
	{
		return str.find(substring) != std::string_view::npos;
	}

	/**
	 * @brief Checks if input string str contains specified substring.
	 * @param str - string to be checked.
	 * @param substring - searched substring or character.
	 * @return True if substring was found in str, false otherwise.
	 */
	template<class String1, class String2>
	inline bool contains(const String1& str, const String2& substring)
	{
		return asio2::contains(asio2::to_basic_string_view(str), asio2::to_basic_string_view(substring));
	}

	/**
	 * @brief trim each space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator>& trim_all(std::basic_string<CharT, Traits, Allocator>& str)
	{
		// https://zh.cppreference.com/w/cpp/algorithm/remove
		str.erase(std::remove_if(str.begin(), str.end(), [](int x) {return std::isspace(x); }), str.end());
		return str;
	}

	/**
	 * @brief trim each space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> trim_all(std::basic_string<CharT, Traits, Allocator>&& str)
	{
		// https://zh.cppreference.com/w/cpp/algorithm/remove
		str.erase(std::remove_if(str.begin(), str.end(), [](int x) {return std::isspace(x); }), str.end());
		return std::move(str);
	}

	/**
	 * @brief trim left space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator>& trim_left(std::basic_string<CharT, Traits, Allocator>& str)
	{
		str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](int ch) { return !std::isspace(ch); }));
		return str;
	}

	/**
	 * @brief trim left space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> trim_left(std::basic_string<CharT, Traits, Allocator>&& str)
	{
		str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](int ch) { return !std::isspace(ch); }));
		return std::move(str);
	}

	/**
	 * @brief trim left space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator>& ltrim(std::basic_string<CharT, Traits, Allocator>& str)
	{
		return asio2::trim_left(str);
	}

	/**
	 * @brief trim left space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> ltrim(std::basic_string<CharT, Traits, Allocator>&& str)
	{
		return asio2::trim_left(std::move(str));
	}

	/**
	 * @brief trim right space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator>& trim_right(std::basic_string<CharT, Traits, Allocator>& str)
	{
		str.erase(std::find_if(str.rbegin(), str.rend(), [](int ch) { return !std::isspace(ch); }).base(), str.end());
		return str;
	}

	/**
	 * @brief trim right space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> trim_right(std::basic_string<CharT, Traits, Allocator>&& str)
	{
		str.erase(std::find_if(str.rbegin(), str.rend(), [](int ch) { return !std::isspace(ch); }).base(), str.end());
		return std::move(str);
	}

	/**
	 * @brief trim right space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator>& rtrim(std::basic_string<CharT, Traits, Allocator>& str)
	{
		return asio2::trim_right(str);
	}

	/**
	 * @brief trim right space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> rtrim(std::basic_string<CharT, Traits, Allocator>&& str)
	{
		return asio2::trim_right(std::move(str));
	}

	/**
	 * @brief trim left and right space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator>& trim_both(std::basic_string<CharT, Traits, Allocator>& str)
	{
		return trim_right(trim_left(str));
	}

	/**
	 * @brief trim left and right space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> trim_both(std::basic_string<CharT, Traits, Allocator>&& str)
	{
		return trim_right(trim_left(std::move(str)));
	}

	/**
	 * @brief trim left and right space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator>& trim(std::basic_string<CharT, Traits, Allocator>& str)
	{
		return asio2::trim_both(str);
	}

	/**
	 * @brief trim left and right space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> trim(std::basic_string<CharT, Traits, Allocator>&& str)
	{
		return asio2::trim_both(std::move(str));
	}

	/**
	 * @brief Trims white spaces from the left side of string.
	 * @param str - input string to remove white spaces from.
	 * @return Copy of input str with trimmed white spaces.
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> trim_left_copy(std::basic_string<CharT, Traits, Allocator> str)
	{
		return asio2::trim_left(std::move(str));
	}

	/**
	 * @brief Trims white spaces from the left side of string.
	 * @param str - input string to remove white spaces from.
	 * @return Copy of input str with trimmed white spaces.
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> ltrim_copy(std::basic_string<CharT, Traits, Allocator> str)
	{
		return asio2::trim_left(std::move(str));
	}

	/**
	  * @brief Trims white spaces from the right side of string.
	  * @param str - input string to remove white spaces from.
	  * @return Copy of input str with trimmed white spaces.
	  */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> trim_right_copy(std::basic_string<CharT, Traits, Allocator> str)
	{
		return asio2::trim_right(std::move(str));
	}

	/**
	  * @brief Trims white spaces from the right side of string.
	  * @param str - input string to remove white spaces from.
	  * @return Copy of input str with trimmed white spaces.
	  */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> rtrim_copy(std::basic_string<CharT, Traits, Allocator> str)
	{
		return asio2::trim_right(std::move(str));
	}

	/**
	  * @brief Trims white spaces from the both sides of string.
	  * @param str - input string to remove white spaces from.
	  * @return Copy of input str with trimmed white spaces.
	  */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> trim_copy(std::basic_string<CharT, Traits, Allocator> str)
	{
		return asio2::trim(std::move(str));
	}

	/**
	 * @brief trim left space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>
	>
	inline std::basic_string_view<CharT, Traits>& trim_left(std::basic_string_view<CharT, Traits>& str)
	{
		if (str.empty())
			return str;
		using size_type = typename std::basic_string_view<CharT, Traits>::size_type;
		size_type pos = 0;
		for (; pos < str.size(); ++pos)
		{
			if (!std::isspace(static_cast<unsigned char>(str[pos])))
				break;
		}
		str.remove_prefix(pos);
		return str;
	}

	/**
	 * @brief trim left space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>
	>
	inline std::basic_string_view<CharT, Traits> trim_left(std::basic_string_view<CharT, Traits>&& str)
	{
		if (str.empty())
			return std::move(str);
		using size_type = typename std::basic_string_view<CharT, Traits>::size_type;
		size_type pos = 0;
		for (; pos < str.size(); ++pos)
		{
			if (!std::isspace(static_cast<unsigned char>(str[pos])))
				break;
		}
		str.remove_prefix(pos);
		return std::move(str);
	}

	/**
	 * @brief trim left space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>
	>
	inline std::basic_string_view<CharT, Traits>& ltrim(std::basic_string_view<CharT, Traits>& str)
	{
		return asio2::trim_left(str);
	}

	/**
	 * @brief trim left space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>
	>
	inline std::basic_string_view<CharT, Traits> ltrim(std::basic_string_view<CharT, Traits>&& str)
	{
		return asio2::trim_left(std::move(str));
	}

	/**
	 * @brief trim right space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>
	>
	inline std::basic_string_view<CharT, Traits>& trim_right(std::basic_string_view<CharT, Traits>& str)
	{
		if (str.empty())
			return str;
		using size_type = typename std::basic_string_view<CharT, Traits>::size_type;
		size_type pos = str.size() - 1;
		for (; pos != size_type(-1); pos--)
		{
			if (!std::isspace(static_cast<unsigned char>(str[pos])))
				break;
		}
		str.remove_suffix(str.size() - pos - 1);
		return str;
	}

	/**
	 * @brief trim right space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>
	>
	inline std::basic_string_view<CharT, Traits> trim_right(std::basic_string_view<CharT, Traits>&& str)
	{
		if (str.empty())
			return std::move(str);
		using size_type = typename std::basic_string_view<CharT, Traits>::size_type;
		size_type pos = str.size() - 1;
		for (; pos != size_type(-1); pos--)
		{
			if (!std::isspace(static_cast<unsigned char>(str[pos])))
				break;
		}
		str.remove_suffix(str.size() - pos - 1);
		return std::move(str);
	}

	/**
	 * @brief trim right space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>
	>
	inline std::basic_string_view<CharT, Traits>& rtrim(std::basic_string_view<CharT, Traits>& str)
	{
		return asio2::trim_right(str);
	}

	/**
	 * @brief trim right space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>
	>
	inline std::basic_string_view<CharT, Traits> rtrim(std::basic_string_view<CharT, Traits>&& str)
	{
		return asio2::trim_right(std::move(str));
	}

	/**
	 * @brief trim left and right space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>
	>
	inline std::basic_string_view<CharT, Traits>& trim_both(std::basic_string_view<CharT, Traits>& str)
	{
		return asio2::trim_right(asio2::trim_left(str));
	}

	/**
	 * @brief trim left and right space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>
	>
	inline std::basic_string_view<CharT, Traits> trim_both(std::basic_string_view<CharT, Traits>&& str)
	{
		return asio2::trim_right(asio2::trim_left(std::move(str)));
	}

	/**
	 * @brief trim left and right space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>
	>
	inline std::basic_string_view<CharT, Traits>& trim(std::basic_string_view<CharT, Traits>& str)
	{
		return asio2::trim_both(str);
	}

	/**
	 * @brief trim left and right space character of the string: space \t \r \n and so on
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>
	>
	inline std::basic_string_view<CharT, Traits> trim(std::basic_string_view<CharT, Traits>&& str)
	{
		return asio2::trim_both(std::move(str));
	}

	/**
	 * @brief Replaces (in-place) the first occurrence of target with replacement.
	 * @param str - input string that will be modified.
	 * @param target - substring that will be replaced with replacement.
	 * @param replacement - substring that will replace target.
	 * @return Replacemented input string.
	 */
	template<
		class String1,
		class String2,
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator>& replace_first(
		std::basic_string<CharT, Traits, Allocator>& str,
		const String1& target,
		const String2& replacement)
	{
		auto t = asio2::to_basic_string_view(target);
		auto r = asio2::to_basic_string_view(replacement);

		const std::size_t start_pos = str.find(t);
		if (start_pos == std::string::npos)
		{
			return str;
		}

		str.replace(start_pos, t.length(), r);
		return str;
	}

	/**
	 * @brief Replaces (in-place) the first occurrence of target with replacement.
	 * @param str - input string that will be modified.
	 * @param target - substring that will be replaced with replacement.
	 * @param replacement - substring that will replace target.
	 * @return Replacemented input string.
	 */
	template<
		class String1,
		class String2,
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> replace_first(
		std::basic_string<CharT, Traits, Allocator>&& str,
		const String1& target,
		const String2& replacement)
	{
		auto t = asio2::to_basic_string_view(target);
		auto r = asio2::to_basic_string_view(replacement);

		const std::size_t start_pos = str.find(t);
		if (start_pos == std::string::npos)
		{
			return std::move(str);
		}

		str.replace(start_pos, t.length(), r);
		return std::move(str);
	}

	/**
	 * @brief Replaces (in-place) last occurrence of target with replacement.
	 * @param str - input string that will be modified.
	 * @param target - substring that will be replaced with replacement.
	 * @param replacement - substring that will replace target.
	 * @return Replacemented input string.
	 */
	template<
		class String1,
		class String2,
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator>& replace_last(
		std::basic_string<CharT, Traits, Allocator>& str,
		const String1& target,
		const String2& replacement)
	{
		auto t = asio2::to_basic_string_view(target);
		auto r = asio2::to_basic_string_view(replacement);

		std::size_t start_pos = str.rfind(t);
		if (start_pos == std::string::npos)
		{
			return str;
		}

		str.replace(start_pos, t.length(), r);
		return str;
	}

	/**
	 * @brief Replaces (in-place) last occurrence of target with replacement.
	 * @param str - input string that will be modified.
	 * @param target - substring that will be replaced with replacement.
	 * @param replacement - substring that will replace target.
	 * @return Replacemented input string.
	 */
	template<
		class String1,
		class String2,
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> replace_last(
		std::basic_string<CharT, Traits, Allocator>&& str,
		const String1& target,
		const String2& replacement)
	{
		auto t = asio2::to_basic_string_view(target);
		auto r = asio2::to_basic_string_view(replacement);

		std::size_t start_pos = str.rfind(t);
		if (start_pos == std::string::npos)
		{
			return std::move(str);
		}

		str.replace(start_pos, t.length(), r);
		return std::move(str);
	}

	/**
	 * @brief Replaces (in-place) all occurrences of target with replacement.
	 * @param str - input string that will be modified.
	 * @param target - substring that will be replaced with replacement.
	 * @param replacement - substring that will replace target.
	 * @return Replacemented input string.
	 */
	template<
		class String1,
		class String2,
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator>& replace_all(
		std::basic_string<CharT, Traits, Allocator>& str,
		const String1& target,
		const String2& replacement)
	{
		auto t = asio2::to_basic_string_view(target);
		auto r = asio2::to_basic_string_view(replacement);

		if (t.empty())
		{
			return str;
		}

		std::size_t start_pos = 0;

		while ((start_pos = str.find(t, start_pos)) != std::string::npos)
		{
			str.replace(start_pos, t.length(), r);
			start_pos += r.length();
		}

		return str;
	}

	/**
	 * @brief Replaces (in-place) all occurrences of target with replacement.
	 * @param str - input string that will be modified.
	 * @param target - substring that will be replaced with replacement.
	 * @param replacement - substring that will replace target.
	 * @return Replacemented input string.
	 */
	template<
		class String1,
		class String2,
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> replace_all(
		std::basic_string<CharT, Traits, Allocator>&& str,
		const String1& target,
		const String2& replacement)
	{
		auto t = asio2::to_basic_string_view(target);
		auto r = asio2::to_basic_string_view(replacement);

		if (t.empty())
		{
			return std::move(str);
		}

		std::size_t start_pos = 0;

		while ((start_pos = str.find(t, start_pos)) != std::string::npos)
		{
			str.replace(start_pos, t.length(), r);
			start_pos += r.length();
		}

		return std::move(str);
	}

	/**
	 * @brief Replaces (in-place) all occurrences of target with replacement.
	 * @param str - input string that will be modified.
	 * @param target - substring that will be replaced with replacement.
	 * @param replacement - substring that will replace target.
	 * @return Replacemented input string.
	 */
	template<
		class String1,
		class String2,
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator>& replace(
		std::basic_string<CharT, Traits, Allocator>& str,
		const String1& target,
		const String2& replacement)
	{
		return asio2::replace_all(str, target, replacement);
	}

	/**
	 * @brief Replaces (in-place) all occurrences of target with replacement.
	 * @param str - input string that will be modified.
	 * @param target - substring that will be replaced with replacement.
	 * @param replacement - substring that will replace target.
	 * @return Replacemented input string.
	 */
	template<
		class String1,
		class String2,
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::basic_string<CharT, Traits, Allocator> replace(
		std::basic_string<CharT, Traits, Allocator>&& str,
		const String1& target,
		const String2& replacement)
	{
		return asio2::replace_all(std::move(str), target, replacement);
	}

	/**
	 * @brief Checks if string str ends with specified suffix.
	 * @param str - input string that will be checked.
	 * @param suffix - searched suffix in str.
	 * @return True if suffix was found, false otherwise.
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>
	>
	inline bool ends_with(
		std::basic_string_view<CharT, Traits> str,
		std::basic_string_view<CharT, Traits> suffix)
	{
		const auto suffix_start = str.size() - suffix.size();
		const auto result = str.find(suffix, suffix_start);
		return (result == suffix_start) && (result != std::string_view::npos);
	}

	/**
	 * @brief Checks if string str ends with specified suffix.
	 * @param str - input string that will be checked.
	 * @param suffix - searched suffix in str.
	 * @return True if suffix was found, false otherwise.
	 */
	template<class String1, class String2>
	inline bool ends_with(const String1& str1, const String2& str2)
	{
		return asio2::ends_with(asio2::to_basic_string_view(str1), asio2::to_basic_string_view(str2));
	}

	/**
	 * @brief Checks if string str starts with specified prefix.
	 * @param str - input string that will be checked.
	 * @param prefix - searched prefix in str.
	 * @return True if prefix was found, false otherwise.
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>
	>
	inline bool starts_with(
		std::basic_string_view<CharT, Traits> str,
		std::basic_string_view<CharT, Traits> prefix)
	{
		return str.rfind(prefix, 0) == 0;
	}

	/**
	 * @brief Checks if string str starts with specified prefix.
	 * @param str - input string that will be checked.
	 * @param prefix - searched prefix in str.
	 * @return True if prefix was found, false otherwise.
	 */
	template<class String1, class String2>
	inline bool starts_with(const String1& str1, const String2& str2)
	{
		return asio2::starts_with(asio2::to_basic_string_view(str1), asio2::to_basic_string_view(str2));
	}

	/**
	 * @brief Splits input string str according to input string delim.
	 * @param str - string that will be split.
	 * @param delim - the delimiter.
	 * @return std::vector<string> that contains all splitted tokens.
	 */
	template<
		class String1,
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::vector<std::basic_string<CharT, Traits, Allocator>> split(
		const std::basic_string<CharT, Traits, Allocator>& str,
		const String1& delim)
	{
		auto d = asio2::to_basic_string_view(delim);

		std::size_t pos_start = 0, pos_end, delim_len = d.length();
		std::basic_string<CharT, Traits, Allocator> token;
		std::vector<std::basic_string<CharT, Traits, Allocator>> tokens;

		while ((pos_end = str.find(d, pos_start)) != std::string::npos)
		{
			token = str.substr(pos_start, pos_end - pos_start);
			pos_start = pos_end + delim_len;
			tokens.emplace_back(std::move(token));
		}

		tokens.emplace_back(str.substr(pos_start));
		return tokens;
	}

	/**
	 * @brief Splits input string str according to input string delim.
	 * @param str - string that will be split.
	 * @param delim - the delimiter.
	 * @return std::vector<string_view> that contains all splitted tokens.
	 */
	template<
		class String1,
		class CharT,
		class Traits = std::char_traits<CharT>
	>
	inline std::vector<std::basic_string_view<CharT, Traits>> split(
		const std::basic_string_view<CharT, Traits>& str,
		const String1& delim)
	{
		auto d = asio2::to_basic_string_view(delim);

		std::size_t pos_start = 0, pos_end, delim_len = d.length();
		std::basic_string_view<CharT, Traits> token;
		std::vector<std::basic_string_view<CharT, Traits>> tokens;

		while ((pos_end = str.find(d, pos_start)) != std::string::npos)
		{
			token = str.substr(pos_start, pos_end - pos_start);
			pos_start = pos_end + delim_len;
			tokens.emplace_back(std::move(token));
		}

		tokens.emplace_back(str.substr(pos_start));
		return tokens;
	}

	/**
	 * @brief Splits input string str according to input string delim.
	 * @param str - string that will be split.
	 * @param delim - the delimiter.
	 * @return std::vector<string> that contains all splitted tokens.
	 */
	template<class String1, class String2>
	inline auto split(const String1& str, const String2& delim)
	{
		using CharT = typename detail::char_type<String1>::type;

		auto s = asio2::to_basic_string_view(str);
		auto d = asio2::to_basic_string_view(delim);

		std::size_t pos_start = 0, pos_end, delim_len = d.length();
		std::basic_string<CharT> token;
		std::vector<std::basic_string<CharT>> tokens;

		while ((pos_end = s.find(d, pos_start)) != std::string::npos)
		{
			token = s.substr(pos_start, pos_end - pos_start);
			pos_start = pos_end + delim_len;
			tokens.emplace_back(std::move(token));
		}

		tokens.emplace_back(s.substr(pos_start));
		return tokens;
	}

	/**
	 * @brief Splits input string using regex as a delimiter.
	 * @param src - string that will be split.
	 * @param rgx_str - the set of delimiter characters.
	 * @return vector of resulting tokens.
	 */
	template<class String1, class String2>
	inline auto regex_split(const String1& src, const String2& rgx_str)
	{
		using CharT = typename detail::char_type<String1>::type;

		auto s = asio2::to_basic_string(src);
		auto d = asio2::to_basic_string_view(rgx_str);

		using IterType = typename std::basic_string<CharT>::const_iterator;

		std::vector<std::basic_string<CharT>> elems;
		const std::basic_regex<CharT> rgx(d.begin(), d.end());
		std::regex_token_iterator<IterType> iter(s.begin(), s.end(), rgx, -1);
		std::regex_token_iterator<IterType> end;
		while (iter != end)
		{
			elems.emplace_back(*iter);
			++iter;
		}
		return elems;
	}

	/**
	 * @brief Splits input string using regex as a delimiter.
	 * @param src - string that will be split.
	 * @param dest - map of matched delimiter and those being splitted.
	 * @param rgx_str - the set of delimiter characters.
	 * @return True if the parsing is successfully done.
	 */
	template<class String1, class String2>
	inline auto regex_split_map(const String1& src, const String2& rgx_str)
	{
		using CharT = typename detail::char_type<String1>::type;

		auto d = asio2::to_basic_string_view(rgx_str);

		using IterType = typename std::basic_string<CharT>::const_iterator;

		std::map<std::basic_string<CharT>, std::basic_string<CharT>> dest;
		std::basic_string<CharT> tstr = src + static_cast<CharT>(' ');
		std::basic_regex<CharT> rgx(d.begin(), d.end());
		std::regex_token_iterator<IterType> niter(tstr.begin(), tstr.end(), rgx);
		std::regex_token_iterator<IterType> viter(tstr.begin(), tstr.end(), rgx, -1);
		std::regex_token_iterator<IterType> end;
		++viter;
		while (niter != end)
		{
			dest[*niter] = *viter;
			++niter;
			++viter;
		}

		return dest;
	}

	/**
	 * @brief Splits input string using any delimiter in the given set.
	 * @param str - string that will be split.
	 * @param delims - the set of delimiter characters.
	 * @return vector of resulting tokens.
	 */
	template<
		class String1,
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::vector<std::basic_string<CharT, Traits, Allocator>> split_any(
		const std::basic_string<CharT, Traits, Allocator>& str,
		const String1& delims)
	{
		auto d = asio2::to_basic_string_view(delims);

		std::basic_string<CharT, Traits, Allocator> token;
		std::vector<std::basic_string<CharT, Traits, Allocator>> tokens;

		std::size_t pos_start = 0;
		for (std::size_t pos_end = 0; pos_end < str.length(); ++pos_end)
		{
			if (asio2::contains(d, str[pos_end]))
			{
				token = str.substr(pos_start, pos_end - pos_start);
				tokens.emplace_back(std::move(token));
				pos_start = pos_end + 1;
			}
		}

		tokens.emplace_back(str.substr(pos_start));
		return tokens;
	}

	/**
	 * @brief Splits input string using any delimiter in the given set.
	 * @param str - string that will be split.
	 * @param delims - the set of delimiter characters.
	 * @return vector of resulting tokens.
	 */
	template<
		class String1,
		class CharT,
		class Traits = std::char_traits<CharT>
	>
	inline std::vector<std::basic_string_view<CharT, Traits>> split_any(
		const std::basic_string_view<CharT, Traits>& str,
		const String1& delims)
	{
		auto d = asio2::to_basic_string_view(delims);

		std::basic_string_view<CharT, Traits> token;
		std::vector<std::basic_string_view<CharT, Traits>> tokens;

		std::size_t pos_start = 0;
		for (std::size_t pos_end = 0; pos_end < str.length(); ++pos_end)
		{
			if (asio2::contains(d, str[pos_end]))
			{
				token = str.substr(pos_start, pos_end - pos_start);
				tokens.emplace_back(std::move(token));
				pos_start = pos_end + 1;
			}
		}

		tokens.emplace_back(str.substr(pos_start));
		return tokens;
	}

	/**
	 * @brief Splits input string using any delimiter in the given set.
	 * @param str - string that will be split.
	 * @param delims - the set of delimiter characters.
	 * @return vector of resulting tokens.
	 */
	template<class String1, class String2>
	inline auto split_any(const String1& str, const String2& delims)
	{
		using CharT = typename detail::char_type<String1>::type;

		auto s = asio2::to_basic_string_view(str);
		auto d = asio2::to_basic_string_view(delims);

		std::basic_string<CharT> token;
		std::vector<std::basic_string<CharT>> tokens;

		std::size_t pos_start = 0;
		for (std::size_t pos_end = 0; pos_end < s.length(); ++pos_end)
		{
			if (asio2::contains(d, s[pos_end]))
			{
				token = s.substr(pos_start, pos_end - pos_start);
				tokens.emplace_back(std::move(token));
				pos_start = pos_end + 1;
			}
		}

		tokens.emplace_back(s.substr(pos_start));
		return tokens;
	}

	/**
	 * @brief Joins all elements of std::vector tokens of arbitrary datatypes
	 *        into one string with delimiter delim.
	 * @tparam T - arbitrary datatype.
	 * @param tokens - vector of tokens.
	 * @param delim - the delimiter.
	 * @return string with joined elements of vector tokens with delimiter delim.
	 */
	template<
		class T,
		class String1
	>
	inline auto join(const std::vector<T>& tokens, const String1& delim)
	{
		using CharT = typename detail::char_type<String1>::type;

		std::basic_ostringstream<CharT> result;
		for (auto it = tokens.begin(); it != tokens.end(); ++it)
		{
			if (it != tokens.begin())
			{
				result << delim;
			}

			result << *it;
		}

		return result.str();
	}

	/**
	 * @brief Inplace removal of all empty strings in a vector<string>
	 * @param tokens - vector of strings.
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline void drop_empty(std::vector<std::basic_string<CharT, Traits, Allocator>>& tokens)
	{
		auto last = std::remove_if(tokens.begin(), tokens.end(),
			[](const std::basic_string<CharT, Traits, Allocator>& s) { return s.empty(); });
		tokens.erase(last, tokens.end());
	}

	/**
	 * @brief Inplace removal of all empty strings in a vector<string>
	 * @param tokens - vector of strings.
	 * @return vector of non-empty tokens.
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::vector<std::basic_string<CharT, Traits, Allocator>> drop_empty_copy(
		std::vector<std::basic_string<CharT, Traits, Allocator>> tokens)
	{
		drop_empty(tokens);
		return tokens;
	}

	/**
	 * @brief Inplace removal of all duplicate strings in a vector<string> where order is not to be maintained
	 *        Taken from: C++ Primer V5
	 * @param tokens - vector of strings.
	 * @return vector of non-duplicate tokens.
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline void drop_duplicate(std::vector<std::basic_string<CharT, Traits, Allocator>>& tokens)
	{
		std::sort(tokens.begin(), tokens.end());
		auto end_unique = std::unique(tokens.begin(), tokens.end());
		tokens.erase(end_unique, tokens.end());
	}

	/**
	 * @brief Removal of all duplicate strings in a vector<string> where order is not to be maintained
	 *        Taken from: C++ Primer V5
	 * @param tokens - vector of strings.
	 * @return vector of non-duplicate tokens.
	 */
	template<
		class CharT,
		class Traits = std::char_traits<CharT>,
		class Allocator = std::allocator<CharT>
	>
	inline std::vector<std::basic_string<CharT, Traits, Allocator>> drop_duplicate_copy(
		std::vector<std::basic_string<CharT, Traits, Allocator>> tokens)
	{
		std::sort(tokens.begin(), tokens.end());
		auto end_unique = std::unique(tokens.begin(), tokens.end());
		tokens.erase(end_unique, tokens.end());
		return tokens;
	}

	/**
	 * @brief Creates new string with repeated n times substring str.
	 * @param str - substring that needs to be repeated.
	 * @param n - number of iterations.
	 * @return string with repeated substring str.
	 */
	template<class String1>
	inline auto repeat(const String1& str, unsigned n)
	{
		using CharT = typename detail::char_type<String1>::type;

		std::basic_string<CharT> result;

		for (unsigned i = 0; i < n; ++i)
		{
			result += str;
		}

		return result;
	}

	/**
	 * @brief Checks if input string str matches specified reular expression regex.
	 * @param str - string to be checked.
	 * @param regex - the std::regex regular expression.
	 * @return True if regex matches str, false otherwise.
	 */
	template<class String1>
	inline bool matches(const String1& str, const std::basic_regex<typename detail::char_type<String1>::type>& regex)
	{
		return std::regex_match(str, regex);
	}

	/**
	 * @brief Sort input std::vector<string> strs in ascending order.
	 * @param strs - std::vector<string> to be checked.
	 */
	template<typename T>
	inline void sorting_ascending(std::vector<T>& strs)
	{
		std::sort(strs.begin(), strs.end());
	}

	/**
	 * @brief Sorted input std::vector<string> strs in descending order.
	 * @param strs - std::vector<string> to be checked.
	 */
	template<typename T>
	inline void sorting_descending(std::vector<T>& strs)
	{
		std::sort(strs.begin(), strs.end(), std::greater<T>());
	}

	/**
	 * @brief Reverse input std::vector<string> strs.
	 * @param strs - std::vector<string> to be checked.
	 */
	template<typename T>
	inline void reverse_inplace(std::vector<T>& strs)
	{
		std::reverse(strs.begin(), strs.end());
	}

	/**
	 * @brief Reverse input std::vector<string> strs.
	 * @param strs - std::vector<string> to be checked.
	 */
	template<typename T>
	inline std::vector<T> reverse_copy(std::vector<T> strs)
	{
		std::reverse(strs.begin(), strs.end());
		return strs;
	}

	/**
	 * @brief Find substring in the string src, using a case-insensitive comparison.
	 * @return The finded index, or std::string::npos if not found.
	 */
	template<class String1, class String2>
	inline std::size_t ifind(const String1& src, const String2& dest, std::string::size_type pos = 0) noexcept
	{
		auto s = asio2::to_basic_string_view(src);
		auto d = asio2::to_basic_string_view(dest);

		if (pos >= s.size() || d.empty())
			return std::string::npos;

		// Outer loop
		for (auto OuterIt = std::next(s.begin(), pos); OuterIt != s.end(); ++OuterIt)
		{
			auto InnerIt = OuterIt;
			auto SubstrIt = d.begin();
			for (; InnerIt != s.end() && SubstrIt != d.end(); ++InnerIt, ++SubstrIt)
			{
				if (std::tolower(*InnerIt) != std::tolower(*SubstrIt))
					break;
			}

			// Substring matching succeeded
			if (SubstrIt == d.end())
				return std::distance(s.begin(), OuterIt);
		}

		return std::string::npos;
	}
}

#include <asio2/base/detail/pop_options.hpp>

#endif // !__ASIO2_STRING_HPP__