/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <thrift/lib/cpp/protocol/TDebugProtocol.h>

#include <cassert>
#include <cctype>
#include <cstdio>
#include <stdexcept>
#include <boost/lexical_cast.hpp>

using std::string;

static string byte_to_hex(const uint8_t byte) {
  char buf[3];
  if (std::sprintf(buf, "%02x", (int)byte) != 2) {
    assert(false);
  }
  assert(buf[2] == '\0');
  return buf;
}

namespace apache {
namespace thrift {
namespace protocol {

string TDebugProtocol::fieldTypeName(TType type) {
  switch (type) {
    case T_STOP:
      return "stop";
    case T_VOID:
      return "void";
    case T_BOOL:
      return "bool";
    case T_BYTE:
      return "byte";
    case T_I16:
      return "i16";
    case T_I32:
      return "i32";
    case T_U64:
      return "u64";
    case T_I64:
      return "i64";
    case T_DOUBLE:
      return "double";
    case T_FLOAT:
      return "float";
    case T_STRING:
      return "string";
    case T_STRUCT:
      return "struct";
    case T_MAP:
      return "map";
    case T_SET:
      return "set";
    case T_LIST:
      return "list";
    case T_UTF8:
      return "utf8";
    case T_UTF16:
      return "utf16";
    default:
      return "unknown";
  }
}

void TDebugProtocol::indentUp() {
  indent_str_ += string(indent_inc, ' ');
}

void TDebugProtocol::indentDown() {
  if (indent_str_.length() < (string::size_type)indent_inc) {
    throw TProtocolException(TProtocolException::INVALID_DATA);
  }
  indent_str_.erase(indent_str_.length() - indent_inc);
}

uint32_t TDebugProtocol::writePlain(const string& str) {
  trans_->write((uint8_t*)str.data(), str.length());
  return str.length();
}

uint32_t TDebugProtocol::writeIndented(const string& str) {
  trans_->write((uint8_t*)indent_str_.data(), indent_str_.length());
  trans_->write((uint8_t*)str.data(), str.length());
  return indent_str_.length() + str.length();
}

uint32_t TDebugProtocol::startItem() {
  uint32_t size;

  switch (write_state_.back()) {
    case UNINIT:
      // XXX figure out what to do here.
      // throw TProtocolException(TProtocolException::INVALID_DATA);
      // return writeIndented(str);
      return 0;
    case STRUCT:
      return 0;
    case SET:
      return writeIndented("");
    case MAP_KEY:
      return writeIndented("");
    case MAP_VALUE:
      return writePlain(" -> ");
    case LIST:
      size = writeIndented(
          "[" + boost::lexical_cast<string>(list_idx_.back()) + "] = ");
      list_idx_.back()++;
      return size;
    default:
      throw std::logic_error("Invalid enum value.");
  }
}

uint32_t TDebugProtocol::endItem() {
  // uint32_t size;

  switch (write_state_.back()) {
    case UNINIT:
      // XXX figure out what to do here.
      // throw TProtocolException(TProtocolException::INVALID_DATA);
      // return writeIndented(str);
      return 0;
    case STRUCT:
      return writePlain(",\n");
    case SET:
      return writePlain(",\n");
    case MAP_KEY:
      write_state_.back() = MAP_VALUE;
      return 0;
    case MAP_VALUE:
      write_state_.back() = MAP_KEY;
      return writePlain(",\n");
    case LIST:
      return writePlain(",\n");
    default:
      throw std::logic_error("Invalid enum value.");
  }
}

uint32_t TDebugProtocol::writeItem(const std::string& str) {
  uint32_t size = 0;
  size += startItem();
  size += writePlain(str);
  size += endItem();
  return size;
}

uint32_t TDebugProtocol::writeMessageBegin(
    const std::string& name,
    const TMessageType messageType,
    const int32_t /*seqid*/) {
  string mtype;
  switch (messageType) {
    case T_CALL:
      mtype = "call";
      break;
    case T_REPLY:
      mtype = "reply";
      break;
    case T_EXCEPTION:
      mtype = "exn";
      break;
    case T_ONEWAY:
      mtype = "oneway";
      break;
  }

  uint32_t size = writeIndented("(" + mtype + ") " + name + "(");
  indentUp();
  return size;
}

uint32_t TDebugProtocol::writeMessageEnd() {
  indentDown();
  return writeIndented(")\n");
}

uint32_t TDebugProtocol::writeStructBegin(const char* name) {
  uint32_t size = 0;
  size += startItem();
  size += writePlain(string(name) + " {\n");
  indentUp();
  write_state_.push_back(STRUCT);
  return size;
}

uint32_t TDebugProtocol::writeStructEnd() {
  indentDown();
  write_state_.pop_back();
  uint32_t size = 0;
  size += writeIndented("}");
  size += endItem();
  return size;
}

uint32_t TDebugProtocol::writeFieldBegin(
    const char* name, const TType fieldType, const int16_t fieldId) {
  // sprintf(id_str, "%02d", fieldId);
  string id_str = boost::lexical_cast<string>(fieldId);
  if (id_str.length() == 1)
    id_str = '0' + id_str;

  return writeIndented(
      id_str + ": " + name + " (" + fieldTypeName(fieldType) + ") = ");
}

uint32_t TDebugProtocol::writeFieldEnd() {
  assert(write_state_.back() == STRUCT);
  return 0;
}

uint32_t TDebugProtocol::writeFieldStop() {
  return 0;
  // writeIndented("***STOP***\n");
}

uint32_t TDebugProtocol::writeMapBegin(
    const TType keyType, const TType valType, const uint32_t size) {
  // TODO(dreiss): Optimize short maps?
  uint32_t bsize = 0;
  bsize += startItem();
  bsize += writePlain(
      "map<" + fieldTypeName(keyType) + "," + fieldTypeName(valType) +
      ">"
      "[" +
      boost::lexical_cast<string>(size) + "] {\n");
  indentUp();
  write_state_.push_back(MAP_KEY);
  return bsize;
}

uint32_t TDebugProtocol::writeMapEnd() {
  indentDown();
  write_state_.pop_back();
  uint32_t size = 0;
  size += writeIndented("}");
  size += endItem();
  return size;
}

uint32_t TDebugProtocol::writeListBegin(
    const TType elemType, const uint32_t size) {
  // TODO(dreiss): Optimize short arrays.
  uint32_t bsize = 0;
  bsize += startItem();
  bsize += writePlain(
      "list<" + fieldTypeName(elemType) +
      ">"
      "[" +
      boost::lexical_cast<string>(size) + "] {\n");
  indentUp();
  write_state_.push_back(LIST);
  list_idx_.push_back(0);
  return bsize;
}

uint32_t TDebugProtocol::writeListEnd() {
  indentDown();
  write_state_.pop_back();
  list_idx_.pop_back();
  uint32_t size = 0;
  size += writeIndented("}");
  size += endItem();
  return size;
}

uint32_t TDebugProtocol::writeSetBegin(
    const TType elemType, const uint32_t size) {
  // TODO(dreiss): Optimize short sets.
  uint32_t bsize = 0;
  bsize += startItem();
  bsize += writePlain(
      "set<" + fieldTypeName(elemType) +
      ">"
      "[" +
      boost::lexical_cast<string>(size) + "] {\n");
  indentUp();
  write_state_.push_back(SET);
  return bsize;
}

uint32_t TDebugProtocol::writeSetEnd() {
  indentDown();
  write_state_.pop_back();
  uint32_t size = 0;
  size += writeIndented("}");
  size += endItem();
  return size;
}

uint32_t TDebugProtocol::writeBool(const bool value) {
  return writeItem(value ? "true" : "false");
}

uint32_t TDebugProtocol::writeByte(const int8_t byte) {
  return writeItem("0x" + byte_to_hex(byte));
}

uint32_t TDebugProtocol::writeI16(const int16_t i16) {
  return writeItem(boost::lexical_cast<string>(i16));
}

uint32_t TDebugProtocol::writeI32(const int32_t i32) {
  return writeItem(boost::lexical_cast<string>(i32));
}

uint32_t TDebugProtocol::writeI64(const int64_t i64) {
  return writeItem(boost::lexical_cast<string>(i64));
}

uint32_t TDebugProtocol::writeDouble(const double dub) {
  return writeItem(boost::lexical_cast<string>(dub));
}

uint32_t TDebugProtocol::writeFloat(const float flt) {
  return writeItem(boost::lexical_cast<string>(flt));
}

uint32_t TDebugProtocol::writeString(const string& str) {
  // XXX Raw/UTF-8?

  string to_show = str;
  if (to_show.length() > (string::size_type)string_limit_) {
    to_show = str.substr(0, string_prefix_size_);
    to_show += "[...](" + boost::lexical_cast<string>(str.length()) + ")";
  }

  string output = "\"";

  for (string::const_iterator it = to_show.begin(); it != to_show.end(); ++it) {
    if (*it == '\\') {
      output += "\\\\";
    } else if (*it == '"') {
      output += "\\\"";
    } else if (std::isprint(*it)) {
      output += *it;
    } else {
      switch (*it) {
        case '\a':
          output += "\\a";
          break;
        case '\b':
          output += "\\b";
          break;
        case '\f':
          output += "\\f";
          break;
        case '\n':
          output += "\\n";
          break;
        case '\r':
          output += "\\r";
          break;
        case '\t':
          output += "\\t";
          break;
        case '\v':
          output += "\\v";
          break;
        default:
          output += "\\x";
          output += byte_to_hex(*it);
      }
    }
  }

  output += '\"';
  return writeItem(output);
}

uint32_t TDebugProtocol::writeBinary(const string& str) {
  // XXX Hex?
  return TDebugProtocol::writeString(str);
}

} // namespace protocol
} // namespace thrift
} // namespace apache
