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

#include "cmVersion.h"

cmWIXRichTextFormatWriter::cmWIXRichTextFormatWriter(
  std::string const& filename)
  : File(filename.c_str(), std::ios::binary)
{
  StartGroup();
  WriteHeader();
  WriteDocumentPrefix();
}

cmWIXRichTextFormatWriter::~cmWIXRichTextFormatWriter()
{
  EndGroup();

  /* I haven't seen this in the RTF spec but
   *  wordpad terminates its RTF like this */
  File << "\r\n";
  File.put(0);
}

void cmWIXRichTextFormatWriter::AddText(std::string const& text)
{
  using rtf_byte_t = unsigned char;

  for (size_t i = 0; i < text.size(); ++i) {
    rtf_byte_t c = rtf_byte_t(text[i]);

    switch (c) {
      case '\\':
        File << "\\\\";
        break;
      case '{':
        File << "\\{";
        break;
      case '}':
        File << "\\}";
        break;
      case '\n':
        File << "\\par\r\n";
        break;
      case '\r':
        continue;
      default: {
        if (c <= 0x7F) {
          File << c;
        } else {
          if (c <= 0xC0) {
            EmitInvalidCodepoint(c);
          } else if (c < 0xE0 && i + 1 < text.size()) {
            EmitUnicodeCodepoint((text[i + 1] & 0x3F) | ((c & 0x1F) << 6));
            i += 1;
          } else if (c < 0xF0 && i + 2 < text.size()) {
            EmitUnicodeCodepoint((text[i + 2] & 0x3F) |
                                 ((text[i + 1] & 0x3F) << 6) |
                                 ((c & 0xF) << 12));
            i += 2;
          } else if (c < 0xF8 && i + 3 < text.size()) {
            EmitUnicodeCodepoint(
              (text[i + 3] & 0x3F) | ((text[i + 2] & 0x3F) << 6) |
              ((text[i + 1] & 0x3F) << 12) | ((c & 0x7) << 18));
            i += 3;
          } else {
            EmitInvalidCodepoint(c);
          }
        }
      } break;
    }
  }
}

void cmWIXRichTextFormatWriter::WriteHeader()
{
  ControlWord("rtf1");
  ControlWord("ansi");
  ControlWord("ansicpg1252");
  ControlWord("deff0");
  ControlWord("deflang1033");

  WriteFontTable();
  WriteColorTable();
  WriteGenerator();
}

void cmWIXRichTextFormatWriter::WriteFontTable()
{
  StartGroup();
  ControlWord("fonttbl");

  StartGroup();
  ControlWord("f0");
  ControlWord("fswiss");
  ControlWord("fcharset0 Consolas;");
  EndGroup();

  EndGroup();
}

void cmWIXRichTextFormatWriter::WriteColorTable()
{
  StartGroup();
  ControlWord("colortbl ;");
  ControlWord("red255");
  ControlWord("green0");
  ControlWord("blue0;");
  ControlWord("red0");
  ControlWord("green255");
  ControlWord("blue0;");
  ControlWord("red0");
  ControlWord("green0");
  ControlWord("blue255;");
  EndGroup();
}

void cmWIXRichTextFormatWriter::WriteGenerator()
{
  StartGroup();
  NewControlWord("generator");
  File << " CPack WiX Generator (" << cmVersion::GetCMakeVersion() << ");";
  EndGroup();
}

void cmWIXRichTextFormatWriter::WriteDocumentPrefix()
{
  ControlWord("viewkind4");
  ControlWord("uc1");
  ControlWord("pard"); // noqa: spellcheck disable-line
  ControlWord("f0");
  ControlWord("fs14");
}

void cmWIXRichTextFormatWriter::ControlWord(std::string const& keyword)
{
  File << '\\' << keyword;
}

void cmWIXRichTextFormatWriter::NewControlWord(std::string const& keyword)
{
  File << "\\*\\" << keyword;
}

void cmWIXRichTextFormatWriter::StartGroup()
{
  File.put('{');
}

void cmWIXRichTextFormatWriter::EndGroup()
{
  File.put('}');
}

void cmWIXRichTextFormatWriter::EmitUnicodeCodepoint(int c)
{
  // Do not emit byte order mark (BOM)
  if (c == 0xFEFF) {
    return;
  }
  if (c <= 0xFFFF) {
    EmitUnicodeSurrogate(c);
  } else {
    c -= 0x10000;
    EmitUnicodeSurrogate(((c >> 10) & 0x3FF) + 0xD800);
    EmitUnicodeSurrogate((c & 0x3FF) + 0xDC00);
  }
}

void cmWIXRichTextFormatWriter::EmitUnicodeSurrogate(int c)
{
  ControlWord("u");
  if (c <= 32767) {
    File << c;
  } else {
    File << (c - 65536);
  }
  File << '?';
}

void cmWIXRichTextFormatWriter::EmitInvalidCodepoint(int c)
{
  ControlWord("cf1 ");
  File << "[INVALID-BYTE-" << c << ']';
  ControlWord("cf0 ");
}
