//===-- rosa/core/Message.hpp -----------------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/core/Message.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017
///
/// \brief Declaration of \c rosa::Message base-class.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_CORE_MESSAGE_HPP
#define ROSA_CORE_MESSAGE_HPP

#include "rosa/support/log.h"
#include "rosa/support/tokenized_storages.hpp"

#include "rosa/core/forward_declarations.h"

namespace rosa {

/// *Message* interface.
///
/// The interface provides means to check the type of the stored values, but
/// actual data is to be managed by derived implementations.
///
/// A \c rosa::Message instance is an immutable data object that obtains its
/// data upon creation and provides only constant references for the stored
/// values.
///
/// \note Any reference obtained from a \c rosa::Message instance remains valid
/// only as long as the owning \c rosa::Message object is not destroyed.
///
/// \todo Some member functions of \c rosa::Message duplicate member functions
/// of \c rosa::TokenizedStorage, which cannot be easily factored out into a
/// common base class due to eventual diamond inheritance issues in derived
/// classes. Could this duplication be avoided?
class Message {
protected:
  /// Creates a new instance.
  ///
  /// \note No implementation for empty list.
  ///
  /// \tparam Type type of the mandatory first argument
  /// \tparam Types types of any further arguments
  ///
  /// \note the actual arguments are ignored by the constructor it is only
  /// their type that matters. The actual values are supposed to be handled by
  /// any implementation derived from \c rosa::Message.
  ///
  /// \pre \p Type and \p Types are all built-in types and the number of stored
  /// values does not exceed \c rosa::token::MaxTokenizableListSize.
  template <typename Type, typename... Types>
  Message(const Type &, const Types &...) noexcept;

  /// No copying and moving of \c rosa::Message instances.
  ///@{
  Message(const Message &) = delete;
  Message(Message &&) = delete;
  Message &operator=(const Message &) = delete;
  Message &operator=(Message &&) = delete;
  ///@}

public:
  /// Creates a \c rosa::message_t object from constant lvalue references.
  ///
  /// \tparam Type type of the mandatory first argument
  /// \tparam Types types of any further arguments
  ///
  /// \param T the first value to include in the \c rosa::Message
  /// \param Ts optional further values to include in the \c rosa::Message
  ///
  /// \return new \c rosa::message_t object created from the given arguments
  template <typename Type, typename... Types>
  static message_t create(const Type &T, const Types &... Ts) noexcept;

  /// Creates a \c rosa::message_t object from rvalue references.
  ///
  /// \tparam Type type of the mandatory first argument
  /// \tparam Types types of any further arguments
  ///
  /// \param T the first value to include in the \c rosa::Message
  /// \param Ts optional further values to include in the \c rosa::Message
  ///
  /// \return new \c rosa::message_t object created from the given arguments
  template <typename Type, typename... Types>
  static message_t create(Type &&T, Types &&... Ts) noexcept;

  /// Represents the types of the values stored in \p this object.
  ///
  /// A valid, non-empty \c rosa::Token representing the types of the values
  /// stored in \p this object.
  const Token T;

  /// The number of values stored in \p this object.
  ///
  /// That is the number of types encoded in \c rosa::Message::T.
  const size_t Size;

  /// Destroys \p this object.
  virtual ~Message(void);

  /// Tells if the value stored at a given index is of a given type.
  ///
  /// \note Any \c rosa::AtomConstant is encoded in \c rosa::Token as
  /// the \c rosa::AtomValue wrapped into it.
  ///
  /// \tparam Type type to match against
  ///
  /// \param Pos index the type of the value at is to be matched against \p Type
  ///
  /// \return if the value at index \p Pos of type \p Type
  ///
  /// \pre \p Pos is a valid index:\code
  /// Pos < Size
  /// \endcode
  template <typename Type> bool isTypeAt(const size_t Pos) const noexcept;

