// 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 "sandboxed_api/tools/clang_generator/emitter.h"

#include <string>
#include <utility>
#include <vector>

#include "absl/container/btree_set.h"
#include "absl/container/flat_hash_set.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Type.h"
#include "sandboxed_api/tools/clang_generator/diagnostics.h"
#include "sandboxed_api/tools/clang_generator/emitter_base.h"
#include "sandboxed_api/tools/clang_generator/generator.h"
#include "sandboxed_api/tools/clang_generator/types.h"
#include "sandboxed_api/util/status_macros.h"

namespace sapi {

// Common header description with auto-generation notice.
constexpr absl::string_view kHeaderDescription =
    R"(// AUTO-GENERATED by the Sandboxed API generator.
// Edits will be discarded when regenerating this file.)";

// Common header file prolog with auto-generation notice.
// Note: The includes will be adjusted by Copybara when converting to/from
//       internal code. This is intentional.
// Text template arguments:
//   1. Header guard
constexpr absl::string_view kHeaderIncludes =
    R"(
#include "absl/base/macros.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "sandboxed_api/sandbox.h"
#include "sandboxed_api/util/status_macros.h"
#include "sandboxed_api/vars.h"

)";

// Text template arguments:
//   1. Include for embedded sandboxee objects
constexpr absl::string_view kEmbedInclude = R"(#include "%1$s_embed.h"

)";

// Text template arguments:
//   1. Class name
//   2. Embedded object identifier
constexpr absl::string_view kEmbedClassTemplate = R"(
// Sandbox with embedded sandboxee and default policy
class %1$s : public ::sapi::Sandbox {
 public:
  %1$s()
      : ::sapi::Sandbox([]() {
          static auto* fork_client_context =
              new ::sapi::ForkClientContext(%2$s_embed_create());
          return fork_client_context;
        }()) {}
};

)";

// Sandboxed API class template.
// Text template arguments:
//   1. Class name
constexpr absl::string_view kClassHeaderTemplate = R"(
// Sandboxed API
class %1$s {
 public:
  explicit %1$s(::sapi::Sandbox* sandbox) : sandbox_(sandbox) {}


  ABSL_DEPRECATED("Call sandbox() instead")
  ::sapi::Sandbox* GetSandbox() const { return sandbox(); }
  ::sapi::Sandbox* sandbox() const { return sandbox_; }
)";

// Sandboxed API class template footer.
constexpr absl::string_view kClassFooterTemplate = R"(
 private:
  ::sapi::Sandbox* sandbox_;
};
)";

// Returns a unique name for a parameter. If `decl` has no name, a unique name
// will be generated in the form of `unnamed<index>_`.
std::string GetParamName(const clang::ParmVarDecl* decl, int index) {
  if (std::string name = decl->getName().str(); !name.empty()) {
    return absl::StrCat(name, "_");  // Suffix to avoid collisions
  }
  return absl::StrCat("unnamed", index, "_");
}

// Returns a comment for the given function `decl` which represents the
// unsandboxed function signature.
absl::StatusOr<std::string> PrintFunctionPrototypeComment(
    const clang::FunctionDecl* decl) {
  const clang::ASTContext& context = decl->getASTContext();

  std::string out = absl::StrCat(
      MapQualTypeParameterForCxx(context, decl->getDeclaredReturnType()), " ",
      decl->getQualifiedNameAsString(), "(");

  std::string print_separator;
  for (int i = 0; i < decl->getNumParams(); ++i) {
    const clang::ParmVarDecl* param = decl->getParamDecl(i);

    absl::StrAppend(&out, print_separator);
    print_separator = ", ";
    absl::StrAppend(&out,
                    MapQualTypeParameterForCxx(context, param->getType()));
    if (std::string name = param->getName().str(); !name.empty()) {
      absl::StrAppend(&out, " ", name);
    }
  }
  absl::StrAppend(&out, ")");

  SAPI_ASSIGN_OR_RETURN(
      std::string formatted,
      internal::ReformatGoogleStyle(/*filename=*/"input", out, 75));
  out.clear();
  for (const auto& line : absl::StrSplit(formatted, '\n')) {
    absl::StrAppend(&out, "// ", line, "\n");
  }
  return out;
}

