//===-- rosa/support/tokenized_storages.hpp ---------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/support/tokenized_storages.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017-2019
///
/// \brief Definition of storage helper template for storing values in a
/// type-safe way based on type tokens.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_SUPPORT_TOKENIZED_STORAGES_HPP
#define ROSA_SUPPORT_TOKENIZED_STORAGES_HPP

#include "rosa/support/type_token.hpp"

#include <memory>
#include <vector>

namespace rosa {

/// Defines a simple interface for storing and accessing values of different
/// types.
///
/// While the interface provides features to access values and know their
/// types, it is the users responsibility to use particular values according to
/// their actual types. No facilities for type-safe access of values is
/// provided by the class.
///
/// \see \c rosa::TokenizedStorage for a type-safe specialization of the
/// interface.
class AbstractTokenizedStorage {
protected:
  /// Protected constructor restricts instantiation for derived classes.
  AbstractTokenizedStorage(void) noexcept = default;

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

  /// Destroys \p this object.
  virtual ~AbstractTokenizedStorage(void) noexcept = default;

  /// Tells how many values are stored in \p this object.
  ///
  /// \return number of values stored in \p this object
  virtual size_t size(void) const noexcept = 0;

  /// Tells the type of the value stored at a position.
  ///
  /// \param Pos the index of the value whose type is to returned
  ///
  /// \return \c rosa::TypeNumber for the value stored at index \p Pos
  ///
  /// \pre \p Pos is a valid index:\code
  /// Pos < size()
  /// \endcode
  virtual TypeNumber typeAt(const token_size_t Pos) const noexcept = 0;

  /// Provides an untyped pointer for the value stored at a position.
  ///
  /// \param Pos the index of the value to return an untyped 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 void *pointerTo(const token_size_t Pos) noexcept = 0;

