/*
 * Copyright (C) 2017 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 <stdio.h>
#include <stdlib.h>

#include <fstream>
#include <iostream>
#include <map>
#include <set>
#include <stack>
#include <vector>

#include <google/protobuf/compiler/code_generator.h>
#include <google/protobuf/compiler/importer.h>
#include <google/protobuf/compiler/plugin.h>
#include <google/protobuf/io/printer.h>

#include "perfetto/ext/base/string_utils.h"

namespace protozero {
namespace {

using namespace google::protobuf;
using namespace google::protobuf::compiler;
using namespace google::protobuf::io;
using perfetto::base::SplitString;
using perfetto::base::StripChars;
using perfetto::base::StripSuffix;
using perfetto::base::ToUpper;

static constexpr auto TYPE_STRING = FieldDescriptor::TYPE_STRING;
static constexpr auto TYPE_MESSAGE = FieldDescriptor::TYPE_MESSAGE;
static constexpr auto TYPE_SINT32 = FieldDescriptor::TYPE_SINT32;
static constexpr auto TYPE_SINT64 = FieldDescriptor::TYPE_SINT64;

static const char kHeader[] =
    "// DO NOT EDIT. Autogenerated by Perfetto cppgen_plugin\n";

class CppObjGenerator : public ::google::protobuf::compiler::CodeGenerator {
 public:
  CppObjGenerator();
  ~CppObjGenerator() override;

  // CodeGenerator implementation
  bool Generate(const google::protobuf::FileDescriptor* file,
                const std::string& options,
                GeneratorContext* context,
                std::string* error) const override;

 private:
  std::string GetCppType(const FieldDescriptor* field, bool constref) const;
  std::string GetProtozeroSetter(const FieldDescriptor* field) const;
  std::string GetPackedBuffer(const FieldDescriptor* field) const;
  std::string GetPackedWireType(const FieldDescriptor* field) const;

  void GenEnum(const EnumDescriptor*, Printer*) const;
  void GenEnumAliases(const EnumDescriptor*, Printer*) const;
  void GenClassDecl(const Descriptor*, Printer*) const;
  void GenClassDef(const Descriptor*, Printer*) const;

  std::vector<std::string> GetNamespaces(const FileDescriptor* file) const {
    std::string pkg = file->package() + wrapper_namespace_;
    return SplitString(pkg, ".");
  }

  template <typename T = Descriptor>
  std::string GetFullName(const T* msg, bool with_namespace = false) const {
    std::string full_type;
    full_type.append(msg->name());
    for (const Descriptor* par = msg->containing_type(); par;
         par = par->containing_type()) {
      full_type.insert(0, par->name() + "_");
    }
    if (with_namespace) {
      std::string prefix;
      for (const std::string& ns : GetNamespaces(msg->file())) {
        prefix += ns + "::";
      }
      full_type = prefix + full_type;
    }
    return full_type;
  }

  template <class T>
  bool HasSamePackage(const T* descriptor) const {
    return descriptor->file()->package() == package_;
  }

  mutable std::string wrapper_namespace_;
  mutable std::string package_;
};

CppObjGenerator::CppObjGenerator() = default;
CppObjGenerator::~CppObjGenerator() = default;

bool CppObjGenerator::Generate(const google::protobuf::FileDescriptor* file,
                               const std::string& options,
                               GeneratorContext* context,
                               std::string* error) const {
  for (const std::string& option : SplitString(options, ",")) {
    std::vector<std::string> option_pair = SplitString(option, "=");
    if (option_pair[0] == "wrapper_namespace") {
      wrapper_namespace_ =
          option_pair.size() == 2 ? "." + option_pair[1] : std::string();
    } else {
      *error = "Unknown plugin option: " + option_pair[0];
      return false;
    }
  }

  package_ = file->package();

  auto get_file_name = [](const FileDescriptor* proto) {
    return StripSuffix(proto->name(), ".proto") + ".gen";
  };

  const std::unique_ptr<ZeroCopyOutputStream> h_fstream(
      context->Open(get_file_name(file) + ".h"));
  const std::unique_ptr<ZeroCopyOutputStream> cc_fstream(
      context->Open(get_file_name(file) + ".cc"));

  // Variables are delimited by $.
  Printer h_printer(h_fstream.get(), '$');
  Printer cc_printer(cc_fstream.get(), '$');

  std::string include_guard = file->package() + "_" + file->name() + "_CPP_H_";
  include_guard = ToUpper(include_guard);
  include_guard = StripChars(include_guard, ".-/\\", '_');

  h_printer.Print(kHeader);
  h_printer.Print("#ifndef $g$\n#define $g$\n\n", "g", include_guard);
  h_printer.Print("#include <stdint.h>\n");
  h_printer.Print("#include <bitset>\n");
  h_printer.Print("#include <vector>\n");
  h_printer.Print("#include <string>\n");
  h_printer.Print("#include <type_traits>\n\n");
  h_printer.Print("#include \"perfetto/protozero/cpp_message_obj.h\"\n");
  h_printer.Print("#include \"perfetto/protozero/copyable_ptr.h\"\n");
  h_printer.Print("#include \"perfetto/base/export.h\"\n\n");

  cc_printer.Print("#include \"perfetto/protozero/gen_field_helpers.h\"\n");
  cc_printer.Print("#include \"perfetto/protozero/message.h\"\n");
  cc_printer.Print(
      "#include \"perfetto/protozero/packed_repeated_fields.h\"\n");
  cc_printer.Print("#include \"perfetto/protozero/proto_decoder.h\"\n");
  cc_printer.Print("#include \"perfetto/protozero/scattered_heap_buffer.h\"\n");
  cc_printer.Print(kHeader);
  cc_printer.Print("#if defined(__GNUC__) || defined(__clang__)\n");
  cc_printer.Print("#pragma GCC diagnostic push\n");
  cc_printer.Print("#pragma GCC diagnostic ignored \"-Wfloat-equal\"\n");
  cc_printer.Print("#endif\n");

  // Generate includes for translated types of dependencies.

  // Figure out the subset of imports that are used only for lazy fields. We
  // won't emit a C++ #include for them. This code is overly aggressive at
  // removing imports: it rules them out as soon as it sees one lazy field
  // whose type is defined in that import. A 100% correct solution would require
  // to check that *all* dependent types for a given import are lazy before
  // excluding that. In practice we don't need that because we don't use imports
  // for both lazy and non-lazy fields.
  std::set<std::string> lazy_imports;
  for (int m = 0; m < file->message_type_count(); m++) {
    const Descriptor* msg = file->message_type(m);
    for (int i = 0; i < msg->field_count(); i++) {
      const FieldDescriptor* field = msg->field(i);
      if (field->options().lazy()) {
        lazy_imports.insert(field->message_type()->file()->name());
      }
    }
  }

  // Recursively traverse all imports and turn them into #include(s).
  std::vector<const FileDescriptor*> imports_to_visit;
  std::set<const FileDescriptor*> imports_visited;
  imports_to_visit.push_back(file);

  while (!imports_to_visit.empty()) {
    const FileDescriptor* cur = imports_to_visit.back();
    imports_to_visit.pop_back();
    imports_visited.insert(cur);
    std::string base_name = StripSuffix(cur->name(), ".proto");
    cc_printer.Print("#include \"$f$.gen.h\"\n", "f", base_name);
    for (int i = 0; i < cur->dependency_count(); i++) {
      const FileDescriptor* dep = cur->dependency(i);
      if (imports_visited.count(dep) || lazy_imports.count(dep->name()))
        continue;
      imports_to_visit.push_back(dep);
    }
  }

  // Compute all nested types to generate forward declarations later.

  std::set<const Descriptor*> all_types_seen;  // All deps
  std::set<const EnumDescriptor*> all_enums_seen;

  // We track the types additionally in vectors to guarantee a stable order in
  // the generated output.
  std::vector<const Descriptor*> local_types;  // Cur .proto file only.
  std::vector<const Descriptor*> all_types;    // All deps
  std::vector<const EnumDescriptor*> local_enums;
  std::vector<const EnumDescriptor*> all_enums;

  auto add_enum = [&local_enums, &all_enums, &all_enums_seen,
                   &file](const EnumDescriptor* enum_desc) {
    if (all_enums_seen.count(enum_desc))
      return;
    all_enums_seen.insert(enum_desc);
    all_enums.push_back(enum_desc);
    if (enum_desc->file() == file)
      local_enums.push_back(enum_desc);
  };

  for (int i = 0; i < file->enum_type_count(); i++)
    add_enum(file->enum_type(i));

  std::stack<const Descriptor*> recursion_stack;
  for (int i = 0; i < file->message_type_count(); i++)
    recursion_stack.push(file->message_type(i));

  while (!recursion_stack.empty()) {
    const Descriptor* msg = recursion_stack.top();
    recursion_stack.pop();
    if (all_types_seen.count(msg))
      continue;
    all_types_seen.insert(msg);
    all_types.push_back(msg);
    if (msg->file() == file)
      local_types.push_back(msg);

    for (int i = 0; i < msg->nested_type_count(); i++)
      recursion_stack.push(msg->nested_type(i));

    for (int i = 0; i < msg->enum_type_count(); i++)
      add_enum(msg->enum_type(i));

    for (int i = 0; i < msg->field_count(); i++) {
      const FieldDescriptor* field = msg->field(i);
      if (field->has_default_value()) {
        *error = "field " + field->name() +
                 ": Explicitly declared default values are not supported";
        return false;
      }
      if (field->options().lazy() &&
          (field->is_repeated() || field->type() != TYPE_MESSAGE)) {
        *error = "[lazy=true] is supported only on non-repeated fields\n";
        return false;
      }

      if (field->type() == TYPE_MESSAGE && !field->options().lazy())
        recursion_stack.push(field->message_type());

      if (field->type() == FieldDescriptor::TYPE_ENUM)
        add_enum(field->enum_type());
    }
  }  //  while (!recursion_stack.empty())

  // Generate forward declarations in the header for proto types.
  // Note: do NOT add #includes to other generated headers (either .gen.h or
  // .pbzero.h). Doing so is extremely hard to handle at the build-system level
  // and requires propagating public_deps everywhere.
  cc_printer.Print("\n");

  // -- Begin of fwd declarations.

  // Build up the map of forward declarations.
  std::multimap<std::string /*namespace*/, std::string /*decl*/> fwd_decls;
  enum FwdType { kClass, kEnum };
  auto add_fwd_decl = [&fwd_decls](FwdType cpp_type,
                                   const std::string& full_name) {
    auto dot = full_name.rfind("::");
    PERFETTO_CHECK(dot != std::string::npos);
    auto package = full_name.substr(0, dot);
    auto name = full_name.substr(dot + 2);
    if (cpp_type == kClass) {
      fwd_decls.emplace(package, "class " + name + ";");
    } else {
      PERFETTO_CHECK(cpp_type == kEnum);
      fwd_decls.emplace(package, "enum " + name + " : int;");
    }
  };

  add_fwd_decl(kClass, "protozero::Message");
  for (const Descriptor* msg : all_types) {
    add_fwd_decl(kClass, GetFullName(msg, true));
  }
  for (const EnumDescriptor* enm : all_enums) {
    add_fwd_decl(kEnum, GetFullName(enm, true));
  }

  // Emit forward declarations grouping by package.
  std::string last_package;
  auto close_last_package = [&last_package, &h_printer] {
    if (!last_package.empty()) {
      for (const std::string& ns : SplitString(last_package, "::"))
        h_printer.Print("}  // namespace $ns$\n", "ns", ns);
      h_printer.Print("\n");
    }
  };
  for (const auto& kv : fwd_decls) {
    const std::string& package = kv.first;
    if (package != last_package) {
      close_last_package();
      last_package = package;
      for (const std::string& ns : SplitString(package, "::"))
        h_printer.Print("namespace $ns$ {\n", "ns", ns);
    }
    h_printer.Print("$decl$\n", "decl", kv.second);
  }
  close_last_package();

  // -- End of fwd declarations.

  for (const std::string& ns : GetNamespaces(file)) {
    h_printer.Print("namespace $n$ {\n", "n", ns);
    cc_printer.Print("namespace $n$ {\n", "n", ns);
  }

  // Generate declarations and definitions.
  for (const EnumDescriptor* enm : local_enums)
    GenEnum(enm, &h_printer);

  for (const Descriptor* msg : local_types) {
    GenClassDecl(msg, &h_printer);
    GenClassDef(msg, &cc_printer);
  }

  for (const std::string& ns : GetNamespaces(file)) {
    h_printer.Print("}  // namespace $n$\n", "n", ns);
    cc_printer.Print("}  // namespace $n$\n", "n", ns);
  }
  cc_printer.Print("#if defined(__GNUC__) || defined(__clang__)\n");
  cc_printer.Print("#pragma GCC diagnostic pop\n");
  cc_printer.Print("#endif\n");

  h_printer.Print("\n#endif  // $g$\n", "g", include_guard);

  return true;
}

