// 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_CORE_LIB_GPRPP_TABLE_H
#define GRPC_CORE_LIB_GPRPP_TABLE_H

#include <grpc/support/port_platform.h>

#include <utility>

#include "absl/utility/utility.h"

#include "src/core/lib/gprpp/bitset.h"

namespace grpc_core {

// Meta-programming detail types to aid in building up a Table
namespace table_detail {

// A tuple-like type that contains manually constructed elements.
template <typename... Ts>
struct Elements;
template <typename T, typename... Ts>
struct Elements<T, Ts...> : Elements<Ts...> {
  union U {
    U() {}
    ~U() {}
    GPR_NO_UNIQUE_ADDRESS T x;
  };
  U u;
};
template <>
struct Elements<> {};

// Element accessor for Elements<>
// Provides a static method f that returns a pointer to the value of element I
// for Elements<Ts...>
template <size_t I, typename... Ts>
struct GetElem;

template <typename T, typename... Ts>
struct GetElem<0, T, Ts...> {
  static T* f(Elements<T, Ts...>* e) { return &e->u.x; }
  static const T* f(const Elements<T, Ts...>* e) { return &e->u.x; }
};

template <size_t I, typename T, typename... Ts>
struct GetElem<I, T, Ts...> {
  static auto f(Elements<T, Ts...>* e)
      -> decltype(GetElem<I - 1, Ts...>::f(e)) {
    return GetElem<I - 1, Ts...>::f(e);
  }
  static auto f(const Elements<T, Ts...>* e)
      -> decltype(GetElem<I - 1, Ts...>::f(e)) {
    return GetElem<I - 1, Ts...>::f(e);
  }
};

// CountIncludedStruct is the backing for the CountIncluded function below.
// Sets a member constant N to the number of times Needle is in Haystack.
template <typename Needle, typename... Haystack>
struct CountIncludedStruct;
template <typename Needle, typename Straw, typename... RestOfHaystack>
struct CountIncludedStruct<Needle, Straw, RestOfHaystack...> {
  static constexpr size_t N =
      static_cast<size_t>(std::is_same<Needle, Straw>::value) +
      CountIncludedStruct<Needle, RestOfHaystack...>::N;
};
template <typename Needle>
struct CountIncludedStruct<Needle> {
  static constexpr size_t N = 0;
};
// Returns the number of times Needle is in Haystack.
template <typename Needle, typename... Haystack>
constexpr size_t CountIncluded() {
  return CountIncludedStruct<Needle, Haystack...>::N;
}

// IndexOfStruct is the backing for IndexOf below.
// Set a member constant N to the index of Needle in Haystack.
// Ignored should be void always, and is used for enable_if_t.
template <typename Ignored, typename Needle, typename... Haystack>
struct IndexOfStruct;
template <typename Needle, typename Straw, typename... RestOfHaystack>
struct IndexOfStruct<absl::enable_if_t<std::is_same<Needle, Straw>::value>,
                     Needle, Straw, RestOfHaystack...> {
  // The first element is the one we're looking for. Done.
  static constexpr size_t N = 0;
};
template <typename Needle, typename Straw, typename... RestOfHaystack>
struct IndexOfStruct<absl::enable_if_t<!std::is_same<Needle, Straw>::value>,
                     Needle, Straw, RestOfHaystack...> {
  // The first element is not the one we're looking for, recurse looking at the
  // tail, and sum the number of recursions.
  static constexpr size_t N =
      1 + IndexOfStruct<void, Needle, RestOfHaystack...>::N;
};
// Return the index of Needle in Haystack.
// Guarded by CountIncluded to ensure that the return type is unambiguous.
// If you got here from a compiler error using Table, it's likely that you've
// used the type-based accessor/mutators, but the type you're using is repeated
// more than once in the Table type arguments. Consider either using the indexed
// accessor/mutator variants, or eliminating the ambiguity in type resolution.
template <typename Needle, typename... Haystack>
constexpr absl::enable_if_t<CountIncluded<Needle, Haystack...>() == 1, size_t>
IndexOf() {
  return IndexOfStruct<void, Needle, Haystack...>::N;
}

// TypeIndexStruct is the backing for TypeIndex below.
// Sets member type Type to the type at index I in Ts.
// Implemented as a simple type recursion.
template <size_t I, typename... Ts>
struct TypeIndexStruct;
template <typename T, typename... Ts>
struct TypeIndexStruct<0, T, Ts...> {
  using Type = T;
};
template <size_t I, typename T, typename... Ts>
struct TypeIndexStruct<I, T, Ts...> : TypeIndexStruct<I - 1, Ts...> {};
// TypeIndex is the type at index I in Ts.
template <size_t I, typename... Ts>
using TypeIndex = typename TypeIndexStruct<I, Ts...>::Type;

// Helper to call the destructor of p if p is non-null.
template <typename T>
void DestructIfNotNull(T* p) {
  if (p) p->~T();
}

// Helper function... just ignore the initializer list passed into it.
// Allows doing 'statements' via parameter pack expansion in C++11 - given
// template <typename... Ts>:
//  do_these_things({(foo<Ts>(), 1)});
// will execute foo<T>() for each T in Ts.
// In this example we also leverage the comma operator to make the resultant
// type of each statement be a consistant int so that C++ type deduction works
// as we'd like (note that in the expression (a, 1) in C++, the 'result' of the
// expression is the value after the right-most ',' -- in this case 1, with a
// executed as a side effect.
template <typename T>
void do_these_things(std::initializer_list<T>) {}

}  // namespace table_detail

// A Table<Ts> is much like a tuple<optional<Ts>...> - a set of values that are
// optionally present. Table efficiently packs the presence bits for size, and
// provides a slightly more convenient interface.
template <typename... Ts>
class Table {
  // Helper - TypeIndex<I> is the type at index I in Ts
  template <size_t I>
  using TypeIndex = table_detail::TypeIndex<I, Ts...>;

