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

#include <string>
#include <vector>

#include "google/protobuf/descriptor.pb.h"
#include "absl/strings/string_view.h"
#include "hpb_generator/context.h"
#include "hpb_generator/gen_accessors.h"
#include "hpb_generator/gen_enums.h"
#include "hpb_generator/gen_extensions.h"
#include "hpb_generator/gen_utils.h"
#include "hpb_generator/names.h"
#include "google/protobuf/descriptor.h"
#include "upb_generator/c/names.h"
#include "upb_generator/common.h"
#include "upb_generator/file_layout.h"

namespace google::protobuf::hpb_generator {

namespace protobuf = ::proto2;

// Adds using accessors to reuse base Access class members from a Proxy/CProxy.
void WriteRepeatedFieldUsingAccessors(const protobuf::FieldDescriptor* field,
                                      absl::string_view class_name,
                                      absl::string_view resolved_field_name,
                                      Context& ctx, bool read_only) {
  if (field->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_MESSAGE) {
    ctx.EmitLegacy(
        R"cc(
          using $0Access::$1;
          using $0Access::$1_size;
        )cc",
        class_name, resolved_field_name);
    if (!read_only) {
      ctx.EmitLegacy(
          R"cc(
            using $0Access::add_$1;
            using $0Access::add_alias_$1;
            using $0Access::mutable_$1;
          )cc",
          class_name, resolved_field_name);
    }
  } else {
    ctx.EmitLegacy(
        R"cc(
          using $0Access::$1;
          using $0Access::$1_size;
        )cc",
        class_name, resolved_field_name);
    if (!read_only) {
      ctx.EmitLegacy(
          R"cc(
            using $0Access::add_$1;
            using $0Access::mutable_$1;
            using $0Access::resize_$1;
            using $0Access::set_$1;
          )cc",
          class_name, resolved_field_name);
    }
  }
}

void WriteRepeatedFieldsInMessageHeader(const protobuf::Descriptor* desc,
                                        const protobuf::FieldDescriptor* field,
                                        absl::string_view resolved_field_name,
                                        absl::string_view resolved_upbc_name,
                                        Context& ctx) {
  ctx.EmitLegacy(
      R"cc(
        inline size_t $1_size() const {
          size_t len;
          $0_$2(msg_, &len);
          return len;
        }
      )cc",
      upb::generator::CApiMessageType(desc->full_name()), resolved_field_name,
      resolved_upbc_name);

  if (field->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_MESSAGE) {
    ctx.EmitLegacy(
        R"cc(
          $1 $2(size_t index) const;
          const ::hpb::RepeatedField<const $4>::CProxy $2() const;
          ::hpb::Ptr<::hpb::RepeatedField<$4>> mutable_$2();
          absl::StatusOr<$0> add_$2();
          /**
           * Re-points submsg of repeated field to given target.
           *
           * REQUIRES: both messages must be in the same arena.
           */
          bool add_alias_$2($0 target);
          $0 mutable_$2(size_t index) const;
        )cc",
        MessagePtrConstType(field, /* const */ false),   // $0
        MessagePtrConstType(field, /* const */ true),    // $1
        resolved_field_name,                             // $2
        resolved_upbc_name,                              // $3
        MessageBaseType(field, /* maybe_const */ false)  // $4
    );
  } else if (field->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_STRING) {
    ctx.EmitLegacy(
        R"cc(
          $0 $1(size_t index) const;
          const ::hpb::RepeatedField<$0>::CProxy $1() const;
          ::hpb::Ptr<::hpb::RepeatedField<$0>> mutable_$1();
          bool add_$1($0 val);
          void set_$1(size_t index, $0 val);
          bool resize_$1(size_t len);
        )cc",
        CppConstType(field), resolved_field_name);
  } else {
    ctx.EmitLegacy(
        R"cc(
          $0 $1(size_t index) const;
          const ::hpb::RepeatedField<$0>::CProxy $1() const;
          ::hpb::Ptr<::hpb::RepeatedField<$0>> mutable_$1();
          bool add_$1($0 val);
          void set_$1(size_t index, $0 val);
          bool resize_$1(size_t len);
        )cc",
        CppConstType(field), resolved_field_name);
  }
}

