// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef BASE_TRAITS_BAG_H_
#define BASE_TRAITS_BAG_H_

#include <concepts>
#include <initializer_list>
#include <optional>
#include <tuple>
#include <type_traits>
#include <utility>

#include "base/parameter_pack.h"

// A bag of Traits (structs / enums / etc...) can be an elegant alternative to
// the builder pattern and multiple default arguments for configuring things.
// Traits are terser than the builder pattern and can be evaluated at compile
// time, however they require the use of variadic templates which complicates
// matters. This file contains helpers that make Traits easier to use.
//
// WARNING: Trait bags are currently too heavy for non-constexpr usage in prod
// code due to template bloat, although adding NOINLINE to template constructors
// configured via trait bags can help.
//
// E.g.
//   struct EnableFeatureX {};
//   struct UnusedTrait {};
//   enum Color { RED, BLUE };
//
//   using ValidTraits = ParameterPack<EnableFeatureX, Color>;
//   ...
//   DoSomethingAwesome();                 // Use defaults (Color::BLUE &
//                                         // feature X not enabled)
//   DoSomethingAwesome(EnableFeatureX(),  // Turn feature X on
//                      Color::RED);       // And make it red.
//   DoSomethingAwesome(UnusedTrait(),     // Compile time error.
//                      Color::RED);
//
// DoSomethingAwesome might be defined as:
//
//   template <class... ArgTypes>
//     requires trait_helpers::AreValidTraits<ValidTraits, ArgTypes...>
//   constexpr void DoSomethingAwesome(ArgTypes... args)
//      : enable_feature_x(
//            trait_helpers::HasTrait<EnableFeatureX, ArgTypes...>()),
//        color(trait_helpers::GetEnum<Color, Color::BLUE>(args...)) {}

namespace base {
namespace trait_helpers {

// Return true if `Type` in present in `Args...`.
template <typename Type, typename... Args>
inline constexpr bool HasTypeInVariadicPack =
    ParameterPack<Args...>::template HasType<Type>::value;

// Represents a trait that has been removed by a predicate.
struct EmptyTrait {};

// Predicate used to remove any traits from the given list of types by
// converting them to EmptyTrait. E.g.
//
// template <typename... Args>
// void MyFunc(Args... args) {
//   DoSomethingWithTraits(
//       base::trait_helpers::Exclude<UnwantedTrait1,
//                                    UnwantedTrait2>::Filter(args)...);
// }
//
// NB It's possible to actually remove the unwanted trait from the pack, but
// that requires constructing a filtered tuple and applying it to the function,
// which isn't worth the complexity over ignoring EmptyTrait.
template <typename... TraitsToExclude>
struct Exclude {
  template <typename T>
  static constexpr auto Filter(T t) {
    if constexpr (HasTypeInVariadicPack<T, TraitsToExclude...>) {
      return EmptyTrait();
    } else {
      return t;
    }
  }
};

// CallFirstTag is an argument tag that helps to avoid ambiguous overloaded
// functions. When the following call is made:
//    func(CallFirstTag(), arg...);
// the compiler will give precedence to an overload candidate that directly
// takes CallFirstTag. Another overload that takes CallSecondTag will be
// considered iff the preferred overload candidates were all invalids and
// therefore discarded.
struct CallSecondTag {};
struct CallFirstTag : CallSecondTag {};

// A trait filter class |TraitFilterType| implements the protocol to get a value
// of type |ArgType| from an argument list and convert it to a value of type
// |TraitType|. If the argument list contains an argument of type |ArgType|, the
// filter class will be instantiated with that argument. If the argument list
// contains no argument of type |ArgType|, the filter class will be instantiated
// using the default constructor if available; a compile error is issued
// otherwise. The filter class must have the conversion operator TraitType()
// which returns a value of type TraitType.

// |InvalidTrait| is used to return from GetTraitFromArg when the argument is
// not compatible with the desired trait.
struct InvalidTrait {};

// Returns an object of type |TraitFilterType| constructed from |arg| if
// compatible, or |InvalidTrait| otherwise.
template <class TraitFilterType, class ArgType>
  requires std::constructible_from<TraitFilterType, ArgType>
constexpr TraitFilterType GetTraitFromArg(CallFirstTag, ArgType arg) {
  return TraitFilterType(arg);
}

template <class TraitFilterType, class ArgType>
constexpr InvalidTrait GetTraitFromArg(CallSecondTag, ArgType arg) {
  return InvalidTrait();
}

// Returns an object of type |TraitFilterType| constructed from a compatible
// argument in |args...|, or default constructed if none of the arguments are
// compatible. This is the implementation of GetTraitFromArgList() with a
// disambiguation tag.
template <class TraitFilterType, class... ArgTypes>
  requires(std::constructible_from<TraitFilterType, ArgTypes> || ...)
constexpr TraitFilterType GetTraitFromArgListImpl(CallFirstTag,
                                                  ArgTypes... args) {
  return std::get<TraitFilterType>(std::make_tuple(
      GetTraitFromArg<TraitFilterType>(CallFirstTag(), args)...));
}

template <class TraitFilterType, class... ArgTypes>
constexpr TraitFilterType GetTraitFromArgListImpl(CallSecondTag,
                                                  ArgTypes... args) {
  static_assert(std::constructible_from<TraitFilterType>,
                "The traits bag is missing a required trait.");
  return TraitFilterType();
}

template <typename Trait, typename... Args>
concept IsUniqueTrait =
    (count({std::constructible_from<Trait, Args>...}, true) <= 1);

// Constructs an object of type |TraitFilterType| from a compatible argument in
// |args...|, or using the default constructor, and returns its associated trait
// value using conversion to |TraitFilterType::ValueType|. If there are more
// than one compatible argument in |args|, generates a compile-time error.
template <class TraitFilterType, class... ArgTypes>
  requires IsUniqueTrait<TraitFilterType, ArgTypes...>
constexpr typename TraitFilterType::ValueType GetTraitFromArgList(
    ArgTypes... args) {
  return GetTraitFromArgListImpl<TraitFilterType>(CallFirstTag(), args...);
}

// Generates nicer errors for `GetTraitFromArgList`.
template <class TraitFilterType, class... ArgTypes>
constexpr typename TraitFilterType::ValueType GetTraitFromArgList(
    ArgTypes... args) {
  static_assert(IsUniqueTrait<TraitFilterType, ArgTypes...>,
                "The traits bag contains multiple traits of the same type.");

  // Must return a value to keep the constexpr definition structurally valid.
  // This prevents Clang from emitting a redundant "must be initialized by a
  // constant expression" error at the call site after the static_assert fails.
  return {};
}

// Helper class to implement a |TraitFilterType|.
template <typename T, typename _ValueType = T>
struct BasicTraitFilter {
  using ValueType = _ValueType;

