#include "pch.h"
#include "StringUtil.h"
#include <algorithm> // std::equal
#include <cstring>  // std::memset
#include <locale>   // std::tolower

#if !defined _WIN32
#include <codecvt> // convert_typeX
#endif


// для работы с std::wstring нет многих стандартных нужных функций.

std::string strUtil::trim(const std::string &str, const char trim_char /*= L' '*/)
{
	const auto res = trimLeft(str, trim_char);
	return trimRight(res, trim_char);
}

std::wstring strUtil::trim(const std::wstring &str, const wchar_t trim_char)
{
	const auto res = trimLeft(str, trim_char);
	return trimRight(res, trim_char);
}


std::string strUtil::trimLeft(const std::string &str, const char trim_char /*= L' '*/)
{
	std::string res = str;

	while (!res.empty() && (res.front() == trim_char))
	{
		res.erase(res.begin());
	}

	return res;
}

std::wstring strUtil::trimLeft(const std::wstring &str, const wchar_t trim_char)
{
	std::wstring res = str;

	while (!res.empty() && (res.front() == trim_char))
	{
		res.erase(res.begin());
	}

	return res;
}

std::string strUtil::trimRight(const std::string &str, const char trim_char /*= L' '*/)
{
	std::string res = str;

	while (!res.empty() && (res.back() == trim_char))
	{
		res.pop_back();
	}

	return res;
}

std::wstring strUtil::trimRight(const std::wstring &str, const wchar_t trim_char)
{
	std::wstring res = str;

	while (!res.empty() && (res.back() == trim_char))
	{
		res.pop_back();
	}

	return res;
}

std::string strUtil::replaceChar(const std::string &str, const char src, const char dst)
{
	if (!str.empty())
	{
		std::string res = str;

		for (auto &ch : res)
		{
			if (ch == src)
			{
				ch = dst;
			}
		}

		return res;
	}

	return str;
}

std::wstring strUtil::replaceChar(const std::wstring &str, const wchar_t src, const wchar_t dst)
{
	if (!str.empty())
	{
		std::wstring res = str;

		for (auto &wch : res)
		{
			if (wch == src)
			{
				wch = dst;
			}
		}

		return res;
	}

	return str;
}

std::string strUtil::replaceChars(const std::string &str, const std::string &src, const char dst)
{
	if (!str.empty() && !src.empty())
	{
		std::string res = str;

		for (auto &ch : res)
		{
			for (auto s : src)
			{
				if (ch == s)
				{
					ch = dst;
					break;
				}
			}
		}

		return res;
	}

	return str;
}

std::wstring strUtil::replaceChars(const std::wstring &str, const std::wstring &src, const wchar_t dst)
{
	if (!str.empty() && !src.empty())
	{
		std::wstring res = str;

		for (auto &wch : res)
		{
			for (auto s : src)
			{
				if (wch == s)
				{
					wch = dst;
					break;
				}
			}
		}

		return res;
	}

	return str;
}

std::string strUtil::strToUpper(const std::string &str)
{
	std::string res;

	if (!str.empty())
	{
#if defined (_WIN32)
		const std::locale loc("");

		for (const auto c : str)
		{
			res.push_back(std::toupper(static_cast<unsigned char>(c), loc));
		}

#else
		// в линуксе с его дурацким UTF-8 по нормальному невозможно
		const auto ws = s2ws(str);
		const auto lws = strToUpper(ws);
		res = ws2s(lws);
#endif
	}

	return res;
}

std::wstring strUtil::strToUpper(const std::wstring &str)
{
	std::wstring res = str;

	if (!str.empty())
	{
#if defined (_WIN32)
		const std::locale loc("");
#else
		const std::locale loc("ru_RU.UTF-8");
#endif
		std::transform(res.cbegin(), res.cend(), res.begin(), [&](wchar_t n)
		{
			return std::toupper(n, loc);
		});
	}

	return res;
}

std::string strUtil::strToLower(const std::string &str)
{
	std::string res;

	if (!str.empty())
	{
#if defined (_WIN32)
		const std::locale loc("");

		for (const auto c : str)
		{
			res.push_back(std::tolower(static_cast<unsigned char>(c), loc));
		}

#else
		// в линуксе с его дурацким UTF-8 по нормальному невозможно
		const auto ws = s2ws(str);
		const auto lws = strToLower(ws);
		res = ws2s(lws);
#endif
	}

	return res;
}

std::wstring strUtil::strToLower(const std::wstring &str)
{
	std::wstring res = str;

	if (!str.empty())
	{
#if defined (_WIN32)
		const std::locale loc("");
#else
		const std::locale loc("ru_RU.UTF-8");
#endif
		std::transform(res.cbegin(), res.cend(), res.begin(), [&](wchar_t n)
		{
			return std::tolower(n, loc);
		});
	}

	return res;
}

// сокращение строки до заданной длины.
// Вход: str - строка
// len - желаемая длина
// Выход: сформированная строка
std::string strUtil::CropStr(const std::string &str, const size_t len)
{
	auto res = trim(str);

	if (res.length() > len)
	{
		auto crop = res.substr(0, len - 1); // берём первые len-1 символов
		crop += res.back(); // и берём самый последний символ
		return crop;
	}

	return res;
}

// сокращение строки до заданной длины.
// Вход: str - строка
// len - желаемая длина
// Выход: сформированная строка
std::wstring strUtil::CropStr(const std::wstring &str, const size_t len)
{
	auto res = trim(str);

	if (res.length() > len)
	{
		auto crop = res.substr(0, len - 1); // берём первые len-1 символов
		crop += res.back(); // и берём самый последний символ
		return crop;
	}

	return res;
}