std::string CppObjGenerator::GetCppType(const FieldDescriptor* field,
                                        bool constref) const {
  switch (field->type()) {
    case FieldDescriptor::TYPE_DOUBLE:
      return "double";
    case FieldDescriptor::TYPE_FLOAT:
      return "float";
    case FieldDescriptor::TYPE_FIXED32:
    case FieldDescriptor::TYPE_UINT32:
      return "uint32_t";
    case FieldDescriptor::TYPE_SFIXED32:
    case FieldDescriptor::TYPE_INT32:
    case FieldDescriptor::TYPE_SINT32:
      return "int32_t";
    case FieldDescriptor::TYPE_FIXED64:
    case FieldDescriptor::TYPE_UINT64:
      return "uint64_t";
    case FieldDescriptor::TYPE_SFIXED64:
    case FieldDescriptor::TYPE_SINT64:
    case FieldDescriptor::TYPE_INT64:
      return "int64_t";
    case FieldDescriptor::TYPE_BOOL:
      return "bool";
    case FieldDescriptor::TYPE_STRING:
    case FieldDescriptor::TYPE_BYTES:
      return constref ? "const std::string&" : "std::string";
    case FieldDescriptor::TYPE_MESSAGE:
      assert(!field->options().lazy());
      return constref
                 ? "const " +
                       GetFullName(field->message_type(),
                                   !HasSamePackage(field->message_type())) +
                       "&"
                 : GetFullName(field->message_type(),
                               !HasSamePackage(field->message_type()));
    case FieldDescriptor::TYPE_ENUM:
      return GetFullName(field->enum_type(),
                         !HasSamePackage(field->enum_type()));
    case FieldDescriptor::TYPE_GROUP:
      abort();
  }
  abort();  // for gcc
}

