/*******************************************************************************
 *
 * File:     Message.hpp
 *
 * Contents: Declaration of Message base-class.
 *
 * Copyright 2017
 *
 * Author: David Juhasz (david.juhasz@tuwien.ac.at)
 *
 ******************************************************************************/

#ifndef ROSA_CORE_MESSAGE_HPP
#define ROSA_CORE_MESSAGE_HPP

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

#include "rosa/core/forward_declarations.h"

#include <memory>
#include <vector>

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.
// Messages are immutable data objects, obtaining their data upon creation and
// providing only constant references for the stored values.
// NOTE: Any reference obtained from a Message instance remains valid only as
// long as the owning Message object is not destroyed.
class Message {
protected:
  // Ctor.
  // NOTE: No implementation for empty list.
  template <typename Type, typename... Ts>
  Message(const Type &, const Ts &...) noexcept;

  // No copy and move.
  Message(const Message &) = delete;
  Message(Message &&) = delete;
  Message &operator=(const Message &) = delete;
  Message &operator=(Message &&) = delete;

public:
  // Factory function to locally create a Message instance from constant
  // lvalue references.
  template <typename Type, typename... Types>
  static message_t create(const Type &T, const Types &... Ts) noexcept;

  // Factory function to locally create a Message instance from rvalue
  // references.
  template <typename Type, typename... Types>
  static message_t create(Type &&T, Types &&... Ts) noexcept;

  // A valid, non-empty token representing the types of the values stored in
  // the Message.
  const Token T;

  // The number of types encoded in T, that is the number of values stored in
  // Message.
  const size_t Size;

  // Virtual dtor.
  virtual ~Message(void);

  // Tells if the value in position Pos is of type Type.
  // NOTE: Token encodes atoms as AtomValue and not directly AtomConstants.
  // PRE: Pos < Size
  template <typename Type> bool isTypeAt(const size_t Pos) const noexcept;

  // Gives a constant reference of the value of type Type in position Pos.
  // PRE: Pos < Size && isTypeAt(Pos)
  template <typename Type> const Type &valueAt(const size_t Pos) const noexcept;

protected:
  // Provides an untyped pointer for the value in position Pos.
  // PRE: Pos < Size
  virtual const void *pointerTo(const size_t Pos) const noexcept = 0;
};

// Nested namespace with an implementation for Message, consider it private.
namespace {

// Template class for an implementation of Message, definition below.
template <typename... Types> class LocalMessage;

// Initializes Arena with values from const lvalue references of Types.
// NOTE: Arena needs to be a valid pointer to a memory area big enough for
// values of Types.
template <typename... Types>
inline void createMessageElements(void *const Arena,
                                  const Types &... Ts) noexcept;

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

template <size_t Pos, typename Type, typename... Types>
inline void createMessageElement(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);
  createMessageElement<Pos + 1>(Arena, Offsets, Ts...);
}

template <size_t Pos, AtomValue V, typename... Types>
inline void
createMessageElement(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;
  createMessageElement<Pos + 1>(Arena, Offsets, Ts...);
}

template <typename Type, typename... Types>
inline void createMessageElements(void *const Arena, const Type &T,
                                  const Types &... Ts) noexcept {
  ASSERT(Arena != nullptr);
  createMessageElement<0>(Arena, LocalMessage<Type, Types...>::Offsets, T,
                          Ts...);
}

// Initializes Arena with values from rvalue references of Types.
// NOTE: Arena needs to be a valid pointer to a memory area big enough for
// values of Types.
template <typename... Types>
inline void createMessageElements(void *const Arena, Types &&... Ts) noexcept;

template <size_t Pos, typename Type, typename... Types>
inline void createMessageElement(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));
  createMessageElement<Pos + 1>(Arena, Offsets, std::move(Ts)...);
}

template <size_t Pos, AtomValue V, typename... Types>
inline void createMessageElement(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;
  createMessageElement<Pos + 1>(Arena, Offsets, std::move(Ts)...);
}

template <typename Type, typename... Types>
inline void createMessageElements(void *const Arena, Type &&T,
                                  Types &&... Ts) noexcept {
  ASSERT(Arena != nullptr);
  createMessageElement<0>(Arena, LocalMessage<Type, Types...>::Offsets,
                          std::move(T), std::move(Ts)...);
}

// Destroyes values of Types stored in Arena.
// NOTE: Arena needs to be a valid pointer to a memory area where values of
// Types are stored.
template <typename Type, typename... Types>
inline void destroyMessageElements(void *const Arena) noexcept;

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

template <size_t Pos, typename Type, typename... Types>
inline void destroyMessageElement(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();
  destroyMessageElement<Pos + 1, Types...>(Arena, Offsets);
}

template <typename Type, typename... Types>
inline void destroyMessageElements(void *const Arena) noexcept {
  destroyMessageElement<0, Type, Types...>(
      Arena, LocalMessage<Type, Types...>::Offsets);
}

// Implementation of the template LocalMessage. Provides facilities for storing
// values as a Message.
template <typename Type, typename... Types>
class LocalMessage<Type, Types...> : public Message {
public:
  // Type Token for the stored values.
  // NOTE: Only for compile-time checks, static member is not defined.
  static constexpr Token ST =
      TypeToken<typename std::decay<Type>::type,
                typename std::decay<Types>::type...>::Value;

  // Offsets required to access values in Arena.
  static const std::vector<size_t> Offsets;

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

  // Generates offset for accessing values in Arena.
  static std::vector<size_t> offsets(void) noexcept {
    Token T = ST;                            // Need a mutable copy.
    size_t I = 0;                            // Start indexing from position 0.
    std::vector<size_t> O(lengthOfToken(T)); // Allocate vector of proper size.
    O[0] = 0;                                // First offset is always 0.
    while (!emptyToken(T)) {
      ASSERT(I < O.size());
      // Calculate next offset based on the previous one.
      O[I + 1] = O[I] + sizeOfHeadOfToken(T);
      dropHeadOfToken(T), ++I;
    }
    ASSERT(I == O.size());
    return O;
  }

public:
  // Ctor for constant lvalue references.
  LocalMessage(const Type &T, const Types &... Ts) noexcept
      : Message(T, Ts...),
        Arena(::operator new(sizeOfValuesOfToken(ST))) {
    ASSERT(this->T == ST && Size == Offsets.size() &&
           Arena != nullptr); // Sanity check.
    createMessageElements(Arena, T, Ts...);
  }

  // Ctor for rvalue referecens.
  LocalMessage(Type &&T, Types &&... Ts) noexcept
      : Message(T, Ts...),
        Arena(::operator new(sizeOfValuesOfToken(ST))) {
    ASSERT(this->T == ST && Size == Offsets.size() &&
           Arena != nullptr); // Sanity check.
    createMessageElements(Arena, std::move(T), std::move(Ts)...);
  }

  // Dtor.
  ~LocalMessage(void) {
    destroyMessageElements<Type, Types...>(Arena);
    ::operator delete(Arena);
  }

  // Provides an untyped pointer for the constant value at Pos.
  const void *pointerTo(const size_t Pos) const noexcept override {
    ASSERT(Pos < Offsets.size());
    return static_cast<uint8_t *>(Arena) + Offsets[Pos];
  }
};

// Implementation of the static member field Offsets.
template <typename Type, typename... Types>
const std::vector<size_t> LocalMessage<Type, Types...>::Offsets =
    LocalMessage<Type, Types...>::offsets();

} // End namespace

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

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...));
}

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 T_ = T; // NOLINT
  dropNOfToken(T_, Pos);
  return isHeadOfTokenTheSameType<Type>(T_);
}

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