  /// Provides a constant untyped pointer for the value stored at a position.
  ///
  /// \param Pos the index of the value to return an untyped pointer for
  ///
  /// \return constant untyped pointer for the value stored at index \p Pos
  ///
  /// \pre \p Pos is a valid index:\code
  /// Pos < Offsets.size()
  /// \endcode
  virtual const void *pointerTo(const token_size_t Pos) const noexcept = 0;
};

/// Template class storing values and providing dynamic type-safe access to
/// them in a lightweight way based on type tokens.
///
/// \see rosa/support/type_token.hpp
///
/// \tparam Types types whose values are to be stored
template <typename... Types> class TokenizedStorage;

/// \defgroup TokenizedStorageForTypeList Implementation of
/// rosa::TokenizedStorageForTypeList
///
/// \brief Transforms a \c rosa::TypeList instance to the corresponding
/// \c rosa::TokenizedStorage instance.
///
/// A \c rosa::TypeList \c List instance can be turned into a corresponding \c
/// rosa::TokenizedStorage instance as \code
/// typename TokenizedStorageForTypeList<List>::Type
/// \endcode
///
/// For example, the following expression evaluates to `true`: \code
/// std::is_same<typename TokenizedStorageForTypeList<TypeList<T1, T2>>::Type,
///              TokenizedStorage<T1, T2>>::value
/// \endcode
///@{

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

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

///@}

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

/// Initializes a pre-allocated memory area with values from constant lvalue
/// references.
///
/// \tparam Types types whose values are to be stored
///
/// \param Arena pre-allocated memory area to store values to
/// \param Ts the values to store in \p Arena
///
/// \note \p Arena needs to be a valid pointer to a memory area big enough for
/// values of \p Types.
template <typename... Types>
inline void createArenaElements(void *const Arena,
                                const Types &... Ts) noexcept;

/// \defgroup createLvalueArenaElement Implementation of creating lvalue arena elements
///
/// Stores values from constant lvalue references into a pre-allocated memory
/// area.
///
/// \note To be used by the implementation of \c createArenaElements.
///
/// \todo Document these functions.
///@{

/// \note This terminal case is used for both constant lvalue references and
/// value references.
template <size_t Pos>
inline void createArenaElement(void *const,
                               const std::vector<size_t> &Offsets) {
  ASSERT(Pos == Offsets.size());
}

template <size_t Pos, typename Type, typename... Types>
inline void createArenaElement(void *const Arena,
                               const std::vector<size_t> &Offsets,
                               const Type &T, const Types &... Ts) noexcept {
  ASSERT(Arena != nullptr && Pos < Offsets.size());
  new (static_cast<Type *>(static_cast<void *>(static_cast<uint8_t *>(Arena) +
                                               Offsets[Pos]))) Type(T);
  createArenaElement<Pos + 1>(Arena, Offsets, Ts...);
}

template <size_t Pos, AtomValue V, typename... Types>
inline void
createArenaElement(void *const Arena, const std::vector<size_t> &Offsets,
                   const AtomConstant<V> &, const Types &... Ts) noexcept {
  ASSERT(Arena != nullptr && Pos < Offsets.size());
  *static_cast<AtomValue *>(
      static_cast<void *>(static_cast<uint8_t *>(Arena) + Offsets[Pos])) = V;
  createArenaElement<Pos + 1>(Arena, Offsets, Ts...);
}

///@}

/// Implementation of the template.
///
/// \tparam Types types of values to store
///
/// \param Arena pre-allocated memory area to store values to
/// \param Ts values to store in \p Arena
///
/// \pre \p Arena is not \p nullptr.
template <typename... Types>
inline void createArenaElements(void *const Arena,
                                const Types &... Ts) noexcept {
  ASSERT(Arena != nullptr);
  createArenaElement<0>(Arena, TokenizedStorage<Types...>::Offsets, Ts...);
}

/// Initializes a pre-allocated memory area with values from rvalue references.
///
/// \tparam Types types whose values are to be stored
///
/// \param Arena pre-allocated memory area to store values to
/// \param Ts the values to store in \p Arena
///
/// \note \p Arena needs to be a valid pointer to a memory area big enough for
/// values of \p Types.
template <typename... Types>
inline void createArenaElements(void *const Arena, Types &&... Ts) noexcept;

/// \defgroup createRvalueArenaElement Implementation of creating rvalue arena elements
///
/// Stores values from rvalue references into a pre-allocated memory area.
///
/// \note To be used by the implementation of \c createArenaElements.
///
/// \todo Document these functions.
///@{

template <size_t Pos, typename Type, typename... Types>
inline void createArenaElement(void *const Arena,
                               const std::vector<size_t> &Offsets, Type &&T,
                               Types &&... Ts) noexcept {
  ASSERT(Arena != nullptr && Pos < Offsets.size());
  new (static_cast<Type *>(static_cast<void *>(
      static_cast<uint8_t *>(Arena) + Offsets[Pos]))) Type(std::move(T));
  createArenaElement<Pos + 1>(Arena, Offsets, std::move(Ts)...);
}

template <size_t Pos, AtomValue V, typename... Types>
inline void createArenaElement(void *const Arena,
                               const std::vector<size_t> &Offsets,
                               AtomConstant<V> &&, Types &&... Ts) noexcept {
  ASSERT(Arena != nullptr && Pos < Offsets.size());
  *static_cast<AtomValue *>(
      static_cast<void *>(static_cast<uint8_t *>(Arena) + Offsets[Pos])) = V;
  createArenaElement<Pos + 1>(Arena, Offsets, std::move(Ts)...);
}

///@}

/// Implementation of the template.
///
/// \tparam Types types of values to store
///
/// \param Arena pre-allocated memory area to store values to
/// \param Ts values to store in \p Arena
///
/// \pre \p Arena is not \c nullptr.
template <typename... Types>
inline void createArenaElements(void *const Arena, Types &&... Ts) noexcept {
  ASSERT(Arena != nullptr);
  createArenaElement<0>(Arena, TokenizedStorage<Types...>::Offsets,
                        std::move(Ts)...);
}

/// Destroys values allocated by \c createArenaElements.
///
/// \tparam Types types whose values are stored in \p Arena
///
/// \param Arena the memory area to destroy values from
///
/// \note \p Arena needs to be a valid pointer to a memory area where values of
/// \p Types are stored.
template <typename... Types>
inline void destroyArenaElements(void *const Arena) noexcept;

/// \defgroup destroyArenaElement Implementation of destroying arena elements
///
/// Destroys values from a memory area.
///
/// \note To be used by the implementation of \c destroyArenaElements.
///
/// \todo Document these functions.
///@{

template <size_t Pos>
inline void destroyArenaElement(void *const,
                                const std::vector<size_t> &Offsets) noexcept {
  ASSERT(Pos == Offsets.size());
}

template <size_t Pos, typename Type, typename... Types>
inline void destroyArenaElement(void *const Arena,
                                const std::vector<size_t> &Offsets) noexcept {
  ASSERT(Arena != nullptr && Pos < Offsets.size());
  static_cast<Type *>(
      static_cast<void *>(static_cast<uint8_t *>(Arena) + Offsets[Pos]))
      ->~Type();
  destroyArenaElement<Pos + 1, Types...>(Arena, Offsets);
}

///@}

/// Implementation of the template.
///
/// \tparam Types types of values to destroy
///
/// \param Arena the memory area to destroy values from
///
/// \pre \p Arena is not \c nullptr.
template <typename... Types>
inline void destroyArenaElements(void *const Arena) noexcept {
  ASSERT(Arena != nullptr);
  destroyArenaElement<0, Types...>(Arena, TokenizedStorage<Types...>::Offsets);
}

} // End namespace

