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

#include "cmProcessOutput.h"

#if defined(_WIN32)
#  include <cm/memory>

#  include <windows.h>

unsigned int cmProcessOutput::defaultCodepage =
  KWSYS_ENCODING_DEFAULT_CODEPAGE;
#endif

cm::optional<cmProcessOutput::Encoding> cmProcessOutput::FindEncoding(
  std::string const& name)
{
  cm::optional<Encoding> encoding;
  if ((name == "UTF8") || (name == "UTF-8")) {
    encoding = UTF8;
  } else if (name == "NONE") {
    encoding = None;
  } else if (name == "ANSI") {
    encoding = ANSI;
  } else if (name == "AUTO") {
    encoding = Auto;
  } 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
}

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, nullptr);
      } 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, nullptr);
    }
  }
  return success;
#endif
}

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

bool cmProcessOutput::DecodeText(std::vector<char> raw,
                                 std::vector<char>& decoded, size_t id)
{
  std::string str;
  bool const success =
    this->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;
  int const wlength =
    MultiByteToWideChar(codepage, 0, raw.c_str(), int(raw.size()), nullptr, 0);
  auto wdata = cm::make_unique<wchar_t[]>(wlength);
  int r = MultiByteToWideChar(codepage, 0, raw.c_str(), int(raw.size()),
                              wdata.get(), 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.get(), wlength,
                                     nullptr, 0, nullptr, nullptr);
    auto data = cm::make_unique<char[]>(length);
    r = WideCharToMultiByte(defaultCodepage, 0, wdata.get(), wlength,
                            data.get(), length, nullptr, nullptr);
    if (r > 0) {
      decoded = std::string(data.get(), length);
      success = true;
    }
  }
  return success;
}
#endif
