/*
 * Copyright (c) 2009-2021, Google LLC
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Google LLC nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "protos_generator/gen_repeated_fields.h"

#include <string>
#include <vector>

#include "google/protobuf/descriptor.pb.h"
#include "absl/strings/string_view.h"
#include "google/protobuf/descriptor.h"
#include "protos_generator/gen_accessors.h"
#include "protos_generator/gen_enums.h"
#include "protos_generator/gen_extensions.h"
#include "protos_generator/gen_utils.h"
#include "protos_generator/names.h"
#include "protos_generator/output.h"
#include "upbc/common.h"
#include "upbc/file_layout.h"
#include "upbc/names.h"

namespace protos_generator {
namespace protobuf = ::google::protobuf;

// 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,
                                      Output& output, bool read_only) {
  if (field->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_MESSAGE) {
    output(
        R"cc(
          using $0Access::$1;
          using $0Access::$1_size;
        )cc",
        class_name, resolved_field_name);
    if (!read_only) {
      output(
          R"cc(
            using $0Access::add_$1;
            using $0Access::mutable_$1;
          )cc",
          class_name, resolved_field_name);
    }
  } else {
    output(
        R"cc(
          using $0Access::$1;
          using $0Access::$1_size;
        )cc",
        class_name, resolved_field_name);
    if (!read_only) {
      output(
          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,
                                        Output& output) {
  output(
      R"cc(
        inline size_t $1_size() const {
          size_t len;
          $0_$2(msg_, &len);
          return len;
        }
      )cc",
      MessageName(desc), resolved_field_name, resolved_upbc_name);

  if (field->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_MESSAGE) {
    output(
        R"cc(
          $1 $2(size_t index) const;
          const ::protos::RepeatedField<const $4>::CProxy $2() const;
          ::protos::Ptr<::protos::RepeatedField<$4>> mutable_$2();
          absl::StatusOr<$0> add_$2();
          $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) {
    output(
        R"cc(
          $0 $1(size_t index) const;
          const ::protos::RepeatedField<$0>::CProxy $1() const;
          ::protos::Ptr<::protos::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 {
    output(
        R"cc(
          $0 $1(size_t index) const;
          const ::protos::RepeatedField<$0>::CProxy $1() const;
          ::protos::Ptr<::protos::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* message,
                                  const protobuf::FieldDescriptor* field,
                                  const absl::string_view resolved_field_name,
                                  const absl::string_view class_name,
                                  Output& output) {
  const char arena_expression[] = "arena_";
  absl::string_view upbc_name = field->name();
  output(
      R"cc(
        $1 $0::$2(size_t index) const {
          size_t len;
          auto* ptr = $3_$5(msg_, &len);
          assert(index < len);
          return ::protos::internal::CreateMessage<$4>(
              (upb_Message*)*(ptr + index), arena_);
        }
      )cc",
      class_name, MessagePtrConstType(field, /* is_const */ true),
      resolved_field_name, MessageName(message),
      MessageBaseType(field, /* maybe_const */ false), upbc_name);
  output(
      R"cc(
        absl::StatusOr<$1> $0::add_$2() {
          auto new_msg = $3_add_$6(msg_, $5);
          if (!new_msg) {
            return ::protos::MessageAllocationError();
          }
          return ::protos::internal::CreateMessageProxy<$4>((upb_Message*)new_msg, $5);
        }
      )cc",
      class_name, MessagePtrConstType(field, /* const */ false),
      resolved_field_name, MessageName(message),
      MessageBaseType(field, /* maybe_const */ false), arena_expression,
      upbc_name);
  output(
      R"cc(
        $1 $0::mutable_$2(size_t index) const {
          size_t len;
          auto* ptr = $3_$6(msg_, &len);
          assert(index < len);
          return ::protos::internal::CreateMessageProxy<$4>(
              (upb_Message*)*(ptr + index), $5);
        }
      )cc",
      class_name, MessagePtrConstType(field, /* is_const */ false),
      resolved_field_name, MessageName(message),
      MessageBaseType(field, /* maybe_const */ false), arena_expression,
      upbc_name);
  output(
      R"cc(
        const ::protos::RepeatedField<const $1>::CProxy $0::$2() const {
          size_t size;
          const upb_Array* arr = _$3_$4_$5(msg_, &size);
          return ::protos::RepeatedField<const $1>::CProxy(arr, arena_);
        };
        ::protos::Ptr<::protos::RepeatedField<$1>> $0::mutable_$2() {
          size_t size;
          upb_Array* arr = _$3_$4_$6(msg_, &size, arena_);
          return ::protos::RepeatedField<$1>::Proxy(arr, arena_);
        }
      )cc",
      class_name,                                       // $0
      MessageBaseType(field, /* maybe_const */ false),  // $1
      resolved_field_name,                              // $2
      MessageName(message),                             // $3
      upbc_name,                                        // $4
      upbc::kRepeatedFieldArrayGetterPostfix,           // $5
      upbc::kRepeatedFieldMutableArrayGetterPostfix     // $6
  );
}

