//===-- rosa/support/tokenized_storages.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/support/tokenized_storages.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017-2020
///
/// \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/log.h"
#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 < 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;

  /// Generates byte offsets for accessing values stored in
  /// \c rosa::TokenizedStorage::Arena.
  ///
  /// \note Alignment requirement of each value is taken into account.
  ///
  /// \return \c std::vector containing byte offsets for accessing values stored
  /// in \c rosa::TokenizedStorage::Arena
  static const std::vector<size_t> &offsets(void) noexcept {
    static std::vector<size_t> Offsets;

    // Initialize Offsets during the first invocation.
    if (Offsets.empty()) {
      Token T = ST; // Need a mutable copy.
      const token_size_t N =
          lengthOfToken(T); // Number of types encoded in \c T.
      Offsets.resize(N);    // Allocate proper size right away.

      ASSERT(N > 0); // Sanity check, 0 element case has a specialization.

      token_size_t I = 0; // Start indexing from position \c 0.
      Offsets[0] = 0;     // First offset is always \c 0; correctly aligned.
      while (I < N - 1) {
        ASSERT((size_t)I + 1 < Offsets.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.

        const auto LastOffset = Offsets[I];
        auto &Offset = Offsets[++I];

        const auto LastSize = sizeOfHeadOfToken(T);
        dropHeadOfToken(T);
        const auto CurrentAlign = alignmentOfHeadOfToken(T);

        Offset = LastOffset + LastSize;
        Offset += CurrentAlign - 1;
        Offset &= -std::make_signed_t<decltype(CurrentAlign)>(CurrentAlign);
      }
      ASSERT((size_t)I + 1 == Offsets.size() && lengthOfToken(T) == 1);
    }
    return Offsets;
  }

  /// Tells the size of \c rosa::TokenizedStorage::Arena.
  ///
  /// \note Alignment requirement of each value is taken into account.
  ///
  /// \return size of \c rosa::TokenizedStorage::Arena to store values
  static size_t arenaSize(void) noexcept {
    static size_t ArenaSize = 0;
    // Initialize ArenaSize during first invocation.
    if (!ArenaSize) {
      const auto &Offsets = offsets();
      Token T = ST;
      const auto N = lengthOfToken(T);
      dropNOfToken(T, N - 1); // Get to the last type
      // Full size is the offset for plus the size of the last value.
      ArenaSize = Offsets[N - 1] + sizeOfHeadOfToken(T);
    }
    return ArenaSize;
  }

private:
  /// A BLOB storing all the values one after the other with necessary padding
  /// for correct alignment.
  void *const Arena;

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(arenaSize())) {
    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(arenaSize())) {
    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(arenaSize())) {
    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 < 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 < 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);
  }
};

/// 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;

  /// Creates an instance.
  TokenizedStorage(void) noexcept = default;

  /// 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) override {}

  /// 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 {
    LOG_ERROR("Called unsupported function");
    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 {
    LOG_ERROR("Called unsupported function");
    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 {
    LOG_ERROR("Called unsupported function");
    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 {
    LOG_ERROR("Called unsupported function");
    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 {
    LOG_ERROR("Called unsupported function");
    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 {
    LOG_ERROR("Called unsupported function");
    // \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