  /// Gives a constant reference of a value of a given type stored at a given
  /// index.
  ///
  /// \tparam Type type to give a reference of
  ///
  /// \param Pos index to set the reference for
  ///
  /// \return constant reference of \p Type for the value stored at index \p Pos
  ///
  /// \pre \p Pos is a valid index and the value at index \p Pos is of type
  /// \p Type:
  /// \code
  /// Pos < Size && isTypeAt<Type>(Pos)
  /// \endcode
  template <typename Type> const Type &valueAt(const size_t Pos) const noexcept;

protected:
  /// Provides an untyped pointer for the value at a given index.
  ///
  /// \param Pos index to take a pointer for
  ///
  /// \return untyped pointer for the value stored at index \p Pos
  ///
  /// \pre \p Pos is a valid index:\code
  /// Pos < Size
  /// \endcode
  virtual const void *pointerTo(const size_t Pos) const noexcept = 0;
};

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

/// Template class for an implementation of \c rosa::Message.
///
/// \tparam Types types whose values are to be stored
template <typename... Types> class LocalMessage;

/// Implementation of the template \c rosa::LocalMessage providing facilities
/// for storing values as a \c rosa::Message object.
///
/// \tparam Type type of the first mandatory value of the \c rosa::Message
/// \tparam Types of any further values
template <typename Type, typename... Types>
class LocalMessage<Type, Types...> final
    : public Message,
      private TokenizedStorage<Type, Types...> {
public:
  /// Creates an instance from constant lvalue references.
  ///
  /// \param T the mandatory first value to store in the \c rosa::Message object
  /// \param Ts optional further values to store in the \c rosa::Message object
  LocalMessage(const Type &T, const Types &... Ts) noexcept
      : Message(T, Ts...),
        TokenizedStorage<Type, Types...>(T, Ts...) {
    ASSERT(this->T == this->ST && Size == this->size()); // Sanity check.
  }

  /// Creates an instance from rvalue references.
  ///
  /// \param T the mandatory first value to store in the \c rosa::Message object
  /// \param Ts optional further values to store in the \c rosa::Message object
  LocalMessage(Type &&T, Types &&... Ts) noexcept
      : Message(T, Ts...),
        TokenizedStorage<Type, Types...>(std::move(T), std::move(Ts)...) {
    ASSERT(this->T == this->ST && Size == this->size()); // Sanity check.
  }

  /// Provides an untyped pointer for the constant value stored at a position.
  ///
  /// \param Pos the index of the value to return an untyped pointer for
  ///
  /// \return untyped pointer for the constant value stored at index \p Pos
  ///
  /// \pre \p Pos is a valid index:\code
  /// Pos < Size
  /// \endcode
  const void *pointerTo(const size_t Pos) const noexcept override {
    ASSERT(Pos < Size);
    return TokenizedStorage<Type, Types...>::pointerTo(Pos);
  }

  /// Aborts the program!
  ///
  /// Since \c rosa::Message instances are supposed to be immutable, the
  /// non-const inherited function is overridden so that it aborts execution.
  void *pointerTo(const size_t) noexcept override {
    ROSA_CRITICAL("Unallowed operation of rosa::LocalMessage");
  }
};

} // End namespace

template <typename Type, typename... Types>
Message::Message(const Type &, const Types &...) noexcept
    : T(TypeToken<typename std::decay<Type>::type,
                  typename std::decay<Types>::type...>::Value),
      Size(lengthOfToken(T)) {
  ASSERT(validToken(T) &&
         lengthOfToken(T) == (1 + sizeof...(Types))); // Sanity check.
  LOG_TRACE("Creating Message with Token(" + to_string(T) + ")");
}

/// \note The implementation instantiates a private local template class
/// \c LocalMessage.
template <typename Type, typename... Types>
message_t Message::create(const Type &T, const Types &... Ts) noexcept {
  return message_t(new LocalMessage<Type, Types...>(T, Ts...));
}

/// \note The implementation instantiates a private local template class
/// \c LocalMessage.
template <typename Type, typename... Types>
message_t Message::create(Type &&T, Types &&... Ts) noexcept {
  return message_t(
      new LocalMessage<Type, Types...>(std::move(T), std::move(Ts)...));
}

template <typename Type>
bool Message::isTypeAt(const size_t Pos) const noexcept {
  ASSERT(Pos < Size);
  Token TT = T;
  dropNOfToken(TT, Pos);
  return isHeadOfTokenTheSameType<Type>(TT);
}

template <typename Type>
const Type &Message::valueAt(const size_t Pos) const noexcept {
  ASSERT(Pos < Size && isTypeAt<Type>(Pos));
  return *static_cast<const Type *>(pointerTo(Pos));
}

} // End namespace rosa

#endif // ROSA_CORE_MESSAGE_HPP
