/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */

#include "cmProcessOutput.h"

#if defined(_WIN32)
#    include <windows.h>
unsigned int cmProcessOutput::defaultCodepage = KWSYS_ENCODING_DEFAULT_CODEPAGE;
#endif

cmProcessOutput::Encoding
cmProcessOutput::FindEncoding(std::string const& name)
{
    Encoding encoding = Auto;
    if((name == "UTF8") || (name == "UTF-8"))
    {
        encoding = UTF8;
    } else if(name == "NONE")
    {
        encoding = None;
    } else if(name == "ANSI")
    {
        encoding = ANSI;
    } else if(name == "OEM")
    {
        encoding = OEM;
    }
    return encoding;
}

cmProcessOutput::cmProcessOutput(Encoding encoding, unsigned int maxSize)
{
#if defined(_WIN32)
    codepage   = 0;
    bufferSize = maxSize;
    if(encoding == None)
    {
        codepage = defaultCodepage;
    } else if(encoding == Auto)
    {
        codepage = GetConsoleCP();
    } else if(encoding == UTF8)
    {
        codepage = CP_UTF8;
    } else if(encoding == OEM)
    {
        codepage = GetOEMCP();
    }
    if(!codepage || encoding == ANSI)
    {
        codepage = GetACP();
    }
#else
    static_cast<void>(encoding);
    static_cast<void>(maxSize);
#endif
}

cmProcessOutput::~cmProcessOutput() {}

bool
cmProcessOutput::DecodeText(std::string raw, std::string& decoded, size_t id)
{
#if !defined(_WIN32)
    static_cast<void>(id);
    decoded.swap(raw);
    return true;
#else
    bool success = true;
    decoded      = raw;
    if(id > 0)
    {
        if(rawparts.size() < id)
        {
            rawparts.reserve(id);
            while(rawparts.size() < id) rawparts.push_back(std::string());
        }
        raw = rawparts[id - 1] + raw;
        rawparts[id - 1].clear();
        decoded = raw;
    }
    if(raw.size() > 0 && codepage != defaultCodepage)
    {
        success = false;
        CPINFOEXW cpinfo;
        if(id > 0 && bufferSize > 0 && raw.size() == bufferSize &&
           GetCPInfoExW(codepage, 0, &cpinfo) == 1 && cpinfo.MaxCharSize > 1)
        {
            if(cpinfo.MaxCharSize == 2 && cpinfo.LeadByte[0] != 0)
            {
                LPSTR prevChar   = CharPrevExA(codepage, raw.c_str(),
                                             raw.c_str() + raw.size(), 0);
                bool  isLeadByte = (*(prevChar + 1) == 0) &&
                                  IsDBCSLeadByteEx(codepage, *prevChar);
                if(isLeadByte)
                {
                    rawparts[id - 1] += *(raw.end() - 1);
                    raw.resize(raw.size() - 1);
                }
                success = DoDecodeText(raw, decoded, NULL);
            } else
            {
                bool        restoreDecoded = false;
                std::string firstDecoded   = decoded;
                wchar_t     lastChar       = 0;
                for(UINT i = 0; i < cpinfo.MaxCharSize; i++)
                {
                    success = DoDecodeText(raw, decoded, &lastChar);
                    if(success && lastChar != 0)
                    {
                        if(i == 0)
                        {
                            firstDecoded = decoded;
                        }
                        if(lastChar == cpinfo.UnicodeDefaultChar)
                        {
                            restoreDecoded = true;
                            rawparts[id - 1] =
                                *(raw.end() - 1) + rawparts[id - 1];
                            raw.resize(raw.size() - 1);
                        } else
                        {
                            restoreDecoded = false;
                            break;
                        }
                    } else
                    {
                        break;
                    }
                }
                if(restoreDecoded)
                {
                    decoded = firstDecoded;
                    rawparts[id - 1].clear();
                }
            }
        } else
        {
            success = DoDecodeText(raw, decoded, NULL);
        }
    }
    return success;
#endif
}

bool
cmProcessOutput::DecodeText(const char* data, size_t length,
                            std::string& decoded, size_t id)
{
    return DecodeText(std::string(data, length), decoded, id);
}

bool
cmProcessOutput::DecodeText(std::vector<char> raw, std::vector<char>& decoded,
                            size_t id)
{
    std::string str;
    const bool  success =
        DecodeText(std::string(raw.begin(), raw.end()), str, id);
    decoded.assign(str.begin(), str.end());
    return success;
}

#if defined(_WIN32)
bool
cmProcessOutput::DoDecodeText(std::string raw, std::string& decoded,
                              wchar_t* lastChar)
{
    bool      success = false;
    const int wlength =
        MultiByteToWideChar(codepage, 0, raw.c_str(), int(raw.size()), NULL, 0);
    wchar_t* wdata = new wchar_t[wlength];
    int      r = MultiByteToWideChar(codepage, 0, raw.c_str(), int(raw.size()),
                                wdata, wlength);
    if(r > 0)
    {
        if(lastChar)
        {
            *lastChar = 0;
            if((wlength >= 2 && wdata[wlength - 2] != wdata[wlength - 1]) ||
               wlength >= 1)
            {
                *lastChar = wdata[wlength - 1];
            }
        }
        int   length = WideCharToMultiByte(defaultCodepage, 0, wdata, wlength,
                                         NULL, 0, NULL, NULL);
        char* data   = new char[length];
        r = WideCharToMultiByte(defaultCodepage, 0, wdata, wlength, data,
                                length, NULL, NULL);
        if(r > 0)
        {
            decoded = std::string(data, length);
            success = true;
        }
        delete[] data;
    }
    delete[] wdata;
    return success;
}
#endif
