//===-- rosa/app/AppTuple.hpp -----------------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
// Distributed under the terms and conditions of the Boost Software License 1.0.
// See accompanying file LICENSE.
//
// If you did not receive a copy of the license file, see
// http://www.boost.org/LICENSE_1_0.txt.
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/app/AppTuple.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2019-2020
///
/// \brief Facilities for handling multiple input/output values for connections
/// in the *application interface*.
///
/// \see \c rosa::app::Application
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_APP_APPTUPLE_HPP
#define ROSA_APP_APPTUPLE_HPP

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

namespace rosa {
namespace app {

/// A tuple to manage multiple input/output values in the *application
/// 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 AppTuple : 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::app::AppTuple::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.
  AppTuple(void) = default;

  /// Constructor, initializes the underlying \c std::tuple with lvalue
  /// references.
  ///
  /// \param Args value references to the values to store
  AppTuple(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
  AppTuple(std::decay_t<Ts> &&... Args)
      : std::tuple<Ts...>(std::move(Args)...) {}

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

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

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

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

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

private:
  /// Dumps \p this object to a given \c std::ostream.
  ///
  /// \note Provides implementation for \c rosa::app::AppTuple::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 AppTuple<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 AppTuple<Ts...>::dump(std::ostream &OS) const noexcept {
  dump(OS, seq_t<sizeof...(Ts)>());
}

/// Type alias for a \c rosa::app::AppTuple that contains no elements.
using EmptyAppTuple = AppTuple<>;

/// Template specialization for \c rosa::app::EmptyAppTuple.
template <> struct AppTuple<> : 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.
  AppTuple(void) : std::tuple<>() {}

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

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

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

  // Default move-assignment,
  AppTuple &operator=(AppTuple &&) = 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::app::AppTuple instance from the given lvalues
/// references.
///
/// \tparam Ts types of elements of the tuple
///
/// \see \c rosa::app::AppTuple
///
/// \param Args values to store in the tuple
///
/// \return an instance of \c rosa::app::AppTuple<Ts...> with \p Args as
/// elements
template <typename... Ts>
inline AppTuple<Ts...> make_app_tuple(const Ts &... Args) noexcept {
  return AppTuple<Ts...>(Args...);
}

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

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

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

/// Declaration of the template.
///
/// \tparam Tuple \c rosa::app::AppTuple to unwrap
template <typename Tuple> struct UnwrapAppTuple;

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

///@}

///@}

/// \defgroup IsTuple Implementation of \c rosa::app::IsTuple
///
/// \brief Tells if a type is a tuple as in it can be converted to \c
/// rosa::app::AppTuple.
///
/// \see \c rosa::app::MatchingAppTuple
///
/// 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<AppTuple<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 IsAppTuple Implementation of \c rosa::app::IsAppTuple
///
/// \brief Tells if a type is an instance of \c rosa::app::AppTuple.
///
/// Whether a type \c T is an instance of \c rosa::app::AppTuple can be
/// checked as \code
/// IsAppTuple<T>::Value
/// \endcode
///
/// \note `!IsAppTuple<T>::Value || IsTuple<T>::Value`
///@{

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

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

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

///@}

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

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

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

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

///@}

/// Convenience template type alias for easy use of \c
/// rosa::app::MatchingAppTuple.
///
/// Converts a tuple type to the matching \c rosa::app::AppTuple type.
///
/// \tparam Tuple type to convert
template <typename Tuple>
using matching_app_tuple_t = typename MatchingAppTuple<Tuple>::Type;

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

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

/// Specialization for \c rosa::EmptyTypeList.
template <> struct TypeListAllAppTuple<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 TypeListAllAppTuple<TypeList<T, Ts...>> {
  static constexpr bool Value =
      IsAppTuple<T>::Value && TypeListAllAppTuple<TypeList<Ts...>>::Value;
};

///@}

} // End namespace app
} // End namespace rosa

namespace std {

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

} // End namespace std

#endif // ROSA_APP_APPTUPLE_HPP
