// Protocol Buffers - Google's data interchange format
// Copyright 2023 Google LLC.  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

#include <cstddef>
#include <string>

#include "google/protobuf/descriptor.upb.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "google/protobuf/compiler/code_generator.h"
#include "google/protobuf/io/printer.h"
#include "upb/mem/arena.hpp"
#include "upb/reflection/def.hpp"
#include "upb/util/def_to_proto.h"
#include "upb_generator/common/names.h"
#include "upb_generator/minitable/names.h"
#include "upb_generator/reflection/context.h"
#include "upb_generator/reflection/header.h"
#include "upb_generator/reflection/names.h"

namespace upb::generator::reflection {
namespace {

using Sub = google::protobuf::io::Printer::Sub;

static std::string DefSourceFilename(upb::FileDefPtr file) {
  return StripExtension(file.name()) + ".upbdefs.c";
}

void WriteIncludes(upb::FileDefPtr file, Context& ctx) {
  ctx.Emit(
      {
          {"def_header_filename", DefHeaderFilename(file)},
          {"mini_table_header_filename", MiniTableHeaderFilename(file.name())},
      },
      R"(
        #include "upb/reflection/def.h"
        #include "$def_header_filename$"
        #include "$mini_table_header_filename$"
      )");
}

void WriteDefPoolFwdDecls(upb::FileDefPtr file, Context& ctx) {
  if (file.dependency_count() == 0) return;

  for (int i = 0; i < file.dependency_count(); i++) {
    ctx.Emit(
        {{"dllexport_decl", ctx.options().dllexport_decl},
         {"def_init_symbol", ReflectionFileSymbol(file.dependency(i).name())}},
        R"cc(
          extern$ dllexport_decl$ _upb_DefPool_Init $def_init_symbol$;
        )cc");
  }

  ctx.Emit("\n");
}

void WriteStringArrayLine(absl::string_view data, Context& ctx) {
  std::string line = absl::StrJoin(data, ", ", [](std::string* out, char ch) {
    absl::StrAppendFormat(out, "'%v'",
                          absl::CEscape(absl::string_view(&ch, 1)));
  });
  ctx.Emit({{"line", line}},
           R"cc(
             $line$,
           )cc");
}

void WriteStringArray(absl::string_view data, Context& ctx) {
  const size_t kMaxLineLength = 12;
  for (size_t i = 0; i < data.size(); i += kMaxLineLength) {
    WriteStringArrayLine(data.substr(i, kMaxLineLength), ctx);
  }
}

void WriteDescriptor(upb::FileDefPtr file, Context& ctx) {
  upb::Arena arena;
  google_protobuf_FileDescriptorProto* file_proto =
      upb_FileDef_ToProto(file.ptr(), arena.ptr());
  size_t serialized_size;
  const char* serialized = google_protobuf_FileDescriptorProto_serialize(
      file_proto, arena.ptr(), &serialized_size);
  absl::string_view file_data(serialized, serialized_size);

  ctx.Emit(
      {{"serialized_size", file_data.size()},
       {"defpool_init_name", ReflectionFileSymbol(file.name())},
       {"contents", [&] { WriteStringArray(file_data, ctx); }}},
      R"cc(
        static const char descriptor_$defpool_init_name$[$serialized_size$] = {
            $contents$,
        };
      )cc");
  ctx.Emit("\n");
}

void WriteDependencies(upb::FileDefPtr file, Context& ctx) {
  auto write_dep = [&](int i) {
    ctx.Emit({{"sym", ReflectionFileSymbol(file.dependency(i).name())}},
             R"cc(
               &$sym$,
             )cc");
  };

  auto write_deps = [&] {
    for (int i = 0; i < file.dependency_count(); i++) write_dep(i);
  };

  ctx.Emit({{"dep_count", file.dependency_count() + 1},
            {"defpool_init_name", ReflectionFileSymbol(file.name())},
            {google::protobuf::io::Printer::Sub("deps", write_deps).WithSuffix(",")}},
           R"cc(
             static _upb_DefPool_Init* deps_$defpool_init_name$[$dep_count$] = {
                 $deps$,
                 NULL,
             };
           )cc");
  ctx.Emit("\n");
}

void WriteDefPoolInitStruct(upb::FileDefPtr file, Context& ctx) {
  ctx.Emit(
      {
          {"defpool_init_name", ReflectionFileSymbol(file.name())},
          {"file_name", file.name()},
          {"mini_table_file_var_name", MiniTableFileVarName(file.name())},
      },
      R"cc(
        _upb_DefPool_Init $defpool_init_name$ = {
            deps_$defpool_init_name$,
            &$mini_table_file_var_name$,
            "$file_name$",
            UPB_STRINGVIEW_INIT(descriptor_$defpool_init_name$,
                                sizeof(descriptor_$defpool_init_name$)),
        };
      )cc");
}

void WriteDefPoolInit(upb::FileDefPtr file, Context& ctx) {
  WriteDefPoolFwdDecls(file, ctx);
  WriteDescriptor(file, ctx);
  WriteDependencies(file, ctx);
  WriteDefPoolInitStruct(file, ctx);
}

void WriteDefSource(upb::FileDefPtr file, Context& ctx) {
  ctx.Emit(
      {
          {Sub("file_warning", FileWarning(file.name())).WithSuffix(";")},
          {Sub("includes", [&] { WriteIncludes(file, ctx); }).WithSuffix(";")},
          {Sub("def_pool_init", [&] { WriteDefPoolInit(file, ctx); })
               .WithSuffix(";")},
      },
      R"cc(
        $file_warning$;
        $includes$;

        $def_pool_init$;
      )cc");
}

}  // namespace

void GenerateReflectionSource(upb::FileDefPtr file, const Options& options,
                              google::protobuf::compiler::GeneratorContext* context) {
  Context c_context(options, context->Open(DefSourceFilename(file)));
  WriteDefSource(file, c_context);
}

}  // namespace upb::generator::reflection