std::string CppObjGenerator::GetProtozeroSetter(
    const FieldDescriptor* field) const {
  switch (field->type()) {
    case FieldDescriptor::TYPE_BOOL:
      return "::protozero::internal::gen_helpers::SerializeTinyVarInt";
    case FieldDescriptor::TYPE_INT32:
    case FieldDescriptor::TYPE_INT64:
    case FieldDescriptor::TYPE_UINT32:
    case FieldDescriptor::TYPE_UINT64:
    case FieldDescriptor::TYPE_ENUM:
      return "::protozero::internal::gen_helpers::SerializeVarInt";
    case FieldDescriptor::TYPE_SINT32:
    case FieldDescriptor::TYPE_SINT64:
      return "::protozero::internal::gen_helpers::SerializeSignedVarInt";
    case FieldDescriptor::TYPE_FIXED32:
    case FieldDescriptor::TYPE_FIXED64:
    case FieldDescriptor::TYPE_SFIXED32:
    case FieldDescriptor::TYPE_SFIXED64:
    case FieldDescriptor::TYPE_FLOAT:
    case FieldDescriptor::TYPE_DOUBLE:
      return "::protozero::internal::gen_helpers::SerializeFixed";
    case FieldDescriptor::TYPE_STRING:
    case FieldDescriptor::TYPE_BYTES:
      return "::protozero::internal::gen_helpers::SerializeString";
    case FieldDescriptor::TYPE_GROUP:
    case FieldDescriptor::TYPE_MESSAGE:
      abort();
  }
  abort();
}

