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

#ifndef GOOGLE_PROTOBUF_HPB_BACKEND_UPB_INTEROP_H__
#define GOOGLE_PROTOBUF_HPB_BACKEND_UPB_INTEROP_H__

// The sole public header in hpb/backend/upb

#include <cstring>

#include "absl/log/absl_check.h"
#include "absl/strings/string_view.h"
#include "hpb/internal/internal.h"
#include "hpb/ptr.h"
#include "upb/base/string_view.h"
#include "upb/base/upcast.h"
#include "upb/mem/arena.h"
#include "upb/message/message.h"
#include "upb/mini_table/message.h"

namespace hpb::interop::upb {

/**
 * Moves ownership of a message created in a source arena.
 *
 * Utility function to provide a way to move ownership across languages or VMs.
 *
 * Warning: any minitable skew will incur arbitrary memory access. Ensuring
 * minitable compatibility is the responsibility of the caller.
 */
// TODO: b/365824801 - consider rename to OwnMessage
template <typename T>
T MoveMessage(upb_Message* msg, upb_Arena* arena) {
  return internal::PrivateAccess::InvokeConstructor<T>(msg, arena);
}

template <typename T>
const upb_MiniTable* GetMiniTable(const T*) {
  return T::minitable();
}

template <typename T>
const upb_MiniTable* GetMiniTable(Ptr<T>) {
  return T::minitable();
}

template <typename T>
auto* GetMessage(T&& message) {
  return internal::PrivateAccess::GetInternalMsg(std::forward<T>(message));
}

template <typename T>
upb_Arena* GetArena(Ptr<T> message) {
  return internal::PrivateAccess::GetInternalArena(message);
}

template <typename T>
upb_Arena* GetArena(T* message) {
  return internal::PrivateAccess::GetInternalArena(message);
}

template <typename T>
upb_Arena* UnwrapArena(T&& arena) {
  return internal::PrivateAccess::GetInternalUPBArena(std::forward<T>(arena));
}

/**
 * Creates a const Handle to a upb message.
 *
 * The supplied arena must outlive the hpb handle.
 * All messages reachable from from the upb message must
 * outlive the hpb handle.
 *
 * That is:
 * upb allows message M on arena A to point to message M' on
 * arena A'. As a user of hpb, you must guarantee that both A and A'
 * outlive M and M'. In practice, this is enforced by using upb::Fuse,
 * or manual tracking.
 *
 * The upb message must not be mutated directly while the handle is alive.
 *
 * T must match actual type of `msg`.
 * TODO: b/361596328 - revisit GetArena for CHandles
 * TODO: b/362743843 - consider passing in MiniTable to ensure match
 */
// REMARK: This overload will be deleted soon. Prefer the overloads that take in
// the CMessageType or MiniTable.
template <typename T>
typename T::CProxy MakeCHandle(const upb_Message* msg, upb_Arena* arena) {
  return internal::PrivateAccess::CProxy<T>(msg, arena);
}

/* Creates a Handle from a const upb message.
 *
 * The supplied arena must outlive the hpb handle.
 * All messages reachable from from the upb message must
 * outlive the hpb handle.
 */
template <typename T>
typename T::CProxy MakeCHandle(
    const typename internal::AssociatedUpbTypes<T>::CMessageType* msg,
    upb_Arena* arena) {
  return internal::PrivateAccess::CProxy<T>(UPB_UPCAST(msg), arena);
}

/* Creates a Handle from an arbitrary upb message type, but
 * requires the corresponding MiniTable to be specified.
 *
 * The supplied arena must outlive the hpb handle.
 * All messages reachable from from the upb message must
 * outlive the hpb handle.
 *
 * REQUIRES: The MiniTables must match. Otherwise this function will CHECK-FAIL.
 */
template <typename T>
typename T::CProxy MakeCHandle(const upb_Message* msg,
                               const upb_MiniTable* minitable,
                               upb_Arena* arena) {
  ABSL_CHECK(minitable == internal::AssociatedUpbTypes<T>::kMiniTable);
  return internal::PrivateAccess::CProxy<T>(msg, arena);
}

/**
 * Creates a Handle to a mutable upb message.
 *
 * The supplied arena must outlive the hpb handle.
 * All messages reachable from from the upb message must
 * outlive the hpb handle.
 */
// REMARK: This overload will be deleted soon. Prefer the overloads that take in
// the CMessageType or MiniTable.
template <typename T>
typename T::Proxy MakeHandle(upb_Message* msg, upb_Arena* arena) {
  return typename T::Proxy(msg, arena);
}

/* Creates a Handle from a mutable upb message.
 *
 * The supplied arena must outlive the hpb handle.
 * All messages reachable from from the upb message must
 * outlive the hpb handle.
 */
template <typename T>
typename T::Proxy MakeHandle(
    typename internal::AssociatedUpbTypes<T>::CMessageType* msg,
    upb_Arena* arena) {
  return internal::PrivateAccess::Proxy<T>(UPB_UPCAST(msg), arena);
}

/* Creates a Handle from an arbitrary upb message type (mutable), but
 * requires the corresponding MiniTable to be specified.
 *
 * The supplied arena must outlive the hpb handle.
 * All messages reachable from from the upb message must
 * outlive the hpb handle.
 *
 * REQUIRES: The MiniTables must match. Otherwise this function will CHECK-FAIL.
 */
template <typename T>
typename T::Proxy MakeHandle(upb_Message* msg, const upb_MiniTable* minitable,
                             upb_Arena* arena) {
  ABSL_CHECK(minitable == internal::AssociatedUpbTypes<T>::kMiniTable);
  return internal::PrivateAccess::Proxy<T>(msg, arena);
}

/**
 * Creates a message in the given arena and returns a handle to it.
 *
 * The supplied arena must outlive the hpb handle.
 * All messages reachable from from the upb message must
 * outlive the hpb handle.
 */
template <typename T>
typename T::Proxy CreateMessage(upb_Arena* arena) {
  return internal::PrivateAccess::CreateMessage<T>(arena);
}

inline absl::string_view FromUpbStringView(upb_StringView str) {
  return absl::string_view(str.data, str.size);
}

inline upb_StringView CopyToUpbStringView(absl::string_view str,
                                          upb_Arena* arena) {
  const size_t str_size = str.size();
  char* buffer = static_cast<char*>(upb_Arena_Malloc(arena, str_size));
  memcpy(buffer, str.data(), str_size);
  return upb_StringView_FromDataAndSize(buffer, str_size);
}

}  // namespace hpb::interop::upb

#endif  // GOOGLE_PROTOBUF_HPB_BACKEND_UPB_INTEROP_H__
