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

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

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

std::wstring strUtil::trim(const std::wstring &str, const wchar_t trim_char)
{
    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 = str;

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

    return res;
}

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

    if (!res.empty())
    {
#if defined (_WIN32)
        std::locale loc("");
#else
        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 = str;

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

    return res;
}

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

    if (!res.empty())
    {
#if defined (_WIN32)
        std::locale loc("");
#else
        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)
{
    return std::equal(a.begin(), a.end(), b.begin(), b.end(), [ ](char a, char b)
    {
#if defined (_WIN32)
        auto loc = std::locale("");
#else
        auto loc = std::locale("ru_RU.UTF-8");
#endif
        return std::tolower(a, loc) == std::tolower(b, loc);
    });
}

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)
        auto loc = std::locale("");
#else
        auto loc = std::locale("ru_RU.UTF-8");
#endif
        return std::tolower(a, loc) == std::tolower(b, loc);
    });
}

#if defined(_WIN32)
#ifdef __cplusplus
#define STR_EXTERN extern "C"
#else
#define STR_EXTERN extern
#endif
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);
// в виндовсе мы перекодируем из 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 t_len = WideCharToMultiByte(CONVCP, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr);
    const int sizeOfString = t_len;
    auto lpszAscii = std::vector<char>(sizeOfString);
    t_len = WideCharToMultiByte(CONVCP, 0, wstr.c_str(), -1, lpszAscii.data(), sizeOfString, 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