std::string CppObjGenerator::GetPackedBuffer(
    const FieldDescriptor* field) const {
  switch (field->type()) {
    case FieldDescriptor::TYPE_FIXED32:
      return "::protozero::PackedFixedSizeInt<uint32_t>";
    case FieldDescriptor::TYPE_SFIXED32:
      return "::protozero::PackedFixedSizeInt<int32_t>";
    case FieldDescriptor::TYPE_FIXED64:
      return "::protozero::PackedFixedSizeInt<uint64_t>";
    case FieldDescriptor::TYPE_SFIXED64:
      return "::protozero::PackedFixedSizeInt<int64_t>";
    case FieldDescriptor::TYPE_DOUBLE:
      return "::protozero::PackedFixedSizeInt<double>";
    case FieldDescriptor::TYPE_FLOAT:
      return "::protozero::PackedFixedSizeInt<float>";
    case FieldDescriptor::TYPE_INT32:
    case FieldDescriptor::TYPE_SINT32:
    case FieldDescriptor::TYPE_UINT32:
    case FieldDescriptor::TYPE_INT64:
    case FieldDescriptor::TYPE_UINT64:
    case FieldDescriptor::TYPE_SINT64:
    case FieldDescriptor::TYPE_BOOL:
    case FieldDescriptor::TYPE_ENUM:
      return "::protozero::PackedVarInt";
    case FieldDescriptor::TYPE_STRING:
    case FieldDescriptor::TYPE_BYTES:
    case FieldDescriptor::TYPE_MESSAGE:
    case FieldDescriptor::TYPE_GROUP:
      break;  // Will abort()
  }
  abort();
}

std::string CppObjGenerator::GetPackedWireType(
    const FieldDescriptor* field) const {
  switch (field->type()) {
    case FieldDescriptor::TYPE_FIXED32:
    case FieldDescriptor::TYPE_SFIXED32:
    case FieldDescriptor::TYPE_FLOAT:
      return "::protozero::proto_utils::ProtoWireType::kFixed32";
    case FieldDescriptor::TYPE_FIXED64:
    case FieldDescriptor::TYPE_SFIXED64:
    case FieldDescriptor::TYPE_DOUBLE:
      return "::protozero::proto_utils::ProtoWireType::kFixed64";
    case FieldDescriptor::TYPE_INT32:
    case FieldDescriptor::TYPE_SINT32:
    case FieldDescriptor::TYPE_UINT32:
    case FieldDescriptor::TYPE_INT64:
    case FieldDescriptor::TYPE_UINT64:
    case FieldDescriptor::TYPE_SINT64:
    case FieldDescriptor::TYPE_BOOL:
    case FieldDescriptor::TYPE_ENUM:
      return "::protozero::proto_utils::ProtoWireType::kVarInt";
    case FieldDescriptor::TYPE_STRING:
    case FieldDescriptor::TYPE_BYTES:
    case FieldDescriptor::TYPE_MESSAGE:
    case FieldDescriptor::TYPE_GROUP:
      break;  // Will abort()
  }
  abort();
}

