// 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/service.h"

#include <string>

#include "absl/strings/str_cat.h"
#include "google/protobuf/compiler/cpp/helpers.h"
#include "google/protobuf/io/printer.h"

namespace google {
namespace protobuf {
namespace compiler {
namespace cpp {
void ServiceGenerator::GenerateDeclarations(io::Printer* printer) {
  auto vars = printer->WithVars(&vars_);
  printer->Emit(
      {
          {"virts", [&] { GenerateMethodSignatures(kVirtual, printer); }},
          {"impls", [&] { GenerateMethodSignatures(kNonVirtual, printer); }},
      },
      R"cc(
        class $classname$_Stub;
        class $dllexport_decl $$classname$ : public ::$proto_ns$::Service {
         protected:
          $classname$() = default;

         public:
          using Stub = $classname$_Stub;

          $classname$(const $classname$&) = delete;
          $classname$& operator=(const $classname$&) = delete;
          virtual ~$classname$() = default;

          static const ::$proto_ns$::ServiceDescriptor* descriptor();

          $virts$;

          // implements Service ----------------------------------------------
          const ::$proto_ns$::ServiceDescriptor* GetDescriptor() override;

          void CallMethod(const ::$proto_ns$::MethodDescriptor* method,
                          ::$proto_ns$::RpcController* controller,
                          const ::$proto_ns$::Message* request,
                          ::$proto_ns$::Message* response,
                          ::google::protobuf::Closure* done) override;

          const ::$proto_ns$::Message& GetRequestPrototype(
              const ::$proto_ns$::MethodDescriptor* method) const override;

          const ::$proto_ns$::Message& GetResponsePrototype(
              const ::$proto_ns$::MethodDescriptor* method) const override;
        };

        class $dllexport_decl $$classname$_Stub final : public $classname$ {
         public:
          $classname$_Stub(::$proto_ns$::RpcChannel* channel);
          $classname$_Stub(::$proto_ns$::RpcChannel* channel,
                           ::$proto_ns$::Service::ChannelOwnership ownership);

          $classname$_Stub(const $classname$_Stub&) = delete;
          $classname$_Stub& operator=(const $classname$_Stub&) = delete;

          ~$classname$_Stub() override;

          inline ::$proto_ns$::RpcChannel* channel() { return channel_; }

          // implements $classname$ ------------------------------------------
          $impls$;

         private:
          ::$proto_ns$::RpcChannel* channel_;
          bool owns_channel_;
        };
      )cc");
}

void ServiceGenerator::GenerateMethodSignatures(VirtualOrNot virtual_or_not,
                                                io::Printer* printer) {
  for (int i = 0; i < descriptor_->method_count(); ++i) {
    const MethodDescriptor* method = descriptor_->method(i);

    printer->Emit(
        {
            {"name", method->name()},
            {"input", QualifiedClassName(method->input_type(), *options_)},
            {"output", QualifiedClassName(method->output_type(), *options_)},
            {"virtual", virtual_or_not == kVirtual ? "virtual" : ""},
            {"override", virtual_or_not != kVirtual ? "override" : ""},
        },
        // No cc, clang-format does not format this string well due to the
        // $ override$ substitution.
        R"(
          $virtual $void $name$(::$proto_ns$::RpcController* controller,
                                const $input$* request,
                                $output$* response,
                                ::google::protobuf::Closure* done)$ override$;
        )");
  }
}

// ===================================================================

void ServiceGenerator::GenerateImplementation(io::Printer* printer) {
  auto vars = printer->WithVars(&vars_);
  printer->Emit(
      {
          {"index", index_in_metadata_},
          {"no_impl_methods", [&] { GenerateNotImplementedMethods(printer); }},
          {"call_method", [&] { GenerateCallMethod(printer); }},
          {"get_request", [&] { GenerateGetPrototype(kRequest, printer); }},
          {"get_response", [&] { GenerateGetPrototype(kResponse, printer); }},
          {"stub_methods", [&] { GenerateStubMethods(printer); }},
      },
      R"cc(
        const ::$proto_ns$::ServiceDescriptor* $classname$::descriptor() {
          ::$proto_ns$::internal::AssignDescriptors(&$desc_table$);
          return $file_level_service_descriptors$[$index$];
        }

        const ::$proto_ns$::ServiceDescriptor* $classname$::GetDescriptor() {
          return descriptor();
        }

        $no_impl_methods$;

        $call_method$;

        $get_request$;

        $get_response$;

        $classname$_Stub::$classname$_Stub(::$proto_ns$::RpcChannel* channel)
            : channel_(channel), owns_channel_(false) {}

        $classname$_Stub::$classname$_Stub(
            ::$proto_ns$::RpcChannel* channel,
            ::$proto_ns$::Service::ChannelOwnership ownership)
            : channel_(channel),
              owns_channel_(ownership ==
                            ::$proto_ns$::Service::STUB_OWNS_CHANNEL) {}

        $classname$_Stub::~$classname$_Stub() {
          if (owns_channel_) delete channel_;
        }

        $stub_methods$;
      )cc");
}

void ServiceGenerator::GenerateNotImplementedMethods(io::Printer* printer) {
  for (int i = 0; i < descriptor_->method_count(); ++i) {
    const MethodDescriptor* method = descriptor_->method(i);

    printer->Emit(
        {
            {"name", method->name()},
            {"input", QualifiedClassName(method->input_type(), *options_)},
            {"output", QualifiedClassName(method->output_type(), *options_)},
        },
        R"cc(
          void $classname$::$name$(::$proto_ns$::RpcController* controller,
                                   const $input$*, $output$*, ::google::protobuf::Closure* done) {
            controller->SetFailed("Method $name$() not implemented.");
            done->Run();
          }
        )cc");
  }
}

void ServiceGenerator::GenerateCallMethod(io::Printer* printer) {
  printer->Emit(
      {
          {"index", absl::StrCat(index_in_metadata_)},
          {"cases", [&] { GenerateCallMethodCases(printer); }},
      },
      R"cc(
        void $classname$::CallMethod(
            const ::$proto_ns$::MethodDescriptor* method,
            ::$proto_ns$::RpcController* controller,
            const ::$proto_ns$::Message* request,
            ::$proto_ns$::Message* response, ::google::protobuf::Closure* done) {
          ABSL_DCHECK_EQ(method->service(), $file_level_service_descriptors$[$index$]);
          switch (method->index()) {
            $cases$;

            default:
              ABSL_LOG(FATAL) << "Bad method index; this should never happen.";
              break;
          }
        }
      )cc");
}

void ServiceGenerator::GenerateGetPrototype(RequestOrResponse which,
                                            io::Printer* printer) {
  printer->Emit(
      {
          {"which", which == kRequest ? "Request" : "Response"},
          {"which_type", which == kRequest ? "input" : "output"},
          {"cases",
           [&] {
             for (int i = 0; i < descriptor_->method_count(); ++i) {
               const MethodDescriptor* method = descriptor_->method(i);
               const Descriptor* type = which == kRequest
                                            ? method->input_type()
                                            : method->output_type();

               printer->Emit(
                   {
                       {"index", absl::StrCat(i)},
                       {"type", QualifiedClassName(type, *options_)},
                   },
                   R"cc(
                     case $index$:
                       return $type$::default_instance();
                   )cc");
             }
           }},
      },
      R"cc(
        const ::$proto_ns$::Message& $classname$::Get$which$Prototype(
            const ::$proto_ns$::MethodDescriptor* method) const {
          ABSL_DCHECK_EQ(method->service(), descriptor());
          switch (method->index()) {
            $cases$;

            default:
              ABSL_LOG(FATAL) << "Bad method index; this should never happen.";
              return *::$proto_ns$::MessageFactory::generated_factory()
                          ->GetPrototype(method->$which_type$_type());
          }
        }
      )cc");
}

void ServiceGenerator::GenerateCallMethodCases(io::Printer* printer) {
  for (int i = 0; i < descriptor_->method_count(); ++i) {
    const MethodDescriptor* method = descriptor_->method(i);
    printer->Emit(
        {
            {"name", method->name()},
            {"input", QualifiedClassName(method->input_type(), *options_)},
            {"output", QualifiedClassName(method->output_type(), *options_)},
            {"index", absl::StrCat(i)},
        },
        R"cc(
          case $index$:
            $name$(controller,
                   ::$proto_ns$::internal::DownCast<const $input$*>(request),
                   ::$proto_ns$::internal::DownCast<$output$*>(response), done);
            break;
        )cc");
  }
}

void ServiceGenerator::GenerateStubMethods(io::Printer* printer) {
  for (int i = 0; i < descriptor_->method_count(); ++i) {
    const MethodDescriptor* method = descriptor_->method(i);

    printer->Emit(
        {
            {"name", method->name()},
            {"input", QualifiedClassName(method->input_type(), *options_)},
            {"output", QualifiedClassName(method->output_type(), *options_)},
            {"index", absl::StrCat(i)},
        },
        R"cc(
          void $classname$_Stub::$name$(::$proto_ns$::RpcController* controller,
                                        const $input$* request,
                                        $output$* response, ::google::protobuf::Closure* done) {
            channel_->CallMethod(descriptor()->method($index$), controller,
                                 request, response, done);
          }
        )cc");
  }
}

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