// Emits the given function `decl` as SAPI function with a leading comment
// documenting the unsandboxed function signature.
absl::StatusOr<std::string> EmitFunction(const clang::FunctionDecl* decl) {
  const clang::QualType return_type = decl->getDeclaredReturnType();

  // Skip functions returning record by value.
  if (return_type->isRecordType()) {
    return MakeStatusWithDiagnostic(
        decl->getBeginLoc(), absl::StatusCode::kCancelled,
        "Returning record by value, skipping function.");
  }

  SAPI_ASSIGN_OR_RETURN(std::string prototype,
                        PrintFunctionPrototypeComment(decl));
  std::string out;
  absl::StrAppend(&out, "\n", prototype);

  auto function_name = ToStringView(decl->getName());
  const bool returns_void = return_type->isVoidType();
  const clang::ASTContext& context = decl->getASTContext();

  absl::StrAppend(&out, MapQualTypeReturn(context, return_type), " ",
                  function_name, "(");

  struct ParameterInfo {
    clang::QualType qual;
    std::string name;
  };
  std::vector<ParameterInfo> params;

  // Process the function parameter list.
  std::string print_separator;
  for (int i = 0; i < decl->getNumParams(); ++i) {
    const clang::ParmVarDecl* param = decl->getParamDecl(i);

    // Skip functions with record parameters passed by value.
    if (param->getType()->isRecordType()) {
      return MakeStatusWithDiagnostic(
          param->getBeginLoc(), absl::StatusCode::kCancelled,
          absl::StrCat("Passing record parameter '",
                       ToStringView(param->getName()),
                       "' by value, skipping function."));
    }

    ParameterInfo& param_info = params.emplace_back();
    param_info.qual = param->getType();
    param_info.name = GetParamName(param, i);

    absl::StrAppend(&out, print_separator);
    print_separator = ", ";
    absl::StrAppend(&out, MapQualTypeParameter(context, param_info.qual), " ",
                    param_info.name);
  }

  absl::StrAppend(&out, ") {\n");

  // Declare the return value of the SAPI function.
  absl::StrAppend(&out, MapQualType(context, return_type), " v_ret_;\n");

  // Declare the local variables for the parameters.
  for (const auto& [qual, name] : params) {
    if (!IsPointerOrReference(qual)) {
      absl::StrAppend(&out, MapQualType(context, qual), " v_", name, "(", name,
                      ");\n");
    }
  }

  // Call the sandboxed function.
  absl::StrAppend(&out, "\nSAPI_RETURN_IF_ERROR(sandbox_->Call(\"",
                  function_name, "\", &v_ret_");
  for (const auto& [qual, name] : params) {
    absl::StrAppend(&out, ", ", IsPointerOrReference(qual) ? "" : "&v_", name);
  }

  // End the sandboxed function call and return `ok` if the unsandboxed function
  // returns void, or else return the value of the SAPI function.
  absl::StrAppend(&out, "));\nreturn ",
                  (returns_void ? "::absl::OkStatus()" : "v_ret_.GetValue()"),
                  ";\n}\n");
  return out;
}

