// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

// Author: kenton@google.com (Kenton Varda)
//  Based on original Protocol Buffers design by
//  Sanjay Ghemawat, Jeff Dean, and others.

#include "google/protobuf/compiler/cpp/extension.h"

#include <string>
#include <vector>

#include "absl/log/absl_check.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_replace.h"
#include "google/protobuf/compiler/cpp/helpers.h"
#include "google/protobuf/compiler/cpp/options.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/io/printer.h"

namespace google {
namespace protobuf {
namespace compiler {
namespace cpp {

ExtensionGenerator::ExtensionGenerator(const FieldDescriptor* descriptor,
                                       const Options& options,
                                       MessageSCCAnalyzer* scc_analyzer)
    : descriptor_(descriptor), options_(options), scc_analyzer_(scc_analyzer) {
  // Construct type_traits_.
  if (descriptor_->is_repeated()) {
    type_traits_ = "Repeated";
  }

  switch (descriptor_->cpp_type()) {
    case FieldDescriptor::CPPTYPE_ENUM:
      type_traits_.append("EnumTypeTraits< ");
      type_traits_.append(ClassName(descriptor_->enum_type(), true));
      type_traits_.append(", ");
      type_traits_.append(ClassName(descriptor_->enum_type(), true));
      type_traits_.append("_IsValid>");
      break;
    case FieldDescriptor::CPPTYPE_STRING:
      type_traits_.append("StringTypeTraits");
      break;
    case FieldDescriptor::CPPTYPE_MESSAGE:
      type_traits_.append("MessageTypeTraits< ");
      type_traits_.append(ClassName(descriptor_->message_type(), true));
      type_traits_.append(" >");
      break;
    default:
      type_traits_.append("PrimitiveTypeTraits< ");
      type_traits_.append(PrimitiveTypeName(options_, descriptor_->cpp_type()));
      type_traits_.append(" >");
      break;
  }
  SetCommonMessageDataVariables(descriptor_->containing_type(), &variables_);
  variables_["extendee"] =
      QualifiedClassName(descriptor_->containing_type(), options_);
  variables_["type_traits"] = type_traits_;
  variables_["name"] = ResolveKeyword(descriptor_->name());
  variables_["constant_name"] = FieldConstantName(descriptor_);
  variables_["field_type"] =
      absl::StrCat(static_cast<int>(descriptor_->type()));
  variables_["repeated"] = descriptor_->is_repeated() ? "true" : "false";
  variables_["packed"] = descriptor_->is_packed() ? "true" : "false";
  variables_["dllexport_decl"] = options.dllexport_decl;

  std::string scope;
  if (IsScoped()) {
    scope =
        absl::StrCat(ClassName(descriptor_->extension_scope(), false), "::");
  }

  variables_["scope"] = scope;
  variables_["scoped_name"] = ExtensionName(descriptor_);
  variables_["number"] = absl::StrCat(descriptor_->number());
}

ExtensionGenerator::~ExtensionGenerator() = default;

bool ExtensionGenerator::IsScoped() const {
  return descriptor_->extension_scope() != nullptr;
}

void ExtensionGenerator::GenerateDeclaration(io::Printer* p) const {
  auto var = p->WithVars(variables_);
  auto annotate = p->WithAnnotations({{"name", descriptor_}});

  p->Emit({{"qualifier",
            // If this is a class member, it needs to be declared "static".
            // Otherwise, it needs to be "extern".  In the latter case, it
            // also needs the DLL export/import specifier.
            IsScoped() ? "static"
            : options_.dllexport_decl.empty()
                ? "extern"
                : absl::StrCat(options_.dllexport_decl, " extern")}},
          R"cc(
            static const int $constant_name$ = $number$;
            $qualifier$ ::$proto_ns$::internal::ExtensionIdentifier<
                $extendee$, ::$proto_ns$::internal::$type_traits$, $field_type$,
                $packed$>
                $name$;
          )cc");
}

void ExtensionGenerator::GenerateDefinition(io::Printer* p) {
  auto vars = p->WithVars(variables_);
  auto generate_default_string = [&] {
    if (descriptor_->cpp_type() == FieldDescriptor::CPPTYPE_STRING) {
      // We need to declare a global string which will contain the default
      // value. We cannot declare it at class scope because that would require
      // exposing it in the header which would be annoying for other reasons. So
      // we replace :: with _ in the name and declare it as a global.
      return absl::StrReplaceAll(variables_["scoped_name"], {{"::", "_"}}) +
             "_default";
    } else if (descriptor_->message_type()) {
      // We have to initialize the default instance for extensions at
      // registration time.
      return absl::StrCat("&", QualifiedDefaultInstanceName(
                                   descriptor_->message_type(), options_));
    } else {
      return DefaultValue(options_, descriptor_);
    }
  };

  auto local_var = p->WithVars({
      {"default_str", generate_default_string()},
      {"default_val", DefaultValue(options_, descriptor_)},
      {"message_type", descriptor_->message_type() != nullptr
                           ? FieldMessageTypeName(descriptor_, options_)
                           : ""},
  });
  p->Emit(
      {
          {"declare_default_str",
           [&] {
             if (descriptor_->cpp_type() != FieldDescriptor::CPPTYPE_STRING)
               return;

             // If this is a class member, it needs to be declared in its class
             // scope.
             p->Emit(R"cc(
               const std::string $default_str$($default_val$);
             )cc");
           }},
          {"declare_const_var",
           [&] {
             if (!IsScoped()) return;
             // Likewise, class members need to declare the field constant
             // variable.
             p->Emit(R"cc(
#if !defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912)
               const int $scope$$constant_name$;
#endif
             )cc");
           }},
          {"define_extension_id",
           [&] {
             p->Emit(R"cc(
               PROTOBUF_CONSTINIT$ dllexport_decl$
                   PROTOBUF_ATTRIBUTE_INIT_PRIORITY2 ::_pbi::
                       ExtensionIdentifier<$extendee$, ::_pbi::$type_traits$,
                                           $field_type$, $packed$>
                           $scoped_name$($constant_name$, $default_str$);
             )cc");
           }},
      },
      R"cc(
        $declare_default_str$;
        $declare_const_var$;
        $define_extension_id$;
      )cc");
}

bool ExtensionGenerator::WillGenerateRegistration(InitPriority priority) {
  // When not using weak descriptors we initialize everything on priority 102.
  if (!UsingImplicitWeakDescriptor(descriptor_->file(), options_)) {
    return priority == kInitPriority102;
  }
  return true;
}

void ExtensionGenerator::GenerateRegistration(io::Printer* p,
                                              InitPriority priority) {
  ABSL_CHECK(WillGenerateRegistration(priority));
  const bool using_implicit_weak_descriptors =
      UsingImplicitWeakDescriptor(descriptor_->file(), options_);
  const auto find_index = [](auto* desc) {
    const std::vector<const Descriptor*> msgs =
        FlattenMessagesInFile(desc->file());
    return absl::c_find(msgs, desc) - msgs.begin();
  };
  auto vars = p->WithVars(variables_);
  auto vars2 = p->WithVars({{
      {"extendee_table",
       DescriptorTableName(descriptor_->containing_type()->file(), options_)},
      {"extendee_index", find_index(descriptor_->containing_type())},
      {"preregister", priority == kInitPriority101},
  }});
  switch (descriptor_->cpp_type()) {
    case FieldDescriptor::CPPTYPE_ENUM:
      if (using_implicit_weak_descriptors) {
        p->Emit({{"enum_name", ClassName(descriptor_->enum_type(), true)}},
                R"cc(
                  (::_pbi::ExtensionSet::ShouldRegisterAtThisTime(
                       {{&$extendee_table$, $extendee_index$}}, $preregister$)
                       ? ::_pbi::ExtensionSet::RegisterEnumExtension(
                             ::_pbi::GetPrototypeForWeakDescriptor(
                                 &$extendee_table$, $extendee_index$, true),
                             $number$, $field_type$, $repeated$, $packed$,
                             $enum_name$_IsValid)
                       : (void)0),
                )cc");
      } else if (priority == kInitPriority102) {
        p->Emit({{"enum_name", ClassName(descriptor_->enum_type(), true)}},
                R"cc(
                  ::_pbi::ExtensionSet::RegisterEnumExtension(
                      &$extendee$::default_instance(), $number$, $field_type$,
                      $repeated$, $packed$, $enum_name$_IsValid),
                )cc");
      }

      break;
    case FieldDescriptor::CPPTYPE_MESSAGE: {
      const bool should_verify =
          // Only verify msgs.
          descriptor_->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE &&
          // Options say to verify.
          ShouldVerify(descriptor_->message_type(), options_, scc_analyzer_) &&
          ShouldVerify(descriptor_->containing_type(), options_, scc_analyzer_);
      const auto message_type = FieldMessageTypeName(descriptor_, options_);
      auto v = p->WithVars(
          {{"verify", should_verify
                          ? absl::StrCat("&", message_type, "::InternalVerify")
                          : "nullptr"},
           {"message_type", message_type},
           {"lazy", descriptor_->options().has_lazy()
                        ? descriptor_->options().lazy() ? "kLazy" : "kEager"
                        : "kUndefined"}});
      if (using_implicit_weak_descriptors) {
        p->Emit(
            {
                {"extension_table",
                 DescriptorTableName(descriptor_->message_type()->file(),
                                     options_)},
                {"extension_index", find_index(descriptor_->message_type())},
            },
            R"cc(
              (::_pbi::ExtensionSet::ShouldRegisterAtThisTime(
                   {{&$extendee_table$, $extendee_index$},
                    {&$extension_table$, $extension_index$}},
                   $preregister$)
                   ? ::_pbi::ExtensionSet::RegisterMessageExtension(
                         ::_pbi::GetPrototypeForWeakDescriptor(
                             &$extendee_table$, $extendee_index$, true),
                         $number$, $field_type$, $repeated$, $packed$,
                         ::_pbi::GetPrototypeForWeakDescriptor(
                             &$extension_table$, $extension_index$, true),
                         $verify$, ::_pbi::LazyAnnotation::$lazy$)
                   : (void)0),
            )cc");
      } else if (priority == kInitPriority102) {
        p->Emit(R"cc(
          ::_pbi::ExtensionSet::RegisterMessageExtension(
              &$extendee$::default_instance(), $number$, $field_type$,
              $repeated$, $packed$, &$message_type$::default_instance(),
              $verify$, ::_pbi::LazyAnnotation::$lazy$),
        )cc");
      }
      break;
    }

    default:
      if (using_implicit_weak_descriptors) {
        p->Emit(R"cc(
          (::_pbi::ExtensionSet::ShouldRegisterAtThisTime(
               {{&$extendee_table$, $extendee_index$}}, $preregister$)
               ? ::_pbi::ExtensionSet::RegisterExtension(
                     ::_pbi::GetPrototypeForWeakDescriptor(&$extendee_table$,
                                                           $extendee_index$,
                                                           true),
                     $number$, $field_type$, $repeated$, $packed$)
               : (void)0),
        )cc");
      } else if (priority == kInitPriority102) {
        p->Emit(
            R"cc(
              ::_pbi::ExtensionSet::RegisterExtension(
                  &$extendee$::default_instance(), $number$, $field_type$,
                  $repeated$, $packed$),
            )cc");
      }

      break;
  }
}

}  // namespace cpp
}  // namespace compiler
}  // namespace protobuf
}  // namespace google
