/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * 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 <vector>

#include <google/protobuf/descriptor.h>
#include <google/protobuf/descriptor.pb.h>
#include <google/protobuf/message.h>

#include "perfetto/base/logging.h"
#include "perfetto/ext/base/string_utils.h"
#include "src/trace_processor/util/proto_to_json.h"

namespace perfetto {
namespace trace_processor {
namespace proto_to_json {

namespace {

std::string QuoteAndEscapeJsonString(const std::string& raw) {
  std::string ret;
  for (auto it = raw.cbegin(); it != raw.cend(); it++) {
    char c = *it;
    switch (c) {
      case '"':
        // Double quote needs to be escaped.
        ret += "\\\"";
        break;
      case '\n':
        // Escape new line specially because it appears often and so is worth
        // treating specially.
        ret += "\\n";
        break;
      default:
        if (c < 0x20) {
          // All 32 ASCII control codes need to be escaped. Instead of using the
          // short forms, we just always use \u escape sequences instead to make
          // things simpler.
          ret += "\\u00";

          // Print |c| as a hex character. We reserve 3 bytes of space: 2 for
          // the hex code and one for the null terminator.
          base::StackString<3> buf("%02X", c);
          ret += buf.c_str();
        } else {
          // Everything else can be passed through directly.
          ret += c;
        }
        break;
    }
  }
  return '"' + ret + '"';
}

std::string FieldToJson(const google::protobuf::Message& message,
                        const google::protobuf::FieldDescriptor* field_desc,
                        int idx,
                        uint32_t indent) {
  using google::protobuf::FieldDescriptor;

  const google::protobuf::Reflection* ref = message.GetReflection();
  bool is_repeated = field_desc->is_repeated();
  switch (field_desc->cpp_type()) {
    case FieldDescriptor::CppType::CPPTYPE_BOOL:
      return std::to_string(is_repeated
                                ? ref->GetRepeatedBool(message, field_desc, idx)
                                : ref->GetBool(message, field_desc));
    case FieldDescriptor::CppType::CPPTYPE_ENUM:
      return QuoteAndEscapeJsonString(
          is_repeated ? ref->GetRepeatedEnum(message, field_desc, idx)->name()
                      : ref->GetEnum(message, field_desc)->name());
    case FieldDescriptor::CppType::CPPTYPE_FLOAT:
      return std::to_string(
          is_repeated
              ? static_cast<double>(
                    ref->GetRepeatedFloat(message, field_desc, idx))
              : static_cast<double>(ref->GetFloat(message, field_desc)));
    case FieldDescriptor::CppType::CPPTYPE_INT32:
      return std::to_string(
          is_repeated ? ref->GetRepeatedInt32(message, field_desc, idx)
                      : ref->GetInt32(message, field_desc));
    case FieldDescriptor::CppType::CPPTYPE_INT64:
      return std::to_string(
          is_repeated ? ref->GetRepeatedInt64(message, field_desc, idx)
                      : ref->GetInt64(message, field_desc));
    case FieldDescriptor::CppType::CPPTYPE_DOUBLE:
      return std::to_string(
          is_repeated ? ref->GetRepeatedDouble(message, field_desc, idx)
                      : ref->GetDouble(message, field_desc));
    case FieldDescriptor::CppType::CPPTYPE_STRING:
      return QuoteAndEscapeJsonString(
          is_repeated ? ref->GetRepeatedString(message, field_desc, idx)
                      : ref->GetString(message, field_desc));
    case FieldDescriptor::CppType::CPPTYPE_UINT32:
      return std::to_string(
          is_repeated ? ref->GetRepeatedUInt32(message, field_desc, idx)
                      : ref->GetUInt32(message, field_desc));
    case FieldDescriptor::CppType::CPPTYPE_UINT64:
      return std::to_string(
          is_repeated ? ref->GetRepeatedInt64(message, field_desc, idx)
                      : ref->GetInt64(message, field_desc));
    case FieldDescriptor::CppType::CPPTYPE_MESSAGE:
      return MessageToJson(
          is_repeated ? ref->GetRepeatedMessage(message, field_desc, idx)
                      : ref->GetMessage(message, field_desc),
          indent);
  }
  PERFETTO_FATAL("For GCC");
}

std::string RepeatedFieldValuesToJson(
    const google::protobuf::Message& message,
    const google::protobuf::FieldDescriptor* field_desc,
    uint32_t indent) {
  const google::protobuf::Reflection* ref = message.GetReflection();
  std::string ret;
  for (int i = 0; i < ref->FieldSize(message, field_desc); ++i) {
    if (i != 0) {
      ret += ",";
    }
    ret += "\n" + std::string(indent, ' ') +
           FieldToJson(message, field_desc, i, indent);
  }
  return ret;
}

std::string MessageFieldsToJson(const google::protobuf::Message& message,
                                uint32_t indent) {
  const google::protobuf::Reflection* ref = message.GetReflection();
  std::vector<const google::protobuf::FieldDescriptor*> field_descs;
  ref->ListFields(message, &field_descs);

  std::string ret;
  uint32_t next_field_idx = 0;
  for (const google::protobuf::FieldDescriptor* field_desc : field_descs) {
    if (next_field_idx++ != 0) {
      ret += ",";
    }
    std::string value;
    if (field_desc->is_repeated()) {
      value = "[" + RepeatedFieldValuesToJson(message, field_desc, indent + 2) +
              "\n" + std::string(indent, ' ') + "]";
    } else {
      value = FieldToJson(message, field_desc, 0, indent);
    }
    const std::string& name = field_desc->is_extension()
                                  ? field_desc->full_name()
                                  : field_desc->name();
    ret += "\n" + std::string(indent, ' ') + "\"" + name + "\": " + value;
  }
  return ret;
}

// This is a class helps avoid the situation where every function has to take
// field_options_prototype as an argument, which becomes distracting.
class OptionsConverter {
 public:
  explicit OptionsConverter(
      const google::protobuf::Message* field_options_prototype)
      : field_options_prototype_(field_options_prototype) {}