bool strUtil::equalNoCase(const std::string &a, const std::string &b)
{
#if defined (_WIN32)
	return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b)
	{
		const auto loc = std::locale("");
		return std::tolower(static_cast<unsigned char>(a), loc) == std::tolower(static_cast<unsigned char>(b), loc);
	});
#else
	const auto wa = s2ws(a);
	const auto wb = s2ws(b);
	return equalNoCase(wa, wb);
#endif
}

bool strUtil::equalNoCase(const std::wstring &a, const std::wstring &b)
{
	return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](wchar_t a, wchar_t b)
	{
#if defined (_WIN32)
		const auto loc = std::locale("");
#else
		const auto loc = std::locale("ru_RU.UTF-8");
#endif
		return std::tolower(a, loc) == std::tolower(b, loc);
	});
}

#if defined (_WIN32)
#if !defined (_APISETSTRING_)
#ifdef __cplusplus
#define STR_EXTERN extern "C"
#else
#define STR_EXTERN extern
#endif
// вот эту вот фигню не надо объявлять, когда у нас #include <windows.h> объявлено
STR_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide);
STR_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default);
#endif
// в виндовсе мы перекодируем из CP1251
constexpr auto CONVCP = 0; //CP_ACP == 0;
std::wstring strUtil::s2ws(const std::string &str)
{
	int nChars = MultiByteToWideChar(CONVCP, 0, str.c_str(), -1, nullptr, 0);
	auto pwcsName = std::vector<wchar_t>(nChars);
	nChars = MultiByteToWideChar(CONVCP, 0, str.c_str(), -1, pwcsName.data(), nChars);
	return { pwcsName.data() };
}

std::string strUtil::ws2s(const std::wstring &wstr)
{
	// сперва узнаем, какого размера будет новая строка
	int nChars = WideCharToMultiByte(CONVCP, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr);
	auto lpszAscii = std::vector<char>(nChars);
	nChars = WideCharToMultiByte(CONVCP, 0, wstr.c_str(), -1, lpszAscii.data(), nChars, nullptr, nullptr);
	return { lpszAscii.data() };
}

// в виндовсе немного по другому
std::wstring strUtil::_t_s2ws(const std::_tstring &str)
{
#if defined (_UNICODE)
	// если юникод - то эти функции ничего не делают
	return str;
#else
	// иначе - всё таки перекодировка АНСИ -> юникод
	return s2ws(str);
#endif
}

std::_tstring strUtil::_t_ws2s(const std::wstring &wstr)
{
#if defined (_UNICODE)
	// если юникод - то эти функции ничего не делают
	return wstr;
#else
	// иначе - всё таки перекодировка юникод -> АНСИ
	return ws2s(wstr);
#endif
}

#else
// в линуксе мы перекодируем из UTF8
#include <codecvt>

std::wstring strUtil::s2ws(const std::string &str)
{
	using convert_typeX = std::codecvt_utf8<wchar_t>;
	std::wstring_convert<convert_typeX, wchar_t> converterX;
	return converterX.from_bytes(str);
}

std::string strUtil::ws2s(const std::wstring &wstr)
{
	using convert_typeX = std::codecvt_utf8<wchar_t>;
	std::wstring_convert<convert_typeX, wchar_t> converterX;
	return converterX.to_bytes(wstr);
}

// в линуксе эти функции делают то же самое, что и обычные
std::wstring strUtil::_t_s2ws(const std::string &str)
{
	return s2ws(str);
}

std::string strUtil::_t_ws2s(const std::wstring &wstr)
{
	return ws2s(wstr);
}
#endif


// перекодировка юникодной строки в ansi и сохранение её в буфер
// Вход: wstr - строка
//		strDst - буфер
//		bFill - флаг, заполнять остаток буфера пробелами или не надо.
void strUtil::WideStr2Str(const std::wstring &wstr, std::span<char> strDst, const bool bFill)
{
	const auto nDstLen = strDst.size();
	const auto s = ws2s(wstr);
	const auto nChars = s.length();
	// теперь сколько надо, скопируем в буфер
	if (nDstLen <= nChars)
	{
		// если строка длиньше буфера, то копируем сколько влазит
		std::copy(s.cbegin(), s.cbegin() + nDstLen, strDst.begin());
	}
	else
	{
		// если строка короче буфера, то копируем строку
		std::copy(s.cbegin(), s.cbegin() + nChars, strDst.begin());

		// и если надо, остаток заполняем пробелами
		if (bFill)
		{
			std::memset(strDst.data() + nChars, 0x20, nDstLen - nChars);
		}
	}
}

fs::path strUtil::makePath(const std::string &str)
{
#if defined (_WIN32)
	fs::path strArg = fs::path(str);
#else
	std::string strArg1 = str;
	// в линуксе нужно с разделитетями быть строже
	std::replace(strArg1.begin(), strArg1.end(), '\\', '/');
	fs::path strArg = fs::path(strArg1);
#endif
	return strArg.make_preferred();
}

fs::path strUtil::makePath(const std::wstring &wstr)
{
#if defined (_WIN32)
	fs::path strArg = fs::path(wstr);
#else
	std::wstring strArg1 = wstr;
	// в линуксе нужно с разделитетями быть строже
	std::replace(strArg1.begin(), strArg1.end(), L'\\', L'/');
	fs::path strArg = fs::path(strArg1);
#endif
	return strArg.make_preferred();
}
