﻿// TortoiseSVN - a Windows shell extension for easy version control

// Copyright (C) 2003-2006, 2008-2013, 2015, 2020-2021 - TortoiseSVN
// Copyright (C) 2020 - TortoiseGit

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "stdafx.h"
#include "UnicodeUtils.h"
#include <memory>

CUnicodeUtils::CUnicodeUtils()
{
}

CUnicodeUtils::~CUnicodeUtils()
{
}

namespace
{
// simple utility class that provides an efficient
// writable string buffer. std::basic_string<> could
// be used as well but has a less suitable interface.
// Note: using this CBuffer instead of CString.GetBuffer()
// is more than 20 times faster.
template <class T>
class CBuffer
{
private:
    enum
    {
        FIXED_BUFFER_SIZE = 1024
    };

    T                    fixedBuffer[FIXED_BUFFER_SIZE];
    std::unique_ptr<T[]> dynamicBuffer;

    T* buffer;

public:
    CBuffer(size_t minCapacity)
    {
        fixedBuffer[0] = 0;
        if (minCapacity <= FIXED_BUFFER_SIZE)
        {
            buffer = fixedBuffer;
        }
        else
        {
            dynamicBuffer.reset(new T[minCapacity]);
            buffer = dynamicBuffer.get();
        }
    }

    operator T*()
    {
        return buffer;
    }
};

} // namespace

// wrap core routines and have string objects in the signatures
// instead of string buffers

#if defined(_MFC_VER) || defined(CSTRING_AVAILABLE)

CStringA CUnicodeUtils::GetUTF8(const CStringW& string)
{
    int           size = string.GetLength();
    CBuffer<char> buffer(4 * size);

    // Note: always use the Windows API function, do NOT try to implement a function that's faster.
    // The API takes this long because it can handle all edge cases, and that's what we need.
    int len = WideCharToMultiByte(CP_UTF8, 0, static_cast<const wchar_t*>(string), size, buffer, 4 * size, nullptr, nullptr);
    if (len == 0)
        return CStringA();
    return CStringA(buffer, len);
}

CString CUnicodeUtils::GetUnicode(const CStringA& string)
{
    int              size = string.GetLength();
    CBuffer<wchar_t> buffer(2 * size);

    // Note: always use the Windows API function, do NOT try to implement a function that's faster.
    // The API takes this long because it can handle all edge cases, and that's what we need.
    int len = MultiByteToWideChar(CP_UTF8, 0, static_cast<const char*>(string), size, buffer, 2 * size);
    if (len == 0)
        return CString();
    return CString(buffer, len);
}

CString CUnicodeUtils::UTF8ToUTF16(const std::string& string)
{
    int              size = static_cast<int>(string.length());
    CBuffer<wchar_t> buffer(2 * size);

    int len = MultiByteToWideChar(CP_UTF8, 0, string.c_str(), size, buffer, 2 * size);
    if (len == 0)
        return CString();
    return CString(buffer, len);
}
#endif //_MFC_VER

std::string CUnicodeUtils::StdGetUTF8(const std::wstring& wide)
{
    int           size = static_cast<int>(wide.length());
    CBuffer<char> buffer(4 * size);

    int len = WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), size, buffer, 4 * size, nullptr, nullptr);
    if (len == 0)
        return std::string();
    return std::string(buffer, len);
}

std::wstring CUnicodeUtils::StdGetUnicode(const std::string& utf8)
{
    int              size = static_cast<int>(utf8.length());
    CBuffer<wchar_t> buffer(2 * size);

    int len = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), size, buffer, 2 * size);
    if (len == 0)
        return std::wstring();
    return std::wstring(buffer, len);
}

std::string CUnicodeUtils::StdAnsiToUTF8(const std::string& ansi)
{
    int              size = static_cast<int>(ansi.length());
    CBuffer<wchar_t> buffer(2 * size);

    int len = MultiByteToWideChar(CP_ACP, 0, ansi.c_str(), size, buffer, 2 * size);
    if (len == 0)
        return ansi;
    CBuffer<char> buffer2(4 * size);
    len = WideCharToMultiByte(CP_UTF8, 0, buffer, len, buffer2, 4 * size, nullptr, nullptr);
    if (len == 0)
        return ansi;
    return std::string(buffer2, len);
}

// load a string resource

#pragma warning(push)
#pragma warning(disable : 4200)
// ReSharper disable once CppInconsistentNaming
struct STRINGRESOURCEIMAGE
{
    WORD  nLength;
    WCHAR achString[];
};
#pragma warning(pop) // C4200