void CppObjGenerator::GenEnum(const EnumDescriptor* enum_desc,
                              Printer* p) const {
  std::string full_name = GetFullName(enum_desc);

  // When generating enums, there are two cases:
  // 1. Enums nested in a message (most frequent case), e.g.:
  //    message MyMsg { enum MyEnum { FOO=1; BAR=2; } }
  // 2. Enum defined at the package level, outside of any message.
  //
  // In the case 1, the C++ code generated by the official protobuf library is:
  // enum MyEnum {  MyMsg_MyEnum_FOO=1, MyMsg_MyEnum_BAR=2 }
  // class MyMsg { static const auto FOO = MyMsg_MyEnum_FOO; ... same for BAR }
  //
  // In the case 2, the C++ code is simply:
  // enum MyEnum { FOO=1, BAR=2 }
  // Hence this |prefix| logic.
  std::string prefix = enum_desc->containing_type() ? full_name + "_" : "";
  p->Print("enum $f$ : int {\n", "f", full_name);
  for (int e = 0; e < enum_desc->value_count(); e++) {
    const EnumValueDescriptor* value = enum_desc->value(e);
    p->Print("  $p$$n$ = $v$,\n", "p", prefix, "n", value->name(), "v",
             std::to_string(value->number()));
  }
  p->Print("};\n");
}

void CppObjGenerator::GenEnumAliases(const EnumDescriptor* enum_desc,
                                     Printer* p) const {
  int min_value = std::numeric_limits<int>::max();
  int max_value = std::numeric_limits<int>::min();
  std::string min_name;
  std::string max_name;
  std::string full_name = GetFullName(enum_desc);
  for (int e = 0; e < enum_desc->value_count(); e++) {
    const EnumValueDescriptor* value = enum_desc->value(e);
    p->Print("static constexpr auto $n$ = $f$_$n$;\n", "f", full_name, "n",
             value->name());
    if (value->number() < min_value) {
      min_value = value->number();
      min_name = full_name + "_" + value->name();
    }
    if (value->number() > max_value) {
      max_value = value->number();
      max_name = full_name + "_" + value->name();
    }
  }
  p->Print("static constexpr auto $n$_MIN = $m$;\n", "n", enum_desc->name(),
           "m", min_name);
  p->Print("static constexpr auto $n$_MAX = $m$;\n", "n", enum_desc->name(),
           "m", max_name);
}

