//===-- rosa/deluxe/DeluxeTuple.hpp -----------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/deluxe/DeluxeTuple.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2019
///
/// \brief Facilities for handling multiple input/output values for connections
/// in the *deluxe interface*.
///
/// \see \c rosa::deluxe::DeluxeContext
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_DELUXE_DELUXETUPLE_HPP
#define ROSA_DELUXE_DELUXETUPLE_HPP

#include "rosa/support/sequence.hpp"
#include "rosa/support/type_token.hpp"
#include <ostream>
#include <tuple>

namespace rosa {
namespace deluxe {

/// A tuple to manage multiple input/output values in the *deluxe interface*.
///
/// \tparam Ts types of elements of the tuple
///
/// \note The template may be instantiated only with built-in types and the
/// number of those type may not exceed the capacity of a \c rosa::Token.
template <typename... Ts>
struct DeluxeTuple : public std::tuple<Ts...> {
  // Statically enforce that the class template is instantiated only with
  // built-in types.
  STATIC_ASSERT((TypeListSubsetOf<TypeList<Ts...>, BuiltinTypes>::Value),
                "not built-in types");
  // Statically enforce that the class template is instantiated with not too
  // many types.
  // \note Instantiation would fail on \c rosa::deluxe::DeluxeTuple::TT if there
  // are too many types; this assertion is for more readable error reporting.
  STATIC_ASSERT(sizeof...(Ts) <= token::MaxTokenizableListSize,
                "Too many types");

  /// How many elements the instance has.
  static constexpr token_size_t Length = sizeof...(Ts);

  /// What types the class contains.
  ///
  /// Type information encoded as \c rosa::Token.
  static constexpr Token TT = TypeToken<Ts...>::Value;

  /// Default constructor, zero-initializes elements.
  DeluxeTuple(void) = default;

  /// Constructor, initializes the underlying \c std::tuple with lvalue
  /// references.
  ///
  /// \param Args value references to the values to store
  DeluxeTuple(const std::decay_t<Ts> &... Args) : std::tuple<Ts...>(Args...) {}

  /// Constructor, initializes the underlying \c std::tuple with rvalue
  /// references.
  ///
  /// \param Args rvalue references to the values to store
  DeluxeTuple(std::decay_t<Ts> &&... Args)
      : std::tuple<Ts...>(std::move(Args)...) {}

  /// Contructor, initializes the underlying \c std::tuple from another matching
  /// \c std::tuple.
  DeluxeTuple(const std::tuple<Ts...> &Args) : std::tuple<Ts...>(Args) {}

  /// Default copy-constructor.
  DeluxeTuple(const DeluxeTuple &) = default;

  /// Default move-constructor.
  DeluxeTuple(DeluxeTuple &&) = default;

  /// Default copy-assignment.
  DeluxeTuple &operator=(const DeluxeTuple &) = default;

