/******************************************************************************
 *
 * File:     type_numbers.hpp
 *
 * Contents: Facilities for registering supported types and representing them
 *           with numbers.
 *
 * Copyright 2017
 *
 * Author: David Juhasz (david.juhasz@tuwien.ac.at)
 *
 * This implementation is partially based on the type_number implementation of
 * CAF.
 * TODO: Check license.
 *
 ******************************************************************************/

#ifndef ROSA_SUPPORT_TYPE_NUMBERS_HPP
#define ROSA_SUPPORT_TYPE_NUMBERS_HPP

#include "rosa/support/atom.hpp"
#include "rosa/support/math.hpp"
#include "rosa/support/squashed_int.hpp"
#include "rosa/support/type_helper.hpp"
#include "rosa/support/types.hpp"

#include <array>
#include <string>

namespace rosa {

// Compile-time list of all built-in types.
// NOTE: Appending new types to the end of this list maintains backward
// compatibility in the sense that old builtin types have the same type number
// associated to them in both the old and new versions. But changing any of
// the already present types in the list breaks that backward compatibility.
// Should compatibility be broken, step TypeNumberVersion below!
// NOTE: Keep this list in sync with the definition of NumberedTypeNames.
using BuiltinTypes = TypeList<AtomValue,   // atom
                              int16_t,     // i16
                              int32_t,     // i32
                              int64_t,     // i64
                              int8_t,      // i8
                              long double, // ldouble
                              std::string, // str
                              uint16_t,    // u16
                              uint32_t,    // u32
                              uint64_t,    // u64
                              uint8_t,     // u8
                              unit_t,      // unit
                              bool,        // bool
                              double,      // double
                              float        // float
                              >;

// Indicates the version number of BuiltinTypes. Software with the same version
// number are supposed to have backward compatible type numbering.
// NOTE: See note above on backward compatiblity of BultinTypes.
constexpr size_t TypeNumberVersion = 0;

// The number of built-in types.
static constexpr size_t NumberOfBuiltinTypes =
    TypeListSize<BuiltinTypes>::Value;

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

// Tells if T is not UnitType.
template <typename T> struct IsNotUnitType {
  static constexpr bool Value = !std::is_same<T, UnitType>::value;
};

} // End namespace

// Integer type to store type numbers.
// NOTE: The narrowest unsigned integer type that is wide enough to represent
// NumberOfBuiltinTypes different values.
using type_nr_t =
    typename TypeListFind<typename TypeListDrop<log2(NumberOfBuiltinTypes) / 8,
                                                IntegerTypesBySize>::Type,
                          IsNotUnitType>::Type::Second;

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

// A type to cast type numbers 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_tn_t = PRINTABLE(type_nr_t);

// Helper preprocessor macro to cast type numbers into printable_tn_t.
#define PRINTABLE_TN(N) static_cast<printable_tn_t>(N)

// Converts a TypeNumber into string.
inline std::string to_string(const TypeNumber TN) {
  return std::to_string(static_cast<type_nr_t>(TN));
}

// Computes the type number for T.
// NOTE: TypeNumber is the index of T in BuiltinTypes starting from 1,
// index 0 indicates a non-builtin type.
template <typename T, bool IsIntegral = std::is_integral<T>::value>
struct TypeNumberOf {
  static constexpr TypeNumber Value =
      static_cast<TypeNumber>(TypeListIndexOf<BuiltinTypes, T>::Value + 1);
};

template <typename T> struct TypeNumberOf<T, true> {
  using Type = squashed_int_t<T>;

  static constexpr TypeNumber Value =
      static_cast<TypeNumber>(TypeListIndexOf<BuiltinTypes, Type>::Value + 1);
};

template <> struct TypeNumberOf<bool, true> {
  static constexpr TypeNumber Value =
      static_cast<TypeNumber>(TypeListIndexOf<BuiltinTypes, bool>::Value + 1);
};

template <AtomValue V> struct TypeNumberOf<AtomConstant<V>, false> {
  static constexpr TypeNumber Value = TypeNumberOf<AtomValue>::Value;
};

// List of all type names, indexed via TypeNumber.
// NOTE: Keep this definition in sync with BuiltinTypes.
constexpr std::array<const char *, NumberOfBuiltinTypes> NumberedTypeNames {{
  "atom",
  "i16",
  "i32",
  "i64",
  "i8",
  "ldouble",
  "str",
  "u16",
  "u32",
  "u64",
  "u8",
  "unit",
  "bool",
  "double",
  "float"
}};

// Tells if the given TypeNumber is valid in the software.
// NOTE: A type number generated by an incompatible version may be valid but
// supposed to denote a type different than that in the current software.
constexpr bool validTypeNumber(const TypeNumber TN) {
  // FIXME: Duplication of static_cast into a const variable would be
  // possible in C++14.
  return 0 < static_cast<type_nr_t>(TN) &&
         static_cast<type_nr_t>(TN) <= NumberOfBuiltinTypes;
}

// Computes the corresponding builtin type with some information from a type
// number.
// PRE: validTypeNumber(TN)
template <TypeNumber TN> struct TypeForNumber {
  STATIC_ASSERT(validTypeNumber(TN), "not a valid type number");
  static constexpr type_nr_t TNI = static_cast<type_nr_t>(TN);
  using Type = typename TypeListAt<BuiltinTypes, TNI - 1>::Type;
  static constexpr size_t Size = sizeof(Type);
  static constexpr const char *Name = NumberedTypeNames[TNI - 1];
};

} // End namespace rosa

#endif // ROSA_SUPPORT_TYPE_NUMBERS_HPP