void CppObjGenerator::GenClassDecl(const Descriptor* msg, Printer* p) const {
  std::string full_name = GetFullName(msg);
  p->Print(
      "\nclass PERFETTO_EXPORT_COMPONENT $n$ : public "
      "::protozero::CppMessageObj {\n",
      "n", full_name);
  p->Print(" public:\n");
  p->Indent();

  // Do a first pass to generate aliases for nested types.
  // e.g., using Foo = Parent_Foo;
  for (int i = 0; i < msg->nested_type_count(); i++) {
    const Descriptor* nested_msg = msg->nested_type(i);
    p->Print("using $n$ = $f$;\n", "n", nested_msg->name(), "f",
             GetFullName(nested_msg));
  }
  for (int i = 0; i < msg->enum_type_count(); i++) {
    const EnumDescriptor* nested_enum = msg->enum_type(i);
    p->Print("using $n$ = $f$;\n", "n", nested_enum->name(), "f",
             GetFullName(nested_enum));
    GenEnumAliases(nested_enum, p);
  }

  // Generate constants with field numbers.
  p->Print("enum FieldNumbers {\n");
  for (int i = 0; i < msg->field_count(); i++) {
    const FieldDescriptor* field = msg->field(i);
    std::string name = field->camelcase_name();
    name[0] = perfetto::base::Uppercase(name[0]);
    p->Print("  k$n$FieldNumber = $num$,\n", "n", name, "num",
             std::to_string(field->number()));
  }
  p->Print("};\n\n");

  p->Print("$n$();\n", "n", full_name);
  p->Print("~$n$() override;\n", "n", full_name);
  p->Print("$n$($n$&&) noexcept;\n", "n", full_name);
  p->Print("$n$& operator=($n$&&);\n", "n", full_name);
  p->Print("$n$(const $n$&);\n", "n", full_name);
  p->Print("$n$& operator=(const $n$&);\n", "n", full_name);
  p->Print("bool operator==(const $n$&) const;\n", "n", full_name);
  p->Print(
      "bool operator!=(const $n$& other) const { return !(*this == other); }\n",
      "n", full_name);
  p->Print("\n");

  std::string proto_type = GetFullName(msg, true);
  p->Print("bool ParseFromArray(const void*, size_t) override;\n");
  p->Print("std::string SerializeAsString() const override;\n");
  p->Print("std::vector<uint8_t> SerializeAsArray() const override;\n");
  p->Print("void Serialize(::protozero::Message*) const;\n");

  // Generate accessors.
  for (int i = 0; i < msg->field_count(); i++) {
    const FieldDescriptor* field = msg->field(i);
    auto set_bit = "_has_field_.set(" + std::to_string(field->number()) + ")";
    p->Print("\n");
    if (field->options().lazy()) {
      p->Print("const std::string& $n$_raw() const { return $n$_; }\n", "n",
               field->lowercase_name());
      p->Print(
          "void set_$n$_raw(const std::string& raw) { $n$_ = raw; $s$; }\n",
          "n", field->lowercase_name(), "s", set_bit);
    } else if (!field->is_repeated()) {
      p->Print("bool has_$n$() const { return _has_field_[$bit$]; }\n", "n",
               field->lowercase_name(), "bit", std::to_string(field->number()));
      if (field->type() == TYPE_MESSAGE) {
        p->Print("$t$ $n$() const { return *$n$_; }\n", "t",
                 GetCppType(field, true), "n", field->lowercase_name());
        p->Print("$t$* mutable_$n$() { $s$; return $n$_.get(); }\n", "t",
                 GetCppType(field, false), "n", field->lowercase_name(), "s",
                 set_bit);
      } else {
        p->Print("$t$ $n$() const { return $n$_; }\n", "t",
                 GetCppType(field, true), "n", field->lowercase_name());
        p->Print("void set_$n$($t$ value) { $n$_ = value; $s$; }\n", "t",
                 GetCppType(field, true), "n", field->lowercase_name(), "s",
                 set_bit);
        if (field->type() == FieldDescriptor::TYPE_BYTES) {
          p->Print(
              "void set_$n$(const void* p, size_t s) { "
              "$n$_.assign(reinterpret_cast<const char*>(p), s); $s$; }\n",
              "n", field->lowercase_name(), "s", set_bit);
        }
      }
    } else {  // is_repeated()
      p->Print("const std::vector<$t$>& $n$() const { return $n$_; }\n", "t",
               GetCppType(field, false), "n", field->lowercase_name());
      p->Print("std::vector<$t$>* mutable_$n$() { return &$n$_; }\n", "t",
               GetCppType(field, false), "n", field->lowercase_name());

      // Generate accessors for repeated message types in the .cc file so that
      // the header doesn't depend on the full definition of all nested types.
      if (field->type() == TYPE_MESSAGE) {
        p->Print("int $n$_size() const;\n", "t", GetCppType(field, false), "n",
                 field->lowercase_name());
        p->Print("void clear_$n$();\n", "n", field->lowercase_name());
        p->Print("$t$* add_$n$();\n", "t", GetCppType(field, false), "n",
                 field->lowercase_name());
      } else {  // Primitive type.
        p->Print(
            "int $n$_size() const { return static_cast<int>($n$_.size()); }\n",
            "t", GetCppType(field, false), "n", field->lowercase_name());
        p->Print("void clear_$n$() { $n$_.clear(); }\n", "n",
                 field->lowercase_name());
        p->Print("void add_$n$($t$ value) { $n$_.emplace_back(value); }\n", "t",
                 GetCppType(field, false), "n", field->lowercase_name());
        // TODO(primiano): this should be done only for TYPE_MESSAGE.
        // Unfortuntely we didn't realize before and now we have a bunch of code
        // that does: *msg->add_int_value() = 42 instead of
        // msg->add_int_value(42).
        p->Print(
            "$t$* add_$n$() { $n$_.emplace_back(); return &$n$_.back(); }\n",
            "t", GetCppType(field, false), "n", field->lowercase_name());
      }
    }
  }
  p->Outdent();
  p->Print("\n private:\n");
  p->Indent();

  // Generate fields.
  int max_field_id = 1;
  for (int i = 0; i < msg->field_count(); i++) {
    const FieldDescriptor* field = msg->field(i);
    max_field_id = std::max(max_field_id, field->number());
    if (field->options().lazy()) {
      p->Print("std::string $n$_;  // [lazy=true]\n", "n",
               field->lowercase_name());
    } else if (!field->is_repeated()) {
      std::string type = GetCppType(field, false);
      if (field->type() == TYPE_MESSAGE) {
        type = "::protozero::CopyablePtr<" + type + ">";
        p->Print("$t$ $n$_;\n", "t", type, "n", field->lowercase_name());
      } else {
        p->Print("$t$ $n$_{};\n", "t", type, "n", field->lowercase_name());
      }
    } else {  // is_repeated()
      p->Print("std::vector<$t$> $n$_;\n", "t", GetCppType(field, false), "n",
               field->lowercase_name());
    }
  }
  p->Print("\n");
  p->Print("// Allows to preserve unknown protobuf fields for compatibility\n");
  p->Print("// with future versions of .proto files.\n");
  p->Print("std::string unknown_fields_;\n");

  p->Print("\nstd::bitset<$id$> _has_field_{};\n", "id",
           std::to_string(max_field_id + 1));

  p->Outdent();
  p->Print("};\n\n");
}