  // Prints all field options for non-empty fields of a message. Example:
  // --- Message definitions ---
  // FooMessage {
  //   repeated int64 foo = 1 [op1 = val1, op2 = val2];
  //   optional BarMessage bar = 2 [op3 = val3];
  // }
  //
  // BarMessage {
  //   optional int64 baz = 1 [op4 = val4];
  // }
  // --- MessageInstance ---
  // foo_msg = {  // (As JSON)
  //   foo: [23, 24, 25],
  //   bar: {
  //     baz: 42
  //   }
  // }
  // --- Output of MessageFieldOptionsToJson(foo_msg) ---
  //   foo: {
  //     __field_options: {
  //       op1: val1,
  //       op2: val2,
  //     },
  //     __repeated: true
  //   }
  //   bar: {
  //     __field_options: {
  //       op3 = val3,
  //     },
  //     baz: {
  //       __field_options: {
  //         op4 = val4
  //       },
  //     }
  //   }
  // --- Notes ---
  // This function does not produce the surrounding braces for easier use in
  // recursive use cases. The caller needs to surround the output with braces.
  std::string MessageFieldOptionsToJson(
      const google::protobuf::Message& message,
      uint32_t indent) {
    using google::protobuf::FieldDescriptor;
    std::vector<const FieldDescriptor*> field_descs;
    message.GetReflection()->ListFields(message, &field_descs);
    std::vector<std::string> field_outputs;
    for (auto* field_desc : field_descs) {
      std::vector<std::string> field_entries;
      if (HasFieldOptions(field_desc)) {
        std::string options_entry;
        options_entry +=
            std::string(indent + 2, ' ') + R"("__field_options": )";
        options_entry += FieldOptionsToJson(field_desc, indent + 4);
        field_entries.push_back(std::move(options_entry));
      }
      std::string nested_fields =
          NestedMessageFieldOptionsToJson(message, field_desc, indent + 2);
      if (!nested_fields.empty()) {
        field_entries.push_back(std::move(nested_fields));
      }
      // We don't output annotations for a field if that field and all its
      // descendants have no field options.
      if (!field_entries.empty()) {
        if (field_desc->is_repeated()) {
          field_entries.push_back(std::string(indent, ' ') +
                                  R"("__repeated": true)");
        }
        std::string field_output;
        const std::string& name = field_desc->is_extension()
                                      ? field_desc->full_name()
                                      : field_desc->name();
        field_output += std::string(indent, ' ') + "\"" + name + "\": {\n";
        field_output += base::Join(field_entries, ",\n") + "\n";
        field_output += std::string(indent, ' ') + "}";
        field_outputs.push_back(std::move(field_output));
      }
    }
    return base::Join(field_outputs, ",\n");
  }

 private:
  static bool HasFieldOptions(
      const google::protobuf::FieldDescriptor* field_desc) {
    return field_desc->options().ByteSizeLong() > 0;
  }

  std::string NestedMessageFieldOptionsToJson(
      const google::protobuf::Message& message,
      const google::protobuf::FieldDescriptor* field_desc,
      uint32_t indent) {
    using google::protobuf::FieldDescriptor;
    if (field_desc->cpp_type() != FieldDescriptor::CppType::CPPTYPE_MESSAGE)
      return "";
    const auto* reflection = message.GetReflection();
    const google::protobuf::Message& nested_message =
        field_desc->is_repeated()
            ? reflection->GetRepeatedMessage(message, field_desc, 0)
            : reflection->GetMessage(message, field_desc);
    return MessageFieldOptionsToJson(nested_message, indent);
  }

  std::string FieldOptionsToJson(
      const google::protobuf::FieldDescriptor* field_desc,
      uint32_t indent) {
    PERFETTO_DCHECK(HasFieldOptions(field_desc));
    std::unique_ptr<google::protobuf::Message> options(
        field_options_prototype_->New());
    // Field option extensions are compiled at runtime as opposed to being
    // compiled in and being part of the generated pool, so the field option
    // must be re-parsed as a dynamic message for the extensions to show up. If
    // we do not do this, the extension fields remain "unknown fields" to the
    // reflection API.
    options->ParseFromString(field_desc->options().SerializeAsString());
    return MessageToJson(*options, indent);
  }

  const google::protobuf::Message* field_options_prototype_;
};

}  // namespace

std::string MessageToJson(const google::protobuf::Message& message,
                          uint32_t indent) {
  return "{" + MessageFieldsToJson(message, indent + 2) + '\n' +
         std::string(indent, ' ') + "}";
}

std::string MessageToJsonWithAnnotations(
    const google::protobuf::Message& message,
    const google::protobuf::Message* field_options_prototype,
    uint32_t indent) {
  std::string ret;
  OptionsConverter options_converter(field_options_prototype);

  ret = "{" + MessageFieldsToJson(message, indent + 2);
  std::string annotation_fields =
      options_converter.MessageFieldOptionsToJson(message, indent + 4);
  if (annotation_fields.empty()) {
    ret += "\n";
  } else {
    ret += ",\n";
    ret += std::string(indent + 2, ' ') + "\"__annotations\": {\n";
    ret += annotation_fields + "\n";
    ret += std::string(indent + 2, ' ') + "}\n";
  }
  ret += std::string(indent, ' ') + "}\n";
  return ret;
}

}  // namespace proto_to_json
}  // namespace trace_processor
}  // namespace perfetto
