// Copyright 2021 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef GRPC_SRC_CORE_LIB_PROMISE_ARENA_PROMISE_H
#define GRPC_SRC_CORE_LIB_PROMISE_ARENA_PROMISE_H

#include <grpc/support/port_platform.h>
#include <stdlib.h>

#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>

#include "src/core/lib/promise/context.h"
#include "src/core/lib/promise/poll.h"
#include "src/core/lib/resource_quota/arena.h"
#include "src/core/util/construct_destruct.h"
#include "absl/meta/type_traits.h"

namespace grpc_core {

namespace arena_promise_detail {

struct ArgType {
  alignas(std::max_align_t) char buffer[sizeof(void*)];
};

template <typename T>
T*& ArgAsPtr(ArgType* arg) {
  static_assert(sizeof(ArgType) >= sizeof(T**),
                "Must have ArgType of at least one pointer size");
  return *reinterpret_cast<T**>(arg);
}

template <typename T>
T const* const& ArgAsPtr(ArgType const* arg) {
  static_assert(sizeof(ArgType) >= sizeof(T**),
                "Must have ArgType of at least one pointer size");
  return *reinterpret_cast<T const* const*>(arg);
}

template <typename T>
struct Vtable {
  // Poll the promise, once.
  Poll<T> (*poll_once)(ArgType* arg);
  // Move the promise to another memory location.
  void (*move)(ArgType* dst, ArgType* src);
  // Destroy the underlying callable object if there is one.
  // Since we don't delete (the arena owns the memory) but we may need to call a
  // destructor, we expose this for when the ArenaPromise object is destroyed.
  void (*destroy)(ArgType* arg);
  // Get the proto representation of the promise.
  void (*to_proto)(const ArgType* arg, grpc_channelz_v2_Promise* promise_proto,
                   upb_Arena* arena);
};

template <typename T>
struct VtableAndArg {
  const Vtable<T>* vtable;
  ArgType arg;
};

// Implementation of Vtable for an empty object.
// Used when an empty ArenaPromise is created, or when the ArenaPromise is moved
// from. Since in either case these objects should not be polled, we simply
// crash if it is.
template <typename T>
struct Null {
  static const Vtable<T> vtable;

  static Poll<T> PollOnce(ArgType*) {
    abort();
    GPR_UNREACHABLE_CODE(return Pending{});
  }

  static void Move(ArgType*, ArgType*) {}
  static void Destroy(ArgType*) {}
  static void ToProto(const ArgType*, grpc_channelz_v2_Promise* p, upb_Arena*) {
    grpc_channelz_v2_Promise_set_unknown_promise(
        p, StdStringToUpbString(TypeName<T>()));
  }
};

template <typename T>
const Vtable<T> Null<T>::vtable = {PollOnce, Move, Destroy, ToProto};

// Implementation of ImplInterface for a callable object.
template <typename T, typename Callable>
struct AllocatedCallable {
  static const Vtable<T> vtable;

  static Poll<T> PollOnce(ArgType* arg) {
    return poll_cast<T>((*ArgAsPtr<Callable>(arg))());
  }

  static void Move(ArgType* dst, ArgType* src) {
    ArgAsPtr<Callable>(dst) = ArgAsPtr<Callable>(src);
  }

  static void Destroy(ArgType* arg) { Destruct(ArgAsPtr<Callable>(arg)); }

  static void ToProto(const ArgType* arg,
                      grpc_channelz_v2_Promise* promise_proto,
                      upb_Arena* arena) {
    PromiseAsProto(*ArgAsPtr<Callable>(arg), promise_proto, arena);
  }
};

template <typename T, typename Callable>
const Vtable<T> AllocatedCallable<T, Callable>::vtable = {PollOnce, Move,
                                                          Destroy, ToProto};

// Implementation of ImplInterface for a small callable object (one that fits
// within the ArgType arg)
template <typename T, typename Callable>
struct Inlined {
  static const Vtable<T> vtable;

  static Poll<T> PollOnce(ArgType* arg) {
    return poll_cast<T>((*reinterpret_cast<Callable*>(arg))());
  }

  static void Move(ArgType* dst, ArgType* src) {
    new (dst) Callable(reinterpret_cast<Callable&&>(*src));
  }

  static void Destroy(ArgType* arg) {
    Destruct(reinterpret_cast<Callable*>(arg));
  }

  static void ToProto(const ArgType* arg,
                      grpc_channelz_v2_Promise* promise_proto,
                      upb_Arena* arena) {
    PromiseAsProto(*reinterpret_cast<Callable const*>(arg), promise_proto,
                   arena);
  }
};

template <typename T, typename Callable>
const Vtable<T> Inlined<T, Callable>::vtable = {PollOnce, Move, Destroy,
                                                ToProto};

// If a callable object is empty we can substitute any instance of that callable
// for the one we call (for how could we tell the difference)?
// Since this corresponds to a lambda with no fields, and we expect these to be
// reasonably common, we can elide the arena allocation entirely and simply poll
// a global shared instance.
// (this comes up often when the promise only accesses context data from the
// containing activity).
template <typename T, typename Callable>
struct SharedCallable {
  static const Vtable<T> vtable;