  constexpr BasicTraitFilter(ValueType v) : value(v) {}

  constexpr operator ValueType() const { return value; }

  ValueType value = {};
};

template <typename ArgType, ArgType DefaultValue>
struct EnumTraitFilter : public BasicTraitFilter<ArgType> {
  constexpr EnumTraitFilter() : BasicTraitFilter<ArgType>(DefaultValue) {}
  constexpr EnumTraitFilter(ArgType arg) : BasicTraitFilter<ArgType>(arg) {}
};

template <typename ArgType>
struct OptionalEnumTraitFilter
    : public BasicTraitFilter<ArgType, std::optional<ArgType>> {
  constexpr OptionalEnumTraitFilter()
      : BasicTraitFilter<ArgType, std::optional<ArgType>>(std::nullopt) {}
  constexpr OptionalEnumTraitFilter(ArgType arg)
      : BasicTraitFilter<ArgType, std::optional<ArgType>>(arg) {}
};

// Tests whether multiple given argtument types are all valid traits according
// to the provided ValidTraits. To use, define a ValidTraits
template <typename ArgType>
struct RequiredEnumTraitFilter : public BasicTraitFilter<ArgType> {
  constexpr RequiredEnumTraitFilter(ArgType arg)
      : BasicTraitFilter<ArgType>(arg) {}
};

// Note EmptyTrait is always regarded as valid to support filtering.
template <class ValidTraits, class T>
concept IsValidTrait =
    ValidTraits::template HasType<std::remove_cvref_t<T>>::value ||
    std::same_as<T, EmptyTrait>;

// Tests whether a given trait type is valid or invalid by testing whether it is
// present in the provided ValidTraits ParameterPack. To use, define a
// ValidTraits type like this:
//
// using ValidTraits = ParameterPack<MyTrait, ...>;
//
// You can 'inherit' valid traits like so:
//
// using MoreValidTraits = ConcatParameterPacks<ValidTraits,
//                                               ParameterPack<MyOtherTrait>>;
template <class ValidTraits, class... ArgTypes>
concept AreValidTraits = (IsValidTrait<ValidTraits, ArgTypes> && ...);

template <typename Enum, typename... Args>
concept HasRequiredEnumTrait =
    (std::constructible_from<RequiredEnumTraitFilter<Enum>, Args> || ...);

// Helper to make getting an enum from a trait more readable.
template <typename Enum, typename... Args>
  requires HasRequiredEnumTrait<Enum, Args...>
static constexpr Enum GetEnum(Args... args) {
  return GetTraitFromArgList<RequiredEnumTraitFilter<Enum>>(args...);
}

// Generates nicer errors for `GetEnum` for required enums.
template <typename Enum, typename... Args>
static constexpr Enum GetEnum(Args... args) {
  static_assert(HasRequiredEnumTrait<Enum, Args...>,
                "The traits bag is missing a required trait.");

  // Must return a value to keep the constexpr definition structurally valid.
  // This prevents Clang from emitting a redundant "must be initialized by a
  // constant expression" error at the call site after the static_assert fails.
  return {};
}

// Helper to make getting an enum from a trait with a default more readable.
template <typename Enum, Enum DefaultValue, typename... Args>
static constexpr Enum GetEnum(Args... args) {
  return GetTraitFromArgList<EnumTraitFilter<Enum, DefaultValue>>(args...);
}

// Helper to make getting an optional enum from a trait with a default more
// readable.
template <typename Enum, typename... Args>
static constexpr std::optional<Enum> GetOptionalEnum(Args... args) {
  return GetTraitFromArgList<OptionalEnumTraitFilter<Enum>>(args...);
}

// Helper to make checking for the presence of a trait more readable.
template <typename Trait, typename... Args>
  requires IsUniqueTrait<Trait, Args...>
constexpr bool HasTrait() {
  return HasTypeInVariadicPack<Trait, Args...>;
}

// Generates nicer errors for `HasTrait`.
template <typename Trait, typename... Args>
constexpr bool HasTrait() {
  static_assert(IsUniqueTrait<Trait, Args...>,
                "The traits bag contains multiple traits of the same type.");
  return false;
}

// If you need a template vararg constructor to delegate to a private
// constructor, you may need to add this to the private constructor to ensure
// it's not matched by accident.
struct NotATraitTag {};

}  // namespace trait_helpers
}  // namespace base

#endif  // BASE_TRAITS_BAG_H_
