//===-- rosa/support/type_numbers.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/type_numbers.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017-2020
///
/// \brief Facilities for registering supported types and representing them with
///        numbers.
///
/// \note This implementation is partially based on the \c type_number
/// implementation of CAF.
///
//===----------------------------------------------------------------------===//

#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 \c rosa::TypeNumberVersion below!
/// \note Keep this list in sync with the definition of
/// \c rosa::NumberedTypeNames.
/// \note The built-in types are explicitly listed in the definition of
/// rosa::app::AppAgent. Keep those definitions in sync with this list.
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 \c rosa::BuiltinTypes.
///
/// Software with the same version number are supposed to have backward
/// compatible type numbering.
///
/// \sa \c rosa::BultinTypes on backward compatibility.
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 a type is not \c rosa::NoneType.
///
/// \tparam T the type to check
template <typename T> struct IsNotNoneType {
  /// Denotes if \p T is the \c rosa::NoneType or not.
  static constexpr bool Value = !std::is_same<T, NoneType>::value;
};

} // End namespace

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

/// Turn \c rosa::type_nr_t into a strongly typed enumeration.
///
/// Values of \c rosa::type_nr_t casted to \c rosa::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 \c uint8_t values.
using printable_tn_t = printable_t<type_nr_t>;

/// Casts a \c rosa::TypeNumber into \c rosa::printable_tn_t.
///
/// \param TN \c rosa::TypeNumber to cast.
#define PRINTABLE_TN(TN) static_cast<printable_tn_t>(TN)

/// \name TypeNumberOf
/// \brief Computes \c rosa::TypeNumber for a type.
///
/// The \c rosa::TypeNumber for a type \c T can be obtained as \code
/// TypeNumberOf<T>::Value
/// \endcode
///
/// \note \c rosa::TypeNumber for a type is based on the corresponding squashed
/// type, except for \c rosa::AtomConstant types.
///
/// \sa \c rosa::SquashedType
///
/// \note \c rosa::TypeNumber is the index of the type in \c rosa::BuiltinTypes
/// starting from \c 1; index \c 0 indicates a non-builtin type.
///@{

/// Definition of the template for the general case.
///
/// \tparam T type to get \c rosa::TypeNumber for
template <typename T> struct TypeNumberOf {
  static constexpr TypeNumber Value = static_cast<TypeNumber>(
      TypeListIndexOf<BuiltinTypes, squashed_t<T>>::Value + 1);
};

/// Specialization for \c rosa::AtomConstant.
///
/// \note For a \c rosa::AtomConstant type, \c rosa::TypeNumber is based on the
/// \c rosa::AtomValue wrapped into the actual \c rosa::AtomConstant.
template <AtomValue V> struct TypeNumberOf<AtomConstant<V>> {
  static constexpr TypeNumber Value = TypeNumberOf<AtomValue>::Value;
};

///@}

// clang-format off

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

// clang-format on

/// Tells if a \c rosa::TypeNumber is valid in the software.
///
/// \note A \c rosa::TypeNumber generated by an incompatible version may be
/// valid but may denote a type that is different from the \c rosa::TypeNumber
/// denotes in the current software. That is why this validation needs to be
/// done in connection to checking \c rosa::TypeNumberVersion as well.
///
/// \param TN \c rosa::TypeNumber to validate in the context of the current
/// software
///
/// \return Whether \p TN is valid in the current software
constexpr bool validTypeNumber(const TypeNumber TN) {
  // \todo 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;
}

/// Provides information about the type corresponding to a \c rosa::TypeNumber.
///
/// \tparam TN \c rosa::TypeNumber to get information for
///
/// \pre Statically, \p TN is a valid \c rosa::TypeNumber:
/// \code
/// validTypeNumber(TN)
/// \endcode
template <TypeNumber TN> struct TypeForNumber {
  STATIC_ASSERT(validTypeNumber(TN), "not a valid type number");

  /// \p TN as \c rosa::type_nr_t.
  static constexpr type_nr_t TNI = static_cast<type_nr_t>(TN);

  /// The builtin-type corresponding to \p TN.
  using Type = typename TypeListAt<BuiltinTypes, TNI - 1>::Type;

  /// The size of \c Type.
  static constexpr size_t Size = sizeof(Type);

  /// The alignment of \c Type.
  static constexpr size_t Align = alignof(Type);

  /// Textual representation of the builtin-type.
  static constexpr const char *Name = NumberedTypeNames[TNI - 1];
};

} // End namespace rosa

namespace std {

/// Converts a \c rosa::TypeNumber into \c std::string.
///
/// \param TN \c rosa::TypeNumber to convert
///
/// \return \c std::string representing \p TN
inline string to_string(const rosa::TypeNumber TN) {
  return to_string(static_cast<rosa::type_nr_t>(TN));
}

/// Dumps a \c rosa::TypeNumber to a given \c std::ostream.
///
/// \param [in,out] OS output stream to dump to
/// \param TN \c rosa::TypeNumber to dump
///
/// \return \p OS after dumping \p TN to it
inline ostream &operator<<(ostream &OS, const rosa::TypeNumber &TN) {
  OS << to_string(TN);
  return OS;
}

} // End namespace std

#endif // ROSA_SUPPORT_TYPE_NUMBERS_HPP