void WriteRepeatedMessageAccessor(const protobuf::Descriptor* desc,
                                  const protobuf::FieldDescriptor* field,
                                  const absl::string_view resolved_field_name,
                                  const absl::string_view class_name,
                                  Context& ctx) {
  const char arena_expression[] = "arena_";
  absl::string_view upbc_name = field->name();
  ctx.EmitLegacy(
      R"cc(
        $1 $0::$2(size_t index) const {
          size_t len;
          auto* ptr = $3_$5(msg_, &len);
          assert(index < len);
          return ::hpb::interop::upb::MakeCHandle<$4>(
              (upb_Message*)*(ptr + index), arena_);
        }
      )cc",
      class_name, MessagePtrConstType(field, /* is_const */ true),
      resolved_field_name, upb::generator::CApiMessageType(desc->full_name()),
      MessageBaseType(field, /* maybe_const */ false), upbc_name);
  ctx.EmitLegacy(
      R"cc(
        absl::StatusOr<$1> $0::add_$2() {
          auto new_msg = $3_add_$6(msg_, $5);
          if (!new_msg) {
            return ::hpb::MessageAllocationError();
          }
          return hpb::interop::upb::MakeHandle<$4>((upb_Message *)new_msg, $5);
        }

        bool $0::add_alias_$2($1 target) {
          ABSL_CHECK(upb_Arena_IsFused(arena_, hpb::interop::upb::GetArena(target)));
          size_t size = 0;
          $3_$2(msg_, &size);
          auto elements = $3_resize_$2(msg_, size + 1, arena_);
          if (!elements) {
            return false;
          }
          elements[size] = ($9 *)hpb::interop::upb::GetMessage(target);
          return true;
        }
      )cc",
      class_name, MessagePtrConstType(field, /* const */ false),
      resolved_field_name, upb::generator::CApiMessageType(desc->full_name()),
      MessageBaseType(field, /* maybe_const */ false), arena_expression,
      upbc_name, ClassName(desc), field->index(),
      upb::generator::CApiMessageType(field->message_type()->full_name()));
  ctx.EmitLegacy(
      R"cc(
        $1 $0::mutable_$2(size_t index) const {
          size_t len;
          auto* ptr = $3_$6(msg_, &len);
          assert(index < len);
          return hpb::interop::upb::MakeHandle<$4>((upb_Message*)*(ptr + index), $5);
        }
      )cc",
      class_name, MessagePtrConstType(field, /* is_const */ false),
      resolved_field_name, upb::generator::CApiMessageType(desc->full_name()),
      MessageBaseType(field, /* maybe_const */ false), arena_expression,
      upbc_name);
  ctx.EmitLegacy(
      R"cc(
        const ::hpb::RepeatedField<const $1>::CProxy $0::$2() const {
          size_t size;
          const upb_Array* arr = _$3_$4_$5(msg_, &size);
          return ::hpb::RepeatedField<const $1>::CProxy(arr, arena_);
        };
        ::hpb::Ptr<::hpb::RepeatedField<$1>> $0::mutable_$2() {
          size_t size;
          upb_Array* arr = _$3_$4_$6(msg_, &size, arena_);
          return ::hpb::RepeatedField<$1>::Proxy(arr, arena_);
        }
      )cc",
      class_name,                                              // $0
      MessageBaseType(field, /* maybe_const */ false),         // $1
      resolved_field_name,                                     // $2
      upb::generator::CApiMessageType(desc->full_name()),      // $3
      upbc_name,                                               // $4
      upb::generator::kRepeatedFieldArrayGetterPostfix,        // $5
      upb::generator::kRepeatedFieldMutableArrayGetterPostfix  // $6
  );
}