/// Implementation of the template \c rosa::TokenizedStorage as a
/// specialization of \c rosa::AbstractTokenizedStorage.
///
/// The class provides facilities for storing values and providing type-safe
/// access to them.
///
/// \tparam Types types of values to store
template <typename... Types>
class TokenizedStorage : public AbstractTokenizedStorage {
public:
  /// \c rosa::Token for the stored values.
  static constexpr Token ST = TypeToken<std::decay_t<Types>...>::Value;

  /// Byte offsets to access stored values in \c rosa::TokenizedStorage::Arena.
  static const std::vector<size_t> Offsets;

private:
  /// A BLOB storing all the values one after the other.
  void *const Arena;

  /// Generates byte offsets for accessing values stored in
  /// \c rosa::TokenizedStorage::Arena.
  ///
  /// \return \c std::vector containing byte offsets for accessing values stored
  /// in \c rosa::TokenizedStorage::Arena
  static std::vector<size_t> offsets(void) noexcept {
    Token T = ST;                      // Need a mutable copy.
    const token_size_t N = lengthOfToken(T); // Number of types encoded in \c T.
    std::vector<size_t> O(N);          // Allocate vector of proper size.
    // Do nothing for 0 elements.
    if (N > 0) {
      token_size_t I = 0; // Start indexing from position \c 0.
      O[0] = 0; // First offset is always \c 0.
      while (I < N - 1) {
        ASSERT(I + 1 < O.size() && lengthOfToken(T) == N - I);
        // Calculate next offset based on the previous one.
        // \note The offset of the last value is stored at `O[N - 1]`, which is
        // set when `I == N - 2`. Hence the limit of the loop.
        O[I + 1] = O[I] + sizeOfHeadOfToken(T);
        dropHeadOfToken(T), ++I;
      }
      ASSERT(I + 1 == O.size() && lengthOfToken(T) == 1);
    }
    return O;
  }

public:
  /// Creates an instance with default values.
  ///
  /// \note This constructor requires that all actual template arguments \p
  /// Types... are default constructible.
  TokenizedStorage(void) noexcept
      : Arena(::operator new(sizeOfValuesOfToken(ST))) {
    ASSERT(Arena != nullptr); // Sanity check.
    createArenaElements(Arena, Types()...);
  }

