// Protocol Buffers - Google's data interchange format
// Copyright 2023 Google LLC.  All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// 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 THE COPYRIGHT
// OWNER OR CONTRIBUTORS 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.

#ifndef UPB_PROTOS_REPEATED_FIELD_H_
#define UPB_PROTOS_REPEATED_FIELD_H_

#include <cstddef>
#include <iterator>
#include <type_traits>

#include "absl/strings/string_view.h"
#include "protos/protos.h"
#include "protos/protos_traits.h"
#include "protos/repeated_field_iterator.h"
#include "upb/base/string_view.h"
#include "upb/mem/arena.h"
#include "upb/message/array.h"
#include "upb/message/copy.h"
#include "upb/message/types.h"

// Must be last:
#include "upb/port/def.inc"

namespace protos {

namespace internal {

// Shared implementation of repeated fields for absl::string_view and
// message types for mutable and immutable variants.
//
// Immutable (const accessor), constructs this class with a nullptr upb_Array*
// when the underlying array in the message is empty.
//
// Mutable accessors on the other hand, will allocate a new empty non-null
// upb_Array* for the message when the RepeatedFieldProxy is constructed.
template <class T>
class RepeatedFieldProxyBase {
  using Array = add_const_if_T_is_const<T, upb_Array>;

 public:
  explicit RepeatedFieldProxyBase(Array* arr, upb_Arena* arena)
      : arr_(arr), arena_(arena) {}

  size_t size() const { return arr_ != nullptr ? upb_Array_Size(arr_) : 0; }

  bool empty() const { return size() == 0; }

 protected:
  // Returns upb_Array message member.
  inline upb_Message* GetMessage(size_t n) const;

  Array* arr_;
  upb_Arena* arena_;
};

template <class T>
upb_Message* RepeatedFieldProxyBase<T>::GetMessage(size_t n) const {
  auto** messages =
      static_cast<upb_Message**>(upb_Array_MutableDataPtr(this->arr_));
  return messages[n];
}

template <class T>
class RepeatedFieldProxyMutableBase : public RepeatedFieldProxyBase<T> {
 public:
  RepeatedFieldProxyMutableBase(upb_Array* arr, upb_Arena* arena)
      : RepeatedFieldProxyBase<T>(arr, arena) {}

