/******************************************************************************
 *
 * 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");

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

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

// The number of bits in one token.
constexpr size_t TokenBits = sizeof(token_t) * 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_t Token = 0;
};

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

// 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 Token.
// NOTE: The returned type number is not validated.
inline type_nr_t typeNumberOfHeadOfToken(const token_t Token) {
  return static_cast<type_nr_t>(Token & ((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 Token);

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

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

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

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

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

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

} // End namespace rosa

#endif // ROSA_SUPPORT_TYPE_TOKEN_HPP