void CppObjGenerator::GenClassDef(const Descriptor* msg, Printer* p) const {
  p->Print("\n");
  std::string full_name = GetFullName(msg);

  p->Print("$n$::$n$() = default;\n", "n", full_name);
  p->Print("$n$::~$n$() = default;\n", "n", full_name);
  p->Print("$n$::$n$(const $n$&) = default;\n", "n", full_name);
  p->Print("$n$& $n$::operator=(const $n$&) = default;\n", "n", full_name);
  p->Print("$n$::$n$($n$&&) noexcept = default;\n", "n", full_name);
  p->Print("$n$& $n$::operator=($n$&&) = default;\n", "n", full_name);

  p->Print("\n");

  // Comparison operator
  p->Print("bool $n$::operator==(const $n$& other) const {\n", "n", full_name);
  p->Indent();

  p->Print(
      "return ::protozero::internal::gen_helpers::EqualsField(unknown_fields_, "
      "other.unknown_fields_)");
  for (int i = 0; i < msg->field_count(); i++)
    p->Print(
        "\n && ::protozero::internal::gen_helpers::EqualsField($n$_, "
        "other.$n$_)",
        "n", msg->field(i)->lowercase_name());
  p->Print(";");
  p->Outdent();
  p->Print("\n}\n\n");

  // Accessors for repeated message fields.
  for (int i = 0; i < msg->field_count(); i++) {
    const FieldDescriptor* field = msg->field(i);
    if (field->options().lazy() || !field->is_repeated() ||
        field->type() != TYPE_MESSAGE) {
      continue;
    }
    p->Print(
        "int $c$::$n$_size() const { return static_cast<int>($n$_.size()); }\n",
        "c", full_name, "t", GetCppType(field, false), "n",
        field->lowercase_name());
    p->Print("void $c$::clear_$n$() { $n$_.clear(); }\n", "c", full_name, "n",
             field->lowercase_name());
    p->Print(
        "$t$* $c$::add_$n$() { $n$_.emplace_back(); return &$n$_.back(); }\n",
        "c", full_name, "t", GetCppType(field, false), "n",
        field->lowercase_name());
  }

  std::string proto_type = GetFullName(msg, true);

  // Generate the ParseFromArray() method definition.
  p->Print("bool $f$::ParseFromArray(const void* raw, size_t size) {\n", "f",
           full_name);
  p->Indent();
  for (int i = 0; i < msg->field_count(); i++) {
    const FieldDescriptor* field = msg->field(i);
    if (field->is_repeated())
      p->Print("$n$_.clear();\n", "n", field->lowercase_name());
  }
  p->Print("unknown_fields_.clear();\n");
  p->Print("bool packed_error = false;\n");
  p->Print("\n");
  p->Print("::protozero::ProtoDecoder dec(raw, size);\n");
  p->Print("for (auto field = dec.ReadField(); field.valid(); ");
  p->Print("field = dec.ReadField()) {\n");
  p->Indent();
  p->Print("if (field.id() < _has_field_.size()) {\n");
  p->Print("  _has_field_.set(field.id());\n");
  p->Print("}\n");
  p->Print("switch (field.id()) {\n");
  p->Indent();
  for (int i = 0; i < msg->field_count(); i++) {
    const FieldDescriptor* field = msg->field(i);
    p->Print("case $id$ /* $n$ */:\n", "id", std::to_string(field->number()),
             "n", field->lowercase_name());
    p->Indent();
    if (field->options().lazy()) {
      p->Print(
          "::protozero::internal::gen_helpers::DeserializeString(field, "
          "&$n$_);\n",
          "n", field->lowercase_name());
    } else {
      std::string statement;
      if (field->type() == TYPE_MESSAGE) {
        statement = "$rval$.ParseFromArray(field.data(), field.size());\n";
      } else {
        if (field->type() == TYPE_SINT32 || field->type() == TYPE_SINT64) {
          // sint32/64 fields are special and need to be zig-zag-decoded.
          statement = "field.get_signed(&$rval$);\n";
        } else if (field->type() == TYPE_STRING) {
          statement =
              "::protozero::internal::gen_helpers::DeserializeString(field, "
              "&$rval$);\n";
        } else {
          statement = "field.get(&$rval$);\n";
        }
      }
      if (field->is_packed()) {
        PERFETTO_CHECK(field->is_repeated());
        if (field->type() == TYPE_SINT32 || field->type() == TYPE_SINT64) {
          PERFETTO_FATAL("packed signed (zigzag) fields are not supported");
        }
        p->Print(
            "if "
            "(!::protozero::internal::gen_helpers::DeserializePackedRepeated"
            "<$w$, $c$>(field, &$n$_)) {\n",
            "w", GetPackedWireType(field), "c", GetCppType(field, false), "n",
            field->lowercase_name());
        p->Print("  packed_error = true;");
        p->Print("}\n");
      } else if (field->is_repeated()) {
        p->Print("$n$_.emplace_back();\n", "n", field->lowercase_name());
        p->Print(statement.c_str(), "rval",
                 field->lowercase_name() + "_.back()");
      } else if (field->type() == TYPE_MESSAGE) {
        p->Print(statement.c_str(), "rval",
                 "(*" + field->lowercase_name() + "_)");
      } else {
        p->Print(statement.c_str(), "rval", field->lowercase_name() + "_");
      }
    }
    p->Print("break;\n");
    p->Outdent();
  }  // for (field)
  p->Print("default:\n");
  p->Print("  field.SerializeAndAppendTo(&unknown_fields_);\n");
  p->Print("  break;\n");
  p->Outdent();
  p->Print("}\n");  // switch(field.id)
  p->Outdent();
  p->Print("}\n");                                           // for(field)
  p->Print("return !packed_error && !dec.bytes_left();\n");  // for(field)
  p->Outdent();
  p->Print("}\n\n");

  // Generate the SerializeAsString() method definition.
  p->Print("std::string $f$::SerializeAsString() const {\n", "f", full_name);
  p->Indent();
  p->Print("::protozero::internal::gen_helpers::MessageSerializer msg;\n");
  p->Print("Serialize(msg.get());\n");
  p->Print("return msg.SerializeAsString();\n");
  p->Outdent();
  p->Print("}\n\n");

  // Generate the SerializeAsArray() method definition.
  p->Print("std::vector<uint8_t> $f$::SerializeAsArray() const {\n", "f",
           full_name);
  p->Indent();
  p->Print("::protozero::internal::gen_helpers::MessageSerializer msg;\n");
  p->Print("Serialize(msg.get());\n");
  p->Print("return msg.SerializeAsArray();\n");
  p->Outdent();
  p->Print("}\n\n");

  // Generate the Serialize() method that writes the fields into the passed
  // protozero |msg| write-only interface |msg|.
  p->Print("void $f$::Serialize(::protozero::Message* msg) const {\n", "f",
           full_name);
  p->Indent();
  for (int i = 0; i < msg->field_count(); i++) {
    const FieldDescriptor* field = msg->field(i);
    std::map<std::string, std::string> args;
    args["id"] = std::to_string(field->number());
    args["n"] = field->lowercase_name();
    p->Print(args, "// Field $id$: $n$\n");
    if (field->is_packed()) {
      PERFETTO_CHECK(field->is_repeated());
      p->Print("{\n");
      p->Indent();
      p->Print("$p$ pack;\n", "p", GetPackedBuffer(field));
      p->Print(args, "for (auto& it : $n$_)\n");
      p->Print(args, "  pack.Append(it);\n");
      p->Print(args, "msg->AppendBytes($id$, pack.data(), pack.size());\n");
      p->Outdent();
      p->Print("}\n");
    } else {
      if (field->is_repeated()) {
        p->Print(args, "for (auto& it : $n$_) {\n");
        args["lvalue"] = "it";
        args["rvalue"] = "it";
      } else {
        p->Print(args, "if (_has_field_[$id$]) {\n");
        args["lvalue"] = "(*" + field->lowercase_name() + "_)";
        args["rvalue"] = field->lowercase_name() + "_";
      }
      p->Indent();
      if (field->options().lazy()) {
        p->Print(args, "msg->AppendString($id$, $rvalue$);\n");
      } else if (field->type() == TYPE_MESSAGE) {
        p->Print(args,
                 "$lvalue$.Serialize("
                 "msg->BeginNestedMessage<::protozero::Message>($id$));\n");
      } else {
        args["setter"] = GetProtozeroSetter(field);
        p->Print(args, "$setter$($id$, $rvalue$, msg);\n");
      }
      p->Outdent();
      p->Print("}\n");
    }

    p->Print("\n");
  }  // for (field)
  p->Print(
      "protozero::internal::gen_helpers::SerializeUnknownFields(unknown_fields_"
      ", msg);\n");
  p->Outdent();
  p->Print("}\n\n");
}

}  // namespace
}  // namespace protozero

int main(int argc, char** argv) {
  ::protozero::CppObjGenerator generator;
  return google::protobuf::compiler::PluginMain(argc, argv, &generator);
}