  void clear() { upb_Array_Resize(this->arr_, 0, this->arena_); }
};

// RepeatedField proxy for repeated messages.
template <class T>
class RepeatedFieldProxy
    : public std::conditional_t<std::is_const_v<T>, RepeatedFieldProxyBase<T>,
                                RepeatedFieldProxyMutableBase<T>> {
  static_assert(!std::is_same_v<T, absl::string_view>, "");
  static_assert(!std::is_same_v<T, const absl::string_view>, "");
  static_assert(!std::is_arithmetic_v<T>, "");
  static constexpr bool kIsConst = std::is_const_v<T>;

 public:
  using value_type = std::remove_const_t<T>;
  using size_type = size_t;
  using difference_type = ptrdiff_t;
  using iterator = internal::Iterator<MessageIteratorPolicy<T>>;
  using reference = typename iterator::reference;
  using pointer = typename iterator::pointer;
  using reverse_iterator = std::reverse_iterator<iterator>;

  explicit RepeatedFieldProxy(const upb_Array* arr, upb_Arena* arena)
      : RepeatedFieldProxyBase<T>(arr, arena) {}
  RepeatedFieldProxy(upb_Array* arr, upb_Arena* arena)
      : RepeatedFieldProxyMutableBase<T>(arr, arena) {}
  // Constructor used by ::protos::Ptr.
  RepeatedFieldProxy(const RepeatedFieldProxy&) = default;

  // T::CProxy [] operator specialization.
  typename T::CProxy operator[](size_t n) const {
    upb_MessageValue message_value = upb_Array_Get(this->arr_, n);
    return ::protos::internal::CreateMessage<typename std::remove_const_t<T>>(
        (upb_Message*)message_value.msg_val, this->arena_);
  }

  // TODO : Audit/Finalize based on Iterator Design.
  // T::Proxy [] operator specialization.
  template <int&... DeductionBlocker, bool b = !kIsConst,
            typename = std::enable_if_t<b>>
  typename T::Proxy operator[](size_t n) {
    return ::protos::internal::CreateMessageProxy<T>(this->GetMessage(n),
                                                     this->arena_);
  }

  // Mutable message reference specialization.
  template <int&... DeductionBlocker, bool b = !kIsConst,
            typename = std::enable_if_t<b>>
  void push_back(const T& t) {
    upb_MessageValue message_value;
    message_value.msg_val = upb_Message_DeepClone(
        PrivateAccess::GetInternalMsg(&t), ::protos::internal::GetMiniTable(&t),
        this->arena_);
    upb_Array_Append(this->arr_, message_value, this->arena_);
  }

  // Mutable message add using move.
  template <int&... DeductionBlocker, bool b = !kIsConst,
            typename = std::enable_if_t<b>>
  void push_back(T&& msg) {
    upb_MessageValue message_value;
    message_value.msg_val = PrivateAccess::GetInternalMsg(&msg);
    upb_Arena_Fuse(GetArena(&msg), this->arena_);
    upb_Array_Append(this->arr_, message_value, this->arena_);
    T moved_msg = std::move(msg);
  }

  iterator begin() const {
    return iterator({static_cast<upb_Message**>(
                         const_cast<void*>(upb_Array_DataPtr(this->arr_))),
                     this->arena_});
  }
  iterator end() const { return begin() + this->size(); }
  reverse_iterator rbegin() const { return reverse_iterator(end()); }
  reverse_iterator rend() const { return reverse_iterator(begin()); }

 private:
  friend class ::protos::Ptr<T>;
};

// RepeatedField proxy for repeated strings.
template <class T>
class RepeatedFieldStringProxy
    : public std::conditional_t<std::is_const_v<T>, RepeatedFieldProxyBase<T>,
                                RepeatedFieldProxyMutableBase<T>> {
  static_assert(std::is_same_v<T, absl::string_view> ||
                    std::is_same_v<T, const absl::string_view>,
                "");
  static constexpr bool kIsConst = std::is_const_v<T>;

 public:
  using value_type = std::remove_const_t<T>;
  using size_type = size_t;
  using difference_type = ptrdiff_t;
  using iterator = internal::Iterator<StringIteratorPolicy<T>>;
  using reference = typename iterator::reference;
  using pointer = typename iterator::pointer;
  using reverse_iterator = std::reverse_iterator<iterator>;

  // Immutable constructor.
  explicit RepeatedFieldStringProxy(const upb_Array* arr, upb_Arena* arena)
      : RepeatedFieldProxyBase<T>(arr, arena) {}
  // Mutable constructor.
  RepeatedFieldStringProxy(upb_Array* arr, upb_Arena* arena)
      : RepeatedFieldProxyMutableBase<T>(arr, arena) {}
  // Constructor used by ::protos::Ptr.
  RepeatedFieldStringProxy(const RepeatedFieldStringProxy&) = default;

  reference operator[](size_t n) const { return begin()[n]; }

  template <int&... DeductionBlocker, bool b = !kIsConst,
            typename = std::enable_if_t<b>>
  void push_back(T t) {
    upb_MessageValue message_value;
    // Copy string to arena.
    UPB_ASSERT(this->arena_);
    char* data = (char*)upb_Arena_Malloc(this->arena_, t.size());
    UPB_ASSERT(data);
    memcpy(data, t.data(), t.size());
    message_value.str_val = upb_StringView_FromDataAndSize(data, t.size());
    upb_Array_Append(this->arr_, message_value, this->arena_);
  }

  iterator begin() const { return iterator({this->arr_, this->arena_, 0}); }
  iterator end() const {
    return iterator({this->arr_, this->arena_, this->size()});
  }
  reverse_iterator rbegin() const { return reverse_iterator(end()); }
  reverse_iterator rend() const { return reverse_iterator(begin()); }
};

// RepeatedField proxy for repeated scalar types.
template <typename T>
class RepeatedFieldScalarProxy
    : public std::conditional_t<std::is_const_v<T>, RepeatedFieldProxyBase<T>,
                                RepeatedFieldProxyMutableBase<T>> {
  static_assert(std::is_arithmetic_v<T>, "");
  static constexpr bool kIsConst = std::is_const_v<T>;

 public:
  using value_type = std::remove_const_t<T>;
  using size_type = size_t;
  using difference_type = ptrdiff_t;
  using iterator = internal::Iterator<ScalarIteratorPolicy<T>>;
  using reference = typename iterator::reference;
  using pointer = typename iterator::pointer;
  using reverse_iterator = std::reverse_iterator<iterator>;

  explicit RepeatedFieldScalarProxy(const upb_Array* arr, upb_Arena* arena)
      : RepeatedFieldProxyBase<T>(arr, arena) {}
  RepeatedFieldScalarProxy(upb_Array* arr, upb_Arena* arena)
      : RepeatedFieldProxyMutableBase<T>(arr, arena) {}
  // Constructor used by ::protos::Ptr.
  RepeatedFieldScalarProxy(const RepeatedFieldScalarProxy&) = default;

  T operator[](size_t n) const {
    upb_MessageValue message_value = upb_Array_Get(this->arr_, n);
    typename std::remove_const_t<T> val;
    memcpy(&val, &message_value, sizeof(T));
    return val;
  }

  template <int&... DeductionBlocker, bool b = !kIsConst,
            typename = std::enable_if_t<b>>
  void push_back(T t) {
    upb_MessageValue message_value;
    memcpy(&message_value, &t, sizeof(T));
    upb_Array_Append(this->arr_, message_value, this->arena_);
  }

  iterator begin() const { return iterator({unsafe_array()}); }
  iterator cbegin() const { return begin(); }
  iterator end() const { return iterator({unsafe_array() + this->size()}); }
  iterator cend() const { return end(); }

  // Reverse iterator support.
  reverse_iterator rbegin() const { return reverse_iterator(end()); }
  reverse_iterator rend() const { return reverse_iterator(begin()); }
  reverse_iterator crbegin() const { return reverse_iterator(end()); }
  reverse_iterator crend() const { return reverse_iterator(begin()); }

 private:
  T* unsafe_array() const {
    if (kIsConst) {
      const void* unsafe_ptr = ::upb_Array_DataPtr(this->arr_);
      return static_cast<T*>(const_cast<void*>(unsafe_ptr));
    }
    if (!kIsConst) {
      void* unsafe_ptr =
          ::upb_Array_MutableDataPtr(const_cast<upb_Array*>(this->arr_));
      return static_cast<T*>(unsafe_ptr);
    }
  }
};

}  // namespace internal

template <typename T>
class RepeatedField {
  static constexpr bool kIsString = std::is_same_v<T, absl::string_view>;
  static constexpr bool kIsScalar = std::is_arithmetic_v<T>;