void WriteRepeatedStringAccessor(const protobuf::Descriptor* message,
                                 const protobuf::FieldDescriptor* field,
                                 const absl::string_view resolved_field_name,
                                 const absl::string_view class_name,
                                 Output& output) {
  absl::string_view upbc_name = field->name();
  output(
      R"cc(
        $1 $0::$2(size_t index) const {
          size_t len;
          auto* ptr = $3_mutable_$4(msg_, &len);
          assert(index < len);
          return ::protos::UpbStrToStringView(*(ptr + index));
        }
      )cc",
      class_name, CppConstType(field), resolved_field_name,
      MessageName(message), upbc_name);
  output(
      R"cc(
        bool $0::resize_$1(size_t len) {
          return $2_resize_$3(msg_, len, arena_);
        }
      )cc",
      class_name, resolved_field_name, MessageName(message), upbc_name);
  output(
      R"cc(
        bool $0::add_$2($1 val) {
          return $3_add_$4(msg_, ::protos::UpbStrFromStringView(val, arena_), arena_);
        }
      )cc",
      class_name, CppConstType(field), resolved_field_name,
      MessageName(message), upbc_name);
  output(
      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) = ::protos::UpbStrFromStringView(val, arena_);
        }
      )cc",
      class_name, CppConstType(field), resolved_field_name,
      MessageName(message), upbc_name);
  output(
      R"cc(
        const ::protos::RepeatedField<$1>::CProxy $0::$2() const {
          size_t size;
          const upb_Array* arr = _$3_$4_$5(msg_, &size);
          return ::protos::RepeatedField<$1>::CProxy(arr, arena_);
        };
        ::protos::Ptr<::protos::RepeatedField<$1>> $0::mutable_$2() {
          size_t size;
          upb_Array* arr = _$3_$4_$6(msg_, &size, arena_);
          return ::protos::RepeatedField<$1>::Proxy(arr, arena_);
        }
      )cc",
      class_name,                                    // $0
      CppConstType(field),                           // $1
      resolved_field_name,                           // $2
      MessageName(message),                          // $3
      upbc_name,                                     // $4
      upbc::kRepeatedFieldArrayGetterPostfix,        // $5
      upbc::kRepeatedFieldMutableArrayGetterPostfix  // $6
  );
}

void WriteRepeatedScalarAccessor(const protobuf::Descriptor* message,
                                 const protobuf::FieldDescriptor* field,
                                 const absl::string_view resolved_field_name,
                                 const absl::string_view class_name,
                                 Output& output) {
  absl::string_view upbc_name = field->name();
  output(
      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,
      MessageName(message), upbc_name);
  output(
      R"cc(
        bool $0::resize_$1(size_t len) {
          return $2_resize_$3(msg_, len, arena_);
        }
      )cc",
      class_name, resolved_field_name, MessageName(message), upbc_name);
  output(
      R"cc(
        bool $0::add_$2($1 val) { return $3_add_$4(msg_, val, arena_); }
      )cc",
      class_name, CppConstType(field), resolved_field_name,
      MessageName(message), upbc_name);
  output(
      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,
      MessageName(message), upbc_name);
  output(
      R"cc(
        const ::protos::RepeatedField<$1>::CProxy $0::$2() const {
          size_t size;
          const upb_Array* arr = _$3_$4_$5(msg_, &size);
          return ::protos::RepeatedField<$1>::CProxy(arr, arena_);
        };
        ::protos::Ptr<::protos::RepeatedField<$1>> $0::mutable_$2() {
          size_t size;
          upb_Array* arr = _$3_$4_$6(msg_, &size, arena_);
          return ::protos::RepeatedField<$1>::Proxy(arr, arena_);
        }
      )cc",
      class_name,                                    // $0
      CppConstType(field),                           // $1
      resolved_field_name,                           // $2
      MessageName(message),                          // $3
      upbc_name,                                     // $4
      upbc::kRepeatedFieldArrayGetterPostfix,        // $5
      upbc::kRepeatedFieldMutableArrayGetterPostfix  // $6
  );
}

}  // namespace protos_generator