  /// Creates an instance from constant lvalue references.
  ///
  /// \param Ts values to store
  TokenizedStorage(const std::decay_t<Types> &... Ts) noexcept
      : Arena(::operator new(sizeOfValuesOfToken(ST))) {
    ASSERT(Arena != nullptr); // Sanity check.
    createArenaElements(Arena, Ts...);
  }

  /// Creates an instance from rvalue references.
  ///
  /// \param Ts values to store
  TokenizedStorage(std::decay_t<Types> &&... Ts) noexcept
      : Arena(::operator new(sizeOfValuesOfToken(ST))) {
    ASSERT(Arena != nullptr); // Sanity check.
    createArenaElements(Arena, std::move(Ts)...);
  }

  /// No copying and moving of \c rosa::TokenizedStorage instances.
  ///
  /// \note This restriction may be relaxed as moving should be easy to
  /// implement, only requires the possiblity to validate Arena pointer.
  ///@{
  TokenizedStorage(const TokenizedStorage&) = delete;
  TokenizedStorage &operator=(const TokenizedStorage&) = delete;
  TokenizedStorage(TokenizedStorage&& Other) = delete;
  TokenizedStorage &operator=(TokenizedStorage&&) = delete;
  ///@}

  // Destroys \p this object.
  ~TokenizedStorage(void) {
    destroyArenaElements<std::decay_t<Types>...>(Arena);
    ::operator delete(Arena);
  }

  /// Tells how many values are stored in \p this object.
  ///
  /// \return number of values stored in \p this object
  size_t size(void) const noexcept override {
    return Offsets.size();
  }

  /// Tells the type of the value stored at a position.
  ///
  /// \param Pos the index of the value whose type is to returned
  ///
  /// \return \c rosa::TypeNumber for the value stored at index \p Pos
  ///
  /// \pre \p Pos is a valid index:\code
  /// Pos < size()
  /// \endcode
  TypeNumber typeAt(const token_size_t Pos) const noexcept override {
    ASSERT(Pos < size());
    Token TT = ST;
    dropNOfToken(TT, Pos);
    return headOfToken(TT);
  }

  /// Provides an untyped pointer for the value stored at a position.
  ///
  /// \param Pos the index of the value to return an untyped pointer for
  ///
  /// \return untyped pointer for the value stored at index \p Pos
  ///
  /// \pre \p Pos is a valid index:\code
  /// Pos < size()
  /// \endcode
  void *pointerTo(const token_size_t Pos) noexcept override {
    ASSERT(Pos < size());
    return static_cast<uint8_t *>(Arena) + Offsets[Pos];
  }

  /// Provides a constant untyped pointer for the value stored at a position.
  ///
  /// \param Pos the index of the value to return an untyped pointer for
  ///
  /// \return constant untyped pointer for the value stored at index \p Pos
  ///
  /// \pre \p Pos is a valid index:\code
  /// Pos < Offsets.size()
  /// \endcode
  const void *pointerTo(const token_size_t Pos) const noexcept override {
    ASSERT(Pos < size());
    return static_cast<const uint8_t *>(Arena) + Offsets[Pos];
  }

  /// 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 T 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 T
  ///
  /// \pre \p Pos is a valid index:\code
  /// Pos < Offsets.size()
  /// \endcode
  template <typename T> bool isTypeAt(const size_t Pos) const noexcept {
    ASSERT(Pos < size());
    Token TT = ST;
    dropNOfToken(TT, Pos);
    return isHeadOfTokenTheSameType<T>(TT);
  }

  /// Gives a reference of a value of a given type stored at a given index.
  ///
  /// \note The constant variant of the function relies on this implementation,
  /// the function may not modify \p this object!
  ///
  /// \tparam T type to give a reference of
  ///
  /// \param Pos index to set the reference for
  ///
  /// \return reference of \p T 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 T:
  /// \code
  /// Pos < Size && isTypeAt<T>(Pos)
  /// \endcode
  template <typename T> T &valueAt(const token_size_t Pos) noexcept {
    ASSERT(Pos < size() && isTypeAt<T>(Pos));
    return *static_cast<T *>(pointerTo(Pos));
  }