 public:
  using Proxy = std::conditional_t<
      kIsScalar, internal::RepeatedFieldScalarProxy<T>,
      std::conditional_t<kIsString, internal::RepeatedFieldStringProxy<T>,
                         internal::RepeatedFieldProxy<T>>>;
  using CProxy = std::conditional_t<
      kIsScalar, internal::RepeatedFieldScalarProxy<const T>,
      std::conditional_t<kIsString, internal::RepeatedFieldStringProxy<const T>,
                         internal::RepeatedFieldProxy<const T>>>;
  // TODO: T supports incomplete type from fwd.h forwarding headers
  // We would like to reference T::CProxy. Validate forwarding header design.
  using ValueProxy = std::conditional_t<
      kIsScalar, T,
      std::conditional_t<kIsString, absl::string_view, ::protos::Ptr<T>>>;
  using ValueCProxy = std::conditional_t<
      kIsScalar, const T,
      std::conditional_t<kIsString, absl::string_view, ::protos::Ptr<const T>>>;
  using Access = std::conditional_t<
      kIsScalar, internal::RepeatedFieldScalarProxy<T>,
      std::conditional_t<kIsString, internal::RepeatedFieldStringProxy<T>,
                         internal::RepeatedFieldProxy<T>>>;
};

}  // namespace protos

#include "upb/port/undef.inc"

#endif  // UPB_PROTOS_REPEATED_FIELD_H_