  static Poll<T> PollOnce(ArgType* arg) {
    return (*reinterpret_cast<Callable*>(arg))();
  }

  static void ToProto(const ArgType* arg,
                      grpc_channelz_v2_Promise* promise_proto,
                      upb_Arena* arena) {
    PromiseAsProto(*reinterpret_cast<const Callable*>(arg), promise_proto,
                   arena);
  }
};

template <typename T, typename Callable>
const Vtable<T> SharedCallable<T, Callable>::vtable = {
    PollOnce, Null<T>::Move, Null<T>::Destroy, ToProto};

// Redirector type: given a callable type, expose a Make() function that creates
// the appropriate underlying implementation.
template <typename T, typename Callable, typename Ignored = void>
struct ChooseImplForCallable;

template <typename T, typename Callable>
struct ChooseImplForCallable<
    T, Callable,
    absl::enable_if_t<!std::is_empty<Callable>::value &&
                      (sizeof(Callable) > sizeof(ArgType))>> {
  static void Make(Callable&& callable, VtableAndArg<T>* out) {
    out->vtable = &AllocatedCallable<T, Callable>::vtable;
    ArgAsPtr<Callable>(&out->arg) = GetContext<Arena>()->template New<Callable>(
        std::forward<Callable>(callable));
  }
};

template <typename T, typename Callable>
struct ChooseImplForCallable<
    T, Callable,
    absl::enable_if_t<!std::is_empty<Callable>::value &&
                      (sizeof(Callable) <= sizeof(ArgType))>> {
  static void Make(Callable&& callable, VtableAndArg<T>* out) {
    out->vtable = &Inlined<T, Callable>::vtable;
    Construct(reinterpret_cast<Callable*>(&out->arg),
              std::forward<Callable>(callable));
  }
};

template <typename T, typename Callable>
struct ChooseImplForCallable<
    T, Callable, absl::enable_if_t<std::is_empty<Callable>::value>> {
  static void Make(Callable&&, VtableAndArg<T>* out) {
    out->vtable = &SharedCallable<T, Callable>::vtable;
  }
};

// Wrap ChooseImplForCallable with a friend approachable syntax.
template <typename T, typename Callable>
void MakeImplForCallable(Callable&& callable, VtableAndArg<T>* out) {
  ChooseImplForCallable<T, Callable>::Make(std::forward<Callable>(callable),
                                           out);
}

}  // namespace arena_promise_detail

// A promise for which the state memory is allocated from an arena.
template <typename T>
class ArenaPromise {
 public:
  // Construct an empty, uncallable, invalid ArenaPromise.
  ArenaPromise() = default;

  // Construct an ArenaPromise that will call the given callable when polled.
  template <typename Callable,
            typename Ignored =
                absl::enable_if_t<!std::is_same<Callable, ArenaPromise>::value>>
  // NOLINTNEXTLINE(google-explicit-constructor)
  ArenaPromise(Callable&& callable) {
    arena_promise_detail::MakeImplForCallable(std::forward<Callable>(callable),
                                              &vtable_and_arg_);
  }

  // ArenaPromise is not copyable.
  ArenaPromise(const ArenaPromise&) = delete;
  ArenaPromise& operator=(const ArenaPromise&) = delete;
  // ArenaPromise is movable.
  ArenaPromise(ArenaPromise&& other) noexcept {
    vtable_and_arg_.vtable = other.vtable_and_arg_.vtable;
    vtable_and_arg_.vtable->move(&vtable_and_arg_.arg,
                                 &other.vtable_and_arg_.arg);
    other.vtable_and_arg_.vtable = &arena_promise_detail::Null<T>::vtable;
  }
  ArenaPromise& operator=(ArenaPromise&& other) noexcept {
    vtable_and_arg_.vtable->destroy(&vtable_and_arg_.arg);
    vtable_and_arg_.vtable = other.vtable_and_arg_.vtable;
    vtable_and_arg_.vtable->move(&vtable_and_arg_.arg,
                                 &other.vtable_and_arg_.arg);
    other.vtable_and_arg_.vtable = &arena_promise_detail::Null<T>::vtable;
    return *this;
  }

  // Destruction => call Destroy on the underlying impl object.
  ~ArenaPromise() { vtable_and_arg_.vtable->destroy(&vtable_and_arg_.arg); }

  // Expose the promise interface: a call operator that returns Poll<T>.
  Poll<T> operator()() {
    return vtable_and_arg_.vtable->poll_once(&vtable_and_arg_.arg);
  }

  bool has_value() const {
    return vtable_and_arg_.vtable != &arena_promise_detail::Null<T>::vtable;
  }

  void ToProto(grpc_channelz_v2_Promise* promise_proto,
               upb_Arena* arena) const {
    vtable_and_arg_.vtable->to_proto(&vtable_and_arg_.arg, promise_proto,
                                     arena);
  }

 private:
  // Underlying impl object.
  arena_promise_detail::VtableAndArg<T> vtable_and_arg_ = {
      &arena_promise_detail::Null<T>::vtable, {}};
};

}  // namespace grpc_core

#endif  // GRPC_SRC_CORE_LIB_PROMISE_ARENA_PROMISE_H