int LoadStringEx(HINSTANCE hInstance, UINT uID, LPWSTR lpBuffer, int nBufferMax, WORD wLanguage)
{
    STRINGRESOURCEIMAGE* pImage;
    STRINGRESOURCEIMAGE* pImageEnd;
    ULONG                nResourceSize;
    HGLOBAL              hGlobal;
    UINT                 iIndex;
#ifndef UNICODE
    BOOL defaultCharUsed;
#endif
    int ret;

    if (lpBuffer == nullptr)
        return 0;
    lpBuffer[0]     = 0;
    HRSRC hResource = FindResourceEx(hInstance, RT_STRING, MAKEINTRESOURCE(((uID >> 4) + 1)), wLanguage);
    if (!hResource)
    {
        //try the default language before giving up!
        hResource = FindResource(hInstance, MAKEINTRESOURCE(((uID >> 4) + 1)), RT_STRING);
        if (!hResource)
            return 0;
    }
    hGlobal = LoadResource(hInstance, hResource);
    if (!hGlobal)
        return 0;
    pImage = static_cast<STRINGRESOURCEIMAGE*>(::LockResource(hGlobal));
    if (!pImage)
        return 0;

    nResourceSize = ::SizeofResource(hInstance, hResource);
    pImageEnd     = reinterpret_cast<STRINGRESOURCEIMAGE*>(reinterpret_cast<LPBYTE>(pImage) + nResourceSize);
    iIndex        = uID & 0x000f;

    while ((iIndex > 0) && (pImage < pImageEnd))
    {
        pImage = reinterpret_cast<STRINGRESOURCEIMAGE*>(reinterpret_cast<LPBYTE>(pImage) + (sizeof(STRINGRESOURCEIMAGE) + (pImage->nLength * sizeof(WCHAR))));
        iIndex--;
    }
    if (pImage >= pImageEnd)
        return 0;
    if (pImage->nLength == 0)
        return 0;
#ifdef UNICODE
    ret = pImage->nLength;
    if (ret >= nBufferMax)
        ret = nBufferMax - 1;
    wcsncpy_s(static_cast<wchar_t*>(lpBuffer), nBufferMax, pImage->achString, ret);
    lpBuffer[ret] = 0;
#else
    ret           = WideCharToMultiByte(CP_ACP, 0, pImage->achString, pImage->nLength, (LPSTR)lpBuffer, nBufferMax - 1, ".", &defaultCharUsed);
    lpBuffer[ret] = 0;
#endif
    return ret;
}

#if defined(_DEBUG)
// Some test cases for these classes
[[maybe_unused]] static class CUnicodeUtilsTests
{
public:
    CUnicodeUtilsTests()
    {
#    if defined(_MFC_VER) || defined(CSTRING_AVAILABLE)
        CStringA result  = CUnicodeUtils::GetUTF8(L"<value>退订</value>");
        CStringW resultW = CUnicodeUtils::GetUnicode(result);
        ATLASSERT(resultW == L"<value>退订</value>");
        result  = CUnicodeUtils::GetUTF8(L"äöü");
        resultW = CUnicodeUtils::GetUnicode(result);
        ATLASSERT(resultW == L"äöü");
        ATLASSERT(resultW.GetLength() == 3);
        result  = CUnicodeUtils::GetUTF8(L"Продолжить выполнение скрипта?");
        resultW = CUnicodeUtils::GetUnicode(result);
        ATLASSERT(resultW == L"Продолжить выполнение скрипта?");
        result  = CUnicodeUtils::GetUTF8(L"dvostruki klik za automtsko uključivanje alfa");
        resultW = CUnicodeUtils::GetUnicode(result);
        ATLASSERT(resultW == L"dvostruki klik za automtsko uključivanje alfa");
        result  = CUnicodeUtils::GetUTF8(L"包含有错误的结构。");
        resultW = CUnicodeUtils::GetUnicode(result);
        ATLASSERT(resultW == L"包含有错误的结构。");
        result  = CUnicodeUtils::GetUTF8(L"个文件，共有 %2!d! 个文件");
        resultW = CUnicodeUtils::GetUnicode(result);
        ATLASSERT(resultW == L"个文件，共有 %2!d! 个文件");
        result  = CUnicodeUtils::GetUTF8(L"は予期せぬオブジェクトを含んでいます。");
        resultW = CUnicodeUtils::GetUnicode(result);
        ATLASSERT(resultW == L"は予期せぬオブジェクトを含んでいます。");
        result  = CUnicodeUtils::GetUTF8(L"Verify that the correct path and file name are given.");
        resultW = CUnicodeUtils::GetUnicode(result);
        ATLASSERT(resultW == L"Verify that the correct path and file name are given.");
#    endif
    }

    // ReSharper disable once CppInconsistentNaming
} UnicodeTestobject;
#endif
