/******************************************************************************
 *
 * File:     type_token.hpp
 *
 * Contents: Facilities for encoding TypeLists as unsigned integer values.
 *
 * Copyright 2017
 *
 * Author: David Juhasz (david.juhasz@tuwien.ac.at)
 *
 ******************************************************************************/

#ifndef ROSA_SUPPORT_TYPE_TOKEN_HPP
#define ROSA_SUPPORT_TYPE_TOKEN_HPP

#include "rosa/support/type_numbers.hpp"

namespace rosa {

// NOTE on compatibility between different versions of the type token
// implementation:
// Different software versions produce compatible type tokens as long as
// backward compatibility of BuiltinTypes is maintained (denoted by
// TypeNumberVersion, see a note on that) and the type token implementation
// uses the same type as token_t (boiling down to the same token::TokenBits
// value) and the same token::RepresentationBits value. Thus, interacting
// software need to cross-validate the aforementioned values to check
// compatibility. Interoperation between compatible sofware is limited to
// backward compatiblity, that is builtin types defined in both software
// versions are handled correctly but a newer system may produce a type token
// which is invalid in an old one. Therefore, tokens obtained from a compatible
// remote system need to be validated.

// Integer type to store type tokens.
// NOTE: The trade-off between the binary overhead of type encoding and the
// maximal size of encodable lists can be tuned by using unsigned integer types
// of different widths as token_t.
using token_t = uint64_t;

// Sanity check in case someone would change token_t.
STATIC_ASSERT(std::is_unsigned<token_t>::value,
              "token_t is not an unsigned integer");

// Turn token_t into a strongly typed enumeration, so Tokens can be used in a
// type-safe way.
enum class Token : token_t {};

// A type to cast tokens into in order to output them to streams as
// numbers and not ASCII-codes.
// NOTE: Use it for safety, necessary for printing uint8_t values.
using printable_token_t = PRINTABLE(token_t);

// Helper preprocessor macro to cast tokens into printable_token_t.
#define PRINTABLE_TOKEN(T) static_cast<printable_token_t>(T)

// Converts a Token into string.
inline std::string to_string(const Token T) {
  return std::to_string(static_cast<token_t>(T));
}

// Nested namespace for protecting constants related to type tokens.
namespace token {

// The number of bits in one token.
constexpr size_t TokenBits = sizeof(Token) * 8;

// The number of bits a builtin type can be uniquely encoded into, that is any
// valid TypeNumber can fit into.
// NOTE: There is one extra bit position added for encoding so that providing a
// better chance to maintain backward comaptibility when BuiltinTypes is
// extended.
constexpr size_t RepresentationBits = log2(NumberOfBuiltinTypes) + 1;

// Maximal size of uniquely tokenizable TypeList.
constexpr size_t MaxTokenizableListSize = TokenBits / RepresentationBits;

} // End namespace token

// Generates a token, unsigned integer representation, for the TypeList.
// NOTE: The TypeList cannot have more than MaxTokenizableListSize elements and
// must be a subset of BuiltinTypes with respect to squashed integers.
// NOTE: A generated token uniquely represents a list of types, except for
// AtomConstant types. Observe that any AtomConstant is encoded as the type
// AtomValue. The type information on all separate AtomConstant types are lost
// and replaced by the AtomValue type whose actual value needs to be considered
// in order to obtain the original AtomConstant type and so the full type
// information on the encoded TypeList.
template <typename List> struct TypeListTokenImpl;

template <> struct TypeListTokenImpl<EmptyTypeList> {
  static constexpr Token Value = static_cast<Token>(0);
};

template <typename T, typename... Ts>
struct TypeListTokenImpl<TypeList<T, Ts...>> {
  static constexpr TypeNumber TN = TypeNumberOf<T>::Value;
  // Check if the generated type number is valid.
  STATIC_ASSERT(validTypeNumber(TN), "non-builtin type");
  static constexpr Token Value = static_cast<Token>(
      (static_cast<token_t>(TypeListTokenImpl<TypeList<Ts...>>::Value)
       << token::RepresentationBits) |
      static_cast<type_nr_t>(TN));
};

template <typename List> struct TypeListToken;

template <typename... Ts> struct TypeListToken<TypeList<Ts...>> {
  // NOTE: TypeNumber is computed against squased_int_t for integral types, so
  // let's do the same here.
  using List = typename SquashedTypeList<TypeList<Ts...>>::Type;
  // Check the length of the list here.
  // NOTE: Type validation is done one-by-one in TypeListTokenImpl.
  STATIC_ASSERT((TypeListSize<List>::Value <= token::MaxTokenizableListSize),
                "too long list of types");
  static constexpr Token Value = TypeListTokenImpl<List>::Value;
};

// Convenience template turning a list of types into a TypeList to generate
// TypeListToken for it.
template <typename... Ts> using TypeToken = TypeListToken<TypeList<Ts...>>;

// Anonymous namespace with helper facilities, consider it private.
namespace {

// Extracts the type number of the first encoded type of T.
// NOTE: The returned type number is not validated.
inline TypeNumber typeNumberOfHeadOfToken(const Token T) {
  return static_cast<TypeNumber>(static_cast<token_t>(T) &
                                 ((1 << token::RepresentationBits) - 1));
}

} // End namespace


// Tells if the given Token is valid, can be decoded by the current software.
// NOTE: Validation gives a correct result only when Token was generated by a
// compatible software (see note above).
bool validToken(const Token T);

// Tells if the token does encode an empty list.
bool emptyToken(const Token T);

// Tells how many types are encoded in T.
size_t lengthOfToken(const Token T);

// Tells the full memory size of the list encoded in T.
// PRE: validToken(T)
size_t sizeOfValuesOfToken(const Token T);

// Tells the memory size of the first type encoded in T.
// PRE: !empty(T) && validToken(T)
size_t sizeOfHeadOfToken(const Token T);

// Tells the name of the first type encoded in T.
// PRE: !empty(T) && validToken(T)
const char *nameOfHeadOfToken(const Token T);

// Drops the head element of the encoded list.
void dropHeadOfToken(Token &T);

// Drops the first N element of the encoded list.
void dropNOfToken(Token &T, const size_t N);

// Tells if the head of the encoded list is of type Type.
// PRE: !empty(T) && validToken(T)
template <typename Type>
bool isHeadOfTokenTheSameType(const Token T) {
  ASSERT(!emptyToken(T) && validToken(T));
  return TypeNumberOf<Type>::Value == typeNumberOfHeadOfToken(T);
}

} // End namespace rosa

#endif // ROSA_SUPPORT_TYPE_TOKEN_HPP