void WriteRepeatedStringAccessor(const protobuf::Descriptor* desc,
                                 const protobuf::FieldDescriptor* field,
                                 const absl::string_view resolved_field_name,
                                 const absl::string_view class_name,
                                 Context& ctx) {
  absl::string_view upbc_name = field->name();
  ctx.EmitLegacy(
      R"cc(
        $1 $0::$2(size_t index) const {
          size_t len;
          auto* ptr = $3_mutable_$4(msg_, &len);
          assert(index < len);
          return hpb::interop::upb::FromUpbStringView(*(ptr + index));
        }
      )cc",
      class_name, CppConstType(field), resolved_field_name,
      upb::generator::CApiMessageType(desc->full_name()), upbc_name);
  ctx.EmitLegacy(
      R"cc(
        bool $0::resize_$1(size_t len) {
          return $2_resize_$3(msg_, len, arena_);
        }
      )cc",
      class_name, resolved_field_name,
      upb::generator::CApiMessageType(desc->full_name()), upbc_name);
  ctx.EmitLegacy(
      R"cc(
        bool $0::add_$2($1 val) {
          return $3_add_$4(msg_,
                           hpb::interop::upb::CopyToUpbStringView(val, arena_),
                           arena_);
        }
      )cc",
      class_name, CppConstType(field), resolved_field_name,
      upb::generator::CApiMessageType(desc->full_name()), upbc_name);
  ctx.EmitLegacy(
      R"cc(
        void $0::set_$2(size_t index, $1 val) {
          size_t len;
          auto* ptr = $3_mutable_$4(msg_, &len);
          assert(index < len);
          *(ptr + index) = hpb::interop::upb::CopyToUpbStringView(val, arena_);
        }
      )cc",
      class_name, CppConstType(field), resolved_field_name,
      upb::generator::CApiMessageType(desc->full_name()), upbc_name);
  ctx.EmitLegacy(
      R"cc(
        const ::hpb::RepeatedField<$1>::CProxy $0::$2() const {
          size_t size;
          const upb_Array* arr = _$3_$4_$5(msg_, &size);
          return ::hpb::RepeatedField<$1>::CProxy(arr, arena_);
        };
        ::hpb::Ptr<::hpb::RepeatedField<$1>> $0::mutable_$2() {
          size_t size;
          upb_Array* arr = _$3_$4_$6(msg_, &size, arena_);
          return ::hpb::RepeatedField<$1>::Proxy(arr, arena_);
        }
      )cc",
      class_name,                                              // $0
      CppConstType(field),                                     // $1
      resolved_field_name,                                     // $2
      upb::generator::CApiMessageType(desc->full_name()),      // $3
      upbc_name,                                               // $4
      upb::generator::kRepeatedFieldArrayGetterPostfix,        // $5
      upb::generator::kRepeatedFieldMutableArrayGetterPostfix  // $6
  );
}

void WriteRepeatedScalarAccessor(const protobuf::Descriptor* desc,
                                 const protobuf::FieldDescriptor* field,
                                 const absl::string_view resolved_field_name,
                                 const absl::string_view class_name,
                                 Context& ctx) {
  absl::string_view upbc_name = field->name();
  ctx.EmitLegacy(
      R"cc(
        $1 $0::$2(size_t index) const {
          size_t len;
          auto* ptr = $3_mutable_$4(msg_, &len);
          assert(index < len);
          return *(ptr + index);
        }
      )cc",
      class_name, CppConstType(field), resolved_field_name,
      upb::generator::CApiMessageType(desc->full_name()), upbc_name);
  ctx.EmitLegacy(
      R"cc(
        bool $0::resize_$1(size_t len) {
          return $2_resize_$3(msg_, len, arena_);
        }
      )cc",
      class_name, resolved_field_name,
      upb::generator::CApiMessageType(desc->full_name()), upbc_name);
  ctx.EmitLegacy(
      R"cc(
        bool $0::add_$2($1 val) { return $3_add_$4(msg_, val, arena_); }
      )cc",
      class_name, CppConstType(field), resolved_field_name,
      upb::generator::CApiMessageType(desc->full_name()), upbc_name);
  ctx.EmitLegacy(
      R"cc(
        void $0::set_$2(size_t index, $1 val) {
          size_t len;
          auto* ptr = $3_mutable_$4(msg_, &len);
          assert(index < len);
          *(ptr + index) = val;
        }
      )cc",
      class_name, CppConstType(field), resolved_field_name,
      upb::generator::CApiMessageType(desc->full_name()), upbc_name);
  ctx.EmitLegacy(
      R"cc(
        const ::hpb::RepeatedField<$1>::CProxy $0::$2() const {
          size_t size;
          const upb_Array* arr = _$3_$4_$5(msg_, &size);
          return ::hpb::RepeatedField<$1>::CProxy(arr, arena_);
        };
        ::hpb::Ptr<::hpb::RepeatedField<$1>> $0::mutable_$2() {
          size_t size;
          upb_Array* arr = _$3_$4_$6(msg_, &size, arena_);
          return ::hpb::RepeatedField<$1>::Proxy(arr, arena_);
        }
      )cc",
      class_name,                                              // $0
      CppConstType(field),                                     // $1
      resolved_field_name,                                     // $2
      upb::generator::CApiMessageType(desc->full_name()),      // $3
      upbc_name,                                               // $4
      upb::generator::kRepeatedFieldArrayGetterPostfix,        // $5
      upb::generator::kRepeatedFieldMutableArrayGetterPostfix  // $6
  );
}

}  // namespace protobuf
}  // namespace google::hpb_generator
