//===-- rosa/core/MessageMatcher.hpp ----------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/core/MessageMatcher.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017
///
/// \brief Facilities for checking and matching types of values stored in
///        \c rosa::Message instances.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_CORE_MESSAGEMATCHER_HPP
#define ROSA_CORE_MESSAGEMATCHER_HPP

#include "rosa/core/Message.hpp"

#include <tuple>

namespace rosa {

/// Provides features to type-check a \c rosa::Message instance and extract
/// stored values from it into an \c std::tuple instance with matching type
/// arguments.
///
/// \tparam List \c rosa::TypeList to check the stored values against
template <typename List> struct MessageMatcher;

/// Definition of \c rosa::MessageMatcher for non-empty lists of types, like
/// \c rosa::Message itself.
///
/// \tparam Type first mandatory type
/// \tparam Types any further types
template <typename Type, typename... Types>
struct MessageMatcher<TypeList<Type, Types...>> {

  /// \c rosa::Token associated to the given \c rosa::TypeList.
  static constexpr Token T = TypeToken<Type, Types...>::Value;

  /// Tells if the values stored in a \c rosa::Message instance are matching
  /// types given as \c rosa::TypeList<T, Types...>, considering
  /// \c rosa::AtomConstant instead of \c rosa::AtomValue.
  ///
  /// \param Msg \c rosa::Message to match
  ///
  /// \return whether the types of values stored in \p Msg matches
  /// \c rosa::TypeList<Type, Types...>
  static inline bool doesStronglyMatch(const Message &Msg) noexcept;

  /// Gives a \c std::tuple with references to the values stored in a
  /// type-matching instance of \c rosa::Message.
  ///
  /// \param Msg \c rosa::Message to extract values from
  ///
  /// \return \c std::tuple with references to the values stored in \p Msg
  ///
  /// \pre Types of the values stored in \p Msg matches
  /// \c rosa::TypeList<Type, Types...>:\code
  /// doesStronglyMatch(Msg)
  /// \endcode
  static inline std::tuple<const Type &, const Types &...>
  extractedValues(const Message &Msg) noexcept;
};

/// Turns a list of types into a \c rosa::TypeList for \c rosa::MessageMatcher.
template <typename Type, typename... Types>
using MsgMatcher = MessageMatcher<TypeList<Type, Types...>>;

/// Nested namespace with implementation for features of
/// \c rosa::MessageMatcher, consider it private.
namespace {

/// \defgroup MessageMatcherImpl Implementation for rosa::MessageMatcher
///
/// An implementation of type-checking and value extraction for
/// \c rosa::MessageMatcher.
///
///@{

/// Template declaration of \c MessageMatcherImpl.
///
/// \tparam List \c rosa::TypeList to match against
template <typename List> struct MessageMatcherImpl;

/// Specialization for \c rosa::EmptyTypeList.
template <> struct MessageMatcherImpl<EmptyTypeList> {
  static bool doesStronglyMatchFrom(const Message &Msg,
                                    const size_t Pos) noexcept {
    // Matching EmptyTypeList only if reached the end of the stored types.
    return Pos == Msg.Size;
  }

  static std::tuple<> extractedValuesFrom(const Message &Msg,
                                          const size_t Pos) noexcept {
    // It is valid to extract an empty list only if we reached the end of
    // stored values.
    ASSERT(doesStronglyMatchFrom(Msg, Pos));
    return std::tie();
  }
};

/// Specialization for \c rosa::AtomValue in the head.
template <AtomValue V, typename... Ts>
struct MessageMatcherImpl<TypeList<AtomConstant<V>, Ts...>> {

  static bool doesHeadStronglyMatchAt(const Message &Msg,
                                      const size_t Pos) noexcept {
    // Matching a \c rosa::AtomConstant in the head if there is a type stored at
    // \p Pos, the stored type is \c rosa::AtomValue, and the corresponding
    // value matches the \c rosa::AtomValue \p V.
    return Pos < Msg.Size && Msg.isTypeAt<AtomValue>(Pos) &&
           Msg.valueAt<AtomValue>(Pos) == V;
  }

  static bool doesStronglyMatchFrom(const Message &Msg,
                                    const size_t Pos) noexcept {
    // Matching a non-empty list if the head is matching and the rest of the
    // list is matching.
    return doesHeadStronglyMatchAt(Msg, Pos) &&
           MessageMatcherImpl<TypeList<Ts...>>::doesStronglyMatchFrom(Msg,
                                                                      Pos + 1);
  }

  static std::tuple<const AtomConstant<V> &, const Ts &...>
  extractedValuesFrom(const Message &Msg, const size_t Pos) noexcept {
    // Extracting for a non-empty list with a matching \c rosa::AtomConstant in
    // the head by getting the encoded \c rosa::AtomConstant and concatenating
    // it with values extracted for the rest of the list.
    ASSERT(doesHeadStronglyMatchAt(Msg, Pos));
    return std::tuple_cat(
        std::tie(AtomConstant<V>::Value),
        MessageMatcherImpl<TypeList<Ts...>>::extractedValuesFrom(Msg, Pos + 1));
  }
};

/// Definition for the general case when a regular built-in type (not a
/// \c rosa::AtomConstant) is in the head.
template <typename T, typename... Ts>
struct MessageMatcherImpl<TypeList<T, Ts...>> {

  static bool doesHeadStronglyMatchAt(const Message &Msg,
                                      const size_t Pos) noexcept {
    // Matching the head if there is a type stored at \p Pos, and the stored
    // type is \p T.
    return Pos < Msg.Size && Msg.isTypeAt<T>(Pos);
  }

  static bool doesStronglyMatchFrom(const Message &Msg,
                                    const size_t Pos) noexcept {
    // Matching a non-empty list if the head is matching and the rest of the
    // list is matching.
    return doesHeadStronglyMatchAt(Msg, Pos) &&
           MessageMatcherImpl<TypeList<Ts...>>::doesStronglyMatchFrom(Msg,
                                                                      Pos + 1);
  }

  static std::tuple<const T &, const Ts &...>
  extractedValuesFrom(const Message &Msg, const size_t Pos) noexcept {
    // Extracting for a non-empty list with a matching head by getting the
    // value for the head and concatenating it with values extracted for the
    // rest of the list.
    ASSERT(doesHeadStronglyMatchAt(Msg, Pos));
    return std::tuple_cat(
        std::tie(Msg.valueAt<T>(Pos)),
        MessageMatcherImpl<TypeList<Ts...>>::extractedValuesFrom(Msg, Pos + 1));
  }
};

///@}

} // End namespace

template <typename Type, typename... Types>
bool MessageMatcher<TypeList<Type, Types...>>::doesStronglyMatch(
    const Message &Msg) noexcept {
  // \note Fail quick on \c rosa::MessageMatcher::T, then match against list
  // with squashed integers the way \c rosa::Token is generated.
  return T == Msg.T &&
         MessageMatcherImpl<typename SquashedTypeList<
             TypeList<Type, Types...>>::Type>::doesStronglyMatchFrom(Msg, 0);
}

template <typename Type, typename... Types>
std::tuple<const Type &, const Types &...>
MessageMatcher<TypeList<Type, Types...>>::extractedValues(
    const Message &Msg) noexcept {
  ASSERT(doesStronglyMatch(Msg));
  // \note Match against a list with squashed integers as \c rosa::Token is
  // generated.
  return MessageMatcherImpl<typename SquashedTypeList<
      TypeList<Type, Types...>>::Type>::extractedValuesFrom(Msg, 0);
}

} // End namespace rosa

#endif // ROSA_CORE_MESSAGEMATCHER_HPP