  /// Gives a constant reference of a value of a given type stored at a given
  /// index.
  ///
  /// \tparam T type to give a reference of
  ///
  /// \param Pos index to set the reference for
  ///
  /// \return constant reference of \p T 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 T:
  /// \code
  /// Pos < Size && isTypeAt<T>(Pos)
  /// \endcode
  template <typename T>
  const T &valueAt(const token_size_t Pos) const noexcept {
    // \note Just use the non-const implementation as that does not modify
    // \p this object.
    return const_cast<TokenizedStorage *>(this)->valueAt<T>(Pos);
  }
};

// Implementation of the static member field \c rosa::TokenizedStorage::Offsets.
template <typename... Types>
const std::vector<size_t>
    TokenizedStorage<Types...>::Offsets = TokenizedStorage<Types...>::offsets();

/// Specialization of the template \c rosa::TokenizedStorage for storing
/// nothing.
///
/// \note The specialization implements the interface defined by \c
/// rosa::AbstractTokenizedStorage but most of the functions cannot be called
/// because nothing is stored in instances of the class.
template <> class TokenizedStorage<> : public AbstractTokenizedStorage {
public:
  /// \c rosa::Token for the stored values.
  static constexpr Token ST = TypeToken<>::Value;

  /// Byte offsets to access stored values in \c rosa::TokenizedStorage::Arena.
  static const std::vector<size_t> Offsets;

  /// Creates an instance.
  TokenizedStorage(void) noexcept {}

  /// No copying and moving of \c rosa::TokenizedStorage instances.
  ///
  /// \note This restriction may be relaxed as moving should be easy to
  /// implement, only requires the possiblity to validate Arena pointer.
  ///@{
  TokenizedStorage(const TokenizedStorage &) = delete;
  TokenizedStorage &operator=(const TokenizedStorage &) = delete;
  TokenizedStorage(TokenizedStorage &&Other) = delete;
  TokenizedStorage &operator=(TokenizedStorage &&) = delete;
  ///@}

  // Destroys \p this object.
  ~TokenizedStorage(void) {}

  /// Tells how many values are stored in \p this object.
  ///
  /// \return `0`
  size_t size(void) const noexcept override { return 0; }

  /// Tells the type of the value stored at a position.
  ///
  /// \pre Do not call.
  TypeNumber typeAt(const token_size_t) const noexcept override {
    ASSERT(false);
    return TypeNumber(0);
  }

  /// Provides an untyped pointer for the value stored at a position.
  ///
  /// \pre Do not call.
  void *pointerTo(const token_size_t) noexcept override {
    ASSERT(false);
    return nullptr;
  }

  /// Provides a constant untyped pointer for the value stored at a position.
  ///
  /// \pre Do not call.
  const void *pointerTo(const token_size_t) const noexcept override {
    ASSERT(false);
    return nullptr;
  }

  /// Tells if the value stored at a given index is of a given type.
  ///
  /// \pre Do not call.
  template <typename> bool isTypeAt(const size_t) const noexcept {
    ASSERT(false);
    return false;
  }

  /// Gives a reference of a value of a given type stored at a given index.
  ///
  /// \tparam T type to give a reference of
  /// \pre Do not call.
  template <typename T> T &valueAt(const token_size_t) noexcept {
    ASSERT(false);
    return *static_cast<T *>(nullptr);
  }

  /// Gives a constant reference of a value of a given type stored at a given
  /// index.
  ///
  /// \tparam T type to give a reference of
  ///
  /// \pre Do not call.
  template <typename T> const T &valueAt(const token_size_t) const noexcept {
    // \note Just use the non-const implementation as that does not modify
    // \p this object.
    return *static_cast<T *>(nullptr);
  }
};

} // End namespace rosa

#endif // ROSA_SUPPORT_TOKENIZED_STORAGES_HPP
