// Copyright 2020 Google LLC
//
// 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
//
//      https://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 "generator/internal/stub_factory_generator.h"
#include "generator/internal/codegen_utils.h"
#include "generator/internal/printer.h"
#include "absl/strings/str_split.h"
#include <google/protobuf/descriptor.h>

namespace google {
namespace cloud {
namespace generator_internal {

StubFactoryGenerator::StubFactoryGenerator(
    google::protobuf::ServiceDescriptor const* service_descriptor,
    VarsDictionary service_vars,
    std::map<std::string, VarsDictionary> service_method_vars,
    google::protobuf::compiler::GeneratorContext* context,
    std::vector<MixinMethod> const& mixin_methods)
    : ServiceCodeGenerator("stub_factory_header_path", "stub_factory_cc_path",
                           service_descriptor, std::move(service_vars),
                           std::move(service_method_vars), context,
                           mixin_methods) {}

Status StubFactoryGenerator::GenerateHeader() {
  HeaderPrint(CopyrightLicenseFileHeader());
  HeaderPrint(  // clang-format off
    "\n"
    "// Generated by the Codegen C++ plugin.\n"
    "// If you make any local changes, they will be lost.\n"
    "// source: $proto_file_name$\n"
    "\n"
    "#ifndef $header_include_guard$\n"
    "#define $header_include_guard$\n");
  // clang-format on

  // includes
  HeaderPrint("\n");
  HeaderLocalIncludes({vars("stub_header_path"),
                       "google/cloud/internal/unified_grpc_credentials.h",
                       "google/cloud/options.h", "google/cloud/version.h"});
  HeaderSystemIncludes({"memory"});
  HeaderGrpcPortsDefInclude();
  auto result = HeaderOpenNamespaces(NamespaceType::kInternal);
  if (!result.ok()) return result;

  HeaderPrint(
      R"""(
std::shared_ptr<$stub_class_name$> CreateDefault$stub_class_name$(
    std::shared_ptr<internal::GrpcAuthenticationStrategy> auth,
    Options const& options);
)""");

  HeaderCloseNamespaces();
  HeaderGrpcPortsUndefInclude();
  // close header guard
  HeaderPrint("\n#endif  // $header_include_guard$\n");
  return {};
}

Status StubFactoryGenerator::GenerateCc() {
  CcPrint(CopyrightLicenseFileHeader());
  CcPrint(  // clang-format off
    "\n"
    "// Generated by the Codegen C++ plugin.\n"
    "// If you make any local changes, they will be lost.\n"
    "// source: $proto_file_name$\n");
  // clang-format on

  // includes
  CcPrint("\n");
  CcLocalIncludes({vars("stub_factory_header_path"), vars("auth_header_path"),
                   vars("logging_header_path"), vars("metadata_header_path"),
                   vars("stub_header_path"), vars("tracing_stub_header_path"),
                   "google/cloud/common_options.h",
                   "google/cloud/grpc_options.h",
                   "google/cloud/internal/algorithm.h",
                   "google/cloud/internal/opentelemetry.h",
                   "google/cloud/options.h", "google/cloud/log.h"});

  std::vector<std::string> headers =
      absl::StrSplit(vars("mixin_proto_grpc_header_paths"), ',');
  headers.insert(headers.end(), {vars("proto_grpc_header_path")});
  CcProtobufGenCodeIncludes(headers);
  CcSystemIncludes({"memory", "utility"});
  CcGrpcPortsDefInclude();
  auto result = CcOpenNamespaces(NamespaceType::kInternal);
  if (!result.ok()) return result;

  std::unordered_map<std::string, std::string> mixin_grpc_stubs;
  for (auto const& mixin_method : MixinMethods()) {
    // We create operations stub for service with LRO no matter it has LRO mixin
    // or not, so we skip creating mixin operations stub for the services with
    // LRO.
    if (HasLongrunningMethod() &&
        mixin_method.grpc_stub_name == "operations_stub")
      continue;
    mixin_grpc_stubs[mixin_method.grpc_stub_name] = mixin_method.grpc_stub_fqn;
  }
  std::string mixin_stub_inits;
  std::string mixin_stub_moves;
  for (auto const& mixin_grpc_stub : mixin_grpc_stubs) {
    mixin_stub_inits += absl::StrFormat(
        R"""(
  auto service_%s = %s::NewStub(channel);)""",
        mixin_grpc_stub.first, mixin_grpc_stub.second);
    mixin_stub_moves +=
        absl::StrFormat(", std::move(service_%s)", mixin_grpc_stub.first);
  }

  // factory function implementation
  CcPrint(R"""(
std::shared_ptr<$stub_class_name$>
CreateDefault$stub_class_name$(
    std::shared_ptr<internal::GrpcAuthenticationStrategy> auth,
    Options const& options) {
  auto channel = auth->CreateChannel(
    options.get<EndpointOption>(), internal::MakeChannelArguments(options));
  auto service_grpc_stub = $grpc_stub_fqn$::NewStub(channel);)""");

  CcPrint(mixin_stub_inits);

  if (!HasLongrunningMethod()) {
    CcPrint(absl::StrFormat(R"""(
  std::shared_ptr<$stub_class_name$> stub =
    std::make_shared<Default$stub_class_name$>(std::move(service_grpc_stub)%s);
)""",
                            mixin_stub_moves));
  } else {
    CcPrint(absl::StrFormat(R"""(
  std::shared_ptr<$stub_class_name$> stub =
    std::make_shared<Default$stub_class_name$>(
      std::move(service_grpc_stub)%s,
      google::longrunning::Operations::NewStub(channel));
)""",
                            mixin_stub_moves));
  }
  CcPrint(R"""(
  if (auth->RequiresConfigureContext()) {
    stub = std::make_shared<$auth_class_name$>(
        std::move(auth), std::move(stub));
  }
  stub = std::make_shared<$metadata_class_name$>(
      std::move(stub), std::multimap<std::string, std::string>{});
  if (internal::Contains(
      options.get<LoggingComponentsOption>(), "rpc")) {
    GCP_LOG(INFO) << "Enabled logging for gRPC calls";
    stub = std::make_shared<$logging_class_name$>(
        std::move(stub),
        options.get<GrpcTracingOptionsOption>(),
        options.get<LoggingComponentsOption>());
  }
  if (internal::TracingEnabled(options)) {
    stub = Make$tracing_stub_class_name$(std::move(stub));
  }
  return stub;
}
)""");

  CcCloseNamespaces();
  CcGrpcPortsUndefInclude();
  return {};
}

}  // namespace generator_internal
}  // namespace cloud
}  // namespace google