  /// Default move-assignment.
  DeluxeTuple &operator=(DeluxeTuple &&) = default;

private:
  /// Dumps \p this object to a given \c std::ostream.
  ///
  /// \note Provides implementation for \c rosa::deluxe::DeluxeTuple::dump.
  ///
  /// \tparam S0 Indices for accessing elements.
  ///
  /// \param [in,out] OS output stream to dump to
  ///
  /// \note The second argument provides indices statically as template
  /// arguments \p S0..., so its actual value is ignored.
  ///
  /// \pre Statically, \p S0... matches number of types \p this object was
  /// created: \code
  /// sizeof...(S0) == sizeof...(Ts)
  /// \endcode
  template <size_t... S0>
  void dump(std::ostream &OS, Seq<S0...>) const noexcept;

public:
  /// Dumps \p this object to a given \c std::ostream.
  ///
  /// \param [in,out] OS output stream to dump to
  void dump(std::ostream &OS) const noexcept;
};

template <typename... Ts>
template <size_t... S0>
void DeluxeTuple<Ts...>::dump(std::ostream &OS, Seq<S0...>) const noexcept {
  STATIC_ASSERT(sizeof...(S0) == sizeof...(Ts), "inconsistent type arguments");
  // Convert value to std::string with std::to_string except for a value of
  // std::string that does not need conversion.
  auto dump_to_string = [](const auto &V) {
    if constexpr (std::is_same<std::decay_t<decltype(V)>, std::string>::value) {
      return V;
    } else {
      return std::to_string(V);
    }
  };
  OS << "{";
  (OS << ... << (" " + dump_to_string(std::get<S0>(*this))));
  OS << " }";
}

template <typename... Ts>
void DeluxeTuple<Ts...>::dump(std::ostream &OS) const noexcept {
  dump(OS, seq_t<sizeof...(Ts)>());
}

/// Type alias for a \c rosa::deluxe::DeluxeTuple that contains no elements.
using EmptyDeluxeTuple = DeluxeTuple<>;

/// Template specialization for \c rosa::deluxe::EmptyDeluxeTuple.
template <> struct DeluxeTuple<> : public std::tuple<> {
  /// How many elements the instance has.
  static constexpr token_size_t Length = 0;

  /// What types the class contains.
  ///
  /// Type information encoded as \c rosa::Token.
  static constexpr Token TT = TypeToken<>::Value;

  /// Constructor, initializes the underlying \c std::tuple.
  DeluxeTuple(void) : std::tuple<>() {}

  /// Default copy-constructor.
  DeluxeTuple(const DeluxeTuple &) = default;

  // Default move-constructor.
  DeluxeTuple(DeluxeTuple &&) = default;

  /// Default copy-assignment.
  DeluxeTuple &operator=(const DeluxeTuple &) = default;

  // Default move-assignment,
  DeluxeTuple &operator=(DeluxeTuple &&) = default;

  /// Dumps \p this object to a given \c std::ostream.
  ///
  /// \param [in,out] OS output stream to dump to
  static void dump(std::ostream &OS) noexcept;
};

/// Creates a \c rosa::deluxe::DeluxeTuple instance from the given lvalues
/// references.
///
/// \tparam Ts types of elements of the tuple
///
/// \see \c rosa::deluxe::DeluxeTuple
///
/// \param Args values to store in the tuple
///
/// \return an instance of \c rosa::deluxe::DeluxeTuple<Ts...> with \p Args as
/// elements
template <typename... Ts>
inline DeluxeTuple<Ts...> make_deluxe_tuple(const Ts &... Args) noexcept {
  return DeluxeTuple<Ts...>(Args...);
}

/// Creates a \c rosa::deluxe::DeluxeTuple instance from the given rvalue
/// references.
///
/// \tparam Ts types of elements of the tuple
///
/// \see \c rosa::deluxe::DeluxeTuple
///
/// \param Args values to store in the tuple
///
/// \return an instance of \c rosa::deluxe::DeluxeTuple<Ts...> with \p Args as
/// elements
template <typename... Ts>
inline DeluxeTuple<Ts...> make_deluxe_tuple(Ts&&... Args) noexcept {
  return DeluxeTuple<Ts...>(std::move(Args)...);
}

/// Creates a \c rosa::deluxe::DeluxeTuple instance from the given \c std::tuple
/// reference.
///
/// \tparam Ts types of elements of the tuple
///
/// \see \c rosa::deluxe::DeluxeTuple
///
/// \param Args values to store in the tuple
///
/// \return an instance of \c rosa::deluxe::DeluxeTuple<Ts...> with \p Args as
/// elements
template <typename... Ts>
inline DeluxeTuple<Ts...>
make_deluxe_tuple(const std::tuple<Ts...> &Args) noexcept {
  return DeluxeTuple<Ts...>(Args);
}

/// \defgroup UnwrapDeluxeTuple Implementation of
/// rosa::deluxe::UnwrapDeluxeTuple
///
/// \brief Unwraps element types from an instance of \c
/// rosa::deluxe::DeluxeTuple into a \c rosa::TypeList
///
/// Types can be unwrapped from a \c rosa::deluxe::DeluxeTuple instance as \code
/// typename UnwrapDeluxeTuple<List>::Type
/// \endcode
///
/// For example, the following expression evaluates to `true`: \code
/// std::is_same<typename UnwrapDeluxeTuple<DeluxeTuple<T1, T2>>::Type,
///              TypeList<T1, T2>>::value
/// \endcode
///@{

/// Declaration of the template.
///
/// \tparam Tuple \c rosa::deluxe::DeluxeTuple to unwrap
template <typename Tuple> struct UnwrapDeluxeTuple;

/// Implementation of the template for \c rosa::deluxe::DeluxeTuple instances.
template <typename... Ts> struct UnwrapDeluxeTuple<DeluxeTuple<Ts...>> {
  using Type = TypeList<Ts...>;
};

///@}

///@}

/// \defgroup IsTuple Implementation of \c rosa::deluxe::IsTuple
///
/// \brief Tells if a type is a tuple as in it can be converted to \c
/// rosa::deluxe::DeluxeTuple.
///
/// \see \c rosa::deluxe::MatchingDeluxeTuple
///
/// Whether a type \c T is a tuple can be checked as \code
/// IsTuple<T>::Value
/// \endcode
///@{

/// Declaration of the template.
///
/// \tparam T type to check
template <typename T> struct IsTuple;

/// Specialization for the case when the type is an instance of \c std::tuple.
template <typename... Ts> struct IsTuple<std::tuple<Ts...>> {
  static constexpr bool Value = true;
};

/// Specialization for the case when the type is an instance of \c std::tuple.
template <typename... Ts> struct IsTuple<DeluxeTuple<Ts...>> {
  static constexpr bool Value = true;
};

/// Implementation for a general case of type \p T.
template <typename T> struct IsTuple { static constexpr bool Value = false; };

///@}

/// \defgroup IsDeluxeTuple Implementation of \c rosa::deluxe::IsDeluxeTuple
///
/// \brief Tells if a type is an instance of \c rosa::deluxe::DeluxeTuple.
///
/// Whether a type \c T is an instance of \c rosa::deluxe::DeluxeTuple can be
/// checked as \code
/// IsDeluxeTuple<T>::Value
/// \endcode
///
/// \note `!IsDeluxeTuple<T>::Value || IsTuple<T>::Value`
///@{

/// Declaration of the template.
///
/// \tparam T type to check
template <typename T> struct IsDeluxeTuple;

/// Specialization for the case when the type is an instance of \c
/// rosa::deluxe::DeluxeTuple.
template <typename... Ts>
struct IsDeluxeTuple<DeluxeTuple<Ts...>> {
  static constexpr bool Value = true;
};

/// Implementation for a general case of type \p T.
template <typename T>
struct IsDeluxeTuple {
  static constexpr bool Value = false;
};

///@}

/// \defgroup MatchingDeluxeTuple Implementation of \c
/// rosa::deluxe::MatchingDeluxeTuple
///
/// \brief Gives the \c rosa::deluxe::DeluxeTuple type that matches a given
/// tuple type.
///
/// The matching \c rosa::deluxe::DeluxeTuple type for a tuple type \p T can be
/// obtained as \code
/// typename MatchingDeluxeTuple<T>::Type
/// \endcode
/// If \p T is \c rosa::deluxe::DeluxeTuple, the matching type is \p T itself.
/// If \p T is \c std::tuple, the matching type if \c rosa::deluxe::DeluxeTuple
/// with the same type parameters as \p T.
/// \c rosa::deluxe::MatchingDeluxeTuple is not defined for other type
/// parameters.
///
/// \note The template is defined for type \p T only if
/// `rosa::deluxe::IsTuple<T>::Value`. Values of such types can be used to
/// construct a new instance of the class \c rosa::deluxe::DeluxeTuple.
///
///\see \c rosa::deluxe::IsTuple
///@{

/// Declaration of the template.
///
/// \tparam T type to check
template <typename T> struct MatchingDeluxeTuple;

/// Specialization for the case when the type is an instance of \c
/// rosa::deluxe::DeluxeTuple.
template <typename... Ts> struct MatchingDeluxeTuple<DeluxeTuple<Ts...>> {
  using Type = DeluxeTuple<Ts...>;
};

/// Specialization for the case when the type is an instance of \c
/// std::tuple.
template <typename... Ts> struct MatchingDeluxeTuple<std::tuple<Ts...>> {
  using Type = DeluxeTuple<Ts...>;
};

///@}

/// Convenience template type alias for easy use of \c
/// rosa::deluxe::MatchingDeluxeTuple.
///
/// Converts a tuple type to the matching \c rosa::deluxe::DeluxeTuple type.
///
/// \tparam Tuple type to convert
template <typename Tuple>
using matching_deluxe_tuple_t = typename MatchingDeluxeTuple<Tuple>::Type;

/// \defgroup TypeListAllDeluxeTuple Implementation of
/// \c rosa::deluxe::TypeListAllDeluxeTuple
///
/// \brief Tells if all types in a \c rosa::TypeList is an instance of \c
/// rosa::deluxe::DeluxeTuple.
///
/// Whether a \c rosa::TypeList \c List contains instances of \c
/// rosa::deluxe::DeluxeTuple only can be checked as \code
/// TypeListAllDeluxeTuple<List>::Value
/// \endcode
///@{

/// Declaration of the template.
///
/// \tparam List \c rosa::TypeList to check
template <typename List> struct TypeListAllDeluxeTuple;

/// Specialization for \c rosa::EmptyTypeList.
template <> struct TypeListAllDeluxeTuple<EmptyTypeList> {
  static constexpr bool Value = true;
};

/// Implementation for the general case when there is at leasst one element in
/// the list.
template <typename T, typename... Ts>
struct TypeListAllDeluxeTuple<TypeList<T, Ts...>> {
  static constexpr bool Value =
      IsDeluxeTuple<T>::Value && TypeListAllDeluxeTuple<TypeList<Ts...>>::Value;
};

///@}

} // End namespace deluxe
} // End namespace rosa

namespace std {

/// Dumps a \c rosa::deluxe::Deluxe instance to a given \c std::ostream.
///
/// \param [in,out] OS output stream to dump to
/// \param Tuple \c rosa::deluxe::Deluxe to dump
///
/// \return \p OS after dumping \p Tuple to it
template <typename... Ts>
ostream &operator<<(ostream &OS,
                    const rosa::deluxe::DeluxeTuple<Ts...> &Tuple) {
  Tuple.dump(OS);
  return OS;
}

} // End namespace std

#endif // ROSA_DELUXE_DELUXETUPLE_HPP
