#include "UnicodeUtils.h"

#include "Win32BaseIncludes.h"

#include <vector>
#include <stdexcept>

std::string UnicodeUtils::utf16ToUtf8String(const std::wstring& input)
{
    if (input.empty())
    {
        return std::string();
    }

#ifdef _WIN32
    const auto size = ::WideCharToMultiByte(CP_UTF8, 0, &input[0], static_cast<int>(input.size()), nullptr, 0, nullptr, nullptr);

    std::string result(size, 0);
    ::WideCharToMultiByte(CP_UTF8, 0, &input[0], static_cast<int>(input.size()), &result[0], size, nullptr, nullptr);
    return result;
#else
    throw std::logic_error("Not implemented");
#endif
}

std::wstring UnicodeUtils::utf8ToUtf16WString(const std::string& input)
{
    if (input.empty())
    {
        return std::wstring();
    }

#ifdef _WIN32
    const auto charsNeeded = ::MultiByteToWideChar(CP_UTF8, 0, input.data(), static_cast<int>(input.size()), nullptr, 0);
    std::vector<wchar_t> buffer(charsNeeded);
    const auto charsConverted = ::MultiByteToWideChar(CP_UTF8, 0, input.data(), static_cast<int>(input.size()), &buffer[0], static_cast<int>(buffer.size()));
    return std::wstring(&buffer[0], charsConverted);
#else
    throw std::logic_error("Not implemented");
#endif
}

std::vector<uint32_t> UnicodeUtils::utf8ToUtf32(const std::string& input)
{
    const auto utf_16 = utf8ToUtf16WString(input);

    std::vector<uint32_t> output;
    std::size_t pos = 0;
    while (pos < utf_16.size())
    {
        const auto c = utf_16[pos];
        pos++;
        if (!isSurrogate(c))
        {
            output.push_back(c);
        }
        else
        {
            if (isHighSurrogate(c) && pos < utf_16.size() && isLowSurrogate(utf_16[pos]))
            {
                output.push_back(surrogateToUtf32(c, utf_16[pos]));
                pos++;
            }
            else
            {
                throw std::logic_error("Unexpected UTF16 content given for conversion to UTF32");
            }
        }
    }
    return output;
}

bool UnicodeUtils::isSurrogate(wchar_t c)
{ 
    return (c - 0xd800u) < 2048u; 
}

bool UnicodeUtils::isHighSurrogate(wchar_t c)
{
    return (c & 0xfffffc00) == 0xd800;
}

bool UnicodeUtils::isLowSurrogate(wchar_t c)
{
    return (c & 0xfffffc00) == 0xdc00;
}

uint32_t UnicodeUtils::surrogateToUtf32(wchar_t high, wchar_t low)
{
    return (high << 10) + low - 0x35fdc00;
}