// Emits the SAPI header.
absl::StatusOr<std::string> EmitHeader(
    const std::vector<std::string>& function_definitions,
    const std::vector<const RenderedType*>& rendered_types,
    const absl::btree_set<std::string>& rendered_includes,
    const GeneratorOptions& options) {
  // Log a warning message if the number of requested functions is not equal to
  // the number of functions generated.
  if (!options.function_names.empty() &&
      (options.function_names.size() != function_definitions.size())) {
    LOG(WARNING) << "Generated output has fewer functions than expected - some "
                    "function signatures might use language features that "
                    "SAPI does not support. For debugging, we recommend you "
                    "compare the list of functions in your sapi_library() rule "
                    "with the generated *.sapi.h file. Expected: "
                 << options.function_names.size()
                 << ", generated: " << function_definitions.size();
  }
  std::string out;
  const std::string include_guard = GetIncludeGuard(options.out_file);
  absl::StrAppend(&out, kHeaderDescription);
  absl::StrAppendFormat(&out, kHeaderProlog, include_guard);

  // Emit the collected includes.
  absl::StrAppend(&out, absl::StrJoin(rendered_includes, "\n"));

  // Emit the common includes.
  absl::StrAppend(&out, kHeaderIncludes);

  // When embedding the sandboxee, add embed header include
  if (!options.embed_name.empty()) {
    // Not using JoinPath() because even on Windows include paths use plain
    // slashes.
    std::string include_file(absl::StripSuffix(
        absl::StrReplaceAll(options.embed_dir, {{"\\", "/"}}), "/"));
    if (!include_file.empty()) {
      absl::StrAppend(&include_file, "/");
    }
    absl::StrAppend(&include_file, options.embed_name);
    absl::StrAppendFormat(&out, kEmbedInclude, include_file);
  }

  // If specified, wrap the generated API in a namespace
  if (options.has_namespace()) {
    absl::StrAppendFormat(&out, kNamespaceBeginTemplate,
                          options.namespace_name);
  }

  // Emit type dependencies
  if (!rendered_types.empty()) {
    absl::StrAppend(&out, "// Types this API depends on\n");
    std::string last_ns_name = options.namespace_name;
    for (const RenderedType* rt : rendered_types) {
      const auto& [ns_name, spelling] = *rt;
      if (last_ns_name != ns_name) {
        if (!last_ns_name.empty() && last_ns_name != options.namespace_name) {
          absl::StrAppend(&out, "}  // namespace ", last_ns_name, "\n\n");
        }

        if (!ns_name.empty() && ns_name != options.namespace_name) {
          absl::StrAppend(&out, "namespace ", ns_name, " {\n");
        }
        last_ns_name = ns_name;
      }

      absl::StrAppend(&out, spelling, ";\n");
    }
    if (!last_ns_name.empty() && last_ns_name != options.namespace_name) {
      absl::StrAppend(&out, "}  // namespace ", last_ns_name, "\n\n");
    }
  }

  // Optionally emit a default sandbox that instantiates an embedded sandboxee
  if (!options.embed_name.empty()) {
    absl::StrAppendFormat(
        &out, kEmbedClassTemplate, absl::StrCat(options.name, "Sandbox"),
        absl::StrReplaceAll(options.embed_name, {{"-", "_"}}));
  }

  // Emit the actual Sandboxed API
  absl::StrAppendFormat(&out, kClassHeaderTemplate,
                        absl::StrCat(options.name, "Api"));
  absl::StrAppend(&out, absl::StrJoin(function_definitions, "\n"));
  absl::StrAppend(&out, kClassFooterTemplate);

  // Close out the header: close namespace (if needed) and end include guard
  if (options.has_namespace()) {
    absl::StrAppendFormat(&out, kNamespaceEndTemplate, options.namespace_name);
  }
  absl::StrAppendFormat(&out, kHeaderEpilog, include_guard);
  return out;
}

absl::Status Emitter::AddFunction(clang::FunctionDecl* decl) {
  if (rendered_functions_.insert(decl->getQualifiedNameAsString()).second) {
    SAPI_ASSIGN_OR_RETURN(std::string function, EmitFunction(decl));
    rendered_functions_ordered_.push_back(function);
  }
  return absl::OkStatus();
}

absl::StatusOr<std::string> Emitter::EmitHeader(
    const GeneratorOptions& options) {
  SAPI_ASSIGN_OR_RETURN(
      const std::string header,
      ::sapi::EmitHeader(rendered_functions_ordered_, rendered_types_ordered_,
                         rendered_includes_ordered_, options));
  return internal::ReformatGoogleStyle(options.out_file, header);
}

}  // namespace sapi