 public:
  // Construct a table with no values set.
  Table() = default;
  // Destruct - forwards to the Destruct member with an integer sequence so we
  // can destruct field-wise.
  ~Table() { Destruct(absl::make_index_sequence<sizeof...(Ts)>()); }

  // Copy another table
  Table(const Table& rhs) {
    // Since we know all fields are clear initially, pass false for or_clear.
    Copy<false>(absl::make_index_sequence<sizeof...(Ts)>(), rhs);
  }

  // Copy another table
  Table& operator=(const Table& rhs) {
    // Since we may not be all clear, pass true for or_clear to have Copy()
    // clear newly emptied fields.
    Copy<true>(absl::make_index_sequence<sizeof...(Ts)>(), rhs);
    return *this;
  }

  // Move from another table
  Table(Table&& rhs) noexcept {
    // Since we know all fields are clear initially, pass false for or_clear.
    Move<false>(absl::make_index_sequence<sizeof...(Ts)>(),
                std::forward<Table>(rhs));
  }

  // Move from another table
  Table& operator=(Table&& rhs) noexcept {
    // Since we may not be all clear, pass true for or_clear to have Move()
    // clear newly emptied fields.
    Move<true>(absl::make_index_sequence<sizeof...(Ts)>(),
               std::forward<Table>(rhs));
    return *this;
  }

  // Check if this table has a value for type T.
  // Only available if there exists only one T in Ts.
  template <typename T>
  bool has() const {
    return has<index_of<T>()>();
  }

  // Check if this table has index I.
  template <size_t I>
      absl::enable_if_t < I<sizeof...(Ts), bool> has() const {
    return present_bits_.is_set(I);
  }

  // Return the value for type T, or nullptr if it is un-set.
  // Only available if there exists only one T in Ts.
  template <typename T>
  T* get() {
    return get<index_of<T>()>();
  }

  // Return the value for type T, or nullptr if it is un-set.
  // Only available if there exists only one T in Ts.
  template <typename T>
  const T* get() const {
    return get<index_of<T>()>();
  }

  // Return the value for index I, or nullptr if it is un-set.
  template <size_t I>
  TypeIndex<I>* get() {
    if (has<I>()) return element_ptr<I>();
    return nullptr;
  }

  // Return the value for index I, or nullptr if it is un-set.
  template <size_t I>
  const TypeIndex<I>* get() const {
    if (has<I>()) return element_ptr<I>();
    return nullptr;
  }

  // Return the value for type T, default constructing it if it is un-set.
  template <typename T>
  T* get_or_create() {
    return get_or_create<index_of<T>()>();
  }

  // Return the value for index I, default constructing it if it is un-set.
  template <size_t I>
  TypeIndex<I>* get_or_create() {
    auto* p = element_ptr<I>();
    if (!set_present<I>(true)) {
      new (p) TypeIndex<I>();
    }
    return element_ptr<I>();
  }

  // Set the value for type T - using Args as construction arguments.
  template <typename T, typename... Args>
  T* set(Args&&... args) {
    return set<index_of<T>()>(std::forward<Args>(args)...);
  }

  // Set the value for index I - using Args as construction arguments.
  template <size_t I, typename... Args>
  TypeIndex<I>* set(Args&&... args) {
    auto* p = element_ptr<I>();
    if (set_present<I>(true)) {
      TypeIndex<I> replacement(std::forward<Args>(args)...);
      *p = std::move(replacement);
    } else {
      new (p) TypeIndex<I>(std::forward<Args>(args)...);
    }
    return p;
  }

  template <size_t I>
  TypeIndex<I>* set(TypeIndex<I>&& value) {
    auto* p = element_ptr<I>();
    if (set_present<I>(true)) {
      *p = std::forward<TypeIndex<I>>(value);
    } else {
      new (p) TypeIndex<I>(std::forward<TypeIndex<I>>(value));
    }
    return p;
  }

  // Clear the value for type T, leaving it un-set.
  template <typename T>
  void clear() {
    clear<index_of<T>()>();
  }

  // Clear the value for index I, leaving it un-set.
  template <size_t I>
  void clear() {
    if (set_present<I>(false)) {
      using T = TypeIndex<I>;
      element_ptr<I>()->~T();
    }
  }

  // Iterate through each set field in the table
  template <typename F>
  void ForEach(F f) const {
    ForEachImpl(std::move(f), absl::make_index_sequence<sizeof...(Ts)>());
  }

  // Count the number of set fields in the table
  size_t count() const { return present_bits_.count(); }

  // Check if the table is completely empty
  bool empty() const { return present_bits_.none(); }

  // Clear all elements in the table.
  void ClearAll() { ClearAllImpl(absl::make_index_sequence<sizeof...(Ts)>()); }

 private:
  // Bit field for which elements of the table are set (true) or un-set (false,
  // the default) -- one bit for each type in Ts.
  using PresentBits = BitSet<sizeof...(Ts)>;
  // The tuple-like backing structure for Table.
  using Elements = table_detail::Elements<Ts...>;
  // Extractor from Elements
  template <size_t I>
  using GetElem = table_detail::GetElem<I, Ts...>;

  // Given a T, return the unambiguous index of it within Ts.
  template <typename T>
  static constexpr size_t index_of() {
    return table_detail::IndexOf<T, Ts...>();
  }

  // Given an index, return a point to the (maybe uninitialized!) data value at
  // index I.
  template <size_t I>
  TypeIndex<I>* element_ptr() {
    return GetElem<I>::f(&elements_);
  }

  // Given an index, return a point to the (maybe uninitialized!) data value at
  // index I.
  template <size_t I>
  const TypeIndex<I>* element_ptr() const {
    return GetElem<I>::f(&elements_);
  }

  // Set the present bit to value (if true - value is present/set, if false,
  // value is un-set). Returns the old value so that calling code can note
  // transition edges.
  template <size_t I>
  bool set_present(bool value) {
    bool out = present_bits_.is_set(I);
    present_bits_.set(I, value);
    return out;
  }

  // Set the value of index I to the value held in rhs index I if it is set.
  // If it is unset, if or_clear is true, then clear our value, otherwise do
  // nothing.
  template <bool or_clear, size_t I>
  void CopyIf(const Table& rhs) {
    if (auto* p = rhs.get<I>()) {
      set<I>(*p);
    } else if (or_clear) {
      clear<I>();
    }
  }

  // Set the value of index I to the value moved from rhs index I if it was set.
  // If it is unset, if or_clear is true, then clear our value, otherwise do
  // nothing.
  template <bool or_clear, size_t I>
  void MoveIf(Table&& rhs) {
    if (auto* p = rhs.get<I>()) {
      set<I>(std::move(*p));
    } else if (or_clear) {
      clear<I>();
    }
  }

  // Call (*f)(value) if that value is in the table.
  template <size_t I, typename F>
  void CallIf(F* f) const {
    if (auto* p = get<I>()) {
      (*f)(*p);
    }
  }

  // For each field (element I=0, 1, ...) if that field is present, call its
  // destructor.
  template <size_t... I>
  void Destruct(absl::index_sequence<I...>) {
    table_detail::do_these_things<int>(
        {(table_detail::DestructIfNotNull(get<I>()), 1)...});
  }

  // For each field (element I=0, 1, ...) copy that field into this table -
  // or_clear as per CopyIf().
  template <bool or_clear, size_t... I>
  void Copy(absl::index_sequence<I...>, const Table& rhs) {
    table_detail::do_these_things<int>({(CopyIf<or_clear, I>(rhs), 1)...});
  }

  // For each field (element I=0, 1, ...) move that field into this table -
  // or_clear as per MoveIf().
  template <bool or_clear, size_t... I>
  void Move(absl::index_sequence<I...>, Table&& rhs) {
    table_detail::do_these_things<int>(
        {(MoveIf<or_clear, I>(std::forward<Table>(rhs)), 1)...});
  }

  // For each field (element I=0, 1, ...) if that field is present, call f.
  template <typename F, size_t... I>
  void ForEachImpl(F f, absl::index_sequence<I...>) const {
    table_detail::do_these_things<int>({(CallIf<I>(&f), 1)...});
  }

  template <size_t... I>
  void ClearAllImpl(absl::index_sequence<I...>) {
    table_detail::do_these_things<int>({(clear<I>(), 1)...});
  }

  // Bit field indicating which elements are set.
  GPR_NO_UNIQUE_ADDRESS PresentBits present_bits_;
  // The memory to store the elements themselves.
  GPR_NO_UNIQUE_ADDRESS Elements elements_;
};

}  // namespace grpc_core

#endif  // GRPC_CORE_LIB_GPRPP_TABLE_H
